写点什么

Go 中最常用的数据校验库

作者:fliter
  • 2024-02-04
    上海
  • 本文字数:9234 字

    阅读完需:约 30 分钟

<br>


项目地址: github.com/go-playground/validator/v10



<br>

单字段校验

<br>


对前端传参进行校验

单字段多个条件 校验

<br>


package main
import ( "fmt" "strings"
"github.com/go-playground/locales/en" "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" // entrans "github.com/go-playground/validator/v10/translations/en")
/*https://www.cnblogs.com/jiujuan/p/13823864.html
https://www.liwenzhou.com/posts/Go/validator-usages/
https://juejin.cn/post/7056823502640250893
https://juejin.cn/post/6847902214279659533*/type Student struct { Name string `validate:required` Email string `validate:"email"` Age int `validate:"max=30,min=12"`}
func main() { en := en.New() //英文翻译器 zh := zh.New() //中文翻译器
// 第一个参数是必填,如果没有其他的语言设置,就用这第一个 // 后面的参数是支持多语言环境( // uni := ut.New(en, en) 也是可以的 // uni := ut.New(en, zh, tw) uni := ut.New(en, zh) trans, _ := uni.GetTranslator("zh") //获取需要的语言
student := Student{ Name: "tom", Email: "testemal", Age: 40, } validate := validator.New()
zhtrans.RegisterDefaultTranslations(validate, trans)
err := validate.Struct(student) if err != nil { // fmt.Println(err)
errs := err.(validator.ValidationErrors) fmt.Println(removeStructName(errs.Translate(trans))) }}
func removeStructName(fields map[string]string) map[string]string { result := map[string]string{}
for field, err := range fields { result[field[strings.Index(field, ".")+1:]] = err } return result}
复制代码


在线代码


输出:


map[Age:Age必须小于或等于30 Email:Email必须是一个有效的邮箱]


<br>


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"` Email string `validate:"required,email"` Test string `validate:"len=0|min=6,max=24,len=0|alphanum"` // 或者 条件之一, 使用|。 但每个,都是独立的一个逻辑,之间是&的关系(有一个不满足就报错"Error:Field validation"),且条件没有传递,所以要在alphanum前面也加一个len=0|。 而max=24和长度为0不冲突,所以不需要加 Products []CreateOrderProduct `validate:"min=1"` // 产品列表}
type CreateOrderProduct struct { SkuCode string `json:"skuCode"` // sku编码 Quantity int64 `json:"quantity"` // 商品数量}
// oneof:只能是列举出的值其中一个,这些值必须是数值或字符串,以空格分隔,如果字符串中有空格,将字符串用单引号包围,validate:"oneof=red green"// https://www.cnblogs.com/jiujuan/p/13823864.html
func main() {
user := &User{ FirstName: "Badger", LastName: "Smith", Age: 115, Email: "Badger.Smith@gmail.com", Test: "", Products: []CreateOrderProduct{}, }
validate := validator.New() err := validate.Struct(user) if err != nil { fmt.Println("=== error msg ====") fmt.Println(err)
//if _, ok := err.(*validator.InvalidValidationError); ok { // fmt.Println(err) // return //} // //fmt.Println("\r\n=========== error field info ====================") //for _, err := range err.(validator.ValidationErrors) { // // 列出效验出错字段的信息 // fmt.Println("Namespace: ", err.Namespace()) // fmt.Println("Fild: ", err.Field()) // fmt.Println("StructNamespace: ", err.StructNamespace()) // fmt.Println("StructField: ", err.StructField()) // fmt.Println("Tag: ", err.Tag()) // fmt.Println("ActualTag: ", err.ActualTag()) // fmt.Println("Kind: ", err.Kind()) // fmt.Println("Type: ", err.Type()) // fmt.Println("Value: ", err.Value()) // fmt.Println("Param: ", err.Param()) // fmt.Println() //}
// from here you can create your own error messages in whatever language you wish return }}
复制代码


在线代码



输出:


=== error msg ====Key: 'User.Products' Error:Field validation for 'Products' failed on the 'min' tag
复制代码


<br>




<br>

跨字段验证

<br>

eqfield 同一结构体字段验证相等

<br>


eqfield=Field:必须等于 Field 的值


最常见的就是输入 2 次密码验证


package main
import ( "fmt"
"github.com/go-playground/validator/v10")
// 多字段联合校验
// eqfield:同一结构体字段验证相等,最常见的就是输入2次密码验证
type Account struct { Name string `validate:"lte=16"` Age int `validate:"min=20"` Password string `validate:"min=8"` Password2 string `validate:"eqfield=Password"`}
func main() { account := &Account{ Name: "Badger", Age: 115, Password: "qwert12345", Password2: "111111", }
validate := validator.New() err := validate.Struct(account) if err != nil { fmt.Println("=== error msg ====") fmt.Println(err)
if _, ok := err.(*validator.InvalidValidationError); ok { fmt.Println(err) return }
fmt.Println("\r\n=========== error field info ====================") for _, err := range err.(validator.ValidationErrors) { // 列出效验出错字段的信息 fmt.Println("Namespace: ", err.Namespace()) fmt.Println("Fild: ", err.Field()) fmt.Println("StructNamespace: ", err.StructNamespace()) fmt.Println("StructField: ", err.StructField()) fmt.Println("Tag: ", err.Tag()) fmt.Println("ActualTag: ", err.ActualTag()) fmt.Println("Kind: ", err.Kind()) fmt.Println("Type: ", err.Type()) fmt.Println("Value: ", err.Value()) fmt.Println("Param: ", err.Param()) fmt.Println() }
// from here you can create your own error messages in whatever language you wish return }}
复制代码


在线运行


输出:


=== error msg ====Key: 'Account.Password2' Error:Field validation for 'Password2' failed on the 'eqfield' tag
=========== error field info ====================Namespace: Account.Password2Fild: Password2StructNamespace: Account.Password2StructField: Password2Tag: eqfieldActualTag: eqfieldKind: stringType: stringValue: 111111Param: Password
复制代码


<br>

nefield:同一结构体字段验证不相等

<br>


nefield=Field:必须不等于 Field 的值


例如,验证密码不能和用户名相同


package main
import ( "fmt"
"github.com/go-playground/validator/v10")
// 多字段联合校验
// eqfield:同一结构体字段验证相等,最常见的就是输入2次密码验证
type Account struct { Name string `validate:"lte=16"` Age int `validate:"min=20"` Password string `validate:"min=1,nefield=Name"`}
func main() { account := &Account{ Name: "Badger", Age: 115, Password: "Badger", }
validate := validator.New() err := validate.Struct(account) if err != nil { fmt.Println("=== error msg ====") fmt.Println(err)
if _, ok := err.(*validator.InvalidValidationError); ok { fmt.Println(err) return }
fmt.Println("\r\n=========== error field info ====================") for _, err := range err.(validator.ValidationErrors) { // 列出效验出错字段的信息 fmt.Println("Namespace: ", err.Namespace()) fmt.Println("Fild: ", err.Field()) fmt.Println("StructNamespace: ", err.StructNamespace()) fmt.Println("StructField: ", err.StructField()) fmt.Println("Tag: ", err.Tag()) fmt.Println("ActualTag: ", err.ActualTag()) fmt.Println("Kind: ", err.Kind()) fmt.Println("Type: ", err.Type()) fmt.Println("Value: ", err.Value()) fmt.Println("Param: ", err.Param()) fmt.Println() }
// from here you can create your own error messages in whatever language you wish return }}
复制代码


输出:


=== error msg ====Key: 'Account.Password' Error:Field validation for 'Password' failed on the 'nefield' tag
=========== error field info ====================Namespace: Account.PasswordFild: PasswordStructNamespace: Account.PasswordStructField: PasswordTag: nefieldActualTag: nefieldKind: stringType: stringValue: BadgerParam: Name
复制代码


<br>


类似的还有


  • gtfield=Field:必须大于 Field 的值。

  • gtefield=Field: 必须大于等于 Field 的值。

  • ltfield=Field:必须小于 Field 的值。

  • ltefield=Field:必须小于等于 Field 的值。


<br>




<br>

eqcsfield=Other.Field:必须等于 struct Other 中 Field 的值。

<br>


用于验证跨结构体的两个字段是否相等,需要指定另一个字段的名称或路径作为参数,比如 eqcsfield=Other.Field 中的 Other.Field 就是指定的另一个字段。


在使用该选项时,会比较当前字段和指定的另一个字段的值是否相等,如果相等则验证通过,否则验证失败。这个选项通常用于验证密码和确认密码等类似的场景。


<br>


package main
import ( "fmt"
"github.com/go-playground/validator/v10")
type Struct1 struct { Field1 string `validate:"eqcsfield=Struct2.Field2""` Struct2 struct { Field2 string }}
func main() {
s := &Struct1{ Field1: "必须一致", Struct2: struct{ Field2 string }{Field2: "没有一致"}, }
validate := validator.New() err := validate.Struct(s) if err != nil { fmt.Println("=== error msg ====") fmt.Println(err)
if _, ok := err.(*validator.InvalidValidationError); ok { fmt.Println(err) return }
fmt.Println("\r\n=========== error field info ====================") for _, err := range err.(validator.ValidationErrors) { // 列出效验出错字段的信息 fmt.Println("Namespace: ", err.Namespace()) fmt.Println("Fild: ", err.Field()) fmt.Println("StructNamespace: ", err.StructNamespace()) fmt.Println("StructField: ", err.StructField()) fmt.Println("Tag: ", err.Tag()) fmt.Println("ActualTag: ", err.ActualTag()) fmt.Println("Kind: ", err.Kind()) fmt.Println("Type: ", err.Type()) fmt.Println("Value: ", err.Value()) fmt.Println("Param: ", err.Param()) fmt.Println() }
// from here you can create your own error messages in whatever language you wish return }}
复制代码


输出:


=== error msg ====Key: 'Struct1.Field1' Error:Field validation for 'Field1' failed on the 'eqcsfield' tag
=========== error field info ====================Namespace: Struct1.Field1Fild: Field1StructNamespace: Struct1.Field1StructField: Field1Tag: eqcsfieldActualTag: eqcsfieldKind: stringType: stringValue: 必须一致Param: Struct2.Field2
复制代码


看起来只支持嵌套结构体,不支持两个独立的结构体之间某个字段的比较


<br>


eqfieldeqcsfield 的区别在于它们用于比较的字段的位置不同:eqfield 比较的是同一个结构体中的两个字段的值,而 eqcsfield 比较的是当前结构体中的某个字段和另一个(子?)结构体中的字段的值


<br>


类似的还有


  • necsfield=Other.Field:必须不等于 struct Other 中 Field 的值。

  • gtcsfield=Other.Field:必须大于 struct Other 中 Field 的值;

  • gtecsfield=Other.Field:必须大于等于 struct Other 中 Field 的值。

  • ltcsfield=Other.Field:必须小于 struct Other 中 Field 的值。

  • ltecsfield=Other.Field:必须小于等于 struct Other 中 Field 的值。


<br>


如何比较两个独立结构体中某两个字段的值?


<br>




<br>

required_with=Field1 Field2:在 Field1 或者 Field2 存在时,必须;

required_with=Field2:在 Field2 被填写(即不为空)时,Field1 也必须不能为空


<br>


package main
import ( "fmt" "github.com/go-playground/validator/v10")
type User struct { Name string `validate:"required"` Email string `validate:"required_with=Phone"` Phone string}
func main() { user1 := User{ Name: "John", Email: "", Phone: "", }
user2 := User{ Name: "Mary", Email: "mary@example.com", Phone: "", }
validate := validator.New()
err1 := validate.Struct(user1) if err1 != nil { fmt.Println(err1) }
err2 := validate.Struct(user2) if err2 != nil { fmt.Println(err2) }}
复制代码


验证通过~


在这个例子中,User 结构体包含 Name、Email 和 Phone 字段。Email 字段被标记为 required_with=Phone,这意味着当 Phone 字段被填写时,Email 字段也必须被填写。


而如果把 user1 改为:


user1 := User{    Name:  "John",    Email: "",    Phone: "123",  }
复制代码


则会报错:


Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required_with' tag
复制代码


验证不通过


<br>




<br>

required_with_all=Field1 Field2:在 Field1 与 Field2 都存在时,必须;(仅当所有其他指定的字段都存在时,验证字段才必须存在)

<br>


要么有这个 tag 的全部为空,如果有一个不为空,那所有其他的也都不能为空~


package main
import ( "fmt"
"github.com/go-playground/validator/v10")
type User6 struct { Name string `validate:"required"` Email string `validate:"required_with_all=Phone"` Phone string `validate:"required_with_all=Email"`}
func main() { user := User6{ Name: "John", Email: "", Phone: "", }
validate := validator.New()
err := validate.Struct(user) if err != nil { fmt.Println(err) }}
复制代码


Email 和 Phone 字段都被标记为 required_with_all,


这意味着当 Email 和 Phone


  • 要么全都为空

  • 如果其中的任何一个被填写时,另一个也必须被填写(不为空即可,可以不一样)


所以上面代码可以验证通过


<br>


如下也是合法的:


package main
import ( "fmt"
"github.com/go-playground/validator/v10")
type User6 struct { Name string `validate:"required"` Email string `validate:"required_with_all=Phone"` Phone string `validate:"required_with_all=Email"`}
func main() { user := User6{ Name: "John", Email: "1", Phone: "2", }
validate := validator.New()
err := validate.Struct(user) if err != nil { fmt.Println(err) }}
复制代码


<br>


类似的还有:

required_without=Field1 Field2:在 Field1 或者 Field2 不存在时,必须;

Field1 Field2 字段其中(至少)一个为空,则当前字段不能为空


package main
import ( "fmt" "github.com/go-playground/validator/v10")
type User struct { Name string `validate:"required"` Email string Phone string Address string `validate:"required_without=Email Phone"`// Email Phone 中间不能加逗号}
func main() { user1 := User{ Name: "John", Email: "", Phone: "", Address: "123 Main St.", }
user2 := User{ Name: "Mary", Email: "mary@example.com", Phone: "", Address: "", }
validate := validator.New()
err1 := validate.Struct(user1) if err1 != nil { fmt.Println("err1:", err1) }
err2 := validate.Struct(user2) if err2 != nil { fmt.Println("err2:", err2) }}
复制代码


输出:


err2: Key: 'User7.Address' Error:Field validation for 'Address' failed on the 'required_without' tag
复制代码


User 结构体包含 Name、Email、Phone 和 Address 字段。Address 字段被标记为 required_without=Email Phone,这意味着当 Email 和 Phone 字段至少一个为空时,Address 字段必须被填写。


<br>

required_without_all=Field1 Field2:在 Field1 与 Field2 都存在时,必须; (仅当所有其他指定字段都不存在时,验证字段才必须...)

<br>


Field1 Field2 字段都为空时,则当前字段不能为空


package main
import ( "fmt"
"github.com/go-playground/validator/v10")
type User7 struct { Name string `validate:"required"` Email string Phone string Address string `validate:"required_without_all=Email Phone"`}
func main() { user1 := User7{ Name: "John", Email: "", Phone: "111", Address: "123 Main St.", }
user2 := User7{ Name: "Mary", Email: "", Phone: "", Address: "", }
validate := validator.New()
err1 := validate.Struct(user1) if err1 != nil { fmt.Println("err1:", err1) }
err2 := validate.Struct(user2) if err2 != nil { fmt.Println("err2:", err2) }}
复制代码


输出:


err2: Key: 'User7.Address' Error:Field validation for 'Address' failed on the 'required_without_all' tag
复制代码


<br>




<br>

验证 proto

可参考 Go gRPC进阶-proto数据验证(九)


<br>




<br>

更复杂的判断

<br>


type User struct {Age uint8 validate:"gte=0,lte=130"Email string validate:"required,email"Score int validate:"min=1" // 分数Gender string validate:"required,oneof=男 女"}
复制代码


如果满足以下任意一项则认为通过:


  • Gender=男,Age 小于 35,Score 大于 60

  • Gender=女,Age 小于 40,Score 大于 50


这种用validator/v10能判断吗?..


<br>


这种复杂的验证规则超出了validator/v10的基本功能,需要进行自定义验证函数。可以使用 validator/v10 的 Func 函数,通过编写自定义的验证函数来实现这种验证规则。


如下是一个示例代码:


package main
import ( "fmt"
"github.com/go-playground/validator/v10")
type User struct { Age uint8 `validate:"gte=0,lte=130,customValidation"` Email string `validate:"required,email"` Score int `validate:"required,gte=0,customValidation"` Gender string `validate:"required,oneof=男 女,customValidation"`}
func validateUser(fl validator.FieldLevel) bool {
user, ok := fl.Top().Interface().(*User)
fmt.Println("user is:", user)
if !ok { return false }
if user.Gender == "男" && user.Age < 35 && user.Score > 60 { return true } if user.Gender == "女" && user.Age < 40 && user.Score > 50 { return true } return false}
// - Gender=男,Age小于35,Score大于60// - Gender=女,Age小于40,Score大于50
func main() { user := &User{ Age: 36, Email: "example@gmail.com", Score: 1711, Gender: "男", }
validate := validator.New() err := validate.RegisterValidation("customValidation", validateUser) if err != nil { fmt.Println(err) return }
err = validate.Struct(user)
fmt.Println("err is:", err) if err != nil { fmt.Println(err) return }
fmt.Println("validation succeeded")}
复制代码


输出:


user is: &{36 example@gmail.com 1711 男}user is: &{36 example@gmail.com 1711 男}user is: &{36 example@gmail.com 1711 男}err is: Key: 'User.Age' Error:Field validation for 'Age' failed on the 'customValidation' tagKey: 'User.Score' Error:Field validation for 'Score' failed on the 'customValidation' tagKey: 'User.Gender' Error:Field validation for 'Gender' failed on the 'customValidation' tagKey: 'User.Age' Error:Field validation for 'Age' failed on the 'customValidation' tagKey: 'User.Score' Error:Field validation for 'Score' failed on the 'customValidation' tagKey: 'User.Gender' Error:Field validation for 'Gender' failed on the 'customValidation' tag
复制代码


<br>




<br>


参考资料:


golang之验证器validator


【Go】数据验证-validator


Go 使用validator进行后端数据校验


gopkg.in/go-playground/validator.v10


结构字段验证--validator.v9


Golang验证器之validator使用详解


Go 每日一库之 validator


golang常用库:字段参数验证库-validator使用


<br>


有空还想探究下,这么一个工具怎么可以讲这么多期..


B站视频:论一款强大的验证组件在web开发中的重要性


B站视频:go语言验证框架Validator的6种高端操作


B站视频:go开源验证框架validator必会的3个操作


B站视频:validator验证框架3种自定义验证方法


B站视频:validator快速实现字段格式的验证


B站视频:validator快速搞定字段格式验证


B站视频:validator自定义验证与本地化提示

用户头像

fliter

关注

www.dashen.tech 2018-06-21 加入

Software Engineer. Focus on Micro Service,Containerization

评论

发布
暂无评论
Go中最常用的数据校验库_fliter_InfoQ写作社区