零、背景


node.js 应用中,req.query / req.body 传来的参数需要做 valication( 合法性验证 )

一、安装


https://github.com/hapijs/joi

npm i joi --save

const Joi = require('Joi');

二、基本用法


Joi.validate(value, schema, [options]);

1、通过验证


这里我们定义了三个字段:name(姓名)、age(年龄)、sex(性别)

router.post('/create', function (req, res, next) {

  const schema = Joi.object().keys({
name: Joi.string().min(2).max(20).required(),
age: Joi.number().min(0).max(100).required(),
sex: Joi.string().valid(['男', '女']),
}) const result = Joi.validate({ name: '小明', age: 12, sex: "男" }, schema); res.send(result); });

return:

{
"error": null,
"value": {
"name": "小明",
"age": 12,
"sex": "男"
}
}

总结:

判断 result.error === null 为 true 后,直接拿 result.value

2、不通过验证


代码同上,不同的地方如下:

const result = Joi.validate({ name: '小', age: -1, sex: "跨性别者" }, schema);

return:

{
"error": {
"isJoi": true,
"name": "ValidationError",
"details": [
{
"message": "\"name\" length must be at least 2 characters long",
"path": [
"name"
],
"type": "string.min",
"context": {
"limit": 2,
"value": "小",
"key": "name",
"label": "name"
}
}
],
"_object": {
"name": "小",
"age": -1,
"sex": "跨性别者"
}
},
"value": {
"name": "小",
"age": -1,
"sex": "跨性别者"
}
}

总结:

判断 result.error === null 为 false 后,直接拿 result.error 值。

注 1:哪怕 name、age、sex 三个变量我都传非法值,result.error.details 这个数组也只有一个元素,所以想要打印错误信息,直接取 result.error.details[0].message

注 2:如果想打印出所有的错误信息,改写如下:


const result = Joi.validate({ name: '小', age: 12, sex: "跨性别者" }, schema, { abortEarly: false });

return:

{
"error": {
"isJoi": true,
"name": "ValidationError",
"details": [
{
"message": "\"name\" length must be at least 2 characters long",
"path": [
"name"
],
"type": "string.min",
"context": {
"limit": 2,
"value": "小",
"key": "name",
"label": "name"
}
},
{
"message": "\"sex\" must be one of [男, 女]",
"path": [
"sex"
],
"type": "any.allowOnly",
"context": {
"value": "跨性别者",
"valids": [
"男",
"女"
],
"key": "sex",
"label": "sex"
}
}
],
"_object": {
"name": "小",
"age": 12,
"sex": "跨性别者"
}
},
"value": {
"name": "小",
"age": 12,
"sex": "跨性别者"
}
}

三、单独使用


joi 不仅仅作用于 scheme 对象,还可以单独使用。

1、通过验证


  const result = Joi.validate("小明", Joi.string().min(2).max(20).required());

  res.send(result);

return:

{
"error": null,
"value": "小明"
}

2、不通过验证


  const result = Joi.validate("小", Joi.string().min(2).max(20).required());

  res.send(result);

return:

{
"error": {
"isJoi": true,
"name": "ValidationError",
"details": [
{
"message": "\"value\" length must be at least 2 characters long",
"path": [],
"type": "string.min",
"context": {
"limit": 2,
"value": "小",
"label": "value"
}
}
],
"_object": "小"
},
"value": "小"
}

四、验证规则


对一个字段的基本的验证规则是:

类型 / 长度范围 / 取值范围 / 是否必填 / 与其它字段的关系 / 默认值

1、类型


//任意类型

any()

//指定类型

array()

boolean()

binary()

date()

func()

number()

object()

string()

类型下还有子约束,如下面的integer()alphanum()等:

//Requires the number to be an integer (no floating point).
Joi.number().integer(), //Requires the string value to only contain a-z, A-Z, and 0-9.
Joi.string().alphanum() Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/), Joi.string().email()

注1:除了 类型约束 ,其他约束都叫 子约束

注2:先写 类型约束 才能继续“点写” 子约束

注3:类型约束 和 子约束 的适用关系详看:https://github.com/hapijs/joi/blob/v13.4.0/API.md

注4:any() 类型 下的 子约束 可以应用在其它任意类型下

