Go 中最常用的數據校驗庫
項目地址: github.com/go-playground/validator/v10
單字段校驗
模擬對前端傳參進行校驗
單字段多個條件 校驗
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
類似的還有
-
gtfield=Field:必須大於 Field 的值。
-
gtefield=Field: 必須大於等於 Field 的值。
-
ltfield=Field:必須小於 Field 的值。
-
ltefield=Field:必須小於等於 Field 的值。
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
看起來只支持嵌套結構體,不支持兩個獨立的結構體之間某個字段的比較
eqfield
和 eqcsfield
的區別在於它們用於比較的字段的位置不同:eqfield 比較的是同一個結構體中的兩個字段的值,而 eqcsfield 比較的是當前結構體中的某個字段和另一個(子?)結構體中的字段的值
類似的還有
-
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 的值。
如何比較兩個獨立結構體中某兩個字段的值?
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=男 女"
}
如果滿足以下任意一項則認爲通過:
-
Gender = 男,Age 小於 35,Score 大於 60
-
Gender = 女,Age 小於 40,Score 大於 50
這種用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