gin参数校验使用的是 validator库,因此本文的内容同样适用于使用validator来做参数校验的地方
完整的校验规则可参考https://godoc.org/github.com/go-playground/validator,下面分享常用的校验规则规则:
数字值限制
注:如果限制之间存在冲突,如
eq=10,ne=10
,则会根据先后顺序,后面的会覆盖前面的定义,以后面定义的为准;校验内容为eq=10,ne=10
,只会生效ne=10
,如果存在冲突,所有校验规则均符合此规律。
max=10 # 最大值为10,即小于等于10
min=10 # 最小值为10,即大于等于10
gt=10 # 大于 10
gte=10 # 大于等于10
lt=10 # 小于10
lte=10 # 小于等于10
eq=10 # 等于10
ne=10 # 不等于10
oneof=1 2 3 # 值只能是1、2 或 3
len=3 # 限制位数为3位数
字符串长度限制
限制长度范围
max=10 # 最大长度为10
min=10 # 最小长度为10
gt=10 # 长度大于10
lt=10 # 长度小于10
gte=10 # 长度大于等于10
let=10 # 长度小于等于10
限制值内容
eq=aaa # 值为aaa
ne=aaa # 值不能为aaa
oneof=a b c # 枚举,只能为a、b 或 c
len=3 # 字符长度为3
存在性校验
注:这里需要特别注意的是,这里是否存在是根据0值与非0值判断的,如果字段中传了对应的0值,也会被认为是不存在的
必选
required # 必须
可选
omitempty,xxx=xxx # 可选,如果存在,则继续向后校验规则xxx=xxx,如果不存在,则xxx=xxx不生效,但是如果omitempty之前存在校验规则,则前面的校验规则还是生效的,如 gte=-1,omitempty,len=3,则gte=-1规则始终生效,而len=3只有在值不为0时生效。
关联校验
required_with=AAA # 当AAA存在时,此字段也必须存在
required_with_all=AAA BBB CCC # 当AAA BBB CCC 都存在时,此字段必须存在
required_without=AAA # 当AAA不存在时,此字段必须存在
required_without_all=AAA BBB # 当AAA BBB 都不存在时,此字段必须存在
结构体字段间校验
一个层级内部校验
eqfield=AAA # 和字段AAA值相等
nefield=AAA # 和字段AAA值不相等
gtfield=AAA # 大于字段AAA的值
gtefield=AAA # 大于等于字段AAA的值
ltfield=AAA # 小于字段AAA的值
ltefield=AAA # 小于等于AAA字段的值
多个层级之间校验
eqcsfield=AAA.B # 等于AAA中的B字段
necsfield=AAA.B # 不等于AAA中的B字段
gtcsfield=AAA.B # 大于AAA中的B字段
gtecsfield=AAA.B # 大于等于AAA中的B字段
ltcsfield=AAA.B # 小于AAA中的B字段
ltecsfield=AAA.B # 小于等于AAA中的B字段
注:此处要注意的是,多个结构体之间的校验必须是字段之间存在层级联系,且只能是上层的与下层的做关联,不能反过来,如下:
type Timeout struct {
Connect int `json:"connect" validate:"required"`
Read int `json:"read" validate:"required"`
Send int `json:"send" validate:"required"`
}
type MyStruct struct {
Name string `json:"name" validate:"required"`
Out int `json:"out" validate:"eqcsfield=Timeout.Connect"` // 只能是Timeout同层级的去关联Timeout下的字段,不能Timeout下的字段关联上层字段
Timeout Timeout `json:"timeout"`
}
dive 对数组校验
dive 的意思是潜水,潜水也就是深入到水下,dive在这里的意思是也差不多,就是更深入一层的意思,如当前字段类型是 []string
,那么深入一层,就从 整体(array)
到 局部(array中的一个元素)
,对应到校验上,就是dive前是对整体校验,dive后是对整体中的元素校验。示例:
一般数组
Domains []string `binding:"gt=0,dive,required,min=1,max=100"`
检验内容:[]string
长度必须大于0,数组中元素string长度必须在1-100之间
结构体数组校验
Cors []AStruct `binding:"dive"`
虽然这里dive
前后未定义相关校验规则,但是如果要启用 AStruct 中定义的校验规则,那么dive是必须的,否则AStruct
中定义的规则不会生效
dive对Map校验
dive
的含义在这里仍然类似于数组,整体 -> 局部
的规则不变,但是map的特殊性在于不仅有value值,还有key值,这里就需要方法去区分是校验value,还是校验key;这里使用了 keys 和 endkeys来标记key值的校验范围,从keys开始,至endkeys结束
ReqHeaders map[string]string `binding:"dive,keys,min=1,max=100,endkeys,required,min=1,max=100"`
校验内容:未对整体做校验,限制key值长度必须在1-100之间,value值的长度也必须在1-100之间
structonly
当一个结构体定义了校验规则,但是在某些地方,不需要这些校验规则生效的时候,可以使用structonly
标记,存在此标记的结构体内部的校验规则将不会再生效。如下:
type Timeout struct {
Connect int `json:"connect" binding:"required"`
Read int `json:"read" binding:"required"`
Send int `json:"send" binding:"required"`
}
type MyStruct struct {
Name string `json:"name" binding:"required"`
Timeout Timeout `json:"timeout" binding:"structonly"`
}
结构体Timeout
各个字段定义了校验内容,但是我在MyStruct
的Timeout
字段使用了structonly
标记,那么Timeout
中定义的校验内容将不再生效
nostructlevel
nostructlevel 类似于structonly,唯一不同之处是要想忽略规则的效果生效,必须要使用required
或 omitempty
type Timeout struct {
Connect int `json:"connect" binding:"required,gte=1,lte=1000"`
Read int `json:"read" binding:"gte=1,lte=1000"`
Send int `json:"send" binding:"gte=1,lte=1000"`
}
type MyStruct struct {
Name string `form:"name" json:"name" binding:"required"`
UseMeta bool `json:"use_meta"`
Timeout Timeout `json:"timeout" binding:"required,nostructlevel"`
}
忽略校验规则
- # 忽略字段校验规则,常用于嵌套结构中,用来忽略嵌套结构下的校验规则
多校验规则或
运算
| # 多个检验规则默认之间是与关系,使用 | 可实现多运算规则之间进行或运算
校验参数是定义在结构体上的,因此在首先需要定义好接受数据的结构体,再定义每个属性的校验参数:
type Timeout struct {
Connect int `binding:"omitempty,gte=1,lte=75"`
Read int `binding:"omitempty,gte=1,lte=1000"`
Send int `binding:"omitempty,gte=1,lte=1000"`
}
type MyTestStruct struct {
Domains []string `binding:"gt=0,dive,required,min=1,max=100"`
Name string `binding:"required"`
UseTimeout bool
Timeout Timeout `binding:"required_with=UseTimeout"`
}
结构体字段 binding
tag的内容就是校验参数。
gin中提供了常用的 ShouldBindWith/ShouldBindUri
方法,来实现自动的数据与结构体的绑定,提供了以下类型的数据:
数据类型 | Tag | Context-Type | 数据源 | 使用示例 |
---|---|---|---|---|
JSON | json | application/json | Body | ShouldBindWith(&data, binding.Query) |
XML | xml | application/xml、text/xml | Body | ShouldBindWith(&data, binding.XML) |
Form | form | application/x-www-form-urlencoded | Body | c.ShouldBindWith(&data, binding.Form) |
Query | form | url query | ShouldBindWith(&data, binding.Query) | |
FormPost | form | application/x-www-form-urlencoded | Body | ShouldBindWith(&data, binding.FormPost) |
FormMultipart | form | multipart/form-data | Body | ShouldBindWith(&data, binding.FormMultipart) |
ProtoBuf | application/x-protobuf | Body | ShouldBindWith(&data, binding.ProtoBuf) | |
MsgPack | application/x-msgpack、application/msgpack | Body | ShouldBindWith(&data, binding.MsgPack) | |
YAML | application/x-yaml | Body | ShouldBindWith(&data, binding.YAML) | |
Uri | uri | uri | ShouldBindUri(&data) | |
Header | header | Header | ShouldBindWith(&forwardRule, binding.Header) |
说明:
数据类型:指的是传输的数据类型
Tag:结构体定义的Tag,揭示了数据字段与结构体字段的对应关系,如:Form类型数据中 id
对应结构体中的ID
字段,那么就需要定义一下内容:
type xxxx struct {
...
ID int64 `form:"id"`
...
}
Context-Type:HTTP中的Header 字段之一,表示Body的数据类型,gin.Context
中的Bind
方法会根据Content-Type
自动绑定数据到对应类型上
func (c *Context) Bind(obj interface{}) error {
b := binding.Default(c.Request.Method, c.ContentType()) // 获取数据类型
return c.MustBindWith(obj, b) // 校验数据
}
注:
MustBindWith
与ShouldBindWith
的区别是MustBindWith
会自动返回错误信息,如果你有统一的错误返回格式,MustBindWith
可能会破坏返回格式的统一性。
数据源:指的是从哪里取数据,Body
指的是从HTTP Body中获取数据,url query
指从URL的quey参数中获取数据,url
指的是从url中获取参数,如:/api/v1/xxx/:id
,id参数就是从url中来获取的
使用示例:使用的例程
ShouldBindWith
会返回error信息,如果error信息不为空,则表明出现错误,错误信息就包含在error中,如果error为空,则表明校验通过,示例如下:
var data MyTestStruct // 存储数据内容
// 获取 & 校验数据
if err = c.ShouldBindWith(&data, binding.JSON); err != nil {
// 出现错误,打印错误内容
fmt.Println(err)
return
}
// 校验成功,打印数据内容
fmt.Println(data)