gin 自带参数校验,你知道怎么使用吗

gin 框架内置参数验证,写在 binging tag 中,如下所示:

go 复制代码
type Tag struct {
  ID   int32  `json:"id" binding:"required"`
  Name string `json:"name" binding:"required"`
}

这个验证器是由 validator 提供的,文档

常用的操作符:

  • ,:且,多个验证之间同时满足
  • |:或,满足其中一个
  • -:跳过验证
  • =:等于

例子来源于官方文档:

go 复制代码
type User struct {
  FirstName      string     `binding:"required"`
  LastName       string     `binding:"required"`
  Age            uint8      `binding:"gte=0,lte=130"`
  Email          string     `binding:"required,email"` // 验证是否是一个有效的 email 地址
  Gender         string     `binding:"oneof=male female prefer_not_to` // oneof 表示只能是其中之一 可以用 eq=male|eq=female|eq=prefer_not_to 代替
  FavouriteColor string     `binding:"iscolor"`                // iscolor 表示是否是一个有效的颜色值
  Addresses      []*Address `binding:"required,dive,required"` // dive 对嵌套结构体进行递归验证
}
type Address struct {
  Street string `binding:"required"`
  City   string `binding:"required"`
  Planet string `binding:"required"`
  Phone  string `binding:"required"`
}

在我们日常中会经常做手机号验证,但是官方只有一个 e164 的验证,e164 是国际通用标准(要加区号),就不太符合国内用户

这就需要自定义验证器 mobile,根据官网提供的例子,实现它也是比较简单的

自定义验证器

首先定义一个 ValidateMobile 方法

go 复制代码
import (
  "regexp"

  "github.com/go-playground/validator/v10"
)
func ValidateMobile(fl validator.FieldLevel) bool {
  mobile := fl.Field().String()
  ok, _ := regexp.MatchString(`^1([38][0-9]|14[579]|5[^4]16[6]|7[1-35-8]|9[189])\d{8}$`, mobile) // 用正则去匹配
  return ok
}

将方法注册到 validator

go 复制代码
import (
  "github.com/gin-gonic/gin/binding"
)
// 从 gin 中获取到 validator 验证器,然后注册 mobile
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
  _ = v.RegisterValidation("mobile", myvalidator.ValidateMobile)
}

自定义验证器就定义好了,使用的时候就可以这样写了

go 复制代码
type User struct {
  Name    string `json:"name" binding:"required"`
  Mobile  string `json:"mobile" binding:"required,mobile"`
}

错误信息翻译

validator 默认错误信息是英文,如果需要翻译成中文,自己做转换:

  1. 跳过 json tag- 的字段
  2. 实例化 zhen 翻译包
  3. 初始化翻译器
  4. 注册翻译器
  5. 调用 InitTrans,传入 zh 或者 en 即可

具体代码如下:

go 复制代码
import (
  "fmt"
  "mxshop_api/order_web/global"
  "reflect"
  "strings"

  "github.com/gin-gonic/gin/binding"
  "github.com/go-playground/locales/en"
  "github.com/go-playground/locales/zh"
  "github.com/go-playground/validator/v10"
  ut "github.com/go-playground/universal-translator"
  en_translations "github.com/go-playground/validator/v10/translations/en"
  zh_translations "github.com/go-playground/validator/v10/translations/zh"
)

func InitTrans(local string) (err error) {
  // 修改 gin 中 validator 实现定制
  if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
    // 将 json tag 作为字段名
    v.RegisterTagNameFunc(func(fld reflect.StructField) string {
      name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
      // 如果 json tag 为 - 则不处理
      if name == "-" {
        return ""
      }
      return name
    })

    zhT := zh.New()
    enT := en.New()
    // 第一个参数是备用的语言环境,后面的参数是应该支持的语言环境
    uni := ut.New(enT, zhT, enT)
    // 初始化翻译器
    global.Trans, ok = uni.GetTranslator(local)
    if !ok {
      return fmt.Errorf("uni.GetTranslator(%s)", local)
    }
    switch local {
    case "en":
      en_translations.RegisterDefaultTranslations(v, global.Trans)
    case "zh":
      zh_translations.RegisterDefaultTranslations(v, global.Trans)
    default:
      en_translations.RegisterDefaultTranslations(v, global.Trans)
    }
    return
  }
  return
}

使用:

go 复制代码
if err := initialize.InitTrans("zh"); err != nil {
  panic(err)
}

这个翻译器只能够翻译内置的错误信息,自定义的验证器需要自己翻译

比如我们上面定义的 mobile 验证器,如果验证失败,返回的错误信息需要我们自己翻译

参照官方例子,如下代码:

go 复制代码
// 四个参数
// 需要翻译的字段名 翻译器实例 翻译方法 返回错误的信息
_ = validator.RegisterTranslation("mobile", global.Trans, func(ut ut.Translator) error {
  // 翻译的内容
  return ut.Add("mobile", "{0} 非法的手机号码!", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
  // fe.Field() 获取到的是字段名,而不是 json tag
  t, _ := ut.T("mobile", fe.Field())
  return t
})

这样就可以翻译自定义的验证器了,最终代码如下:

go 复制代码
// 注册验证器
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
  _ = v.RegisterValidation("mobile", myvalidator.ValidateMobile)
  _ = v.RegisterTranslation("mobile", global.Trans, func(ut ut.Translator) error {
    return ut.Add("mobile", "{0} 非法的手机号码!", true) // see universal-translator for details
  }, func(ut ut.Translator, fe validator.FieldError) string {
    t, _ := ut.T("mobile", fe.Field())
    return t
  })
}

格式化返回信息

validator 默认返回的错误信息是 json tag,这不是我们希望的

我们希望抛出去的错误是结构化的

  1. 使用 shouldBindJSON 将参数接写到对应的结构体中
  2. 如果解析失败,则断言这个错误为 validator.ValidationErrors
  3. 如果断言成功,则将错误进行翻译
  4. 去掉结构体中的前缀,并生成一个 map
  5. 将这个 map 抛出去

最终代码如下:

go 复制代码
if err := ctx.ShouldBindJSON(&bannerForm); err != nil {
  HandleValidatorError(ctx, err)
  return
}
func HandleValidatorError(c *gin.Context, err error) {
  errs, ok := err.(validator.ValidationErrors)
  if !ok {
    c.JSON(http.StatusOK, gin.H{
      "msg": err.Error(),
    })
  }
  c.JSON(http.StatusBadRequest, gin.H{
    "error": removeTopStruct(errs.Translate(global.Trans)),
  })
  return
}
func removeTopStruct(fields map[string]string) map[string]string {
  rsp := map[string]string{}
  for field, err := range fields {
    // 去掉结构体中的前缀 {User.mobile: "mobile 非法的手机号码!"}
    rsp[field[strings.Index(field, ".")+1:]] = err
  }
  return rsp
}

错误类型在文档中有说明type FieldError

  • Tag():得到的是你写的 requiremobile
  • Field():结构体中定义的字段名
  • Translate():翻译

总结

validator 能够满足我们日常的需求:

  1. 对嵌套结构体进行递归验证
  2. 自定义验证器
  3. 错误信息翻译
  4. 格式化返回信息
相关推荐
bearpping13 分钟前
SpringBoot最佳实践之 - 使用AOP记录操作日志
java·spring boot·后端
一叶飘零_sweeeet15 分钟前
线上故障零扩散:全链路监控、智能告警与应急响应 SOP 完整落地指南
java·后端·spring
开心就好20251 小时前
不同阶段的 iOS 应用混淆工具怎么组合使用,源码混淆、IPA混淆
后端·ios
架构师沉默1 小时前
程序员如何避免猝死?
java·后端·架构
椰奶燕麦2 小时前
Windows PackageManager (winget) 核心故障排错与通用修复指南
后端
zjjsctcdl2 小时前
springBoot发布https服务及调用
spring boot·后端·https
zdl6863 小时前
Spring Boot文件上传
java·spring boot·后端
世界哪有真情3 小时前
哇!绝了!原来这么简单!我的 Java 项目代码终于被 “拯救” 了!
java·后端
RMB Player3 小时前
Spring Boot 集成飞书推送超详细教程:文本消息、签名校验、封装工具类一篇搞定
java·网络·spring boot·后端·spring·飞书
重庆小透明3 小时前
【搞定面试之mysql】第三篇 mysql的锁
java·后端·mysql·面试·职场和发展