【Go】参数验证,对象数组核验-validator
创始人
2024-04-27 07:35:05
0

文章目录

  • 背景
  • 功能介绍
    • 范围比较验证
    • 标记之间特殊符号说明
    • 字符串验证
    • 特殊字符串验证
    • 例子
  • 扩展问题
    • 我的问题
      • 验证slice
        • 举例
        • 输出
        • 说明
        • 详细举例
      • 二维slice
        • 举例:
        • 说明
      • map核验
        • 举例
        • 输出
        • 说明
      • 嵌套map核验
        • 举例
        • 说明
  • 参考

背景

一直做的 go 项目中想要方便简洁的对接口参数字段进行核验,选择与 gin 框架适配的validator库作为验证组件,基础用法都比较简单,对字符数字型值校验,对切片、数组、map 类型的范围检验,基础使用起来都比较简单易懂好上手,但近期突然遇到一个意外错误,本来该校验的对象数组,却没有按照我预计的核验这个数组里每个对象所属字段的值是否符合要求,由此记录一下。

功能介绍

范围比较验证

范围验证: 切片、数组和map、字符串,验证其长度;数值,验证大小范围

  • lte:小于等于参数值,validate:“lte=3” (小于等于3)
  • gte:大于等于参数值,validate:“lte=120,gte=0” (大于等于0小于等于120)
  • lt:小于参数值,validate:“lt=3” (小于3)
  • gt:大于参数值,validate:“lt=120,gt=0” (大于0小于120)
  • len:等于参数值,validate:“len=2”,字符串长度必须为n,或者是数组、切片、map的len的值
  • max:最大值,小于等于参数值,validate:“max=20” (小于等于20)
  • min:最小值,大于等于参数值,validate:“min=2,max=20” (大于等于2小于等于20)
  • ne:不等于,validate:“ne=2” (不等于2)
  • oneof:只能是列举出的值其中一个,这些值必须是数值或字符串,以空格分隔,如果字符串中有空格,将字符串用单引号包围,validate:“oneof=red green”

标记之间特殊符号说明

  • 逗号( ,):把多个验证标记隔开。注意:隔开逗号之间不能有空格, validate:“lt=0,gt=100”,逗号那里不能有空格,否则panic
  • 横线( - ):跳过该字段不验证
  • 竖线( | ):使用多个验证标记,但是只需满足其中一个即可
  • required:表示该字段值必输设置,且不能为默认值
  • omitempty:如果字段未设置,则忽略它

字符串验证

  • contains:包含参数子串,validate:“contains=tom” (字段的字符串值包含tom)
  • excludes:包含参数子串,validate:“excludes=tom” (字段的字符串值不包含tom)
  • startswith:以参数子串为前缀,validate:“startswith=golang”
  • endswith:以参数子串为后缀,validate:“startswith=world”

特殊字符串验证

  • email:验证字符串是email格式。默认为必填
  • url:验证字符串是URL格式。默认为必填
  • uri:字段值是否包含有效的uri,validate:“uri”
  • ip:字段值是否包含有效的IP地址,validate:“ip”
  • ipv4:字段值是否包含有效的ipv4地址,validate:“ipv4”
  • ipv6:字段值是否包含有效的ipv6地址,validate:“ipv6”

例子

简单举例,可自行测试

type UserInfo struct {ID   int    `validate:"gt=0"`Age  int    `validate:"gt=0"`Name string `validate:"required"`Sex  string `validate:"required"`
}
func InitUserInfo(id, age int, name, sex string) *UserInfo {// new一个校验器valid := validator.New()// 初始化UserInfouserInfo := &UserInfo{ID:   id,Age:  age,Name: name,Sex:  sex,}if err := valid.Struct(userInfo); err != nil {fmt.Println("参数校验不通过", err)}return userInfo
}
func TestValidate(t *testing.T) {InitUserInfo(1, 2, "kevin", "男") // 参数校验通过InitUserInfo(0, 2, "kevin", "男") // 参数校验不通过 Key: 'UserInfo.ID' Error:Field validation for 'ID' failed on the 'gt' tagInitUserInfo(1, 2, "kevin", "")  // 参数校验不通过 Key: 'UserInfo.Sex' Error:Field validation for 'Sex' failed on the 'required' tag
}

扩展问题

我的问题

验证slice

因为项目的接口参数的结构体是复杂,往往嵌套着对象数组或切片,不是简单由一个结构体就可以概括接收,在项目后续突然发现一个本该检验的对象数组里的某个参数,因对方方也疏忽未传值,导致后面检验出现问题,排查之前虽然问题不在我这边,但的却这个校验字段没有生效,后续排查了一下对于数组或切片或 Map 类型的检验需要使用 dive 才能核验到下一层级的字段里。

举例

sliceone := []string{"123", "onetwothree", "myslicetest", "four", "five"} 
validate.Var(sliceone, "max=15,dive,min=4")

输出

Key: '[0]' Error:Field validation for '[0]' failed on the 'min' tag
Key: '' Error:Field validation for '' failed on the 'min' tag

说明

第二个参数中tag关键字 dive 前面的 max=15,验证 [] , 也就是验证slice的长度,
dive 后面的 min=4,验证slice里的值长度,也就是说 dive 后面的 tag 验证 slice 的值

详细举例

结合上面简单使用的例子使用 dive 核验切片数组对象字段实现


import ("fmt""github.com/go-playground/validator/v10""testing"
)type UserInfo struct {ID   int    `validate:"gt=0"`Age  int    `validate:"gt=0"`Name string `validate:"required"`Sex  string `validate:"required"`
}type Teacher struct {Member []*UserInfo `validate:"max=15,dive,required"`
}func InitUserInfo(id, age int, name, sex string) *UserInfo {// new一个校验器valid := validator.New()// 初始化UserInfouserInfo := &UserInfo{ID:   id,Age:  age,Name: name,Sex:  sex,}var menber []*UserInfomenber = append(menber, userInfo)teacher := &Teacher{Member: menber,}if err := valid.Struct(teacher); err != nil {fmt.Println("参数校验不通过", err)}return userInfo
}func TestValidate(t *testing.T) {InitUserInfo(1, 2, "kevin", "男") // 参数校验通过InitUserInfo(0, 2, "kevin", "男") // 参数校验不通过 Key: 'UserInfo.ID' Error:Field validation for 'ID' failed on the 'gt' tagInitUserInfo(1, 2, "kevin", "")  // 参数校验不通过 Key: 'UserInfo.Sex' Error:Field validation for 'Sex' failed on the 'required' tag
}

当教师结构体中的参数内包含用户信息结构体,需要核验用户信息的字段时候就需要加上validate:"max=15,dive,required",这样就很核验用户信息结构体字段是否也符合要求
输出如下
在这里插入图片描述
也可不加前面的长度,直接validate:"dive,required"使用

二维slice

当然二维切片也差不多原理

举例:

slicethree := [][]string{} validate.Var(slicethree, "min=2,dive,len=2,dive,required") 
validate.Var(slicethree, "min=2,dive,dive,required")

说明

这里有2个 dive,刚好深入到二维slice,但他们也有不同之处,第二个表达式的第一个dive后没有设置tag。
第一个验证表达式:
min=2:验证第一个 [] 方括号的值长度 ;
len=2:验证第二个 []string 长度;
required:验证slice里的值
第二个验证表达式:
min=2:验证第一个 [] 方括号的值长度 ;
dive: 后没有设置tag值,不验证第二个 []string ;
required: 验证slice里的值

map核验

map的验证中也需要tag关键字 dive, 另外,它还有 keys 和 endkeys 两tag,验证这2个tag之间map的 key,而不是value值。

举例

var mapone map[string]string mapone = map[string]string{"one": "jimmmy", "two": "tom", "three": ""}validate := validator.New() err := validate.Var(mapone, "gte=3,dive,keys,eq=1|eq=2,endkeys,required")

输出

Key: '[three]' Error:Field validation for '[three]' failed on the 'eq=1|eq=3' tag
Key: '[three]' Error:Field validation for '[three]' failed on the 'required' tag
Key: '[one]' Error:Field validation for '[one]' failed on the 'eq=1|eq=3' tag
Key: '[two]' Error:Field validation for '[two]' failed on the 'eq=1|eq=3' tag

说明

gte=3:验证map自己的长度;
dive后的 keys,eq=1|eq=2,endkeys:验证map的keys个数,也就是验证 [] 里值。上例中定义了一个string,所以明显报了3个错误。
required:验证 map的值value

嵌套map核验

如:map[[3]string]string,和上面slice差不多,使用多个 dive

举例

