一. 结构体验证
用gin框架的数据验证,可以不用解析数据,减少if else。如下面的代码,如果需要增加判断条件,就需要增加if或者if else。
type MyApi struct {
a int
b string
}
func checkMyApi(val *MyApi) bool {
if val.a == 0 {
return false
}
if val.b != "foo" && val.b != "bar" {
return false
}
return true
}
1.1 binding标签
在 Go 语言中,结构体的binding标签用于指定结构体字段在进行数据绑定(如表单数据绑定或请求体绑定)时的规则和验证。用于gin验证器。
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
type MyApi struct {
A int `form:"a" binding:"required"`
B string `form:"b" binding:"required,oneof=foo 'bar'"`
C []float64 `form:"c" binding:"required,gt=0"`
}
func main() {
r := gin.Default()
r.GET("/check", func(c *gin.Context) {
var api MyApi
if err := c.ShouldBind(&api); err != nil {
c.String(http.StatusBadRequest, err.Error())
return
}
str := fmt.Sprintf("a:%d, b:%s, c:%#v", api.A, api.B, api.C)
c.String(http.StatusOK, str)
})
r.Run()
}
required:表示字段不能为对应类型的零值。
oneof:用于限制字段取值必须是指定的多个值中的一个,多个值之间使用空格分隔。如果字符串本身包含空格,可以使用单引号括起来。
gt表示greater than大于。对于数字,这将确保值大于给定的值。对于字符串,它检查字符串长度是否大于给定值。对于切片,数组和映射,验证元素的数量。
常用tag:
required:表示该字段是必需的,不能为空。
min:指定字段的最小值。
max:指定字段的最大值。
eq: 等于,如:binding:“eq=3”
ne: 不等于,如:binding:“ne=12”
gt: 大于
gte: 大于等于
lt: 小于
lte: 小于等于
eqfield: 等于其它字段,如; Password string `bingding:“eqfield=ConfirmPassword”` 表示密码和确认密码一致
nefield: 不等于其它字段
email:验证字段是否为有效的电子邮件地址。如:binding:“email”
url:验证字段是否为有效的 URL。如:binding:“url”
datetime:验证字段是否为有效的日期时间格式。
len:指定字段的长度。
default:指定字段的默认值。
omitempty:指定当字段为空时,绑定时忽略该字段。
oneof:枚举验证,如:binding:“oneof=man woman”, 只能为man或者woman
contains: 字符串验证,包含某字符串,如:binding:“contains=love”
excludes: 字符串验证,不包含某字符串, 如:binding:“contains=money”
startswith 字符串验证,字符串前缀
endswith:字符串验证,字符串后缀
pattern:使用正则表达式验证字段的值。
datetime: 日期格式验证,如:binding:“datetime=2006-01-02 15:04:05”,注:时间必须是2006年1月2号下午3点4分5秒,不可以修改年月日时分秒的值。
忽略字段:binding:“-”
1.2 dive的使用
先看一个实例的请求结构体:
type PostAttributeValuesReq struct {
CreatorId string `binding:"required"` // 创建者ID
Values []struct {
Value string `binding:"required"` // 属性值
Days uint // 天数(计费模式使用)
} `binding:"required,gt=0"` // 属性值数组
}
假设使用json传参,发现Values.Value字段没有识别出来,也就是说切片元素struct字段的required并没有生效。
{
"creatorId":"dablelv",
"values":[{}]
}
可以使用dive标签,这是告诉验证器深入到切片,数组或映射中,并使用元素标签来验证切片,数组或映射元素。
type PostAttributeValuesReq struct {
CreatorId string `binding:"required"` // 创建者ID
Values []struct {
Value string `binding:"required"` // 属性值
Days uint // 天数(计费模式使用)
} `binding:"required,gt=0,dive"` // 属性值数组
}
1.3 validate标签
validate用于数据验证库的字段验证。
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
)
type MyApi struct {
A int `form:"a" validate:"required"`
B string `form:"b" validate:"required,oneof=foo 'bar'"`
C []float64 `form:"c" validate:"required,gt=0"`
}
func main() {
r := gin.Default()
r.GET("/check", func(c *gin.Context) {
var api MyApi
if err := c.ShouldBind(&api); err != nil {
c.String(http.StatusBadRequest, err.Error())
return
}
//数据验证
validate := validator.New()
if err := validate.Struct(api); err != nil {
c.String(http.StatusBadRequest, err.Error())
return
}
str := fmt.Sprintf("a:%d, b:%s, c:%#v", api.A, api.B, api.C)
c.String(http.StatusOK, str)
})
r.Run()
}
validate的常用tag:https://github.com/go-playground/validator/blob/master/README.md
1.4 binding和validate区别
上下文:
- binding:主要用于Web框架的参数绑定,例如:Gin。在请求参数绑定到结构体字段时,验证数据。
- validate:主要用于数据验证库,例如:go-playground/validator。
错误处理:
- 在Web框架中,binding通常会导致框架返回HTTP 400 Bad Request错误,指示客户端请求参数不合法。
- 在数据验证库中,会在验证时产生相应的验证错误,开发者可根据需要进行特别处理。
使用场景:
- 适用于Web框架的参数绑定
- 适用于在通用的数据验证场景中
二. 自定义验证
使用go-playground/validator包。
2.1 validator包简介
validator包是Golang中一个非常受欢迎的数据验证工具,它提供了丰富的验证规则和简单易用的API。使用validator包可以轻松的定义和执行各种验证规则,如必填字段,最大长度,最小值等。同时validator包还支持自定义验证规则,可根据具体业务需求进行扩展。
validator包安装:
go get github.com/go-playground/validator/v10
2.2 基本使用
2.3 自定义规则验证
除了支持内置的验证规则,validator包还支持自定义验证规则。我们可以通过实现validator.Func类型的函数来定义自己的验证规则。
利用validator包,使binding注册自定义规则。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
)
type User struct {
//2. 在参数binding上使用自定义的校验方法函数注册时的名称
Name string `form:"name" binding:"NotNullAndAdmin"`
Age int `form:"age" binding:"gte=0,lte=100"`
Email string `form:"email" binding:"email"`
}
// 1. 自定义校验方法
// func notNullAndAdmin(c *validator.Validate, topStruct reflect.Value, curStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
func notNullAndAdmin(c validator.FieldLevel) bool {
value := c.Field().String()
//字段不能为空,并且不等于admin
return value != "" && !(value == "admin")
}
func main() {
r := gin.Default()
//3.将我们自定义的校验方法注册到validator中
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("NotNullAndAdmin", notNullAndAdmin)
}
r.GET("/check", func(c *gin.Context) {
var u User
if err := c.ShouldBind(&u); err != nil {
c.String(http.StatusBadRequest, err.Error())
}
c.String(http.StatusOK, "check pass")
})
r.Run()
}
利用validator包,使validate注册自定义规则。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
)
type User struct {
//2. 在参数binding上使用自定义的校验方法函数注册时的名称
Name string `form:"name" validate:"NotNullAndAdmin"`
Age int `form:"age" validate:"gte=0,lte=100"`
Email string `form:"email" validate:"email"`
}
// 1. 自定义校验方法
// func notNullAndAdmin(c *validator.Validate, topStruct reflect.Value, curStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
func notNullAndAdmin(c validator.FieldLevel) bool {
value := c.Field().String()
//字段不能为空,并且不等于admin
return value != "" && !(value == "admin")
}
func main() {
r := gin.Default()
r.GET("/check", func(c *gin.Context) {
var u User
if err := c.ShouldBind(&u); err != nil {
c.String(http.StatusBadRequest, err.Error())
}
//3.将我们自定义的校验方法注册到validator中
validate := validator.New()
validate.RegisterValidation("NotNullAndAdmin", notNullAndAdmin)
//数据校验
err := validate.Struct(u)
if err != nil {
c.String(http.StatusBadRequest, err.Error())
}
c.String(http.StatusOK, "check pass")
})
r.Run()
}
原理是:
通过反射获取struct中的tag,根据不同的验证规则进行验证。
三. 多语言翻译验证
validator库本身使支持国际化的,可以借助相应的语言包实现多语言翻译验证。
验证器使用的是:
go get gopkg.in/go-playground/validator.v9
翻译器:
github.com/go-playground/universal-translator
验证器注册翻译器:
gopkg.in/go-playground/validator.v9/translations/en
gopkg.in/go-playground/validator.v9/translations/zh
gopkg.in/go-playground/validator.v9/translations/zh_tw
例如:当业务系统对验证信息有特殊需求时,返回信息需要自定义,手机端返回的信息需要时中文,而pc端发挥返回的信息需要是英文,如何做到请求一个借口满足上述三种情况。
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
"github.com/go-playground/locales/zh_Hant_TW"
ut "github.com/go-playground/universal-translator"
"gopkg.in/go-playground/validator.v9"
en_translations "gopkg.in/go-playground/validator.v9/translations/en"
zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"
zh_tw_translations "gopkg.in/go-playground/validator.v9/translations/zh_tw"
)
var (
Uni *ut.UniversalTranslator
Validate *validator.Validate
Trans ut.Translator
)
type User struct {
Name string `form:"name" validate:"required"`
Tagline string `form:"tag_line" validate:"required,lt=10"`
Tagline2 string `form:"tag_line2" validate:"required,gt=1"`
}
func main() {
en := en.New()
zh := zh.New()
zh_tw := zh_Hant_TW.New()
Uni = ut.New(en, zh, zh_tw)
Validate = validator.New()
r := gin.Default()
r.GET("/check", registerTranslation(), startPage)
r.Run()
}
func registerTranslation() gin.HandlerFunc {
return func(c *gin.Context) {
//获得参数
locale := c.DefaultQuery("locale", "zh")
//翻译器
Trans, _ = Uni.GetTranslator(locale)
//验证器注册翻译器
switch locale {
case "zh":
zh_translations.RegisterDefaultTranslations(Validate, Trans)
case "en":
en_translations.RegisterDefaultTranslations(Validate, Trans)
case "zh_tw":
zh_tw_translations.RegisterDefaultTranslations(Validate, Trans)
default:
zh_translations.RegisterDefaultTranslations(Validate, Trans)
}
}
}
func startPage(c *gin.Context) {
//自定义错误内容
Validate.RegisterTranslation("required", Trans, func(ut ut.Translator) error {
return ut.Add("required", "{0} must have val!", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("required", fe.Field())
return t
})
//验证数据
var u User
if err := c.ShouldBind(&u); err != nil {
c.String(http.StatusBadRequest, err.Error())
return
}
fmt.Println(u)
if err := Validate.Struct(u); err != nil {
errs := err.(validator.ValidationErrors)
sliceErr := []string{}
for _, e := range errs {
//翻译错误
sliceErr = append(sliceErr, e.Translate(Trans))
}
c.String(http.StatusOK, fmt.Sprintf("%#v", sliceErr))
return
}
c.String(http.StatusOK, fmt.Sprintf("%#v", u))
}
演示: