开发过程中,我们往往需要对用户提交的数据进行验证,以保证数据的合法性和完整性。今天我们就来介绍 Go 语言社区推出的一个验证包 —— validator。
validator 包根据 tags 对结构体和单个字段的值进行验证。它具备以下优秀的功能:
- 提供了一系列验证规则用于验证,并且支持自定义验证规则;
- 支持跨字段、跨结构体进行验证;
- 支持多维字段如 array、slice、map 等;
- 在验证接口类型前会先确定它的底层数据类型;
- 支持自定义字段类型比如 sql 驱动程序 Valuer;
- 可以自定义并支持国际化(i18n)的错误信息;
- 是 gin 框架的默认验证器。
1.安装
使用
go get
:
go get github.com/go-playground/validator/v10
然后在你的源码中引入 validator 包:
import"github.com/go-playground/validator/v10"
2.使用
2.1 基本使用
package main
import("fmt""github.com/go-playground/validator/v10")type User struct{
FirstName string`validate:"required"`
LastName string`validate:"required"`
Age uint8`validate:"gte=0,lte=130"`}var validate *validator.Validate
funcmain(){
validate = validator.New()validateStruct()}funcvalidateStruct(){
user :=&User{
FirstName:"Badger",
LastName:"Smith",
Age:135,}
err := validate.Struct(user)if err !=nil{for_, err :=range err.(validator.ValidationErrors){
fmt.Println(err.Namespace())// User.Age
fmt.Println(err.Field())// Age
fmt.Println(err.StructNamespace())// User.Age
fmt.Println(err.StructField())// Age
fmt.Println(err.Tag())// lte
fmt.Println(err.ActualTag())// lte
fmt.Println(err.Kind())// uint8
fmt.Println(err.Type())// uint8
fmt.Println(err.Value())// 135
fmt.Println(err.Param())// 130
fmt.Println(err.Error())// Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
fmt.Println()}}}
2.2 跨字段验证
跨字段的验证规则有很多,具体可以查看文末规则表。下面的示例使用了
eqfield
规则,它表示当前字段必须等于指定的字段。
package main
import("fmt""github.com/go-playground/validator/v10")type User struct{
Password string`validate:"required"`
ConfirmPassword string`validate:"required,eqfield=Password"`}var validate *validator.Validate
funcmain(){
validate = validator.New()validateStruct()}funcvalidateStruct(){
user := User{
Password:"password",
ConfirmPassword:"pass",}
err := validate.Struct(user)if err !=nil{for_, err :=range err.(validator.ValidationErrors){
fmt.Println(err.Error())// Key: 'User.ConfirmPassword' Error:Field validation for 'ConfirmPassword' failed on the 'eqfield' tag}}}
2.3 跨结构体验证
除了跨字段验证,validator 还支持跨结构体验证,在规则表中你会发现,跨结构体的验证规则通常是在跨字段的验证规则的
field
前加上
cs
,可以理解为
cross-struct
。所以下面示例的
eacsfield
就是用来验证当前字段是否等于制定结构体的字段:
package main
import("fmt""github.com/go-playground/validator/v10")type User struct{
Uid string`validate:"required,eqcsfield=Account.PayUid"`
Account Account
}type Account struct{
PayUid string`validate:"required"`}var validate *validator.Validate
funcmain(){
validate = validator.New()validateStruct()}funcvalidateStruct(){
account := Account{
PayUid:"uid-1025",}
user := User{
Uid:"uid-1024",
Account: account,}
err := validate.Struct(user)if err !=nil{for_, err :=range err.(validator.ValidationErrors){
fmt.Println(err.Error())// Key: 'User.Uid' Error:Field validation for 'Uid' failed on the 'eqcsfield' tag}}}
2.4 验证 slice、map
验证 slice 和 map 等复合数据类型时,通常需要加上
dive
表示递归验证,否则将不会深入验证数据类型内部元素。
package main
import("fmt""github.com/go-playground/validator/v10")type User struct{// 第一个 required 规则用于确保切片本身不为零值(即不为 nil)。// dive 规则指定了对切片元素进行递归验证。// 第二个 required 规则用于验证切片中的每个元素是否不为空。
Accounts []string`validate:"required,dive,required"`// 第一个 required 规则用于确保 map 本身不为零值(即不为 nil)。// dive 规则指定了对 map 值进行递归验证。// gt=0 规则用于验证 map 中的每个值是否大于 0。// 第二个 required 规则用于验证 map 中的每个值是否不为空。
Balance map[string]int`validate:"required,dive,gt=0,required"`}var validate *validator.Validate
funcmain(){
validate = validator.New()validateStruct()}funcvalidateStruct(){
user := User{
Accounts:[]string{"account-1","","account-3"},
Balance:map[string]int{"key1":1,"key2":-1,},}
err := validate.Struct(user)if err !=nil{for_, err :=range err.(validator.ValidationErrors){// Key: 'User.Accounts[1]' Error:Field validation for 'Accounts[1]' failed on the 'required' tag// Key: 'User.Balance[key2]' Error:Field validation for 'Balance[key2]' failed on the 'gt' tag
fmt.Println(err.Error())}}}
2.5 自定义类型验证器
package main
import("database/sql""database/sql/driver""fmt""reflect""github.com/go-playground/validator/v10")type DbBackedUser struct{
Name sql.NullString `validate:"required"`
Age sql.NullInt64 `validate:"required"`}var validate *validator.Validate
funcmain(){
validate = validator.New()// 注册所有的 sql.Null* 类型,使用 ValidateValuer 自定义类型函数进行验证
validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{})
x := DbBackedUser{
Name: sql.NullString{String:"", Valid:true},
Age: sql.NullInt64{Int64:0, Valid:false},}
err := validate.Struct(x)if err !=nil{
fmt.Printf("Err(s):\n%+v\n", err)}}// ValidateValuer 实现了 validator.CustomTypeFunc 接口funcValidateValuer(field reflect.Value)interface{}{// 如果字段的类型实现了 driver.Valuer 接口(即支持向数据库写入值),则进行验证if valuer, ok := field.Interface().(driver.Valuer); ok {
val, err := valuer.Value()// 获取字段的值if err ==nil{return val
}// 处理错误}returnnil}
最终输出:
Err(s):
Key: 'DbBackedUser.Name' Error:Field validation for 'Name' failed on the 'required' tag
Key: 'DbBackedUser.Age' Error:Field validation for 'Age' failed on the 'required' tag
2.6 国际化/翻译
在前面的示例中,我们可以看到返回的信息都是英文的,如果需要翻译成中文信息,首先需要安装翻译包:
go get -u github.com/go-playground/locales
go get -u github.com/go-playground/universal-translator
接着我们修改 2.1 的代码,实现翻译:
package main
import("fmt""github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator""github.com/go-playground/validator/v10"
zhTrans "github.com/go-playground/validator/v10/translations/zh")type User struct{
FirstName string`validate:"required"`
LastName string`validate:"required"`
Age uint8`validate:"gte=0,lte=130"`}var validate *validator.Validate
var trans ut.Translator
funcmain(){
validate = validator.New()// 中文翻译器
uniTrans := ut.New(zh.New())
trans,_= uniTrans.GetTranslator("zh")// 注册翻译器到验证器
err := zhTrans.RegisterDefaultTranslations(validate, trans)if err !=nil{panic(fmt.Sprintf("registerDefaultTranslations fail: %s\n", err.Error()))}validateStruct()}funcvalidateStruct(){
user :=&User{
FirstName:"Badger",
LastName:"Smith",
Age:135,}
err := validate.Struct(user)if err !=nil{for_, err :=range err.(validator.ValidationErrors){// 翻译
fmt.Println(err.Translate(trans))// Age必须小于或等于130}}}
3.错误处理
通过源码,我们可以发现
validator
返回的错误类型有两种,分别是参数错误
InvalidValidationError
和验证错误
ValidationErrors
:
// $GOPATH/pkg/mod/github.com/go-playground/validator/v10/errors.go// InvalidValidationError describes an invalid argument passed to// `Struct`, `StructExcept`, StructPartial` or `Field`type InvalidValidationError struct{
Type reflect.Type
}// Error returns InvalidValidationError messagefunc(e *InvalidValidationError)Error()string{if e.Type ==nil{return"validator: (nil)"}return"validator: (nil "+ e.Type.String()+")"}// ValidationErrors is an array of FieldError's// for use in custom error messages post validation.type ValidationErrors []FieldError
// Error is intended for use in development + debugging and not intended to be a production error message.// It allows ValidationErrors to subscribe to the Error interface.// All information to create an error message specific to your application is contained within// the FieldError found within the ValidationErrors arrayfunc(ve ValidationErrors)Error()string{
buff := bytes.NewBufferString("")for i :=0; i <len(ve); i++{
buff.WriteString(ve[i].Error())
buff.WriteString("\n")}return strings.TrimSpace(buff.String())}
通常情况下一般会忽略对
InvalidValidationError
的判断/处理。
4.常用验证规则
完整的验证规则表可以参阅这里。
详细的验证规则介绍可以参阅这里
4.1 比较
规则描述eq等于eq_ignore_case等于,忽略大小写ne不等于ne_ignore_case不等于,忽略大小写gt大于gte大于等于lt小于lte小于等于
4.2 跨字段校验
TagDescriptioneqfield当前字段必须等于指定字段nefield当前字段必须不等于指定字段gtfield当前字段必须大于指定字段gtefield当前字段必须大于等于指定字段ltfield当前字段必须小于指定字段ltefield当前字段必须小于等于指定字段fieldcontains当前字段必须包含指定字段,只能用于字符串类型fieldexcludes当前字段必须不包含指定字段,只能用于字符串类型
4.3 跨结构体跨字段校验
「跨结构体跨字段校验」规则与「跨字段校验」规则类似,一般是在「跨字段校验」规则的
field
前加上
cs
字符。
TagDescriptioneqcsfield当前字段必须等于指定结构体的字段necsfield当前字段必须不等于指定结构体的字段gtcsfield当前字段必须大于指定结构体的字段gtecsfield当前字段必须大于等于指定结构体的字段ltcsfield当前字段必须小于指定结构体的字段ltecsfield当前字段必须小于等于指定结构体的字段
4.4 字符串相关验证
TagDescriptionalpha仅限字母alphanum仅限字母、数字alphanumunicode仅限字母、数字和 Unicodealphaunicode字母和 UnicodeasciiASCIIboolean当前字段必须是能够被
strconv.ParseBool
解析为字符串的值contains当前字段必须包含指定字符串startswith当前字段必须以指定字符串开头startsnotwith当前字段必须不是以指定字符串开头endswith当前字段必须以指定字符串结尾endsnotwith当前字段必须不是以指定字符串结尾uppercase当前字段的字母必须是大写,可包含数字,不能为空lowercase当前字段的字母必须是小写,可包含数字,不能为空
4.5 格式化验证
TagDescriptionbase64当前字段必须是有效的 Base64 值,不能为空base64url当前字段必须是包含根据 RFC4648 规范的有效 base64 URL 安全值。json正确的 JSON 串rgb正确的 RGB 字符串rgba正确的 RGBA 字符串
4.6 其他验证
TagDescriptiondir指定字段的值必须是已存在的目录dirpath指定字段的值必须是合法的目录路径file指定字段的值必须是已存在的文件filepath指定字段的值必须是合法的文件路径len当前字段的长度必须指定值,可用于 string、slice 等max当前字段的最大值必须是指定值min当前字段的最小值必须是指定值required当前字段为必填项,且不能为零值required_if当指定字段等于给定值时,当前字段使用 required 验证。如
required_if=Field1 foo Field2 bar
required_unless当指定字段不等于给定值时,当前字段使用 required 验证。如
required_unless=Field1 foo Field2 bar
required_with当任一指定字段不为零值时,当前字段使用 required 验证。如
required_with=Field1 Field2
required_with_all当所有指定字段不为零值时,当前字段使用 required 验证。
版权归原作者 OliverZ7 所有, 如有侵权,请联系我们删除。