golang常用库:字段参数验证库-validator使用
一、背景#
在平常開發(fā)中,特別是在web應(yīng)用開發(fā)中,為了驗(yàn)證輸入字段的合法性,都會做一些驗(yàn)證操作。比如對用戶提交的表單字段進(jìn)行驗(yàn)證,或者對請求的API接口字段進(jìn)行驗(yàn)證,驗(yàn)證字段的合法性,保證輸入字段值的安全,防止用戶的惡意請求。
一般的做法是用正則表達(dá)式,一個字段一個字段的進(jìn)行驗(yàn)證。一個一個字段驗(yàn)證的話,寫起來比較繁瑣。那有沒更好的方法,進(jìn)行字段的合法性驗(yàn)證?有, 這就是下面要介紹的?validator?這個驗(yàn)證組件。
代碼地址:
https://github.com/go-playground/validator
文檔地址:
validator/README.md at master · go-playground/validator · GitHub
二、功能介紹#
這個驗(yàn)證包?github.com/go-playground/validator?驗(yàn)證功能非常多。
標(biāo)記之間特殊符號說明#
- 逗號(?,):把多個驗(yàn)證標(biāo)記隔開。注意:隔開逗號之間不能有空格,?validate:"lt=0,gt=100",逗號那里不能有空格,否則panic
- 橫線(?-?):跳過該字段不驗(yàn)證
- 豎線(?|?):使用多個驗(yàn)證標(biāo)記,但是只需滿足其中一個即可
- required:表示該字段值必輸設(shè)置,且不能為默認(rèn)值
- omitempty:如果字段未設(shè)置,則忽略它
范圍比較驗(yàn)證#
doc:?https://github.com/go-playground/validator/blob/master/README.md#comparisons
范圍驗(yàn)證: 切片、數(shù)組和map、字符串,驗(yàn)證其長度;數(shù)值,驗(yàn)證大小范圍
- lte:小于等于參數(shù)值,validate:"lte=3"?(小于等于3)
- gte:大于等于參數(shù)值,validate:"lte=120,gte=0"?(大于等于0小于等于120)
- lt:小于參數(shù)值,validate:"lt=3"?(小于3)
- gt:大于參數(shù)值,validate:"lt=120,gt=0"?(大于0小于120)
- len:等于參數(shù)值,validate:"len=2"
- max:最大值,小于等于參數(shù)值,validate:"max=20"?(小于等于20)
- min:最小值,大于等于參數(shù)值,validate:"min=2,max=20"?(大于等于2小于等于20)
- ne:不等于,validate:"ne=2"?(不等于2)
- oneof:只能是列舉出的值其中一個,這些值必須是數(shù)值或字符串,以空格分隔,如果字符串中有空格,將字符串用單引號包圍,validate:"oneof=red green"
例子:
Copy
type User struct { Name string `json:"name" validate:"min=0,max=35"` Age unit8 `json:"age" validate:"lte=90,gte=0"` }
更多功能請參看文檔?validator comparisons doc
字符串驗(yàn)證#
doc:?https://github.com/go-playground/validator/blob/master/README.md#strings
- contains:包含參數(shù)子串,validate:"contains=tom"?(字段的字符串值包含tom)
- excludes:包含參數(shù)子串,validate:"excludes=tom"?(字段的字符串值不包含tom)
- startswith:以參數(shù)子串為前綴,validate:"startswith=golang"
- endswith:以參數(shù)子串為后綴,validate:"startswith=world"
例子:
Copy
type User struct { Name string `validate:"contains=tom"` Age int `validate:"min=1"` }
更多功能請參看文檔?validator strings doc
字段驗(yàn)證#
doc:?https://github.com/go-playground/validator/blob/master/README.md#fields
- eqcsfield:跨不同結(jié)構(gòu)體字段驗(yàn)證,比如說 Struct1 Filed1,與結(jié)構(gòu)體Struct2 Field2相等,
Copy
type Struct1 struct { Field1 string `validate:eqcsfield=Struct2.Field2` Struct2 struct { Field2 string } }
-
necsfield:跨不同結(jié)構(gòu)體字段不相等
-
eqfield:同一結(jié)構(gòu)體字段驗(yàn)證相等,最常見的就是輸入2次密碼驗(yàn)證
Copy
type User struct { Name string `validate:"lte=4"` Age int `validate:"min=20"` Password string `validate:"min=10"` Password2 string `validate:"eqfield=Password"` }
- nefield:同一結(jié)構(gòu)體字段驗(yàn)證不相等
Copy
type User struct { Name string `validate:"lte=4"` Age int `validate:"min=20"` Password string `validate:"min=10,nefield=Name"` }
- gtefield:大于等于同一結(jié)構(gòu)體字段,validate:"gtefiled=Field2"
- ltefield:小于等于同一結(jié)構(gòu)體字段
更多功能請參看文檔:validator Fields DOC
網(wǎng)絡(luò)驗(yàn)證#
doc:?https://github.com/go-playground/validator/blob/master/README.md#network
- ip:字段值是否包含有效的IP地址,validate:"ip"
- ipv4:字段值是否包含有效的ipv4地址,validate:"ipv4"
- ipv6:字段值是否包含有效的ipv6地址,validate:"ipv6"
- uri:字段值是否包含有效的uri,validate:"uri"
- url:字段值是否包含有效的uri,validate:"url"
更多功能請參看文檔:validator network DOC
Format#
doc:?https://github.com/go-playground/validator/blob/master/README.md#format
- base64:字段值是否包含有效的base64值
更多功能請參看文檔?validator strings doc
其他#
請參看文檔:?https://github.com/go-playground/validator/blob/master/README.md#other
三、安裝#
go get:
go get github.com/go-playground/validator/v10
在文件中引用validator包:
import "github.com/go-playground/validator/v10"
四、validator使用#
文檔:https://github.com/go-playground/validator/blob/master/README.md#examples
例子1:驗(yàn)證單個字段變量值#
validation1.go
Copy
package main import ( "fmt" "github.com/go-playground/validator/v10" ) func main() { validate := validator.New() var boolTest bool err := validate.Var(boolTest, "required") if err != nil { fmt.Println(err) } var stringTest string = "" err = validate.Var(stringTest, "required") if err != nil { fmt.Println(err) } var emailTest string = "test@126.com" err = validate.Var(emailTest, "email") if err != nil { fmt.Println(err) } else { fmt.Println("success") // 輸出: success。 說明驗(yàn)證成功 } emailTest2 := "test.126.com" errs := validate.Var(emailTest2, "required,email") if errs != nil { fmt.Println(errs) // 輸出: Key: "" Error:Field validation for "" failed on the "email" tag。驗(yàn)證失敗 } fmt.Println("\r\nEnd!!") }
運(yùn)行輸出:
go run simple1.go
Key: '' Error:Field validation for '' failed on the 'required' tag
Key: '' Error:Field validation for '' failed on the 'required' tag
success
Key: '' Error:Field validation for '' failed on the 'email' tag
End!!
例子2:驗(yàn)證結(jié)構(gòu)體struct#
from:struct validate
validation_struct.go,這個程序還列出了效驗(yàn)出錯字段的一些信息,
Copy
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"` Addresses []*Address `validate:"required,dive,required"` } type Address struct { Street string `validate:"required"` City string `validate:"required"` Planet string `validate:"required"` Phone string `validate:"required"` } func main() { address := &Address{ Street: "Eavesdown Docks", Planet: "Persphone", Phone: "none", } user := &User{ FirstName: "Badger", LastName: "Smith", Age: 135, Email: "Badger.Smith@gmail.com", Addresses: []*Address{address}, } 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) { // 列出效驗(yàn)出錯字段的信息 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 } }
運(yùn)行 輸出:
$ go run validation_struct.go
=== error msg ====
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
Key: 'User.Addresses[0].City' Error:Field validation for 'City' failed on the 'required' tag
=========== error field info ====================
Namespace: User.Age
Fild: Age
StructNamespace: User.Age
StructField: Age
Tag: lte
ActualTag: lte
Kind: uint8
Type: uint8
Value: 135
Param: 130
Namespace: User.Addresses[0].City
Fild: City
StructNamespace: User.Addresses[0].City
StructField: City
Tag: required
ActualTag: required
Kind: string
Type: string
Value:
Param:
還可以給字段加一些其他tag信息,方面form,json的解析,如下:
Copy
type User struct { FirstName string `form:"firstname" json:"firstname" validate:"required"` LastName string `form:"lastname" json:"lastname" validate:"required"` Age uint8 ` form:"age" json:"age"validate:"gte=0,lte=130"` Email string ` form:"email" json:"email" validate:"required,email"` }
例子2.2:驗(yàn)證slice map#
slice#
slice驗(yàn)證中用到一個tag關(guān)鍵字?dive?, 意思深入一層驗(yàn)證。
validate_slice.go
Copy
package main import ( "fmt" "github.com/go-playground/validator/v10" ) func main() { sliceone := []string{"123", "onetwothree", "myslicetest", "four", "five"} validate := validator.New() err := validate.Var(sliceone, "max=15,dive,min=4") if err != nil { fmt.Println(err) } slicetwo := []string{} err = validate.Var(slicetwo, "min=4,dive,required") if err != nil { fmt.Println(err) } }
運(yùn)行輸出:
$ go run validate_slice.go
Key: '[0]' Error:Field validation for '[0]' failed on the 'min' tag
Key: '' Error:Field validation for '' failed on the 'min' tag
說明:
Copy
sliceone := []string{"123", "onetwothree", "myslicetest", "four", "five"} validate.Var(sliceone, "max=15,dive,min=4")
第二個參數(shù)中tag關(guān)鍵字?dive?前面的 max=15,驗(yàn)證 [] , 也就是驗(yàn)證slice的長度,dive?后面的 min=4,驗(yàn)證slice里的值長度,也就是說 dive 后面的 tag 驗(yàn)證 slice 的值
那如果是二維slice驗(yàn)證呢?如:
Copy
slicethree := [][]string{} validate.Var(slicethree, "min=2,dive,len=2,dive,required") validate.Var(slicethree, "min=2,dive,dive,required")
說明:
這里有2個 dive,剛好深入到二維slice,但他們也有不同之處,第二個表達(dá)式的第一個dive后沒有設(shè)置tag。
第一個驗(yàn)證表達(dá)式:
min=2:驗(yàn)證第一個 [] 方括號的值長度 ;
len=2:驗(yàn)證第二個 []string 長度;
required:驗(yàn)證slice里的值
第二個驗(yàn)證表達(dá)式:
min=2:驗(yàn)證第一個 [] 方括號的值長度 ;
dive: 后沒有設(shè)置tag值,不驗(yàn)證第二個 []string?;
required: 驗(yàn)證slice里的值
map#
map的驗(yàn)證中也需要tag關(guān)鍵字?dive, 另外,它還有?keys?和?endkeys?兩tag,驗(yàn)證這2個tag之間map的 key,而不是value值。
validate_map.go
Copy
package main import ( "fmt" "github.com/go-playground/validator/v10" ) func main() { var mapone map[string]string mapone = map[string]string{"one": "jimmmy", "two": "tom", "three": ""} validate := validator.New() err := validate.Var(mapone, "gte=3,dive,keys,eq=1|eq=2,endkeys,required") if err != nil { fmt.Println(err) } }
運(yùn)行輸出:
$ go run validate_map.go
Key: '[three]' Error:Field validation for '[three]' failed on the 'eq=1|eq=3' tag
Key: '[three]' Error:Field validation for '[three]' failed on the 'required' tag
Key: '[one]' Error:Field validation for '[one]' failed on the 'eq=1|eq=3' tag
Key: '[two]' Error:Field validation for '[two]' failed on the 'eq=1|eq=3' tag
說明:
gte=3:驗(yàn)證map自己的長度;
dive后的 keys,eq=1|eq=2,endkeys:驗(yàn)證map的keys個數(shù),也就是驗(yàn)證 [] 里值。上例中定義了一個string,所以明顯報了3個錯誤。
required:驗(yàn)證 map的值value
那嵌套map怎么驗(yàn)證?
如:map[[3]string]string,和上面slice差不多,使用多個?dive
Copy
var maptwo map[[3]string]string{} validate.Var(maptwo, "gte=3,dive,keys,dive,eq=1|eq=3,endkeys,required")
說明:
gte=3: 驗(yàn)證map的長度;
keys,dive,eq=1|eq=3,endkeys:keys和endkeys中有一個dive(深入一級),驗(yàn)證map中key的數(shù)組每一個值
required: 驗(yàn)證map的值
用戶自定義函數(shù)驗(yàn)證#
用戶自定義函數(shù)驗(yàn)證字段是否合法,效驗(yàn)是否正確。
例子3: 通過字段tag自定義函數(shù)#
validate.RegisterValidation
customer_tag.go:
Copy
package main import ( "fmt" "github.com/go-playground/validator/v10" ) type User struct { Name string `form:"name" json:"name" validate:"required,CustomerValidation"` //注意:required和CustomerValidation之間不能有空格,否則panic。CustomerValidation:自定義tag-函數(shù)標(biāo)簽 Age uint8 ` form:"age" json:"age" validate:"gte=0,lte=80"` //注意:gte=0和lte=80之間不能有空格,否則panic } var validate *validator.Validate func main() { validate = validator.New() validate.RegisterValidation("CustomerValidation", CustomerValidationFunc) //注冊自定義函數(shù),前一個參數(shù)是struct里tag自定義,后一個參數(shù)是自定義的函數(shù) user := &User{ Name: "jimmy", Age: 86, } fmt.Println("first value: ", user) err := validate.Struct(user) if err != nil { fmt.Printf("Err(s):\n%+v\n", err) } user.Name = "tom" user.Age = 29 fmt.Println("second value: ", user) err = validate.Struct(user) if err != nil { fmt.Printf("Err(s):\n%+v\n", err) } } // 自定義函數(shù) func CustomerValidationFunc(f1 validator.FieldLevel) bool { // f1 包含了字段相關(guān)信息 // f1.Field() 獲取當(dāng)前字段信息 // f1.Param() 獲取tag對應(yīng)的參數(shù) // f1.FieldName() 獲取字段名稱 return f1.Field().String() == "jimmy" }
運(yùn)行輸出:
$ go run customer.go
first value: &{jimmy 86}
Err(s):
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
second value: &{tom 29}
Err(s):
Key: 'User.Name' Error:Field validation for 'Name' failed on the 'CustomerValidation' tag
**注意:
上面代碼user struct定義中 ,validate里的required和CustomerValidation之間不能有空格,否則運(yùn)行時報panic錯誤:panic: Undefined validation function ' CustomerValidation' on field 'Name'
例子4:自定義函數(shù)-直接注冊函數(shù)1#
不通過字段tag自定義函數(shù),直接注冊函數(shù)。
RegisterStructValidation
https://github.com/go-playground/validator/blob/master/_examples/struct-level/main.go
customer1.go
Copy
package main import ( "fmt" "github.com/go-playground/validator/v10" ) type User struct { FirstName string `json:firstname` LastName string `json:lastname` Age uint8 `validate:"gte=0,lte=130"` Email string `validate:"required,email"` FavouriteColor string `validate:"hexcolor|rgb|rgba"` } var validate *validator.Validate func main() { validate = validator.New() validate.RegisterStructValidation(UserStructLevelValidation, User{}) user := &User{ FirstName: "", LastName: "", Age: 30, Email: "TestFunc@126.com", FavouriteColor: "#000", } err := validate.Struct(user) if err != nil { fmt.Println(err) } } func UserStructLevelValidation(sl validator.StructLevel) { user := sl.Current().Interface().(User) if len(user.FirstName) == 0 && len(user.LastName) == 0 { sl.ReportError(user.FirstName, "FirstName", "firstname", "firstname", "") sl.ReportError(user.LastName, "LastName", "lastname", "lastname", "") } }
運(yùn)行輸出:
$ go run customer1.go
Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'firstname' tag
Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'lastname' tag
例子5:自定義函數(shù)-直接注冊函數(shù)2#
RegisterCustomTypeFunc
https://github.com/go-playground/validator/blob/master/_examples/custom/main.go
validate.RegisterCustomTypeFunc:驗(yàn)證類型的自定義函數(shù)
customer2.go:
Copy
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 func main() { validate = validator.New() validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) // build object for validation 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) } } func ValidateValuer(field reflect.Value) interface{} { if valuer, ok := field.Interface().(driver.Valuer); ok { val, err := valuer.Value() if err == nil { return val } // handle the error how you want } return nil }
運(yùn)行輸出:
$ go run customer.go
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
注意,這個函數(shù):
RegisterCustomTypeFunc,它上面有2行注釋:
// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
//
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
它是一個驗(yàn)證數(shù)據(jù)類型自定義函數(shù),NOTE:這個方法不是線程安全的
例子6:兩字段比較#
兩個字段比較,有一種是密碼比較驗(yàn)證,用戶注冊時候驗(yàn)證2次密碼輸入是否相同。用tag?eqfield?比較兩字段。。
verify_pwd.go:
Copy
package main import ( "fmt" "github.com/go-playground/validator/v10" ) // 注冊用戶 user struct type User struct { UserName string `json:"username" validate:"lte=14,gte=4"` Password string `json:"password" validate:"max=20,min=6"` Password2 string `json:"password2" validate:"eqfield=Password"` } func main() { validate := validator.New() user1 := User{ UserName: "jim", Password: "123456", Password2: "12345", } fmt.Println("validate user1 value: ", user1) err := validate.Struct(user1) if err != nil { fmt.Println(err) } fmt.Println("====================") user2 := User{ UserName: "jimy", Password: "123456", Password2: "123456", } fmt.Println("validate user2 value: ", user2) err = validate.Struct(user2) if err != nil { fmt.Println(err) } }
運(yùn)行輸出:
$ go run verify_pwd.go
validate user1 value: {jim 123456 12345}
Key: 'User.UserName' Error:Field validation for 'UserName' failed on the 'gte' tag
Key: 'User.Password2' Error:Field validation for 'Password2' failed on the 'eqfield' tag
====================
validate user2 value: {jimy 123456 123456}
還有一種是2變量字段比較,見下面例子 eq_field.go:
Copy
package main import ( "fmt" "github.com/go-playground/validator/v10" ) func main() { field1 := "tom" field2 := "jimmy" validate := validator.New() fmt.Println("tag nefield: ") err := validate.VarWithValue(field1, field2, "nefield") if err != nil { fmt.Println(err) } else { fmt.Println("correct") } fmt.Println("===========================") fmt.Println("tag eqfield: ") err = validate.VarWithValue(field1, field2, "eqfield") if err != nil { fmt.Println(err) } }
運(yùn)行輸出:
$ go run eq_field.go
tag nefield:
correct
===========================
tag eqfield:
Key: '' Error:Field validation for '' failed on the 'eqfield' tag
例子7:翻譯/自定義字段錯誤#
- universal-translator
- i10n
Copy
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" ) 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() //中文翻譯器 // 第一個參數(shù)是必填,如果沒有其他的語言設(shè)置,就用這第一個 // 后面的參數(shù)是支持多語言環(huán)境( // 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 }
運(yùn)行輸出:
$ go run customer_err_info3.go
map[Age:Age必須小于或等于30 Email:Email必須是一個有效的郵箱]
五、參考#
- https://github.com/go-playground/validator/blob/master/README.md
- https://github.com/go-playground/validator/tree/master/_examples
- https://github.com/go-playground/universal-translator/tree/master/_examples
- https://github.com/go-playground/validator/issues/633
- validator translator
出處:golang常用庫:字段參數(shù)驗(yàn)證庫-validator使用 - 九卷 - 博客園
總結(jié)
以上是生活随笔為你收集整理的golang常用库:字段参数验证库-validator使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android/Java中使用Proto
- 下一篇: ArcGIS API for Pytho