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"

例子:

  1. type User struct {
  2. Name string `json:"name" validate:"min=0,max=35"`
  3. Age unit8 `json:"age" validate:"lte=90,gte=0"`
  4. }

更多功能请参看文档 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"

例子:

  1. type User struct {
  2. Name string `validate:"contains=tom"`
  3. Age int `validate:"min=1"`
  4. }

字段验证

doc: https://github.com/go-playground/validator/blob/master/README.md#fields

  • eqcsfield:跨不同结构体字段验证,比如说 Struct1 Filed1,与结构体Struct2 Field2相等,

    1. type Struct1 struct {
    2. Field1 string `validate:eqcsfield=Struct2.Field2`
    3. Struct2 struct {
    4. Field2 string
    5. }
    6. }
  • necsfield:跨不同结构体字段不相等

  • eqfield:同一结构体字段验证相等,最常见的就是输入2次密码验证

    1. type User struct {
    2. Name string `validate:"lte=4"`
    3. Age int `validate:"min=20"`
    4. Password string `validate:"min=10"`
    5. Password2 string `validate:"eqfield=Password"`
    6. }
  • nefield:同一结构体字段验证不相等

    1. type User struct {
    2. Name string `validate:"lte=4"`
    3. Age int `validate:"min=20"`
    4. Password string `validate:"min=10,nefield=Name"`
    5. }
  • 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

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/go-playground/validator/v10"
  5. )
  6. func main() {
  7. validate := validator.New()
  8. var boolTest bool
  9. err := validate.Var(boolTest, "required")
  10. if err != nil {
  11. fmt.Println(err)
  12. }
  13. var stringTest string = ""
  14. err = validate.Var(stringTest, "required")
  15. if err != nil {
  16. fmt.Println(err)
  17. }
  18. var emailTest string = "test@126.com"
  19. err = validate.Var(emailTest, "email")
  20. if err != nil {
  21. fmt.Println(err)
  22. } else {
  23. fmt.Println("success") // 输出: success。 说明验证成功
  24. }
  25. emailTest2 := "test.126.com"
  26. errs := validate.Var(emailTest2, "required,email")
  27. if errs != nil {
  28. fmt.Println(errs) // 输出: Key: "" Error:Field validation for "" failed on the "email" tag。验证失败
  29. }
  30. fmt.Println("\r\nEnd!!")
  31. }

运行输出:

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:验证结构体struct

from:struct validate

validation_struct.go,这个程序还列出了效验出错字段的一些信息,

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/go-playground/validator/v10"
  5. )
  6. type User struct {
  7. FirstName string `validate:"required"`
  8. LastName string `validate:"required"`
  9. Age uint8 `validate:"gte=0,lte=130"`
  10. Email string `validate:"required,email"`
  11. Addresses []*Address `validate:"required,dive,required"`
  12. }
  13. type Address struct {
  14. Street string `validate:"required"`
  15. City string `validate:"required"`
  16. Planet string `validate:"required"`
  17. Phone string `validate:"required"`
  18. }
  19. func main() {
  20. address := &Address{
  21. Street: "Eavesdown Docks",
  22. Planet: "Persphone",
  23. Phone: "none",
  24. }
  25. user := &User{
  26. FirstName: "Badger",
  27. LastName: "Smith",
  28. Age: 135,
  29. Email: "Badger.Smith@gmail.com",
  30. Addresses: []*Address{address},
  31. }
  32. validate := validator.New()
  33. err := validate.Struct(user)
  34. if err != nil {
  35. fmt.Println("=== error msg ====")
  36. fmt.Println(err)
  37. if _, ok := err.(*validator.InvalidValidationError); ok {
  38. fmt.Println(err)
  39. return
  40. }
  41. fmt.Println("\r\n=========== error field info ====================")
  42. for _, err := range err.(validator.ValidationErrors) {
  43. // 列出效验出错字段的信息
  44. fmt.Println("Namespace: ", err.Namespace())
  45. fmt.Println("Fild: ", err.Field())
  46. fmt.Println("StructNamespace: ", err.StructNamespace())
  47. fmt.Println("StructField: ", err.StructField())
  48. fmt.Println("Tag: ", err.Tag())
  49. fmt.Println("ActualTag: ", err.ActualTag())
  50. fmt.Println("Kind: ", err.Kind())
  51. fmt.Println("Type: ", err.Type())
  52. fmt.Println("Value: ", err.Value())
  53. fmt.Println("Param: ", err.Param())
  54. fmt.Println()
  55. }
  56. // from here you can create your own error messages in whatever language you wish
  57. return
  58. }
  59. }