枚举类型可以参考下面的3 - (1)

2、长度范围


min() / max()

Joi.number().min(2)

Joi.array().max(5)

3、取值范围


(1) valid - 白名单

可以用来实现枚举类型

a: Joi.any().valid('a'),

b: Joi.any().valid('b', 'B'),

c: Joi.any().valid(['c', 'C'])

(2) invalid - 黑名单

a: Joi.any().invalid('a'),

b: Joi.any().invalid('b', 'B'),

c: Joi.any().invalid(['c', 'C'])

(3) allow - 白名单的补充

a: Joi.any().allow('a'),

b: Joi.any().allow('b', 'B'),

c: Joi.any().allow(['c', 'C'])

4、是否必填


只对 undefined 有效,null 会认为不合法

Joi.any().required()

代码见下面的6 - (2)

5、与其它字段的关系


(1) with / without / or

如现在有 a、b 两个字段:

 const schema = Joi.object().keys({
a: Joi.any(),
b: Joi.any()
}).with('a', 'b');

a.with('a', 'b') //a 和 b 必须都要填写

b.without('a', 'b'); //a 和 b 只能填写其中一个

c.or('a', 'b') //b 和 b 至少填写一个

(2) when

需求:验证条件是男人必须 50-100 岁,女人必须 0-50岁

  const schema = Joi.object().keys({
name: Joi.string().min(2).max(20).required(),
age: Joi.number().min(0).max(100).required().when('sex', {
is: '男',
then: Joi.number().min(50).max(100),
otherwise: Joi.number().min(0).max(50),
}),
sex: Joi.string().valid(['男', '女']),
}) const result = Joi.validate({ name: '小明', age: 60, sex: "女" }, schema);

return:

{
"error": {
"isJoi": true,
"name": "ValidationError",
"details": [
{
"message": "\"age\" must be less than or equal to 50",
"path": [
"age"
],
"type": "number.max",
"context": {
"limit": 50,
"value": 60,
"key": "age",
"label": "age"
}
}
],
"_object": {
"name": "小明",
"age": 60,
"sex": "女"
}
},
"value": {
"name": "小明",
"age": 60,
"sex": "女"
}
}

6、默认值


只对 undefined 有效,null 会认为不合法

(1) 无规则
  const result = Joi.validate(undefined, Joi.string());

return:

{
"error": null
}

注意:没有 value 值

(2) 加上 required()
  const result = Joi.validate(undefined, Joi.string().required());

return:

{
"error": {
"isJoi": true,
"name": "ValidationError",
"details": [
{
"message": "\"value\" is required",
"path": [],
"type": "any.required",
"context": {
"label": "value"
}
}
]
}
}
(3) 加上 default()
  const result = Joi.validate(undefined, Joi.string().default("空"));

return:

{
"error": null,
"value": "空"
}

五、验证规则的补充


1、对某个字段加上多个约束

验证条件为即可是string值也可是number值:

  const result = Joi.validate(23, [Joi.string(), Joi.number()]);

2、对多余传进来的变量不要理会

下面多传了一个 hometown 字段

  const schema = Joi.object().keys({
name: Joi.string().min(2).max(20),
age: Joi.number().min(0).max(100).required(),
sex: Joi.string().valid(['男', '女']),
}) const result = Joi.validate({ name: '小明', age: 12, sex: "男", hometown: "上海" }, schema);

return:

{
"error": {
"isJoi": true,
"name": "ValidationError",
"details": [
{
"message": "\"hometown\" is not allowed",
"path": [
"hometown"
],
"type": "object.allowUnknown",
"context": {
"child": "hometown",
"key": "hometown",
"label": "hometown"
}
}
],
"_object": {
"name": "小明",
"age": 12,
"sex": "男",
"hometown": "上海"
}
},
"value": {
"name": "小明",
"age": 12,
"sex": "男",
"hometown": "上海"
}
}

解决办法:

options 参数加上 { allowUnknown: true }

  const result = Joi.validate({ name: '小明', age: 12, sex: "男", hometown: "上海" }, schema, { allowUnknown: true });

return:

{
"error": null,
"value": {
"name": "小明",
"age": 12,
"sex": "男",
"hometown": "上海"
}
}

