|
@@ -0,0 +1,186 @@
|
|
|
+// package validation 提供了一个基于 go-playground/validator/v10 的通用验证器。
|
|
|
+// 它支持通过结构体标签进行声明式验证,也可以用于验证单个变量。
|
|
|
+//
|
|
|
+// 使用示例 (在 Gin handler 中):
|
|
|
+// type MyRequest struct {
|
|
|
+// IPAddress string `json:"ip_address" validate:"required,ipv4"`
|
|
|
+// Domain string `json:"domain" validate:"required,hostname_rfc1123"`
|
|
|
+// }
|
|
|
+//
|
|
|
+// func MyHandler(c *gin.Context) {
|
|
|
+// var req MyRequest
|
|
|
+// if err := c.ShouldBindJSON(&req); err != nil {
|
|
|
+// // ... handle binding error
|
|
|
+// return
|
|
|
+// }
|
|
|
+//
|
|
|
+// if err := validation.Validate(req); err != nil {
|
|
|
+// // ... handle validation error
|
|
|
+// c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
|
+// return
|
|
|
+// }
|
|
|
+//
|
|
|
+// // ... process valid data
|
|
|
+// }
|
|
|
+//
|
|
|
+// 单个变量验证:
|
|
|
+// err := validation.ValidateVar("192.168.1.1", "ipv4") // success
|
|
|
+// err := validation.ValidateVar("not-an-ip", "ipv4") // error
|
|
|
+package validation
|
|
|
+
|
|
|
+import (
|
|
|
+ "errors"
|
|
|
+ "net"
|
|
|
+ "reflect"
|
|
|
+ "strings"
|
|
|
+ "sync"
|
|
|
+
|
|
|
+ "github.com/gin-gonic/gin/binding"
|
|
|
+ "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"
|
|
|
+ zh_translations "github.com/go-playground/validator/v10/translations/zh"
|
|
|
+)
|
|
|
+
|
|
|
+// validate 是一个单例的 validator 实例,确保全局只有一个验证器,以提高性能。
|
|
|
+// validator 实例的创建是昂贵的,因此我们使用 sync.Once 来确保它只被创建一次。
|
|
|
+// validate 是一个单例的 validator 实例,确保全局只有一个验证器,以提高性能。
|
|
|
+// validator 实例的创建是昂贵的,因此我们使用 sync.Once 来确保它只被创建一次。
|
|
|
+var (
|
|
|
+ validate *validator.Validate
|
|
|
+ trans ut.Translator // 全局翻译器
|
|
|
+ once sync.Once
|
|
|
+)
|
|
|
+
|
|
|
+func init() {
|
|
|
+ getInstance()
|
|
|
+}
|
|
|
+
|
|
|
+// getInstance 返回一个初始化好的 validator 单例实例。
|
|
|
+func getInstance() *validator.Validate {
|
|
|
+ once.Do(func() {
|
|
|
+ validate = validator.New()
|
|
|
+
|
|
|
+ // 注册一个函数,获取struct tag中自定义的json字段名
|
|
|
+ validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
|
|
|
+ name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
|
|
|
+ if name == "-" {
|
|
|
+ return ""
|
|
|
+ }
|
|
|
+ return name
|
|
|
+ })
|
|
|
+
|
|
|
+ // 创建翻译器
|
|
|
+ en := en.New()
|
|
|
+ zh := zh.New()
|
|
|
+ uni := ut.New(en, zh, en) // 第一个参数是备用语言
|
|
|
+
|
|
|
+ // 我们假设服务主要使用中文,所以获取中文翻译器
|
|
|
+ trans, _ = uni.GetTranslator("zh")
|
|
|
+
|
|
|
+ // 注册中文翻译
|
|
|
+ zh_translations.RegisterDefaultTranslations(validate, trans)
|
|
|
+
|
|
|
+ // 注册自定义校验
|
|
|
+ registerCustomValidations(validate)
|
|
|
+
|
|
|
+ // 将我们自定义的校验器与 gin 的校验器绑定
|
|
|
+ binding.Validator = &defaultValidator{validate: validate}
|
|
|
+ // 在这里可以注册自定义的验证函数
|
|
|
+ // 例如: validate.RegisterValidation("custom_tag", customValidationFunc)
|
|
|
+ })
|
|
|
+ return validate
|
|
|
+}
|
|
|
+
|
|
|
+// defaultValidator 实现了 gin.Engine 的 validator 接口
|
|
|
+type defaultValidator struct {
|
|
|
+ validate *validator.Validate
|
|
|
+}
|
|
|
+
|
|
|
+// ValidateStruct 接收一个对象并校验它。
|
|
|
+// ValidateStruct 接收一个对象并校验它,它会返回翻译后的错误信息。
|
|
|
+func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
|
|
+ err := v.validate.Struct(obj)
|
|
|
+ if err != nil {
|
|
|
+ // 如果发生校验错误,则调用我们的翻译函数
|
|
|
+ return translateError(err)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// Engine 返回底层的 validator 引擎
|
|
|
+func (v *defaultValidator) Engine() interface{} {
|
|
|
+ return v.validate
|
|
|
+}
|
|
|
+
|
|
|
+// translateError 将 validator.ValidationErrors 翻译成更友好的中文错误信息
|
|
|
+func translateError(err error) error {
|
|
|
+ var validationErrors validator.ValidationErrors
|
|
|
+ if errors.As(err, &validationErrors) {
|
|
|
+ var errs []string
|
|
|
+ for _, e := range validationErrors {
|
|
|
+ // 使用我们注册的翻译器进行翻译
|
|
|
+ errs = append(errs, e.Translate(trans))
|
|
|
+ }
|
|
|
+ return errors.New(strings.Join(errs, "; "))
|
|
|
+ }
|
|
|
+ return err
|
|
|
+}
|
|
|
+
|
|
|
+// Validate 函数用于验证一个结构体的所有字段。
|
|
|
+// 它现在会返回翻译后的中文错误信息。
|
|
|
+func Validate(s interface{}) error {
|
|
|
+ err := getInstance().Struct(s)
|
|
|
+ if err != nil {
|
|
|
+ return translateError(err)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// ValidateVar 函数用于验证单个变量。
|
|
|
+// 它现在也会返回翻译后的中文错误信息。
|
|
|
+func ValidateVar(field interface{}, tag string) error {
|
|
|
+ err := getInstance().Var(field, tag)
|
|
|
+ if err != nil {
|
|
|
+ return translateError(err)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// registerCustomValidations 注册所有自定义的校验规则和翻译
|
|
|
+func registerCustomValidations(v *validator.Validate) {
|
|
|
+ // 注册 hostport 校验器
|
|
|
+ v.RegisterValidation("hostport", validateHostPort)
|
|
|
+
|
|
|
+ // 为 hostport 校验器注册中文翻译
|
|
|
+ v.RegisterTranslation("hostport", trans, func(ut ut.Translator) error {
|
|
|
+ return ut.Add("hostport", "{0} 必须是有效的 'IP:端口' 或 '域名:端口' 格式", true)
|
|
|
+ }, func(ut ut.Translator, fe validator.FieldError) string {
|
|
|
+ t, _ := ut.T("hostport", fe.Field())
|
|
|
+ return t
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// validateHostPort 是一个自定义校验函数,用于检查字符串是否为有效的 "host:port"。
|
|
|
+// 主机部分可以是 IP 地址或域名。
|
|
|
+func validateHostPort(fl validator.FieldLevel) bool {
|
|
|
+ addr := fl.Field().String()
|
|
|
+
|
|
|
+ // 我们可以使用 net.SplitHostPort 来检查格式。
|
|
|
+ // 它能正确处理 IPv6 地址,例如 "[::1]:8080"。
|
|
|
+ host, port, err := net.SplitHostPort(addr)
|
|
|
+ if err != nil || host == "" || port == "" {
|
|
|
+ return false // 格式不正确,或者主机或端口为空
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查主机部分是否为 IP 地址
|
|
|
+ if net.ParseIP(host) != nil {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果不是 IP,则检查它是否为有效的主机名 (使用 validator 内置的 hostname_rfc1123 逻辑)
|
|
|
+ // 我们不能直接调用,但可以通过 Var 来间接使用它
|
|
|
+ err = getInstance().Var(host, "hostname_rfc1123")
|
|
|
+ return err == nil
|
|
|
+}
|