运行 输出:

$ 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的解析,如下:

  1. type User struct {
  2. FirstName string `form:"firstname" json:"firstname" validate:"required"`
  3. LastName string `form:"lastname" json:"lastname" validate:"required"`
  4. Age uint8 ` form:"age" json:"age"validate:"gte=0,lte=130"`
  5. Email string ` form:"email" json:"email" validate:"required,email"`
  6. }

例子2.2:验证slice map

slice

slice验证中用到一个tag关键字 dive , 意思深入一层验证。
validate_slice.go

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/go-playground/validator/v10"
  5. )
  6. func main() {
  7. sliceone := []string{"123", "onetwothree", "myslicetest", "four", "five"}
  8. validate := validator.New()
  9. err := validate.Var(sliceone, "max=15,dive,min=4")
  10. if err != nil {
  11. fmt.Println(err)
  12. }
  13. slicetwo := []string{}
  14. err = validate.Var(slicetwo, "min=4,dive,required")
  15. if err != nil {
  16. fmt.Println(err)
  17. }
  18. }

运行输出:

$ 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

说明:

  1. sliceone := []string{"123", "onetwothree", "myslicetest", "four", "five"}
  2. validate.Var(sliceone, "max=15,dive,min=4")

第二个参数中tag关键字 dive 前面的 max=15,验证 [] , 也就是验证slice的长度,dive 后面的 min=4,验证slice里的值长度,也就是说 dive 后面的 tag 验证 slice 的值

那如果是二维slice验证呢?如:

  1. slicethree := [][]string{}
  2. validate.Var(slicethree, "min=2,dive,len=2,dive,required")
  3. 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, 另外,它还有 keysendkeys 两tag,验证这2个tag之间map的 key,而不是value值。
validate_map.go

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/go-playground/validator/v10"
  5. )
  6. func main() {
  7. var mapone map[string]string
  8. mapone = map[string]string{"one": "jimmmy", "two": "tom", "three": ""}
  9. validate := validator.New()
  10. err := validate.Var(mapone, "gte=3,dive,keys,eq=1|eq=2,endkeys,required")
  11. if err != nil {
  12. fmt.Println(err)
  13. }
  14. }

运行输出:

$ 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

  1. var maptwo map[[3]string]string{}
  2. 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:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/go-playground/validator/v10"
  5. )
  6. type User struct {
  7. Name string `form:"name" json:"name" validate:"required,CustomerValidation"` //注意:required和CustomerValidation之间不能有空格,否则panic。CustomerValidation:自定义tag-函数标签
  8. Age uint8 ` form:"age" json:"age" validate:"gte=0,lte=80"` //注意:gte=0和lte=80之间不能有空格,否则panic
  9. }
  10. var validate *validator.Validate
  11. func main() {
  12. validate = validator.New()
  13. validate.RegisterValidation("CustomerValidation", CustomerValidationFunc) //注册自定义函数,前一个参数是struct里tag自定义,后一个参数是自定义的函数
  14. user := &User{
  15. Name: "jimmy",
  16. Age: 86,
  17. }
  18. fmt.Println("first value: ", user)
  19. err := validate.Struct(user)
  20. if err != nil {
  21. fmt.Printf("Err(s):\n%+v\n", err)
  22. }
  23. user.Name = "tom"
  24. user.Age = 29
  25. fmt.Println("second value: ", user)
  26. err = validate.Struct(user)
  27. if err != nil {
  28. fmt.Printf("Err(s):\n%+v\n", err)
  29. }
  30. }
  31. // 自定义函数
  32. func CustomerValidationFunc(f1 validator.FieldLevel) bool {
  33. // f1 包含了字段相关信息
  34. // f1.Field() 获取当前字段信息
  35. // f1.Param() 获取tag对应的参数
  36. // f1.FieldName() 获取字段名称
  37. return f1.Field().String() == "jimmy"
  38. }

