常用验证器
// 不能为空,并且不能没有这个字段 required: 必填字段,如:binding:"required" // 针对字符串的长度 min 最小长度,如:binding:"min=5" max 最大长度,如:binding:"max=10" len 长度,如:binding:"len=6" // 针对数字的大小 eq 等于,如:binding:"eq=3" ne 不等于,如:binding:"ne=12" gt 大于,如:binding:"gt=10" gte 大于等于,如:binding:"gte=10" lt 小于,如:binding:"lt=10" lte 小于等于,如:binding:"lte=10" // 针对同级字段的 eqfield 等于其他字段的值,如:PassWord string `binding:"eqfield=Password"` nefield 不等于其他字段的值 - 忽略字段,如:binding:"-"
我们重新改变一下结构体的代码
type User struct { Name string `form:"name" binding:"required"` Age int `form:"age" binding:"min=18"` Gender int `form:"gender" binding:"len=1"` } //一个user简单的user结构体
运行之后就会按照上面的验证。 postman运行结果如下
1:全部正常
2: name不写
3:age<18
4:genderlen>1
当有多个不符合时,只会报道第一个发现的error
Gin自带的验证器
// 枚举 只能是red 或green oneof=red green // 字符串 contains=fengfeng // 包含fengfeng的字符串 excludes // 不包含 startswith // 字符串前缀 endswith // 字符串后缀 // 数组 dive // dive后面的验证就是针对数组中的每一个元素 // 网络验证 ip ipv4 ipv6 uri url // uri 在于I(Identifier)是统一资源标示符,可以唯一标识一个资源。 // url 在于Locater,是统一资源定位符,提供找到该资源的确切路径 // 日期验证 1月2号下午3点4分5秒在2006年 datetime=2006-01-02
与常用验证器使用方法一致,鼓励读者自己去运行测试,其实非常简单,只能说一模一样。
自定义验证的错误信息
我们上面的代码运行过后,当测试失败案例时,返回的meg往往不够明显,所以我们可以自定义验证错误信息,也就是当出现我们预料到的情况时返回给前端。
只需要给结构体加一个msg 的tag。
type User struct { Name string `form:"name" binding:"required" msg:"name没有填写"` Age int `form:"age" binding:"min=18"` Gender int `form:"gender" binding:"len=1"` } //一个user简单的user结构体
之后我们需要获取msg中的信息,这里我们自定义一个方法来获得 当元素不符合需求时,返回标签中的信息
// GetValidMsg 返回结构体中的msg参数 func GetValidMsg(err error, obj any) string { // 使用的时候,需要传obj的指针 getObj := reflect.TypeOf(obj) // 将err接口断言为具体类型 if errs, ok := err.(validator.ValidationErrors); ok { // 断言成功 for _, e := range errs { // 循环每一个错误信息 // 根据报错字段名,获取结构体的具体字段 if f, exits := getObj.Elem().FieldByName(e.Field()); exits { msg := f.Tag.Get("msg") return msg } } } return err.Error() }
GetValidMsg
函数的作用是从结构体的字段中获取 msg
参数,并返回错误信息。
函数签名为:
func GetValidMsg(err error, obj any) string
参数说明:
err
是一个错误类型,用于接收验证错误信息。obj
是一个任意类型,需要传入结构体的指针。
函数内部实现的逻辑如下:
- 通过
reflect.TypeOf(obj)
获取传入对象的具体类型。 - 将
err
断言为validator.ValidationErrors
类型,用于验证是否为验证错误。 - 如果断言成功,则表示错误是由验证器返回的。
- 遍历每一个错误信息,通过报错字段名获取结构体中对应的字段。
- 如果找到了对应的字段,则通过
Tag.Get("msg")
获取字段的msg
参数。 - 返回
msg
参数作为错误提示信息。 - 如果没有找到对应的字段,则返回原始错误信息
err.Error()
。
内部方法解答:
1:
validator.ValidationErrors
是一个类型,它表示验证器返回的错误信息。
如果 err
是 validator.ValidationErrors
类型或其子类型,则转换成功, ok
的值将为 true
,同时将转换后的结果赋值给 errs
。
getObj.Elem().FieldByName(e.Field())
这段代码用于根据报错字段名获取结构体的具体字段。
2:
getObj
是一个reflect.Type
类型的变量,表示传入对象的类型。getObj.Elem()
方法返回getObj
的指针所指向的具体类型。FieldByName(name string) reflect.StructField
方法会返回一个reflect.StructField
类型的值,该值描述了结构体的一个字段。参数name
表示字段的名称,该方法会根据名称在结构体中查找并返回对应的字段。e.Field()
返回验证错误的字段名。
因此,getObj.Elem().FieldByName(e.Field())
的作用是根据报错字段名,在结构体中查找并返回对应的字段的描述信息(reflect.StructField
类型)。
完整代码
package main import ( "github.com/gin-gonic/gin" //引入框架 "github.com/go-playground/validator/v10" "net/http" "reflect" ) type User struct { Name string `form:"name" binding:"required" msg:"name没有填写"` Age int `form:"age" binding:"min=18"` Gender int `form:"gender" binding:"len=1"` } //一个user简单的user结构体 // GetValidMsg 返回结构体中的msg参数 func GetValidMsg(err error, obj any) string { // 使用的时候,需要传obj的指针 getObj := reflect.TypeOf(obj) // 将err接口断言为具体类型 if errs, ok := err.(validator.ValidationErrors); ok { // 断言成功 for _, e := range errs { // 循环每一个错误信息 // 根据报错字段名,获取结构体的具体字段 if f, exits := getObj.Elem().FieldByName(e.Field()); exits { msg := f.Tag.Get("msg") return msg } } } return err.Error() } func get(c *gin.Context) { var user User if err := c.ShouldBind(&user); err != nil { c.JSON(http.StatusBadRequest, gin.H{"msg": GetValidMsg(err, &user)}) //绑定失败,返回错误信息 return } c.JSON(http.StatusOK, gin.H{"message": "响应成功", "user": user}) //绑定成功,响应user } func main() { router := gin.Default() //设置路由 router.GET("/form", get) router.Run() //设置运行接口 }
之后我们在postman里故意不写name,将会给出我们的msg: