koa2-validation这个库用于Controller前的参数校验。
简单介绍它的使用:
const http = require('http');
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const router = require('koa-router')();
const validate = require('koa2-validation'); // 1. import the koa2-validation
const user = require('./user');
// 可以看到路由中间件处理顺序、先进行参数校验
router.post('/users', validate(user.v.addUser), user.addUser); // 3. setup the validate middleware
router.get('/users/:id', validate(user.v.getUserInfo), user.getUserInfo);
router.get('/users', validate(user.v.getUserList), user.getUserList);
const app = new Koa();
// error handler
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || err.code;
ctx.body = {
success: false,
message: err.message,
};
}
});
app.use(bodyParser()); // bodyParser should be before the routers
app.use(router.routes());
const server = http.createServer(app.callback());
module.exports = server;
对应user定义的schema格式:
v.addUser = {
body: {
id: Joi.string().required(),
name: Joi.string(),
age: Joi.number()
}
};
v.getUserInfo = {
params: {
id: Joi.string()
.valid(["001", "002"])
.required()
}
};
v.getUserList = {
query: {
age: Joi.number()
}
};
其实聪明的你也会发现,客户端传递过来的参数无非就是在这三个地方:body、params、query。 是的源码层处理也是比较简单的。
源码
koa2-validation是基于@hapi/joi。源码也比较精炼,我们可以看对应注释即可。
const _ = require("lodash");
const Joi = require("@hapi/joi");
/**
* 简单封装的一个参数校验失败的异常类
*/
class ValidationError extends Error {
constructor(errors) {
// 拼接errors[] =》message字符串
const message = errors
.map(e => e.message)
.join(", ")
.replace(/"/g, "");
super(message);
// 定义400状态码
this.status = 400;
// errors
this.errors = errors;
}
}
class Validation {
constructor(joi) {
this.Joi = joi || Joi;
}
validate(schema = {}, opt = {}) {
const options = _.defaultsDeep(opt, {
allowUnknown: true
});
// 本质也是一个中间件
return async (ctx, next) => {
// 客户端传入过来参数来源key
const defaultValidateKeys = ["body", "query", "params"];
// 取出真正需要被校验的key来源
/**
* 比如schema如下
* params: {
id: Joi.string()
.valid(["001", "002"])
.required()
}
那么needValidateKeys就是params。
*/
const needValidateKeys = _.intersection(
defaultValidateKeys,
Object.keys(schema)
);
// 存储校验失败的error
const errors = [];
// find 找到第一条
needValidateKeys.find(item => {
// 取出客户端接口入参
// Koa2获取接口参数有三种方式
// ctx.query exp: { q: 1 }
// ctx.params exp: { id: 1}
// ctx.request.body exp: { "name": "zj" }
const toValidateObj = item === "body" ? ctx.request.body : ctx[item];
// api doc https://github.com/sideway/joi/blob/v8.0.5/API.md#validatevalue-schema-options-callback
// toValidateObj:需要被校验的value
// schema[item]:是你定义好的schema
// options: 配置对象
const result = this.Joi.validate(toValidateObj, schema[item], options);
// 处理校验失败情况
if (result.error) {
errors.push(result.error.details[0]);
return true;
}
// 校验通过 处理toValidateObj
// result.value exp:{ "a" : 123 }
_.assignIn(toValidateObj, result.value);
return false;
});
// 如果参数校验失败
if (errors.length !== 0) {
// 抛出一个400异常到客户端
// 一般我们写Koa2应用、会在顶层中间件处理throw抛出的异常、并按照restful格式返回给客户端
throw new ValidationError(errors);
}
await next();
};
}
}
const defaultValidation = new Validation();
// 默认导出使用
module.exports = defaultValidation.validate.bind(defaultValidation);
module.exports.ValidationError = ValidationError;
// 用于自行注入不同版本joi
// const joi = require("@hapi/joi");
// const Validation = require("koa2-validation").Validation;
// const validator = new Validation(joi);
// const validate = validator.validate.bind(validator);
module.exports.Validation = Validation;
一个简单的参数校验器流程就这样设计出来了。
好了,今天就到这结束了,下期见。
ps:如果你对node也有兴趣,欢迎关注我公众号:xyz编程日记。