运行输出:

$ 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

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/go-playground/validator/v10"
  5. )
  6. type User struct {
  7. FirstName string `json:firstname`
  8. LastName string `json:lastname`
  9. Age uint8 `validate:"gte=0,lte=130"`
  10. Email string `validate:"required,email"`
  11. FavouriteColor string `validate:"hexcolor|rgb|rgba"`
  12. }
  13. var validate *validator.Validate
  14. func main() {
  15. validate = validator.New()
  16. validate.RegisterStructValidation(UserStructLevelValidation, User{})
  17. user := &User{
  18. FirstName: "",
  19. LastName: "",
  20. Age: 30,
  21. Email: "TestFunc@126.com",
  22. FavouriteColor: "#000",
  23. }
  24. err := validate.Struct(user)
  25. if err != nil {
  26. fmt.Println(err)
  27. }
  28. }
  29. func UserStructLevelValidation(sl validator.StructLevel) {
  30. user := sl.Current().Interface().(User)
  31. if len(user.FirstName) == 0 && len(user.LastName) == 0 {
  32. sl.ReportError(user.FirstName, "FirstName", "firstname", "firstname", "")
  33. sl.ReportError(user.LastName, "LastName", "lastname", "lastname", "")
  34. }
  35. }

运行输出:

$ 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:

  1. package main
  2. import (
  3. "database/sql"
  4. "database/sql/driver"
  5. "fmt"
  6. "reflect"
  7. "github.com/go-playground/validator/v10"
  8. )
  9. type DbBackedUser struct {
  10. Name sql.NullString `validate:"required"`
  11. Age sql.NullInt64 `validate:"required"`
  12. }
  13. var validate *validator.Validate
  14. func main() {
  15. validate = validator.New()
  16. validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{})
  17. // build object for validation
  18. x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}}
  19. err := validate.Struct(x)
  20. if err != nil {
  21. fmt.Printf("Err(s):\n%+v\n", err)
  22. }
  23. }
  24. func ValidateValuer(field reflect.Value) interface{} {
  25. if valuer, ok := field.Interface().(driver.Valuer); ok {
  26. val, err := valuer.Value()
  27. if err == nil {
  28. return val
  29. }
  30. // handle the error how you want
  31. }
  32. return nil
  33. }

运行输出:

$ 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:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/go-playground/validator/v10"
  5. )
  6. // 注册用户 user struct
  7. type User struct {
  8. UserName string `json:"username" validate:"lte=14,gte=4"`
  9. Password string `json:"password" validate:"max=20,min=6"`
  10. Password2 string `json:"password2" validate:"eqfield=Password"`
  11. }
  12. func main() {
  13. validate := validator.New()
  14. user1 := User{
  15. UserName: "jim",
  16. Password: "123456",
  17. Password2: "12345",
  18. }
  19. fmt.Println("validate user1 value: ", user1)
  20. err := validate.Struct(user1)
  21. if err != nil {
  22. fmt.Println(err)
  23. }
  24. fmt.Println("====================")
  25. user2 := User{
  26. UserName: "jimy",
  27. Password: "123456",
  28. Password2: "123456",
  29. }
  30. fmt.Println("validate user2 value: ", user2)
  31. err = validate.Struct(user2)
  32. if err != nil {
  33. fmt.Println(err)
  34. }
  35. }

运行输出:

$ 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:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/go-playground/validator/v10"
  5. )
  6. func main() {
  7. field1 := "tom"
  8. field2 := "jimmy"
  9. validate := validator.New()
  10. fmt.Println("tag nefield: ")
  11. err := validate.VarWithValue(field1, field2, "nefield")
  12. if err != nil {
  13. fmt.Println(err)
  14. } else {
  15. fmt.Println("correct")
  16. }
  17. fmt.Println("===========================")
  18. fmt.Println("tag eqfield: ")
  19. err = validate.VarWithValue(field1, field2, "eqfield")
  20. if err != nil {
  21. fmt.Println(err)
  22. }
  23. }

运行输出:

$ go run eq_field.go tag nefield:

correct

tag eqfield: Key: ‘’ Error:Field validation for ‘’ failed on the ‘eqfield’ tag

五、参考