Gin请求参数校验

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各个字段定义了校验内容,但是我在MyStructTimeout字段使用了structonly标记,那么Timeout中定义的校验内容将不再生效

  • nostructlevel

    nostructlevel 类似于structonly,唯一不同之处是要想忽略规则的效果生效,必须要使用requiredomitempty

    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"`
    }
    
  • 忽略校验规则

    - # 忽略字段校验规则,常用于嵌套结构中,用来忽略嵌套结构下的校验规则
    
  • 多校验规则运算

    | # 多个检验规则默认之间是与关系,使用 | 可实现多运算规则之间进行或运算
    

gin中的使用方法

1. 校验参数定义

校验参数是定义在结构体上的,因此在首先需要定义好接受数据的结构体,再定义每个属性的校验参数:

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的内容就是校验参数。

2. 数据绑定

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) // 校验数据
    }
    

    注:MustBindWithShouldBindWith的区别是MustBindWith会自动返回错误信息,如果你有统一的错误返回格式,MustBindWith可能会破坏返回格式的统一性。

  • 数据源:指的是从哪里取数据,Body指的是从HTTP Body中获取数据,url query指从URL的quey参数中获取数据,url指的是从url中获取参数,如:/api/v1/xxx/:id,id参数就是从url中来获取的

  • 使用示例:使用的例程

3. 校验结果获取

ShouldBindWith会返回error信息,如果error信息不为空,则表明出现错误,错误信息就包含在error中,如果error为空,则表明校验通过,示例如下:

var data MyTestStruct // 存储数据内容
// 获取 & 校验数据
if err = c.ShouldBindWith(&data, binding.JSON); err != nil {
  // 出现错误,打印错误内容
  fmt.Println(err)
  return
}
// 校验成功,打印数据内容
fmt.Println(data)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章