官方地址https://github.com/go-playground/validator
开发文档:https://godoc.org/github.com/go-playground/validator
中文开发参考文档:https://s0godoc0org.icopy.site/gopkg.in/go-playground/validator.v9
开发参考用例:https://github.com/go-playground/validator/tree/master/_examples

HelloWorld

  1. package main
  2. import (
  3. "fmt"
  4. "gopkg.in/go-playground/validator.v9"
  5. )
  6. // User contains user information
  7. type User struct {
  8. FirstName string `validate:"required"`
  9. LastName string `validate:"required"`
  10. Age uint8 `validate:"gte=0,lte=130"`
  11. Email string `validate:"required,email"`
  12. FavouriteColor string `validate:"iscolor"` // alias for 'hexcolor|rgb|rgba|hsl|hsla'
  13. Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
  14. }
  15. // Address houses a users address information
  16. type Address struct {
  17. Street string `validate:"required"`
  18. City string `validate:"required"`
  19. Planet string `validate:"required"`
  20. Phone string `validate:"required"`
  21. }
  22. // use a single instance of Validate, it caches struct info
  23. var validate *validator.Validate
  24. func main() {
  25. validate = validator.New()
  26. validateStruct()
  27. validateVariable()
  28. }
  29. func validateStruct() {
  30. address := &Address{
  31. Street: "Eavesdown Docks",
  32. Planet: "Persphone",
  33. Phone: "none",
  34. }
  35. user := &User{
  36. FirstName: "Badger",
  37. LastName: "Smith",
  38. Age: 135,
  39. Email: "Badger.Smith@gmail.com",
  40. FavouriteColor: "#000-",
  41. Addresses: []*Address{address},
  42. }
  43. // returns nil or ValidationErrors ( []FieldError )
  44. err := validate.Struct(user)
  45. if err != nil {
  46. // this check is only needed when your code could produce
  47. // an invalid value for validation such as interface with nil
  48. // value most including myself do not usually have code like this.
  49. if _, ok := err.(*validator.InvalidValidationError); ok {
  50. fmt.Println(err)
  51. return
  52. }
  53. for _, err := range err.(validator.ValidationErrors) {
  54. fmt.Println(err.Namespace())
  55. fmt.Println(err.Field())
  56. fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered or
  57. fmt.Println(err.StructField()) // by passing alt name to ReportError like below
  58. fmt.Println(err.Tag())
  59. fmt.Println(err.ActualTag())
  60. fmt.Println(err.Kind())
  61. fmt.Println(err.Type())
  62. fmt.Println(err.Value())
  63. fmt.Println(err.Param())
  64. }
  65. return
  66. }
  67. // save user to database
  68. }
  69. func validateVariable() {
  70. myEmail := "joeybloggs.gmail.com"
  71. errs := validate.Var(myEmail, "required,email")
  72. if errs != nil {
  73. fmt.Println(errs.Error()) // output: Key: "" Error:Field validation for "" failed on the "email" tag
  74. return
  75. }
  76. }

打印结果

  1. GOROOT=/usr/local/Cellar/go/1.13.4/libexec #gosetup
  2. GOPATH=/Users/baxiang/go #gosetup
  3. /usr/local/Cellar/go/1.13.4/libexec/bin/go build -o /private/var/folders/61/2qtr44g130x_pp0xn3ym8qv80000gn/T/___go_build_github_com_baxiang_go_note_validator github.com/baxiang/go-note/validator #gosetup
  4. /private/var/folders/61/2qtr44g130x_pp0xn3ym8qv80000gn/T/___go_build_github_com_baxiang_go_note_validator #gosetup
  5. User.Age
  6. Age
  7. User.Age
  8. Age
  9. lte
  10. lte
  11. uint8
  12. uint8
  13. 135
  14. 130
  15. User.FavouriteColor
  16. FavouriteColor
  17. User.FavouriteColor
  18. FavouriteColor
  19. iscolor
  20. hexcolor|rgb|rgba|hsl|hsla
  21. string
  22. string
  23. #000-
  24. User.Addresses[0].City
  25. City
  26. User.Addresses[0].City
  27. City
  28. required
  29. required
  30. string
  31. string
  32. Key: '' Error:Field validation for '' failed on the 'email' tag
  33. Process finished with exit code 0

其中一个区别是v8版本没有指定默认的TagName,需要在每次声明validator实例的时候配置;在v9中设置了默认的TagName就是validate。我们的gin框架在使用的时候设置的TagName为”binding”。

