zoukankan      html  css  js  c++  java
  • joi库 学习笔记

    零、背景


    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

  • 相关阅读:
    Java基础学习(五) String类
    Java基础学习(四) java8线程
    Java基础学习(三) IO
    Java基础学习(二) 集合
    Java基础学习(一) 基本数据类型和引用数据类型
    枚举类常见漏洞解决
    数据校验
    postman如何传递token进行接口测试
    Spring Cloud-OpenFegin
    SpringCloud-Eureka
  • 原文地址:https://www.cnblogs.com/xjnotxj/p/9386208.html
Copyright © 2011-2022 走看看