假设我们有如下结构体:

  1. type User struct {
  2. Id int
  3. Name string
  4. Bio string
  5. Email string
  6. }

我们需要对结构体内的字段进行验证合法性:

  • Id的值在某一个范围内。

  • Name的长度在莫一个范围内。

  • Email格式正确。

我们可能会这么写:

  1. user := User{
  2. Id: 0,
  3. Name: "superlongstring",
  4. Bio: "",
  5. Email: "foobar",
  6. }
  7. if user.Id < 1 && user.Id > 1000 {
  8. return false
  9. }
  10. if len(user.Name) < 2 && len(user.Name) > 10 {
  11. return false
  12. }
  13. if !validateEmail(user.Email) {
  14. return false
  15. }

这样的话代码比较冗余,而且如果结构体新加字段,还需要再修改验证函数再加一段if判断。这样代码比较冗余。我们可以借助golang的structTag来解决上述的问题:

  1. type User struct {
  2. Id int `validate:"number,min=1,max=1000"`
  3. Name string `validate:"string,min=2,max=10"`
  4. Bio string `validate:"string"`
  5. Email string `validate:"email"`
  6. }

validate:”number,min=1,max=1000”就是structTag。

实现思路
golang 验证struct字段的数据格式 - 图1

我们定义一个接口Validator,定义一个方法Validate。再定义有具体意义的验证器例如StringValidator、NumberValidator、EmailValidator来实现接口Validator。

完整实例

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. "regexp"
  6. "strings"
  7. )
  8. const tagName = "validate"
  9. //邮箱验证正则
  10. var mailRe = regexp.MustCompile(`\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z`)
  11. //验证接口
  12. type Validator interface {
  13. Validate(interface{}) (bool, error)
  14. }
  15. type DefaultValidator struct {
  16. }
  17. func (v DefaultValidator) Validate(val interface{}) (bool, error) {
  18. return true, nil
  19. }
  20. type StringValidator struct {
  21. Min int
  22. Max int
  23. }
  24. func (v StringValidator) Validate(val interface{}) (bool, error) {
  25. l := len(val.(string))
  26. if l == 0 {
  27. return false, fmt.Errorf("cannot be blank")
  28. }
  29. if l < v.Min {
  30. return false, fmt.Errorf("should be at least %v chars long", v.Min)
  31. }
  32. if v.Max >= v.Min && l > v.Max {
  33. return false, fmt.Errorf("should be less than %v chars long", v.Max)
  34. }
  35. return true, nil
  36. }
  37. type NumberValidator struct {
  38. Min int
  39. Max int
  40. }
  41. func (v NumberValidator) Validate(val interface{}) (bool, error) {
  42. num := val.(int)
  43. if num < v.Min {
  44. return false, fmt.Errorf("should be greater than %v", v.Min)
  45. }
  46. if v.Max >= v.Min && num > v.Max {
  47. return false, fmt.Errorf("should be less than %v", v.Max)
  48. }
  49. return true, nil
  50. }
  51. type EmailValidator struct {
  52. }
  53. func (v EmailValidator) Validate(val interface{}) (bool, error) {
  54. if !mailRe.MatchString(val.(string)) {
  55. return false, fmt.Errorf("is not a valid email address")
  56. }
  57. return true, nil
  58. }
  59. func getValidatorFromTag(tag string) Validator {
  60. args := strings.Split(tag, ",")
  61. switch args[0] {
  62. case "number":
  63. validator := NumberValidator{}
  64. //将structTag中的min和max解析到结构体中
  65. fmt.Sscanf(strings.Join(args[1:], ","), "min=%d,max=%d", &validator.Min, &validator.Max)
  66. return validator
  67. case "string":
  68. validator := StringValidator{}
  69. fmt.Sscanf(strings.Join(args[1:], ","), "min=%d,max=%d", &validator.Min, &validator.Max)
  70. return validator
  71. case "email":
  72. return EmailValidator{}
  73. }
  74. return DefaultValidator{}
  75. }
  76. func validateStruct(s interface{}) []error {
  77. errs := []error{}
  78. v := reflect.ValueOf(s)
  79. for i := 0; i < v.NumField(); i++ {
  80. //利用反射获取structTag
  81. tag := v.Type().Field(i).Tag.Get(tagName)
  82. if tag == "" || tag == "-" {
  83. continue
  84. }
  85. validator := getValidatorFromTag(tag)
  86. valid, err := validator.Validate(v.Field(i).Interface())
  87. if !valid && err != nil {
  88. errs = append(errs, fmt.Errorf("%s %s", v.Type().Field(i).Name, err.Error()))
  89. }
  90. }
  91. return errs
  92. }
  93. type User struct {
  94. Id int `validate:"number,min=1,max=1000"`
  95. Name string `validate:"string,min=2,max=10"`
  96. Bio string `validate:"string"`
  97. Email string `validate:"email"`
  98. }
  99. func main() {
  100. user := User{
  101. Id: 0,
  102. Name: "superlongstring",
  103. Bio: "",
  104. Email: "foobar",
  105. }
  106. fmt.Println("Errors:")
  107. for i, err := range validateStruct(user) {
  108. fmt.Printf("\t%d. %s\n", i+1, err.Error())
  109. }
  110. }

其实github上已经有现成的验证包了govalidator,支持内置的验证tag和自定义验证tag:

  1. package main
  2. import (
  3. "github.com/asaskevich/govalidator"
  4. "fmt"
  5. "strings"
  6. )
  7. type Server struct {
  8. ID string `valid:"uuid,required"`
  9. Name string `valid:"machine_id"`
  10. HostIP string `valid:"ip"`
  11. MacAddress string `valid:"mac,required"`
  12. WebAddress string `valid:"url"`
  13. AdminEmail string `valid:"email"`
  14. }
  15. func main() {
  16. server := &Server{
  17. ID: "123e4567-e89b-12d3-a456-426655440000",
  18. Name: "IX01",
  19. HostIP: "127.0.0.1",
  20. MacAddress: "01:23:45:67:89:ab",
  21. WebAddress: "www.example.com",
  22. AdminEmail: "admin@exmaple.com",
  23. }
  24. //自定义tag验证函数
  25. govalidator.TagMap["machine_id"] = govalidator.Validator(func(str string) bool {
  26. return strings.HasPrefix(str, "IX")
  27. })
  28. if ok, err := govalidator.ValidateStruct(server); err != nil {
  29. panic(err)
  30. } else {
  31. fmt.Printf("OK: %v\n", ok)
  32. }
  33. }

golang 验证struct字段的数据格式 - 图2