validator中给的例子代码

  1. var validate *validator.Validate
  2. config := &validator.Config{TagName: "validate"}
  3. validate = validator.New(config)

gin中设置的TagName,文件gin/binding/default_validator.go

  1. func (v *defaultValidator) lazyinit() {
  2. v.once.Do(func() {
  3. config := &validator.Config{TagName: "binding"}
  4. v.validate = validator.New(config)
  5. })
  6. }

这也就是为啥gin框架里面example中的定义的struct都是binding开头
其他区别例如实例化不同等

  1. type Booking struct {
  2. CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
  3. CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
  4. }

常用标签字段

validator使用的struct的tag有以下这么多,其中有一些是通过正则表达式实现类型判断的,例如email、rgb、uuid等;有一些是通过第三方库实现的,例如ipv4、cidr等。

  1. // validator/baked_in.go
  2. bakedInValidators = map[string]Func{
  3. "required": hasValue,
  4. "isdefault": isDefault,
  5. "len": hasLengthOf,
  6. "min": hasMinOf,
  7. "max": hasMaxOf,
  8. "eq": isEq,
  9. "ne": isNe,
  10. "lt": isLt,
  11. "lte": isLte,
  12. "gt": isGt,
  13. "gte": isGte,
  14. "eqfield": isEqField,
  15. "eqcsfield": isEqCrossStructField,
  16. "necsfield": isNeCrossStructField,
  17. "gtcsfield": isGtCrossStructField,
  18. "gtecsfield": isGteCrossStructField,
  19. "ltcsfield": isLtCrossStructField,
  20. "ltecsfield": isLteCrossStructField,
  21. "nefield": isNeField,
  22. "gtefield": isGteField,
  23. "gtfield": isGtField,
  24. "ltefield": isLteField,
  25. "ltfield": isLtField,
  26. "alpha": isAlpha,
  27. "alphanum": isAlphanum,
  28. "alphaunicode": isAlphaUnicode,
  29. "alphanumunicode": isAlphanumUnicode,
  30. "numeric": isNumeric,
  31. "number": isNumber,
  32. "hexadecimal": isHexadecimal,
  33. "hexcolor": isHEXColor,
  34. "rgb": isRGB,
  35. "rgba": isRGBA,
  36. "hsl": isHSL,
  37. "hsla": isHSLA,
  38. "email": isEmail,
  39. "url": isURL,
  40. "uri": isURI,
  41. "file": isFile,
  42. "base64": isBase64,
  43. "base64url": isBase64URL,
  44. "contains": contains,
  45. "containsany": containsAny,
  46. "containsrune": containsRune,
  47. "excludes": excludes,
  48. "excludesall": excludesAll,
  49. "excludesrune": excludesRune,
  50. "isbn": isISBN,
  51. "isbn10": isISBN10,
  52. "isbn13": isISBN13,
  53. "eth_addr": isEthereumAddress,
  54. "btc_addr": isBitcoinAddress,
  55. "btc_addr_bech32": isBitcoinBech32Address,
  56. "uuid": isUUID,
  57. "uuid3": isUUID3,
  58. "uuid4": isUUID4,
  59. "uuid5": isUUID5,
  60. "ascii": isASCII,
  61. "printascii": isPrintableASCII,
  62. "multibyte": hasMultiByteCharacter,
  63. "datauri": isDataURI,
  64. "latitude": isLatitude,
  65. "longitude": isLongitude,
  66. "ssn": isSSN,
  67. "ipv4": isIPv4,
  68. "ipv6": isIPv6,
  69. "ip": isIP,
  70. "cidrv4": isCIDRv4,
  71. "cidrv6": isCIDRv6,
  72. "cidr": isCIDR,
  73. "tcp4_addr": isTCP4AddrResolvable,
  74. "tcp6_addr": isTCP6AddrResolvable,
  75. "tcp_addr": isTCPAddrResolvable,
  76. "udp4_addr": isUDP4AddrResolvable,
  77. "udp6_addr": isUDP6AddrResolvable,
  78. "udp_addr": isUDPAddrResolvable,
  79. "ip4_addr": isIP4AddrResolvable,
  80. "ip6_addr": isIP6AddrResolvable,
  81. "ip_addr": isIPAddrResolvable,
  82. "unix_addr": isUnixAddrResolvable,
  83. "mac": isMAC,
  84. "hostname": isHostnameRFC952, // RFC 952
  85. "hostname_rfc1123": isHostnameRFC1123, // RFC 1123
  86. "fqdn": isFQDN,
  87. "unique": isUnique,
  88. "oneof": isOneOf,
  89. "html": isHTML,
  90. "html_encoded": isHTMLEncoded,
  91. "url_encoded": isURLEncoded,
  92. }

