【Go】参数验证,对象数组核验-validator

背景

一直做的 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()
	// 初始化UserInfo
	userInfo := &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' tag
	InitUserInfo(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()
	// 初始化UserInfo
	userInfo := &UserInfo{
    
    
		ID:   id,
		Age:  age,
		Name: name,
		Sex:  sex,
	}
	var menber []*UserInfo
	menber = 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' tag
	InitUserInfo(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代码地址

猜你喜欢

转载自blog.csdn.net/ic_xcc/article/details/128387570