golang常用庫:gorilla/mux-http路由庫使用
golang常用庫:配置文件解析庫/管理工具-viper使用
golang常用庫:操作數據庫的orm框架-gorm基本使用
golang常用庫:字段參數驗證庫-validator使用
一、背景
在平常開發中,特別是在web應用開發中,為了驗證輸入字段的合法性,都會做一些驗證操作。比如對用戶提交的表單字段進行驗證,或者對請求的API接口字段進行驗證,驗證字段的合法性,保證輸入字段值的安全,防止用戶的惡意請求。
一般的做法是用正則表達式,一個字段一個字段的進行驗證。一個一個字段驗證的話,寫起來比較繁瑣。那有沒更好的方法,進行字段的合法性驗證?有, 這就是下面要介紹的 validator 這個驗證組件。
代碼地址:
https://github.com/go-playground/validator
文檔地址:
https://github.com/go-playground/validator/blob/master/README.md
二、功能介紹
這個驗證包 github.com/go-playground/validator 驗證功能非常多。
標記之間特殊符號說明
- 逗號(
,
):把多個驗證標記隔開。注意
:隔開逗號之間不能有空格,validate:"lt=0,gt=100"
,逗號那里不能有空格,否則panic - 橫線(
-
):跳過該字段不驗證 - 豎線(
|
):使用多個驗證標記,但是只需滿足其中一個即可 - required:表示該字段值必輸設置,且不能為默認值
- omitempty:如果字段未設置,則忽略它
范圍比較驗證
doc: https://github.com/go-playground/validator/blob/master/README.md#comparisons
范圍驗證: 切片、數組和map、字符串,驗證其長度;數值,驗證大小范圍
- lte:小於等於參數值,
validate:"lte=3"
(小於等於3) - gte:大於等於參數值,
validate:"lte=120,gte=0"
(大於等於0小於等於120) - lt:小於參數值,
validate:"lt=3"
(小於3) - gt:大於參數值,
validate:"lt=120,gt=0"
(大於0小於120) - len:等於參數值,
validate:"len=2"
- max:最大值,小於等於參數值,
validate:"max=20"
(小於等於20) - min:最小值,大於等於參數值,
validate:"min=2,max=20"
(大於等於2小於等於20) - ne:不等於,
validate:"ne=2"
(不等於2) - oneof:只能是列舉出的值其中一個,這些值必須是數值或字符串,以空格分隔,如果字符串中有空格,將字符串用單引號包圍,
validate:"oneof=red green"
例子:
type User struct {
Name string `json:"name" validate:"min=0,max=35"`
Age unit8 `json:"age" validate:"lte=90,gte=0"`
}
更多功能請參看文檔 validator comparisons doc
字符串驗證
doc: https://github.com/go-playground/validator/blob/master/README.md#strings
- contains:包含參數子串,
validate:"contains=tom"
(字段的字符串值包含tom) - excludes:包含參數子串,
validate:"excludes=tom"
(字段的字符串值不包含tom) - startswith:以參數子串為前綴,
validate:"startswith=golang"
- endswith:以參數子串為后綴,
validate:"startswith=world"
例子:
type User struct {
Name string `validate:"contains=tom"`
Age int `validate:"min=1"`
}
更多功能請參看文檔 validator strings doc
字段驗證
doc: https://github.com/go-playground/validator/blob/master/README.md#fields
- eqcsfield:跨不同結構體字段驗證,比如說 Struct1 Filed1,與結構體Struct2 Field2相等,
type Struct1 struct {
Field1 string `validate:eqcsfield=Struct2.Field2`
Struct2 struct {
Field2 string
}
}
-
necsfield:跨不同結構體字段不相等
-
eqfield:同一結構體字段驗證相等,最常見的就是輸入2次密碼驗證
type User struct {
Name string `validate:"lte=4"`
Age int `validate:"min=20"`
Password string `validate:"min=10"`
Password2 string `validate:"eqfield=Password"`
}
- nefield:同一結構體字段驗證不相等
type User struct {
Name string `validate:"lte=4"`
Age int `validate:"min=20"`
Password string `validate:"min=10,nefield=Name"`
}
- gtefield:大於等於同一結構體字段,
validate:"gtefiled=Field2"
- ltefield:小於等於同一結構體字段
更多功能請參看文檔:validator Fields DOC
網絡驗證
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:驗證單個字段變量值
validation1.go
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。 說明驗證成功
}
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。驗證失敗
}
fmt.Println("\r\nEnd!!")
}
運行輸出:
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' tagEnd!!
例子2:驗證結構體struct
from:struct validate
validation_struct.go,這個程序還列出了效驗出錯字段的一些信息,
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) {
// 列出效驗出錯字段的信息
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
}
}
運行 輸出:
$ 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: 130Namespace: 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的解析,如下:
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:驗證slice map
slice
slice驗證中用到一個tag關鍵字 dive
, 意思深入一層驗證。
validate_slice.go
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)
}
}
運行輸出:
$ 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
說明:
sliceone := []string{"123", "onetwothree", "myslicetest", "four", "five"}
validate.Var(sliceone, "max=15,dive,min=4")
第二個參數中tag關鍵字
dive
前面的 max=15,驗證 [] , 也就是驗證slice的長度,dive
后面的 min=4,驗證slice里的值長度,也就是說 dive 后面的 tag 驗證 slice 的值
那如果是二維slice驗證呢?如:
slicethree := [][]string{}
validate.Var(slicethree, "min=2,dive,len=2,dive,required")
validate.Var(slicethree, "min=2,dive,dive,required")
說明:
這里有2個 dive,剛好深入到二維slice,但他們也有不同之處,第二個表達式的第一個dive后沒有設置tag。
第一個驗證表達式:
min=2:驗證第一個 [] 方括號的值長度 ;
len=2:驗證第二個 []string 長度;
required:驗證slice里的值
第二個驗證表達式:
min=2:驗證第一個 [] 方括號的值長度 ;
dive: 后沒有設置tag值,不驗證第二個 []string ;
required: 驗證slice里的值
map
map的驗證中也需要tag關鍵字 dive
, 另外,它還有 keys
和 endkeys
兩tag,驗證這2個tag之間map的 key,而不是value值。
validate_map.go
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)
}
}
運行輸出:
$ 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:驗證map自己的長度;
dive后的 keys,eq=1|eq=2,endkeys:驗證map的keys個數,也就是驗證 [] 里值。上例中定義了一個string,所以明顯報了3個錯誤。
required:驗證 map的值value
那嵌套map怎么驗證?
如:map[[3]string]string,和上面slice差不多,使用多個 dive
var maptwo map[[3]string]string{}
validate.Var(maptwo, "gte=3,dive,keys,dive,eq=1|eq=3,endkeys,required")
說明:
gte=3: 驗證map的長度;
keys,dive,eq=1|eq=3,endkeys:keys和endkeys中有一個dive(深入一級),驗證map中key的數組每一個值
required: 驗證map的值
用戶自定義函數驗證
用戶自定義函數驗證字段是否合法,效驗是否正確。
例子3: 通過字段tag自定義函數
validate.RegisterValidation
customer_tag.go:
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-函數標簽
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) //注冊自定義函數,前一個參數是struct里tag自定義,后一個參數是自定義的函數
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)
}
}
// 自定義函數
func CustomerValidationFunc(f1 validator.FieldLevel) bool {
// f1 包含了字段相關信息
// f1.Field() 獲取當前字段信息
// f1.Param() 獲取tag對應的參數
// f1.FieldName() 獲取字段名稱
return f1.Field().String() == "jimmy"
}
運行輸出:
$ 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之間不能有空格,否則運行時報panic錯誤:panic: Undefined validation function ' CustomerValidation' on field 'Name'
例子4:自定義函數-直接注冊函數1
不通過字段tag自定義函數,直接注冊函數。
RegisterStructValidation
https://github.com/go-playground/validator/blob/master/_examples/struct-level/main.go
customer1.go
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", "")
}
}
運行輸出:
$ 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:自定義函數-直接注冊函數2
RegisterCustomTypeFunc
https://github.com/go-playground/validator/blob/master/_examples/custom/main.go
validate.RegisterCustomTypeFunc:驗證類型的自定義函數
customer2.go:
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
}
運行輸出:
$ 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
注意,這個函數:
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
它是一個驗證數據類型自定義函數,NOTE:這個方法不是線程安全的
例子6:兩字段比較
兩個字段比較,有一種是密碼比較驗證,用戶注冊時候驗證2次密碼輸入是否相同。用tag eqfield
比較兩字段。。
verify_pwd.go:
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)
}
}
運行輸出:
$ 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:
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)
}
}
運行輸出:
$ go run eq_field.go
tag nefield:
correct
===========================
tag eqfield:
Key: '' Error:Field validation for '' failed on the 'eqfield' tag
例子7:翻譯/自定義字段錯誤
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() //中文翻譯器
// 第一個參數是必填,如果沒有其他的語言設置,就用這第一個
// 后面的參數是支持多語言環境(
// 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
}
運行輸出:
$ go run customer_err_info3.go
map[Age:Age必須小於或等於30 Email:Email必須是一個有效的郵箱]