注意:value 里也会保留多传的 hometown 字段

六、坑


1、Jio 自动转数据类型


例一

  const result = Joi.validate("true", Joi.boolean());

return:

{
"error": null,
"value": true
}

例二

  const result = Joi.validate("1", Joi.number());

return:

{
"error": null,
"value": 1
}

Joi 会在觉得恰当的时候帮你自动转换数据类型使之更容易匹配上规则。但是,这样往往适得其反。

三种方法可以解决这个问题:

(1) 使用 strict() 子约束

拿上文的例一做改造:

  const result = Joi.validate("true", Joi.boolean().strict());

return:

{
"error": {
"isJoi": true,
"name": "ValidationError",
"details": [
{
"message": "\"value\" must be a boolean",
"path": [],
"type": "boolean.base",
"context": {
"label": "value"
}
}
],
"_object": "true"
},
"value": "true"
}
(2) 使用 Joi.extend 扩展 Joi 类

其实原理也是使用 strict() 子约束,但不用显式调用了

    Joi = Joi.extend({
name: 'boolean',
base: Joi.boolean().strict()
}); const result = Joi.validate("true", Joi.boolean());

return:

{
"error": {
"isJoi": true,
"name": "ValidationError",
"details": [
{
"message": "\"value\" must be a boolean",
"path": [],
"type": "boolean.base",
"context": {
"label": "value"
}
}
],
"_object": "true"
},
"value": "true"
}

[拓展]

如何用 Joi.extend 添加新的 类型约束 去校验手机号?

    Joi = Joi.extend({
name: 'mobile',
base: Joi.string().regex(/^1[34578]\d{9}$/)
}); const result = Joi.validate("1230000000", Joi.mobile());

return:

{
"error": {
"isJoi": true,
"name": "ValidationError",
"details": [
{
"message": "\"value\" with value \"1230000000\" fails to match the required pattern: /^1[34578]\\d{9}$/",
"path": [],
"type": "string.regex.base",
"context": {
"pattern": {},
"value": "1230000000",
"label": "value"
}
}
],
"_object": "1230000000"
},
"value": "1230000000"
}
(3) 将错就错,直接拿 result.value 的值

不管怎样,result.value 的值做后续操作是一个好习惯。

七、与 sequelize 混用


待写……

