betterValidate

优质
小牛编辑
133浏览
2023-12-01

egg 中使用 BetterValidate

>[danger] BetterValidate 依赖于 validator 插件

文档:https://www.npmjs.com/package/validator

  1. 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. 建立 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: "用户创建成功"
      })
    );
~~~