var maptwo map[[3]string]string{} validate.Var(maptwo, "gte=3,dive,keys,dive,eq=1|eq=3,endkeys,required")

说明

gte=3: 验证map的长度;
keys,dive,eq=1|eq=3,endkeys:keys和endkeys中有一个dive(深入一级),验证map中key的数组每一个值
required: 验证map的值

参考

golang常用库:字段参数验证库-validator使用
validator代码地址

相关内容

热门资讯

原生安卓系统6.0精简,极致体... 亲爱的手机控们,你是否曾为手机系统臃肿、运行缓慢而烦恼?今天,就让我带你一探究竟,揭秘原生安卓系统6...
安卓系统与嵌入式系统,安卓系统... 你知道吗?在科技的世界里,有一种系统,它就像是个万能的魔法师,既能掌控手机、平板,又能深入到各种智能...
风驰软件安卓系统行吗,引领智能... 你有没有想过,手机上的软件是不是也能像风一样自由驰骋呢?今天,咱们就来聊聊这个话题——风驰软件在安卓...
安卓系统账户哪里查看,轻松查看... 你有没有想过,你的安卓手机里藏着多少秘密?别急,今天就来带你一探究竟,揭秘安卓系统账户的藏身之处!一...
鸿蒙系统和安卓系统跟ios,三... 你知道吗?在智能手机的世界里,有三个小家伙一直在暗中较劲,它们就是鸿蒙系统、安卓系统和iOS。今天,...
安卓系统登苹果账号,体验无缝跨... 你有没有想过,在安卓手机上登录苹果账号,这竟然也能成为一门学问呢?没错,随着科技的发展,跨平台操作变...
安卓系统 投屏 USb,安卓系... 你有没有想过,家里的电视和电脑是不是也能像手机一样,随时随地接上USB设备就能用呢?今天,就让我带你...
索尼平板安装安卓系统,系统升级... 亲爱的读者们,你是否曾为索尼平板电脑的局限性而感到烦恼?想要摆脱原生的系统束缚,体验安卓世界的无限可...
安卓系统的苹果游戏,跨平台体验... 你知道吗?在安卓的世界里,竟然藏着苹果的宝藏!没错,就是那些让人爱不释手的苹果游戏。今天,就让我带你...
安卓系统版本已停用,已停用版本... 你有没有发现,你的安卓手机最近是不是有点儿“老态龙钟”了?别急,让我来给你揭秘为什么你的安卓系统版本...
安卓系统老年拨号界面,关爱长辈... 你有没有发现,随着智能手机的普及,越来越多的老年人也开始尝试使用这些神奇的设备啦!不过,说起安卓系统...
安卓系统如何转换字体,轻松实现... 你有没有发现,手机上的字体有时候看久了眼睛都累了呢?别急,今天就来教你怎么给安卓手机换个新字体,让你...
禁止安卓系统更新运行,安卓系统... 你有没有遇到过这种情况?手机提示更新安卓系统,但你就是不想让它动弹?别急,今天就来聊聊这个让人头疼的...
安卓模拟苹果模拟系统,打造跨平... 你有没有想过,在安卓手机上也能体验到苹果系统的魅力呢?没错,这就是今天我要跟你分享的神奇世界——安卓...
安卓系统自动生成流量,揭秘背后... 你知道吗?最近在安卓系统上,有个小秘密引起了大家的热议。那就是安卓系统竟然会自动生成流量!是不是听起...
电脑上装安卓系统教程,电脑安装... 你是不是也和我一样,对电脑上装安卓系统这个神奇的操作充满了好奇?想象在电脑上就能享受到安卓手机的便捷...
安卓系统手表王者荣耀,指尖上的... 你有没有发现,最近安卓系统手表界可是热闹非凡呢?尤其是那些喜欢玩王者荣耀的小伙伴,简直是不能错过这个...
安卓系统如何操作cad,利用安... 你有没有想过,在安卓手机上也能轻松操作CAD软件呢?没错,现在就让我带你一步步探索如何在安卓系统上玩...
安卓如何操控苹果系统,揭秘跨平... 你知道吗?在这个科技飞速发展的时代,安卓和苹果两大操作系统之间的较量可是从未停歇。虽然它们各自有着忠...
安卓系统账户同步数据,畅享无缝... 你有没有遇到过这种情况:手机里存了那么多宝贝照片、重要文件,结果换了个新手机,却发现那些宝贝全都不翼...