每天一个npm包:koa2-validation

781 阅读2分钟

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编程日记。