本人试用了 joi-sequelize 库 [https://github.com/mibrito/joi-sequelize],发现有很多坑,这里不推荐了。考虑以后自己写一个吧。

joi-sequelize 的缺点:

1、库最近的提交是一年前了,一些 issue 也呈搁置状态

2、Bug [ 我已提交issue ]:使用时,model define 里的 DataTypes 会缺失很多类型值。例如我想定义一个 interest [兴趣爱好]的属性,写为DataTypes.ARRAY(DataTypes.STRING),却报错 TypeError: DataTypes.ARRAY is not a function

---下面这几点也不能怪他,毕竟数据库原生也没有提供这些定义---

3、依旧不能很好的表示 min(n)max(n), 尤其是 min(n)

4、依旧不能很好的不能表示“与其它字段的关系”

八、与 mongoose 混用


待写……

九、与前端混用


详见:joi-browser

https://github.com/jeffbski/joi-browser


参考资料:

[1]http://imweb.io/topic/572561798a0819f17b7d9d3e

[2]https://codeburst.io/joi-validate-input-and-define-databases-in-javascript-84adc6f1474b

joi库 学习笔记的更多相关文章

  1. numpy, matplotlib库学习笔记

    Numpy库学习笔记: 1.array()   创建数组或者转化数组 例如,把列表转化为数组 >>>Np.array([1,2,3,4,5]) Array([1,2,3,4,5]) ...

  2. muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor

    目录 muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor Connector 系统函数connect 处理非阻塞connect的步骤: Connetor时序图 Accep ...

  3. muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制

    目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...

  4. muduo网络库学习笔记(三)TimerQueue定时器队列

    目录 muduo网络库学习笔记(三)TimerQueue定时器队列 Linux中的时间函数 timerfd简单使用介绍 timerfd示例 muduo中对timerfd的封装 TimerQueue的结 ...

  5. C++STL标准库学习笔记(三)multiset

    C++STL标准库学习笔记(三)multiset STL中的平衡二叉树数据结构 前言: 在这个笔记中,我把大多数代码都加了注释,我的一些想法和注解用蓝色字体标记了出来,重点和需要关注的地方用红色字体标 ...

  6. 【python】numpy库和matplotlib库学习笔记

    Numpy库 numpy:科学计算包,支持N维数组运算.处理大型矩阵.成熟的广播函数库.矢量运算.线性代数.傅里叶变换.随机数生成,并可与C++/Fortran语言无缝结合.树莓派Python v3默 ...

  7. C++STL标准库学习笔记(一)sort

    前言: 近来在学习STL标准库,做一份笔记并整理好,方便自己梳理知识.以后查找,也方便他人学习,两全其美,快哉快哉! 这里我会以中国大学慕课上北京大学郭炜老师的<程序设计与算法(一)C语言程序设 ...

  8. pandas库学习笔记(二)DataFrame入门学习

    Pandas基本介绍——DataFrame入门学习 前篇文章中,小生初步介绍pandas库中的Series结构的创建与运算,今天小生继续“死磕自己”为大家介绍pandas库的另一种最为常见的数据结构D ...

  9. libev事件库学习笔记

    一.libev库的安装 因为个人的学习环境是在ubuntu 12.04上进行的,所以本节仅介绍该OS下的安装步骤. 使用系统工具自动化安装: sudo apt-get install libev-de ...

随机推荐

  1. 再读c++primer plus 001

    1. OOP强调的是在运行阶段(而不是编译阶段)进行决策,运行阶段指的是程序正在运行时,编译阶段指的是编译器将程序组合起来时. 2.变量的值都存储在栈中,而new从被称为堆或自由存储区的内存区域分配内 ...

  2. 2018.10.24 bzoj2064: 分裂(状压dp)

    传送门 状压dp好题. 考虑对于两个给出的集合. 如果没有两个元素和相等的子集,那么只能全部拼起来之后再拆开,一共需要n1+n2−2n1+n2-2n1+n2−2. 如果有呢? 那么对于没有的就是子问题 ...

  3. jQuery 常用效果

    hide和show 同样有 fadeInhe fadeOut 的功能 $(document).ready(function(){ $("#hide").click(function ...

  4. myBatis中if test 字符串注意事项

    错误写法: <if test="userName == 'boshen'"> AND `USER_NAME` = #{userName} </if> 正确写 ...

  5. HDU 1404 Digital Deletions (暴力博弈)

    题意:给定一个数字串,最长是6,然后有两种操作. 第一种是,把该串中的一个数字换成一个比该数字小的数,比如 5 可以换成 0,1,2,3,4.   e.g. 12345 --> 12341 第二 ...

  6. 安卓修改开机logo和开机动画的方法

    第一种和第二种方法亲测可用,安卓版本是4.2和安卓5.1均可.第二种方法待验证 以下三种方法 Android 开机其实总共会出现3个画面: 1.第一个就是 linux 系统启动,出现Linux小企鹅画 ...

  7. oss上传大文件

    最近公司做工程项目,实现文件云存储上传. 网上找了一天,发现网上很多代码都存在相似问题,最后终于找到了一个满足我需求的项目. 工程如下: 这里对项目的文件传输功能做出分析,怎么实现文件上传的,如何进行 ...

  8. 20171126--fragment的小项目

    1.在使用fragment时候,初始化的时候报了两个错误,解决方法如下文所示:https://www.2cto.com/kf/201706/650158.html 其实一共报了两个错误: androi ...

  9. 堆操作,malloc

    PS:堆空间缺省值都是cd,栈空间缺省值都是cc 内存有四区:栈.全局(静态).常量.除此以外的空间暂时不能随意使用,但是通过malloc函数申请就可以使用了. 利用malloc申请一个int变量,注 ...

  10. Shell编程-09-Shell中的函数

    目录 基本语法 函数执行 函数示例     函数可以简化程序的代码量,达到更好的代码复用度,因此会让程序变得更加易读.简洁和易修改.其作用就是将需要多次使用的代码整合到一块,使其成为一个整体,然后通过 ...