代码写久了,说不出几句文邹邹的话。。。
一、koa-middle-validator
express有个非常好用的中间件 express-validator
,它既可以用作参数的验证,如校验 request body 、query parmas、headers等等,又支持参数的格式化。
很遗憾的是,这么好用的库没有提供Koa的版本。于是笔者在fork此项目的基础之上,实现了对koa的支持,衍生出了 koa-middle-validator
(npm类似包名太多,所以用了这个)。
这个仓库在一年之前就已经发布过了,笔者也多次运用在实际上线项目中
使用方法
const util = require('util'),
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const convert = require('koa-convert');
const koaValidator = require('koa-middle-validator');
const Router = require('koa-router');
const _ = require('lodash');
const app = new Koa();
const router = new Router();
app.use(convert(bodyParser()));
app.use(koaValidator({
customValidators: {
isArray: function(value) {
return _.isArray(value);
},
isAsyncTest: function(testparam) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
if (testparam === '42') { return resolve(); }
reject();
}, 200);
});
}
},
customSanitizers: {
toTestSanitize: function() {
return "!!!!";
}
}
})); // this line must be immediately after any of the bodyParser middlewares!
router.get(/\/test(\d+)/, validation);
router.get('/:testparam?', validation);
router.post('/:testparam?', validation);
app.use(router.routes())
app.use(router.allowedMethods({
throw: true
}))
function validation (ctx) {
ctx.checkBody('postparam', 'Invalid postparam').notEmpty().isInt();
//ctx.checkParams('urlparam', 'Invalid urlparam').isAlpha();
ctx.checkQuery('getparam', 'Invalid getparam').isInt();
ctx.sanitizeBody('postparam').toBoolean();
//ctx.sanitizeParams('urlparam').toBoolean();
ctx.sanitizeQuery('getparam').toBoolean();
ctx.sanitize('postparam').toBoolean();
return ctx.getValidationResult().then(function(result) {
ctx.body = {
// return something
}
});
}
app.listen(8888);
API
可参照 express-validator
,具体请移步 koa-middle-validator
Middleware Options
errorFormatter
customValidators
customSanitizers
const _ = require('lodash')
const validator = require('validator')
const koaValidator = require('koa-middle-validator')
/**
* 自定义验证
*/
module.exports = () => koaValidator({
errorFormatter: (param, message, value) => {
return {
param,
message,
value,
}
},
customValidators: {
isEmail: value => /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(value),
isMobile: value => /^1[3|4|5|7|8]\d{9}$/.test(value),
isString: value => _.isString(value),
isNumber: value => !isNaN(Number(value)),
isObject: value => _.isObject(value),
isJson: value => Object.prototype.toString.call(value).toLowerCase() === '[object object]',
isArray: value => _.isArray(value),
inArray: (param, ...args) => {
const validatorName = args[0]
return _.every(param, (item) => {
switch (validatorName) {
case 'isEmail': return /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(item)
case 'isMobile': return /^1[3|4|5|7|8]\d{9}$/.test(item)
case 'isString': return _.isString(item)
case 'isNumber': return _.isNumber(item)
case 'isObject': return _.isObject(item)
case 'isArray': return _.isArray(item)
case 'isBoolean':
switch (typeof item) {
case 'string': return item === 'true' || item === 'false'
case 'boolean': return item === true || item === false
default: return false
}
default:
return validator[validatorName].call(this, item)
}
})
},
isBoolean: (value) => {
switch (typeof value) {
case 'string':
return value === 'true' || value === 'false'
case 'boolean':
return value === true || value === false
default:
return false
}
},
custom: (value, callback) => {
if (typeof value !== 'undefined') {
return callback(value)
}
return false
},
},
})
Validation
ctx.check()
ctx.check('testparam', 'Error Message').notEmpty().isInt();
ctx.check('testparam.child', 'Error Message').isInt(); // find nested params
ctx.check(['testparam', 'child'], 'Error Message').isInt(); // find nested params
ctx.assert()
ctx.validate()
ctx.checkBody()
ctx.checkBody({
host: {
notEmpty: {
options: [true],
errorMessage: 'host 不能为空',
},
matches: {
options: [regx.host],
errorMessage: 'host 格式不正确',
},
},
port: {
notEmpty: {
options: [true],
errorMessage: 'port 不能为空',
},
isInt: {
options: [{ min: 0, max: 65535 }],
errorMessage: 'port 需为0-65535之间的整数',
},
},
db: {
notEmpty: {
options: [true],
errorMessage: 'db 不能为空',
},
isString: { errorMessage: 'db 需为字符串' },
},
user: {
optional: true,
isString: { errorMessage: 'user 需为字符串' },
},
pass: {
optional: true,
isString: { errorMessage: 'pass 需为字符串' },
},
})
ctx.checkQuery()
ctx.checkParams()
ctx.checkHeaders()
ctx.check()
Validation result
- 获取验证结果
ctx.validationErrors()
- 异步结果
ctx.getValidationResult()
Sanitizer 参数格式化
ctx.sanitize()
ctx.filter()
ctx.sanitizeBody()
ctx.sanitizeQuery()
ctx.sanitizeParams()
ctx.sanitizeQuery('page').toInt()
ctx.sanitizeQuery('pageSize').toInt()
ctx.sanitizeHeaders()
二、mongoose-validation
习惯使用 node + mongoDB
开发项目,而 mongoose 应该算是 mongoDB 生态圈里最火的操作工具。
在后端业务场景里,绝大部分验证的参数往往就是数据库需要存储的数据,mongoose 在建模的时候,一般都会带上参数所有的校验规则,这也是程序员必须做的。
再加上业务层做的参数验证,这也就导致部分表里的数据做了重复的验证,加大了代码开发量。
最近在写个人项目的同时,尝试着把mongoose内置的验证器利用起来,以减少业务代码的重复。mongoose-validation
就是这么个小东西(后续还想支持express,就这么取名了)。
使用方法
mongoose
const UserSchema = new mongoose.Schema({
name: { type: String, required: true, unique: true },
...
}, {
collection: 'user',
id: false,
})
module.exports = mongoose.model('User', UserSchema)
middleware
const _ = require('lodash')
const validator = require('validator')
const mongooseValidation = require('mongoose-validation')
const { mongoose } = require('../lib/mongodb.lib')
module.exports = () => mongooseValidation({
throwError: true,
mongoose: mongoose,
errorFormatter: errors => {
return {
errors,
code: 'VD99',
}
},
customValidators: {
isMongoId: value => validator.isMongoId(value),
isMultiType: {
validator: value => ['remove', 'add', 'update'].indexOf(value) !== -1,
message: '`{ type }` must in ["remove", "add", "update"]'
},
isEmail: (value) => /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(value),
isMobile: value => /^1[3|4|5|7|8]\d{9}$/.test(value),
isString: value => _.isString(value),
isNumber: value => !isNaN(Number(value)),
isObject: value => _.isObject(value),
isJson: value => Object.prototype.toString.call(value).toLowerCase() === '[object object]',
isArray: value => _.isArray(value),
inArray: (param, ...args) => {
const validatorName = args[0]
return _.every(param, (item) => {
switch (validatorName) {
case 'isEmail': return /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(item)
case 'isMobile': return /^1[3|4|5|7|8]\d{9}$/.test(item)
case 'isString': return _.isString(item)
case 'isNumber': return _.isNumber(item)
case 'isObject': return _.isObject(item)
case 'isArray': return _.isArray(item)
case 'isBoolean':
switch (typeof item) {
case 'string': return item === 'true' || item === 'false'
case 'boolean': return item === true || item === false
default: return false
}
default:
return validator[validatorName].call(this, item)
}
})
},
isBoolean: (value) => {
switch (typeof value) {
case 'string':
return value === 'true' || value === 'false'
case 'boolean':
return value === true || value === false
default:
return false
}
},
custom: (value, callback) => {
if (typeof value !== 'undefined') {
return callback(value)
}
return false
},
},
})
controller
try {
await ctx.mongooseValidate({
data: ctx.request.body,
schema: {
_id: { validate: 'isMongoId', required: true },
multi: [{ type: String, validate: 'isMongoId' }],
},
necessary: ['type', 'multi'],
optional: ['_id'],
}, UserSchema)
} catch (e) {}
API
Middleware options
throwError
是否抛出Error,默认 :false
mongoose
mongoose 对象,必须传。
errorFormatter: function(errors){}
错误信息格式化。默认:function (errors) { return { errors: errors } }
customValidators
自定义验证。与 mongoose schema 的 validate
保持一致
Validate
ctx.mongooseValidate(options)
- options.data {Object}。需要校验的参数
- options.schema {Object}。自定义校验规则
- options.necessary {Array}。必须校验的参数
- options.optional {Array}。没有的话,可以跳过的参数
ctx._validationMongooseErrors
验证的结果。没有错误默认为[]
结语
两个都不是什么好轮子,至少笔者觉得某种场景下还是能派上用场,所以今天整理一下放了出来,读者觉得有用可以给个Star