validation.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. // package validation 提供了一个基于 go-playground/validator/v10 的通用验证器。
  2. // 它支持通过结构体标签进行声明式验证,也可以用于验证单个变量。
  3. //
  4. // 使用示例 (在 Gin handler 中):
  5. // type MyRequest struct {
  6. // IPAddress string `json:"ip_address" validate:"required,ipv4"`
  7. // Domain string `json:"domain" validate:"required,hostname_rfc1123"`
  8. // }
  9. //
  10. // func MyHandler(c *gin.Context) {
  11. // var req MyRequest
  12. // if err := c.ShouldBindJSON(&req); err != nil {
  13. // // ... handle binding error
  14. // return
  15. // }
  16. //
  17. // if err := validation.Validate(req); err != nil {
  18. // // ... handle validation error
  19. // c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  20. // return
  21. // }
  22. //
  23. // // ... process valid data
  24. // }
  25. //
  26. // 单个变量验证:
  27. // err := validation.ValidateVar("192.168.1.1", "ipv4") // success
  28. // err := validation.ValidateVar("not-an-ip", "ipv4") // error
  29. package validation
  30. import (
  31. "errors"
  32. "net"
  33. "reflect"
  34. "strings"
  35. "sync"
  36. "github.com/gin-gonic/gin/binding"
  37. "github.com/go-playground/locales/en"
  38. "github.com/go-playground/locales/zh"
  39. ut "github.com/go-playground/universal-translator"
  40. "github.com/go-playground/validator/v10"
  41. zh_translations "github.com/go-playground/validator/v10/translations/zh"
  42. )
  43. // validate 是一个单例的 validator 实例,确保全局只有一个验证器,以提高性能。
  44. // validator 实例的创建是昂贵的,因此我们使用 sync.Once 来确保它只被创建一次。
  45. // validate 是一个单例的 validator 实例,确保全局只有一个验证器,以提高性能。
  46. // validator 实例的创建是昂贵的,因此我们使用 sync.Once 来确保它只被创建一次。
  47. var (
  48. validate *validator.Validate
  49. trans ut.Translator // 全局翻译器
  50. once sync.Once
  51. )
  52. func init() {
  53. getInstance()
  54. }
  55. // getInstance 返回一个初始化好的 validator 单例实例。
  56. func getInstance() *validator.Validate {
  57. once.Do(func() {
  58. validate = validator.New()
  59. // 注册一个函数,获取struct tag中自定义的json字段名
  60. validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
  61. name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
  62. if name == "-" {
  63. return ""
  64. }
  65. return name
  66. })
  67. // 创建翻译器
  68. en := en.New()
  69. zh := zh.New()
  70. uni := ut.New(en, zh, en) // 第一个参数是备用语言
  71. // 我们假设服务主要使用中文,所以获取中文翻译器
  72. trans, _ = uni.GetTranslator("zh")
  73. // 注册中文翻译
  74. zh_translations.RegisterDefaultTranslations(validate, trans)
  75. // 注册自定义校验
  76. registerCustomValidations(validate)
  77. // 将我们自定义的校验器与 gin 的校验器绑定
  78. binding.Validator = &defaultValidator{validate: validate}
  79. // 在这里可以注册自定义的验证函数
  80. // 例如: validate.RegisterValidation("custom_tag", customValidationFunc)
  81. })
  82. return validate
  83. }
  84. // defaultValidator 实现了 gin.Engine 的 validator 接口
  85. type defaultValidator struct {
  86. validate *validator.Validate
  87. }
  88. // ValidateStruct 接收一个对象并校验它。
  89. // ValidateStruct 接收一个对象并校验它,它会返回翻译后的错误信息。
  90. func (v *defaultValidator) ValidateStruct(obj interface{}) error {
  91. err := v.validate.Struct(obj)
  92. if err != nil {
  93. // 如果发生校验错误,则调用我们的翻译函数
  94. return translateError(err)
  95. }
  96. return nil
  97. }
  98. // Engine 返回底层的 validator 引擎
  99. func (v *defaultValidator) Engine() interface{} {
  100. return v.validate
  101. }
  102. // translateError 将 validator.ValidationErrors 翻译成更友好的中文错误信息
  103. func translateError(err error) error {
  104. var validationErrors validator.ValidationErrors
  105. if errors.As(err, &validationErrors) {
  106. var errs []string
  107. for _, e := range validationErrors {
  108. // 使用我们注册的翻译器进行翻译
  109. errs = append(errs, e.Translate(trans))
  110. }
  111. return errors.New(strings.Join(errs, "; "))
  112. }
  113. return err
  114. }
  115. // Validate 函数用于验证一个结构体的所有字段。
  116. // 它现在会返回翻译后的中文错误信息。
  117. func Validate(s interface{}) error {
  118. err := getInstance().Struct(s)
  119. if err != nil {
  120. return translateError(err)
  121. }
  122. return nil
  123. }
  124. // ValidateVar 函数用于验证单个变量。
  125. // 它现在也会返回翻译后的中文错误信息。
  126. func ValidateVar(field interface{}, tag string) error {
  127. err := getInstance().Var(field, tag)
  128. if err != nil {
  129. return translateError(err)
  130. }
  131. return nil
  132. }
  133. // registerCustomValidations 注册所有自定义的校验规则和翻译
  134. func registerCustomValidations(v *validator.Validate) {
  135. // 注册 hostport 校验器
  136. v.RegisterValidation("hostport", validateHostPort)
  137. // 为 hostport 校验器注册中文翻译
  138. v.RegisterTranslation("hostport", trans, func(ut ut.Translator) error {
  139. return ut.Add("hostport", "{0} 必须是有效的 'IP:端口' 或 '域名:端口' 格式", true)
  140. }, func(ut ut.Translator, fe validator.FieldError) string {
  141. t, _ := ut.T("hostport", fe.Field())
  142. return t
  143. })
  144. }
  145. // validateHostPort 是一个自定义校验函数,用于检查字符串是否为有效的 "host:port"。
  146. // 主机部分可以是 IP 地址或域名。
  147. func validateHostPort(fl validator.FieldLevel) bool {
  148. addr := fl.Field().String()
  149. // 我们可以使用 net.SplitHostPort 来检查格式。
  150. // 它能正确处理 IPv6 地址,例如 "[::1]:8080"。
  151. host, port, err := net.SplitHostPort(addr)
  152. if err != nil || host == "" || port == "" {
  153. return false // 格式不正确,或者主机或端口为空
  154. }
  155. // 检查主机部分是否为 IP 地址
  156. if net.ParseIP(host) != nil {
  157. return true
  158. }
  159. // 如果不是 IP,则检查它是否为有效的主机名 (使用 validator 内置的 hostname_rfc1123 逻辑)
  160. // 我们不能直接调用,但可以通过 Var 来间接使用它
  161. err = getInstance().Var(host, "hostname_rfc1123")
  162. return err == nil
  163. }