required表示字段必须有值,并且不为默认值,例如bool默认值为false、string默认值为””、int默认值为0。如果有些字段是可填的,并且需要满足某些规则的,那么需要使用omitempty

  1. package main
  2. import (
  3. "fmt"
  4. "gopkg.in/go-playground/validator.v9"
  5. )
  6. func main() {
  7. validate := validator.New()
  8. var a =0
  9. err := validate.Var(a,"required")
  10. if err != nil {
  11. fmt.Println(err)
  12. }
  13. var b bool
  14. err = validate.Var(b,"required")
  15. if err != nil {
  16. fmt.Println(err)
  17. }
  18. var s string =""
  19. err = validate.Var(s,"required")
  20. if err != nil {
  21. fmt.Println(err)
  22. }
  23. }

执行结果

  1. Key: '' Error:Field validation for '' failed on the 'required' tag
  2. Key: '' Error:Field validation for '' failed on the 'required' tag
  3. Key: '' Error:Field validation for '' failed on the 'required' tag

dive

tag中出现的dive的使用,dive一般用在slice、array、map、嵌套的struct验证中,作为分隔符表示进入里面一层的验证规则

  1. package main
  2. import (
  3. "fmt"
  4. "gopkg.in/go-playground/validator.v9"
  5. )
  6. type Container struct {
  7. Array []string `validate:"required,gt=0,dive,required"`
  8. Map map[string]string `validate:"required,gt=0,dive,keys,max=10,endkeys,required,max=100"`
  9. }
  10. func main() {
  11. validate := validator.New()
  12. var empty Container
  13. if err := validate.Struct(empty);err!=nil{
  14. fmt.Println(err.Error())
  15. fmt.Println()
  16. }
  17. vContainer := Container{Array:[]string{""},Map:map[string]string{"test > than 10": ""}}
  18. if err := validate.Struct(vContainer);err!=nil{
  19. fmt.Println(err.Error())
  20. }
  21. }

打印结果如下

  1. Key: 'Container.Array' Error:Field validation for 'Array' failed on the 'required' tag
  2. Key: 'Container.Map' Error:Field validation for 'Map' failed on the 'required' tag
  3. Key: 'Container.Array[0]' Error:Field validation for 'Array[0]' failed on the 'required' tag
  4. Key: 'Container.Map[test > than 10]' Error:Field validation for 'Map[test > than 10]' failed on the 'max' tag
  5. Key: 'Container.Map[test > than 10]' Error:Field validation for 'Map[test > than 10]' failed on the 'required' tag

slice使用gt=0代表field.len()>0,所以[]string{}符合required规则,但是不符合gt=0规则
dive代表进入里面一层的验证 例如 a={“b”:”c”}中 dive之前的required代表a是必填,大于0,
dive之后出现keys与endkeys之间代表验证map的keys的tag值:max=10,也就是string长度不大于10
后面的自然而然的是验证value了,required 必填并且stirng最大长度是100

关系比较

  1. "eqfield": isEqField,
  2. "eqcsfield": isEqCrossStructField,
  3. "necsfield": isNeCrossStructField,
  4. "gtcsfield": isGtCrossStructField,
  5. "gtecsfield": isGteCrossStructField,
  6. "ltcsfield": isLtCrossStructField,
  7. "ltecsfield": isLteCrossStructField,
  8. "nefield": isNeField,
  9. "gtefield": isGteField,
  10. "gtfield": isGtField,
  11. "ltefield": isLteField,
  12. "ltfield": isLtField

自定义tag

  1. package main
  2. import (
  3. "fmt"
  4. "gopkg.in/go-playground/validator.v9"
  5. "strings"
  6. )
  7. type MyStruct struct {
  8. String string `validate:"pre-we"`
  9. }
  10. func main() {
  11. validate := validator.New()
  12. validate.RegisterValidation("pre-we", ValidateMyVal)
  13. s := MyStruct{String: "wechat"}
  14. if err := validate.Struct(s);err != nil {
  15. fmt.Printf("Err(s):\n%+v\n", err)
  16. }
  17. s.String = "not wechat"
  18. if err := validate.Struct(s);err != nil {
  19. fmt.Println(err)
  20. }
  21. }
  22. func ValidateMyVal(fl validator.FieldLevel) bool {
  23. return strings.HasPrefix(fl.Field().String(),"we")
  24. }

