node-koa
├─ .gitignore
├─ app
│ ├─ api #api层
│ │ └─ v1
│ │ └─ book.js
│ ├─ config #配置文件目录
│ │ ├─ code-message.js #返回成功码错误码和返回信息配置
│ │ └─ config.js #项目信息配置
│ ├─ core #项目核心代码目录
│ │ ├─ http-exception.js #自定义异常
│ │ ├─ init.js #core初始化
│ │ ├─ lin-validator.js #参数校验工具
│ │ └─ util.js #工具函数
│ ├─ middleware #中间件目录
│ │ └─ global-exception.js #全局处理异常
│ │ └─ extend-context.js #扩展上下文context
│ └─ validator #校验器模块
│ └─ common.js
├─ app.js #启动文件
├─ LICENSE
├─ package-lock.json
├─ package.json
└─ README.md
-
路由系统
-
全局异常处理
-
项目配置
-
参数校验
在前后端分离中,每一个后端都离不开一个路由系统。路由相当于你访问的一个路径。koa
框架本身是没有路由的,需要借助第三方的库集成路由系统,这个第三方库是koa-router
。
下面具体演示一下具体用法。
const Router = require('koa-router')
const router = new Router({
prefix:'/v1/book'
})
router.get('/', async(ctx, next) => {
ctx.body = {
message: 'hello koa-router'
}
})
module.exports = router
上面代码定义了一个路由,路径为/v1/book
。启动服务器发现Not Found
,是因为我们还没有将路由注册到koa中间件中。如果你使用的是本项目,默认会自动将路由注册到koa中间件中,只要你把模块(路由)导出去就可以了。
// 支持两种方式导出去
module.exports = router
module.exports = {
router
}
我们通常用模块的方式把路由分离到多个文件,例如上面写的模块是book
,那么与book模块相关的路由都写在这个文件,所以我是比较推荐module.exports = router
方式导出模块。虽然在一个文件中可以写多个模块,但是也不建议这么干。
实际上路由api上还有其他一些参数,可以参考官方提供的文档。
每一个后端项目几乎都要有全局异常处理,全局捕获整个项目中的所有发生的异常错误。koa
框架本身也没有提供全局处理异常的机制,所以要自己写一个处理异常的机制,我把全局异常处理机制写成一个中间件,具体参考代码middleware/global-exception
。全局异常处理中间件一定要注册为第一个中间件,可以参考core/init.js
,不建议随意改动core
目录下的代码。
下面具体演示一下具体用法。支持两种方式的用法,看个人喜欢。
const Router = require('koa-router')
const {Parameter} = require('../../core/http-exception')
const router = new Router({
prefix:'/v1/book'
})
router.get('/', async(ctx, next) => {
// 第一种方式用法,省了导入模块,较为方便
throw new global.errs.Parameter()
// 第二种方式用法,需要导入模块
throw new Parameter()
})
module.exports = router
可以看到上面我们使用Parameter
。探究一下个东西,这个Parameter写在core/http-exception.js
文件中,以后向追加其他异常处理建议集中写在这个文件中。
class Parameter extends HttpException{
constructor({message, code=10001} = {}) {
super()
this.status = 400
this.code = code
this.message = message
}
}
module.exports = {
Parameter
}
我们可以看到Parameter
继承了HttpException基类
,其实HttpException也继承了Error
,因为这样我们才能通过try..catch
捕获到异常。Parameter编写了一个构造函数,如果以后想自己写自定义异常,请务必写成这个传参格式,如果不这样写,有可能会报错。参数列表中有code
和message
这两个参数,实际上还有个status,但是它是几乎不需要改变,代表一种类型的异常。
code
代表自定异常的错误码,这个code是有具体意义的,而且非常方便于前端的处理。message
代表异常错误的信息。status
代表Http状态码。
Parameter构造函数中参数列表code
有个默认参数10001
,刚才说了这个code
是有具体的意义的,那它代表什么?
可以看到项目目录有config/code-message.js
这个文件记录着这个项目所有的错误码以及对应的错误信息,建议所有的错误码都集中写在这个文件。
function getMessage(code) {
return this[code] || ''
}
module.exports = {
getMessage,
0: 'ok',
1: '创建成功',
2: '删除成功',
3: '更新成功',
9999: '服务器错误',
10000: '参数错误',
10001: '授权失败',
10002: '禁止访问',
10003: '资源不存在'
}
上面代码就是config/code-message.js
中的代码,可以把错误码追加在module.exports
中。getMessage方法
一般来说都不用管,主要用于处理全局异常中间件使用的一个通过code码获取message的一个方法。
我们继续回到这个异常调用这里throw new global.errs.Parameter()
,刚刚说了这个对象是可以传入两个参数的。下面代码演示并解释传参方式。
//第一种不传入参数,会采用默认code以及message
throw new global.errs.ParameterException()
//如果需要传入参数,请务必传入的是一个对象。
//第二种方式只传入一个code码,这个code码必须要求code-message.js文件存在,如果不存在会报错提示你配置code码
throw new global.errs.ParameterException({
code: 10002,
})
//第三种方式只传入message,它会采用默认的code码,而message是采用你传入的message
throw new global.errs.ParameterException({
message: ‘传入参数错误拉拉’,
})
//第四种方式传入code码和message,自然code和message都是采用你传入的。
throw new global.errs.ParameterException({
code: 10002,
message: '传入参数错误拉拉'
})
虽然支持四种不同参数抛出异常,我个人比较建议你把所有的code码以及对应message都配置config/code-message.js
文件中。配置在文件中你可以享受几个优点。
- 集中,方便管理、维护(零散的洒落在各个角落,可能会死翘翘)。
- 国际化处理的一种方案。
项目配置集中写在config/config.js
这个文件中
下面代码是这个项目默认的一个环境配置,在dev模式
下会打印非HttpException的异常错误信息到控制台,方便开发阶段调式。
module.exports = {
environment: 'dev'
}
参数校验每个后端项目中必须做的一个安全措施。可能有些人会想,我前端已经校验了呀,为什么后端还要校验呢。这样告诉你吧,你前端可以不校验,但是后端一定要校验。前端校验只不过可以减少一些不必要的请求,提高了一下用户体验。koa框架本身也没有参数校验的功能,然而市面上好像也没有比较好用的与koa相关的校验库
,这个项目是集成了一个开源项目中内置的一个校验模块
,这个开源项目是Lin-cms-koa
。
Lin-cms-koa文档 https://doc.cms.talelin.com/server/
参数校验模块文档 https://doc.cms.talelin.com/server/koa/validator.html
作用:一般用于对增删改这些API,给用户返回成功的提示。其实它返回的格式是跟HttpException一样的。
用法:
//第一种方式 不传入任何参数,就会采用默认参数
router.get('/', async ctx => {
ctx.success()
})
// 第二种方式传入对象,这个跟之前异常传入的参数是一样的
router.get('/', async ctx => {
ctx.success({
code: 1
})
})
作用:可以在返回数据时,删除掉一些不要的属性,只不过这是一个浅层次的删除,深层次也不推荐,其实深层次删除挺消耗性能的。
用法:
// 第二个参数是一个可选参数,它是一个数组,写入你要删除的属性。
router.get('/', async ctx => {
const obj = {
id: 1,
title: 'koa',
create_time
}
ctx.json(obj,['create_time'])
})