[TOC]
### egg 中使用 BetterValidate
>[danger] BetterValidate 依赖于 validator 插件
文档:[https://www.npmjs.com/package/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: "用户创建成功"
})
);
~~~
- 概述
- 起步
- 跨域配置
- 路径别名
- 路由
- api版本控制
- 错误和异常
- 全局异常处理
- 数据库
- 创建迁移文件
- sequelize数据类型
- 配置
- 新增
- 查询
- 条件查询
- 模糊查询
- 排序查询
- 聚合查询
- 分组查询
- 分页查询
- 修改
- 删除
- 获取器
- 修改器
- 静态属性
- 字段验证
- 外键约束
- 关联模型
- 一对一
- 一对多
- 左外连接
- 多对多
- 字段显示隐藏
- 事务
- 字段自增
- 验证层
- egg-validate
- indicative验证器
- egg-validate-plus
- betterValidate
- 校验规则
- 中间件
- 安全
- 数据加密
- 单向加密
- 示例代码
- 封装egg加密
- 上传
- path模块
- 单文件上传
- 多文件上传
- 按照日期存储
- 工具函数
- egg常用工具函数
- 缓存
- 配置缓存插件
- 设置缓存
- 获取缓存
- 删除缓存
- 消息队列
- rabbitMQ
- 安装
- 简单队列
- 工作队列
- 工作队列(dispach分发)
- 消息应答和持久化
- redis
- 数据类型
- 字符串类型(String)
- 哈希类型(Hash)
- 列表(List)
- 无序集合(Set)
- 可排序集合(Zset)
- 邮件系统
- nodeMailer
- 第三方模块
- 生成随机数
- JWT
- JWT鉴权
- 生成Token
- 短信服务
- 阿里大鱼短信验证码
- 发送短信逻辑
- 阿里短信Node类