gin 自带参数校验,你知道怎么使用吗
gin
框架内置参数验证,写在 binging tag
中,如下所示:
type Tag struct { ID int32 `json:"id" binding:"required"` Name string `json:"name" binding:"required"` }
这个验证器是由 validator
提供的,文档
常用的操作符:
,
:且,多个验证之间同时满足|
:或,满足其中一个-
:跳过验证=
:等于
例子来源于官方文档:
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
方法
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
中
import ( "github.com/gin-gonic/gin/binding" ) // 从 gin 中获取到 validator 验证器,然后注册 mobile if v, ok := binding.Validator.Engine().(*validator.Validate); ok { _ = v.RegisterValidation("mobile", myvalidator.ValidateMobile) }
自定义验证器就定义好了,使用的时候就可以这样写了
type User struct { Name string `json:"name" binding:"required"` Mobile string `json:"mobile" binding:"required,mobile"` }
错误信息翻译
validator
默认错误信息是英文,如果需要翻译成中文,自己做转换:
json tag
为 -
的字段zh
和 en
翻译包InitTrans
,传入 zh
或者 en
即可具体代码如下:
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 }
使用:
if err := initialize.InitTrans("zh"); err != nil { panic(err) }
这个翻译器只能够翻译内置的错误信息,自定义的验证器需要自己翻译
比如我们上面定义的 mobile
验证器,如果验证失败,返回的错误信息需要我们自己翻译
参照官方例子,如下代码:
// 四个参数 // 需要翻译的字段名 翻译器实例 翻译方法 返回错误的信息 _ = 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 })
这样就可以翻译自定义的验证器了,最终代码如下:
// 注册验证器 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
,这不是我们希望的
我们希望抛出去的错误是结构化的
shouldBindJSON
将参数接写到对应的结构体中validator.ValidationErrors
map
map
抛出去最终代码如下:
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()
:得到的是你写的require
、mobile
等Field()
:结构体中定义的字段名Translate()
:翻译
总结
validator
能够满足我们日常的需求: