Go 中最常用的數據校驗庫

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

GC7dDb

單字段校驗

模擬對前端傳參進行校驗

單字段多個條件 校驗

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
}

在線代碼 [1]

輸出:

map[Age:Age必須小於或等於30 Email:Email必須是一個有效的郵箱]

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
 }
}

在線代碼 [2]

輸出:

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

跨字段驗證

eqfield 同一結構體字段驗證相等

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
 }
}

在線運行 [3]

輸出:

=== error msg ====
Key: 'Account.Password2' Error:Field validation for 'Password2' failed on the 'eqfield' tag

=========== error field info ====================
Namespace:  Account.Password2
Fild:  Password2
StructNamespace:  Account.Password2
StructField:  Password2
Tag:  eqfield
ActualTag:  eqfield
Kind:  string
Type:  string
Value:  111111
Param:  Password

nefield:同一結構體字段驗證不相等

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.Password
Fild:  Password
StructNamespace:  Account.Password
StructField:  Password
Tag:  nefield
ActualTag:  nefield
Kind:  string
Type:  string
Value:  Badger
Param:  Name

類似的還有

eqcsfield=Other.Field:必須等於 struct Other 中 Field 的值。

用於驗證_跨結構體的兩個字段_是否相等,需要指定另一個字段的名稱或路徑作爲參數,比如 eqcsfield=Other.Field 中的 Other.Field 就是指定的另一個字段。

在使用該選項時,會比較當前字段和指定的另一個字段的值是否相等,如果相等則驗證通過,否則驗證失敗。這個選項通常用於驗證密碼和確認密碼等類似的場景。

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.Field1
Fild:  Field1
StructNamespace:  Struct1.Field1
StructField:  Field1
Tag:  eqcsfield
ActualTag:  eqcsfield
Kind:  string
Type:  string
Value:  必須一致
Param:  Struct2.Field2

看起來只支持嵌套結構體,不支持兩個獨立的結構體之間某個字段的比較

eqfieldeqcsfield 的區別在於它們用於比較的字段的位置不同:eqfield 比較的是同一個結構體中的兩個字段的值,而 eqcsfield 比較的是當前結構體中的某個字段和另一個(子?)結構體中的字段的值

類似的還有

如何比較兩個獨立結構體中某兩個字段的值?

required_with=Field1 Field2:在 Field1 或者 Field2 存在時,必須;

required_with=Field2:在 Field2 被填寫 (即不爲空) 時,Field1 也必須不能爲空

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

驗證不通過

required_with_all=Field1 Field2:在 Field1 與 Field2 都存在時,必須;(僅當所有其他指定的字段都存在時, 驗證字段才必須存在)

要麼有這個 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

所以上面代碼可以驗證通過

如下也是合法的:

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)
 }
}

類似的還有:

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 字段必須被填寫。

required_without_all=Field1 Field2:在 Field1 與 Field2 都存在時,必須; (僅當所有其他指定字段都不存在時, 驗證字段才必須...)

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

驗證 proto

可參考 Go gRPC 進階 - proto 數據驗證(九) [4]

更復雜的判斷

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=男 女"
}

如果滿足以下任意一項則認爲通過:

這種用validator/v10能判斷嗎?..

這種複雜的驗證規則超出了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' tag
Key: 'User.Score' Error:Field validation for 'Score' failed on the 'customValidation' tag
Key: 'User.Gender' Error:Field validation for 'Gender' failed on the 'customValidation' tag
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'customValidation' tag
Key: 'User.Score' Error:Field validation for 'Score' failed on the 'customValidation' tag
Key: 'User.Gender' Error:Field validation for 'Gender' failed on the 'customValidation' tag

參考資料:

golang 之驗證器 validator[5]

【Go】數據驗證 - validator[6]

Go 使用 validator 進行後端數據校驗 [7]

gopkg.in/go-playground/validator.v10[8]

結構字段驗證--validator.v9[9]

Golang 驗證器之 validator 使用詳解 [10]

Go 每日一庫之 validator[11]

golang 常用庫:字段參數驗證庫 - validator 使用 [12]

參考資料

[1] 在線代碼: https://go.dev/play/p/p0r79v_RuSO

[2] 在線代碼: https://go.dev/play/p/tjZ6PurLe-p

[3] 在線運行: https://go.dev/play/p/UNj1WhSY1fM

[4] Go gRPC 進階 - proto 數據驗證(九) : https://www.cnblogs.com/FireworksEasyCool/p/12761033.html

[5] golang 之驗證器 validator: https://www.cnblogs.com/xingxia/p/golang_validator.html

[6] 【Go】數據驗證 - validator: https://blog.csdn.net/qq_42887507/article/details/120934568

[7] Go 使用 validator 進行後端數據校驗: https://www.admpub.com/blog/post/admin/Go-%E4%BD%BF%E7%94%A8validator%E8%BF%9B%E8%A1%8C%E5%90%8E%E7%AB%AF%E6%95%B0%E6%8D%AE%E6%A0%A1%E9%AA%8C

[8] gopkg.in/go-playground/validator.v10: https://pkg.go.dev/gopkg.in/go-playground/validator.v10

[9] 結構字段驗證--validator.v9: https://www.cnblogs.com/zhzhlong/p/10033234.html

[10] Golang 驗證器之 validator 使用詳解: https://www.jb51.net/article/260639.htm

[11] Go 每日一庫之 validator: https://darjun.github.io/2020/04/04/godailylib/validator/

[12] golang 常用庫:字段參數驗證庫 - validator 使用: https://www.cnblogs.com/jiujuan/p/13823864.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Gp4yLILc2rWQonn2Bg-FTg