零、项目结构
main.js
是项目的入口文件,导入了config/config.default.js
文件和app/index.js
文件。
config/config.default.js
文件是读取你的.env
文件,将配置写到process.env
中
.env
全局默认配置文件
app/index.js
是项目的app服务,导入了koa框架,koa-body并使用,app/errHandler
错误处理中间件和router/user.router
路由模块
router/user.router
是项目的路由文件,导入使用了koa-router,middleware/user.middleware
和controller/user.controller
controller/user.controller
是项目的控制器,用于接口的编写,导入了一个service/user.service
和consitant/err.type
service/user.service
主要是做数据库处理,导入了model/user.model
,调用model完成数据库操作
model/user.model
编写数据表,导入了db/seq
连接数据库,用于service/user.service
数据库处理
db/seq
连接数据库,导入了.env
全局默认配置文件
middleware/user.middleware
为中间件文件,用于处理服务请求的细节,导入了service/user.service
和constant/err.type
constant/err.type
为统一错误定义的文件,定义一些请求中的错误原因
app/errHandler
为错误处理函数,需要在app服务中on()
绑定错误
一、项目初始化
1.npm初始化
生成package.json
文件
2.git初始化
生成.git
隐藏文件夹, git 的本地仓库
二、搭建项目
1.安装Koa框架
2.编写最基本的app
新建一个/src/main.js
文件
1 2 3 4 5 6 7 8 9 10
| const Koa = require('koa')
const app = new Koa()
app.use((ctx,next)=>{ ctx.body = "hello api" }) app.listen(3000,()=>{ console.log("server is running on http://localhost:3000"); })
|
3.测试
在终端,使用命令
或者有安装 nodemon
浏览器显示 hello api
三、项目的基本优化
1.自动重启服务
全局安装nodemon
工具
编写package.json
脚本
1 2 3 4
| "scripts": { "dev": "nodemon ./src/main.js", "test": "echo \"Error: no test specified\" && exit 1" },
|
执行npm run dev
启动服务
2.读取配置文件
安装dotenv
, 读取根目录中的.env
文件, 将配置写到process.env
中
在项目根目录中创建.env
文件
创建src/config/config.default.js
1 2 3 4 5 6 7
| const dotenv = require('dotenv')
dotenv.config()
module.exports = process.env
|
修改main.js
1 2 3 4 5 6 7 8 9 10 11 12
| const Koa = require('koa')
const {APP_PORT} = require('./config/config.default') const app = new Koa()
app.use((ctx,next)=>{ ctx.body = "hello api"
}) app.listen(APP_PORT,()=>{ console.log(`server is running on http://localhost:${APP_PORT}`); })
|
四、添加路由
路由: 根据不同的 URL, 调用对应处理函数
1.安装koa-router
步骤:
- 导入包
- 实例化对象
- 编写路由
- 注册中间件
2.编写路由
创建src/router
目录, 编写user.router.js
1 2 3 4 5 6 7 8 9 10
| const Router = require('koa-router')
const router = new Router({ prefix: '/users'})
router.get('/',(ctx,next)=>{ ctx.body = 'hello users' })
module.exports = router
|
3.修改main.js
1 2 3 4 5 6 7 8 9 10 11 12 13
| const Koa = require('koa')
const {APP_PORT} = require('./config/config.default')
const userRouter = require('./router/user.router')
const app = new Koa()
app.use(userRouter.routes())
app.listen(APP_PORT,()=>{ console.log(`server is running on http://localhost:${APP_PORT}`); })
|
五、目录结构优化
1.将 http 服务和 app 服务拆分
创建src/app/index.js
文件
1 2 3 4 5 6 7 8 9
| const Koa = require('koa')
const userRouter = require('../router/user.router')
const app = new Koa()
app.use(userRouter.routes())
module.exports = app
|
修改main.js
1 2 3 4 5 6 7
| const {APP_PORT} = require('./config/config.default')
const app = require("./app")
app.listen(APP_PORT,()=>{ console.log(`server is running on http://localhost:${APP_PORT}`); })
|
2.将路由和控制器拆分
路由: 解析 URL, 分布给控制器对应的方法
控制器: 处理不同的业务
修改user.route.js
1 2 3 4 5 6 7 8 9 10 11 12
| const Router = require('koa-router')
const {register,login} = require('../controller/user.controller')
const router = new Router({ prefix: '/users'})
router.post('/register',register)
router.post('/login',login)
module.exports = router
|
创建controller/user.controller.js
1 2 3 4 5 6 7 8 9 10 11
| class UserController { async register(ctx, next) { ctx.body = '用户注册成功' }
async login(ctx, next) { ctx.body = '登录成功' } }
module.exports = new UserController()
|
六. 解析 body
1.安装 koa-body
2.注册中间件
改写app/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13
| const Koa = require('koa')
const KoaBody = require('koa-body') const userRouter = require('../router/user.router')
const app = new Koa()
app.use(KoaBody())
app.use(userRouter.routes())
module.exports = app
|
3.解析请求数据
修改user.controller.js
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const { createUser } = require('../service/user.service')
class UserController { async register(ctx, next) { const { user_name, password } = ctx.request.body const res = await createUser(user_name, password) console.log(res); ctx.body = ctx.request.body }
async login(ctx, next) { ctx.body = '登录成功' } }
module.exports = new UserController()
|
4.拆分 service 层
service 层主要是做数据库处理
创建src/service/user.service.js
1 2 3 4 5 6 7 8
| class UserService { async createUser(user_name,password){ return '写入数据库成功' } }
module.exports = new UserService()
|
七. 数据库操作
sequelize ORM 数据库工具
ORM: 对象关系映射
- 数据表映射(对应)一个类
- 数据表中的数据行(记录)对应一个对象
- 数据表字段对应对象的属性
- 数据表的操作对应对象的方法
1.安装 Sequelize 和 MySQL
2.连接数据库
新建src/db/seq.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const { Sequelize } = require('sequelize') const { MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PWD, MYSQL_DB } = require('../config/config.default')
const seq = new Sequelize(MYSQL_DB, MYSQL_USER, MYSQL_PWD, { host: MYSQL_HOST, dialect: 'mysql' })
seq.authenticate().then(() => { console.log('数据库连接成功'); }).catch((err) => { console.log('数据库连接失败', err); })
|
3.编写配置文件
修改.env
1 2 3 4 5 6 7
| APP_PORT = 8000
MYSQL_HOST = localhost MYSQL_PORT = 3306 MYSQL_USER = root MYSQL_PWD = 123456 MYSQL_DB = zdsc
|
八. 创建 User 模型
拆分 Model 层
sequelize 主要通过 Model 对应数据表
创建src/model/user.model.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const {DataTypes} = require('sequelize') const seq = require('../db/seq')
const User = seq.define('zd_user',{ user_name:{ type:DataTypes.STRING, allowNull:false, unique:true, comment:'用户名,唯一', }, password:{ type:DataTypes.CHAR(64), allowNull:false, comment:'密码', }, is_admin:{ type:DataTypes.BOOLEAN, allowNull:false, defaultValue:0, comment:'是否为管理员 0:不是,1:是' }, },
)
module.exports = User
|
九. 添加用户入库
所有数据库的操作都在 Service 层完成, Service 调用 Model 完成数据库操作
修改src/service/user.service.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const User = require('../model/user.model') class UserService { async createUser(user_name, password) {
const res = await User.create({ user_name, password }) return res.dataValues } }
module.exports = new UserService()
|
修改user.controller.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const { createUser } = require('../service/user.service')
class UserController { async register(ctx, next) { console.log(ctx.request.body); const { user_name, password } = ctx.request.body const res = await createUser(user_name, password) console.log(res); ctx.body = { code:200, message:'用户注册成功', result:{ id:res.id, user_name:res.user_name } } }
async login(ctx, next) { ctx.body = '登录成功' } }
module.exports = new UserController()
|
十. 错误处理
在控制器中, 对不同的错误进行处理, 返回不同的提示错误提示, 提高代码质量
user.controller.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| const { createUser,getUserInfo } = require('../service/user.service')
class UserController { async register(ctx, next) { console.log(ctx.request.body); const { user_name, password } = ctx.request.body if(!user_name || !password){ console.error('用户名或密码为空',ctx.request.body); ctx.status = 400, ctx.body = { code: '400', message: '用户名或密码为空', result: '' } return } if(getUserInfo({user_name})){ ctx.status = 409, ctx.body = { code:'409', message: '用户已经存在', result:'' } return } const res = await createUser(user_name, password) ctx.body = { code:200, message:'用户注册成功', result:{ id:res.id, user_name:res.user_name } } }
async login(ctx, next) { ctx.body = '登录成功' } }
module.exports = new UserController()
|
在 service 中封装函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const User = require('../model/user.model') class UserService { async createUser(user_name, password) {
const res = await User.create({ user_name, password }) return res.dataValues }
async getUserInfo({id,user_name,password,is_admin}) { const whereOpt = {} id && Object.assign(whereOpt,{id}) user_name && Object.assign(whereOpt,{user_name}) password && Object.assign(whereOpt,{password}) is_admin && Object.assign(whereOpt,{is_admin})
const res = await User.findOne({ attributes:['id','user_name','password','is_admin'], where:whereOpt }) return res ? res.dataValues : null }
}
module.exports = new UserService()
|
十一、拆分中间件
- 在出错的地方使用
ctx.app.emit
提交错误
- 在 app 中通过
app.on
监听
1.拆分中间件
添加src/middleware/user.middleware.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| const { getUserInfo } = require('../service/user.service') const { userFormateError, userAlreadyExited,userRegisterError} = require('../consitant/err.type') const userValidator = async (ctx, next) => { const { user_name, password } = ctx.request.body if (!user_name || !password) { console.error('用户名或密码为空', ctx.request.body); ctx.app.emit('error', userFormateError, ctx) return } await next() } const userVerify = async (ctx, next) => { const { user_name } = ctx.request.body try { const res = await getUserInfo({user_name}) if (res) { console.error('用户名已经存在',{user_name}); ctx.app.emit('error',userAlreadyExited,ctx) return } } catch (err) { console.error('获取用户信息错误',err); ctx.app.emit('error',userRegisterError,ctx) return } await next() } module.exports = { userValidator, userVerify }
|
2.统一错误处理
编写统一的错误定义文件
新建src/consitant/err.type.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| module.exports = { userFormateError:{ code: '400', message: '用户名或密码为空', result:'' }, userAlreadyExited:{ code: '409', message: '用户已存在', result:'' }, userRegisterError:{ code: '400', message: '用户注册失败', result:'' } }
|
3.错误处理函数
新建src/app/errHandler.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| module.exports = (err,ctx) => { let status = 500 switch (err.code) { case '400': status = 400 break; case '409': status = 409 break; default: status = 500 } ctx.status = status ctx.body = err }
|
修改app/index.js
1 2 3
| const errHandler = require('./errHandler')
app.on('error', errHandler)
|