[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類