betterValidate
egg 中使用 BetterValidate
>[danger] BetterValidate 依赖于 validator 插件
文档:https://www.npmjs.com/package/validator
app.js 构建 validate 目录 并且挂载到ctx
const path = require('path'); async didLoad() { // 1.全局异常处理 const { HttpExceptions } = require('./app/exceptions/http_exceptions') global.myErrors = HttpExceptions; // 2. betterValidate 挂载 ctx const validatorsPaths = this.app.loader.getLoadUnits().map(unit => path.join(unit.path, 'app/validators')); this.app.loader.loadToContext(validatorsPaths, 'validators', { call: true, fieldClass: 'validatorsClasses', }); }
建立 2个 BetterValidate 核心类库
- [ ] app / cores / utils.js
const findMembers = function (instance, { prefix, specifiedType, filter }) { // 递归函数 function _find(instance) { //基线条件(跳出递归) if (instance.proto === null) return []
let names = Reflect.ownKeys(instance)
names = names.filter((name) => {
// 过滤掉不满足条件的属性或方法名
return _shouldKeep(name)
})
return [...names, ..._find(instance.__proto__)]
}
function _shouldKeep(value) {
if (filter) {
if (filter(value)) {
return true
}
}
if (prefix)
if (value.startsWith(prefix))
return true
if (specifiedType)
if (instance[value] instanceof specifiedType)
return true
}
return _find(instance)
}
module.exports = { findMembers }
*****
* [ ] app / cores / valitators.js
const validator = require('validator') const { HttpExceptions } = require('../exceptions/http_exceptions') const { get, last, set, cloneDeep } = require("lodash") const { findMembers } = require('./utils')
class BetterValidate { constructor() { this.data = {} this.parsed = {} }
_assembleAllParams(ctx) {
return {
body: ctx.request.body,
query: ctx.request.query,
path: ctx.params,
header: ctx.request.header
}
}
get(path, parsed = true) {
if (parsed) {
const value = get(this.parsed, path, null)
if (value == null) {
const keys = path.split('.')
const key = last(keys)
return get(this.parsed.default, key)
}
return value
} else {
return get(this.data, path)
}
}
_findMembersFilter(key) {
if (/validate([A-Z])w+/g.test(key)) {
return true
}
if (this[key] instanceof Array) {
this[key].forEach(value => {
const isRuleType = value instanceof Rule
if (!isRuleType) {
throw new Error('验证数组必须全部为Rule类型')
}
})
return true
}
return false
}
async validate(ctx, alias = {}) {
this.alias = alias
let params = this._assembleAllParams(ctx)
this.data = cloneDeep(params)
this.parsed = cloneDeep(params)
const memberKeys = findMembers(this, {
filter: this._findMembersFilter.bind(this)
})
const errorMsgs = []
// const map = new Map(memberKeys)
for (let key of memberKeys) {
const result = await this._check(key, alias)
if (!result.success) {
errorMsgs.push(result.msg)
}
}
if (errorMsgs.length != 0) {
throw new HttpExceptions(errorMsgs)
}
ctx.v = this
return this
}
async _check(key, alias = {}) {
const isCustomFunc = typeof (this[key]) == 'function' ? true : false
let result;
if (isCustomFunc) {
try {
await this[key](this.data)
result = new RuleResult(true)
} catch (error) {
result = new RuleResult(false, error.msg || error.message || '参数错误')
}
// 函数验证
} else {
// 属性验证, 数组,内有一组Rule
const rules = this[key]
const ruleField = new RuleField(rules)
// 别名替换
key = alias[key] ? alias[key] : key
const param = this._findParam(key)
result = ruleField.validate(param.value)
if (result.pass) {
// 如果参数路径不存在,往往是因为用户传了空值,而又设置了默认值
if (param.path.length == 0) {
set(this.parsed, ['default', key], result.legalValue)
} else {
set(this.parsed, param.path, result.legalValue)
}
}
}
if (!result.pass) {
const msg = `${isCustomFunc ? '' : key}${result.msg}`
return {
msg: msg,
success: false
}
}
return {
msg: 'ok',
success: true
}
}
_findParam(key) {
let value
value = get(this.data, ['query', key])
if (value) {
return {
value,
path: ['query', key]
}
}
value = get(this.data, ['body', key])
if (value) {
return {
value,
path: ['body', key]
}
}
value = get(this.data, ['path', key])
if (value) {
return {
value,
path: ['path', key]
}
}
value = get(this.data, ['header', key])
if (value) {
return {
value,
path: ['header', key]
}
}
return {
value: null,
path: []
}
}
}
class RuleResult { constructor(pass, msg = '') { Object.assign(this, { pass, msg }) } }
class RuleFieldResult extends RuleResult { constructor(pass, msg = '', legalValue = null) { super(pass, msg) this.legalValue = legalValue } }
class Rule { constructor(name, msg, ...params) { Object.assign(this, { name, msg, params }) }
validate(field) {
if (this.name == 'isOptional')
return new RuleResult(true)
if (!validator[this.name](field + '', ...this.params)) {
return new RuleResult(false, this.msg || this.message || '参数错误')
}
return new RuleResult(true, '')
}
}
class RuleField { constructor(rules) { this.rules = rules }
validate(field) {
if (field == null) {
// 如果字段为空
const allowEmpty = this._allowEmpty()
const defaultValue = this._hasDefault()
if (allowEmpty) {
return new RuleFieldResult(true, '', defaultValue)
} else {
return new RuleFieldResult(false, '字段是必填参数')
}
}
const filedResult = new RuleFieldResult(false)
for (let rule of this.rules) {
let result = rule.validate(field)
if (!result.pass) {
filedResult.msg = result.msg
filedResult.legalValue = null
// 一旦一条校验规则不通过,则立即终止这个字段的验证
return filedResult
}
}
return new RuleFieldResult(true, '', this._convert(field))
}
_convert(value) {
for (let rule of this.rules) {
if (rule.name == 'isInt') {
return parseInt(value)
}
if (rule.name == 'isFloat') {
return parseFloat(value)
}
if (rule.name == 'isBoolean') {
return value ? true : false
}
}
return value
}
_allowEmpty() {
for (let rule of this.rules) {
if (rule.name == 'isOptional') {
return true
}
}
return false
}
_hasDefault() {
for (let rule of this.rules) {
const defaultValue = rule.params[0]
if (rule.name == 'isOptional') {
return defaultValue
}
}
}
}
module.exports = { Rule, BetterValidate }
*****
3. 使用BetterValidate 进行参数验证
app / validators / user / register.js
* [ ] 这里进行User模块的注册验证
const { BetterValidate, Rule } = require('../../cores/valitators');
/**
用户注册校验 */ class Register extends BetterValidate {
constructor() { super() this.email = [ new Rule('isEmail', '不符合Email规范') ],
this.password = [ new Rule('isLength', '密码至少6个字符,最多32个字符', { min: 6, max: 32 }), new Rule('matches', '密码必须是字母和数字组合', '^(?![0-9]+$)(?![a-zA-Z]+$)[0-9a-zA-Z]') ], this.passwordConfirm = this.password, this.nickname = [ new Rule('isLength', '昵称最少2个字符,最大6个字符', {min:2,max:30}) ]
}
/**
自定义验证规则
@param { String } value POST表单提交过来的值 */ validatePassword(value) { // body.password 表单提交的 password 字段 const pwd = value.body.password; const pwdConfirm = value.body.passwordConfirm;
if (!Object.is(pwd,pwdConfirm)) { throw new Error('两次密码输入不一致!') } } }
module.exports = Register;
*****
### 更多验证规则
const { BetterValidate, Rule } = require('../../cores/valitators'); /**
用户登录校验 */ class Login extends BetterValidate {
constructor() { super() this.account = [ new Rule('isLength', '账号最少4位,不能超过32位', { min: 4, max: 32 }) ], this.secret = [ // 存在则验证,不存在则不验证 new Rule('isOptional'), new Rule('isLength', '至少6个字符', { min: 6, max: 128 }) ] }
/**
- 自定义验证规则
- @param { String } value POST表单提交过来的值 */ validateType(value) { const type = value.body.type; if (!type) throw new Error('type 不能为空!'); if (!this._checkTypes(type)) throw new Error('type类型必须为 100 101 102 200'); }
// 自定义枚举类型 _checkTypes(type) {
const allowed = { USER_MINI_PROGRAM: 100, USER_EMAIL: 101, USER_MOBILE: 102, ADMIN_EMAIL: 200, WEIXIN_LOGIN:300 } for (let [key, val] of Object.entries(allowed)) { if ( val === Number(type) ) { return true; } } return false;
} }
module.exports = Login;
*****
### 控制器进行参数验证
~~~
// 使用
const v = await new RegisterValidator().validate(ctx);
// 取数据
const nickname = v.get("body.nickname");
await userDao.createUser(ctx, v);
ctx.json(
new Success({
msg: "用户创建成功"
})
);
~~~