不管以什么语言编写程序,我们都会写大量的代码用来做参数验证以及错误处理。Node.js 更甚,js 的单线程特性意味着一个简单的语法错误都会导致整个程序崩溃。毫不客气的说,在编写 Node.js 应用时,有三分之一的代码都是在验证参数以及处理错误,而这些代码通常与业务逻辑代码杂糅在一起,随着应用越写越大,代码变得臃肿难以维护。中间件的设计哲学则让参数验证以及错误处理变得极为简单与优雅,我们只需将相关代码编写成中间件,并在路由之前加载,每个请求到来时都会找到对应的规则,如果验证失败直接返回错误,验证成功后才会将请求传递到下一个中间件,这样就实现了验证逻辑与业务逻辑的分离。koa-scheme 是 koa 的一个参数验证中间件,同时它还可以对请求做一些过滤和格式化的工作。我们来看个例子:

app.js

var koa = require('koa');
var bodyParser = require('koa-bodyparser');
var scheme = require('koa-scheme');

var app = koa();
app.use(bodyParser());
app.use(scheme(__dirname + '/scheme', {debug: true}));
app.use(function* () {
  if (this.path === '/signup') {
    console.log(this.request.body.password);
    this.body = {
      user: {
        id: this.request.body.password
      }
    };
  }
});
app.listen(3000);

scheme.js

var validator = require('validator');
var crypto = require('crypto');

module.exports = {
  "/(.*)": {
    "request": {
      "header.version": /^[0-9]+$/
    }
  },
  "(POST|put) /signup": {
    "request": {
      "body": {
        "name": /^[a-zA-Z]+$/,
        "age": validator.isNumeric,
        "email": validator.isEmail,
        "gender": /^(male|female)$/,
        "password": /^[a-zA-Z0-9]{6,12}$/,
        "repassword": checkRepassword
      }
    },
    "response": {
      "body.user.id": /^[a-zA-Z0-9]{32}$/
    }
  }
};

function md5 (str) {
  return crypto.createHash('md5').update(str).digest('hex');
}

function checkRepassword(repassword) {
  var body = this.request.body;
  if (repassword !== body.password) {
    return this.throw(400, '两次密码不一致!');
  }
  body.password = md5(body.password);
  return true;
}

建议读者亲自运行以上代码调试一下。从 sheme.js 中可以看出,我们对所有接收的请求都会检查头中的 version 字段,假如没有该字段或该字段不是数字类型都会返回一个错误。对于 post 或 put 到 /signup 路径的请求,既会检查头中的 version 字段,又会分别检查请求体和响应体中的字段。需要说明的两点:我们在 checkRepassword 中先检查了两次输入的密码是否一致,若不一致则返回错误,若一致则将密码 md5 加密,这样我们在 app.js 中打印的就是加密后的密码了;假如我们只想验证嵌套比较深的某个字段而不是所有的字段时,只需像 body.user.id 那样使用 . 拼接即可。

我们可以把所有的参数验证以及格式化的工作都放到 scheme.js 中,如果需要验证的路径及规则过多的话,则可以按路径拆分成不同的小文件引入到 scheme.js,我们唯一需要保证的就是后续的中间件收到的数据格式都是正确的,这样我们就不必在业务逻辑中编写大量重复的、繁琐的参数验证代码了。