打印结果

  1. Key: 'MyStruct.String' Error:Field validation for 'String' failed on the 'pre-we' tag

自定义结构体

以下例子在验证FirstName与LastName时候不能为空

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/go-playground/validator/v10"
  5. )
  6. // User contains user information
  7. type User struct {
  8. FirstName string `json:"fname"`
  9. LastName string `json:"lname"`
  10. Age uint8 `validate:"gte=0,lte=130"`
  11. Email string `validate:"required,email"`
  12. FavouriteColor string `validate:"hexcolor|rgb|rgba"`
  13. Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
  14. }
  15. // Address houses a users address information
  16. type Address struct {
  17. Street string `validate:"required"`
  18. City string `validate:"required"`
  19. Planet string `validate:"required"`
  20. Phone string `validate:"required"`
  21. }
  22. // use a single instance of Validate, it caches struct info
  23. var validate *validator.Validate
  24. func main() {
  25. validate = validator.New()
  26. // register validation for 'User'
  27. // NOTE: only have to register a non-pointer type for 'User', validator
  28. // interanlly dereferences during it's type checks.
  29. validate.RegisterStructValidation(UserStructLevelValidation, User{})
  30. // build 'User' info, normally posted data etc...
  31. address := &Address{
  32. Street: "Eavesdown Docks",
  33. Planet: "Persphone",
  34. Phone: "none",
  35. City: "Unknown",
  36. }
  37. user := &User{
  38. FirstName: "",
  39. LastName: "",
  40. Age: 45,
  41. Email: "Badger.Smith@gmail.com",
  42. FavouriteColor: "#000",
  43. Addresses: []*Address{address},
  44. }
  45. // returns InvalidValidationError for bad validation input, nil or ValidationErrors ( []FieldError )
  46. err := validate.Struct(user)
  47. if err != nil {
  48. // this check is only needed when your code could produce
  49. // an invalid value for validation such as interface with nil
  50. // value most including myself do not usually have code like this.
  51. if _, ok := err.(*validator.InvalidValidationError); ok {
  52. fmt.Println(err)
  53. return
  54. }
  55. for _, err := range err.(validator.ValidationErrors) {
  56. fmt.Println(err.Namespace())
  57. fmt.Println(err.Field())
  58. fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered or
  59. fmt.Println(err.StructField()) // by passing alt name to ReportError like below
  60. fmt.Println(err.Tag())
  61. fmt.Println(err.ActualTag())
  62. fmt.Println(err.Kind())
  63. fmt.Println(err.Type())
  64. fmt.Println(err.Value())
  65. fmt.Println(err.Param())
  66. fmt.Println()
  67. }
  68. // from here you can create your own error messages in whatever language you wish
  69. return
  70. }
  71. // save user to database
  72. }
  73. // UserStructLevelValidation contains custom struct level validations that don't always
  74. // make sense at the field validation level. For Example this function validates that either
  75. // FirstName or LastName exist; could have done that with a custom field validation but then
  76. // would have had to add it to both fields duplicating the logic + overhead, this way it's
  77. // only validated once.
  78. //
  79. // NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way
  80. // hooks right into validator and you can combine with validation tags and still have a
  81. // common error output format.
  82. func UserStructLevelValidation(sl validator.StructLevel) {
  83. user := sl.Current().Interface().(User)
  84. if len(user.FirstName) == 0 && len(user.LastName) == 0 {
  85. sl.ReportError(user.FirstName, "FirstName", "fname", "fnameorlname", "")
  86. sl.ReportError(user.LastName, "LastName", "lname", "fnameorlname", "")
  87. }
  88. // plus can do more, even with different tag than "fnameorlname"
  89. }

国际化处理

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/go-playground/locales/en"
  5. ut "github.com/go-playground/universal-translator"
  6. "github.com/go-playground/validator/v10"
  7. en_translations "github.com/go-playground/validator/v10/translations/en"
  8. )
  9. // User contains user information
  10. type User struct {
  11. FirstName string `validate:"required"`
  12. LastName string `validate:"required"`
  13. Age uint8 `validate:"gte=0,lte=130"`
  14. Email string `validate:"required,email"`
  15. FavouriteColor string `validate:"iscolor"` // alias for 'hexcolor|rgb|rgba|hsl|hsla'
  16. Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
  17. }
  18. // Address houses a users address information
  19. type Address struct {
  20. Street string `validate:"required"`
  21. City string `validate:"required"`
  22. Planet string `validate:"required"`
  23. Phone string `validate:"required"`
  24. }
  25. // use a single instance , it caches struct info
  26. var (
  27. uni *ut.UniversalTranslator
  28. validate *validator.Validate
  29. )
  30. func main() {
  31. // NOTE: ommitting allot of error checking for brevity
  32. en := en.New()
  33. uni = ut.New(en, en)
  34. // this is usually know or extracted from http 'Accept-Language' header
  35. // also see uni.FindTranslator(...)
  36. trans, _ := uni.GetTranslator("en")
  37. validate = validator.New()
  38. en_translations.RegisterDefaultTranslations(validate, trans)
  39. translateAll(trans)
  40. translateIndividual(trans)
  41. translateOverride(trans) // yep you can specify your own in whatever locale you want!
  42. }
  43. func translateAll(trans ut.Translator) {
  44. type User struct {
  45. Username string `validate:"required"`
  46. Tagline string `validate:"required,lt=10"`
  47. Tagline2 string `validate:"required,gt=1"`
  48. }
  49. user := User{
  50. Username: "Joeybloggs",
  51. Tagline: "This tagline is way too long.",
  52. Tagline2: "1",
  53. }
  54. err := validate.Struct(user)
  55. if err != nil {
  56. // translate all error at once
  57. errs := err.(validator.ValidationErrors)
  58. // returns a map with key = namespace & value = translated error
  59. // NOTICE: 2 errors are returned and you'll see something surprising
  60. // translations are i18n aware!!!!
  61. // eg. '10 characters' vs '1 character'
  62. fmt.Println(errs.Translate(trans))
  63. }
  64. }
  65. func translateIndividual(trans ut.Translator) {
  66. type User struct {
  67. Username string `validate:"required"`
  68. }
  69. var user User
  70. err := validate.Struct(user)
  71. if err != nil {
  72. errs := err.(validator.ValidationErrors)
  73. for _, e := range errs {
  74. // can translate each error one at a time.
  75. fmt.Println(e.Translate(trans))
  76. }
  77. }
  78. }
  79. func translateOverride(trans ut.Translator) {
  80. validate.RegisterTranslation("required", trans, func(ut ut.Translator) error {
  81. return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details
  82. }, func(ut ut.Translator, fe validator.FieldError) string {
  83. t, _ := ut.T("required", fe.Field())
  84. return t
  85. })
  86. type User struct {
  87. Username string `validate:"required"`
  88. }
  89. var user User
  90. err := validate.Struct(user)
  91. if err != nil {
  92. errs := err.(validator.ValidationErrors)
  93. for _, e := range errs {
  94. // can translate each error one at a time.
  95. fmt.Println(e.Translate(trans))
  96. }
  97. }
  98. }

Gin 中v8升级

  1. package main
  2. import (
  3. "reflect"
  4. "sync"
  5. "github.com/gin-gonic/gin/binding"
  6. "github.com/go-playground/validator/v10"
  7. )
  8. type defaultValidator struct {
  9. once sync.Once
  10. validate *validator.Validate
  11. }
  12. var _ binding.StructValidator = &defaultValidator{}
  13. func (v *defaultValidator) ValidateStruct(obj interface{}) error {
  14. if kindOfData(obj) == reflect.Struct {
  15. v.lazyinit()
  16. if err := v.validate.Struct(obj); err != nil {
  17. return error(err)
  18. }
  19. }
  20. return nil
  21. }
  22. func (v *defaultValidator) Engine() interface{} {
  23. v.lazyinit()
  24. return v.validate
  25. }
  26. func (v *defaultValidator) lazyinit() {
  27. v.once.Do(func() {
  28. v.validate = validator.New()
  29. v.validate.SetTagName("binding")
  30. // add any custom validations etc. here
  31. })
  32. }
  33. func kindOfData(data interface{}) reflect.Kind {
  34. value := reflect.ValueOf(data)
  35. valueType := value.Kind()
  36. if valueType == reflect.Ptr {
  37. valueType = value.Elem().Kind()
  38. }
  39. return valueType
  40. }

在main 函数中调用

  1. package main
  2. import "github.com/gin-gonic/gin/binding"
  3. func main() {
  4. binding.Validator = new(defaultValidator)
  5. // regular gin logic
  6. }

参考

https://www.cnblogs.com/zhzhlong/p/10033234.html
https://frankhitman.github.io/zh-CN/gin-validator/