Skip to main content

Validator

validator包实现了基于标签的结构体和单个字段的值验证,主要具有以下功能:

  • 通过使用验证标签或自定义验证器实现跨字段和跨结构体的验证。
  • 支持Slice、Array和Map的递归验证,可以对多维字段的任意或所有层级进行验证。
  • 支持深入验证Map的键和值。
  • 处理接口类型时,会在验证前确定其底层类型。
  • 处理自定义字段类型,例如SQL驱动的Valuer类型。
  • 别名验证标签,可以将多个验证规则映射到一个标签,从而更容易地定义结构体上的验证。
  • 提取自定义定义的字段名称,例如可以指定在验证时提取JSON名称,并在结果中的FieldError中使用。
  • 可定制的支持国际化的错误消息(Translations)。
  • Gin Web的默认验证器。
go get github.com/go-playground/validator/v10

Translator

validator包内置了国际化错误消息,可在不同语言之间进行切换:

import (
"fmt"
"net/http"
"reflect"
"strings"

"github.com/gin-gonic/gin"
"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"

enTranslations "github.com/go-playground/validator/v10/translations/en"
zhTranslations "github.com/go-playground/validator/v10/translations/zh"
)

var translator ut.Translator

func createTranslator(locale string) (err error) {
// 获取 Gin 验证器引擎, 并断言它是 *validator.Validate 类型的实例
validator, ok := binding.Validator.Engine().(*validator.Validate)

if ok {
// 注册标签名提取函数, 提取结构体字段的标签名, 可自定义错误消息的字段名
validator.RegisterTagNameFunc(func(sf reflect.StructField) string {
name := sf.Tag.Get("json")

if name == "-" {
return ""
}

return name
})

// 初始化翻译包
zhTranslation := zh.New()
enTranslation := en.New()

// 创建UniversalTranslator实例,允许在不同语言间切换。
// 第一个参数为默认翻译器
uniTranslations := ut.New(enTranslation, zhTranslation, enTranslation)

// 获取指定语言的翻译器
translator, ok = uniTranslations.GetTranslator(locale)

if !ok {
return fmt.Errorf("uniTranslations.GetTranslator(%s)", locale)
}

// 注册默认的翻译
switch locale {
case "en":
enTranslations.RegisterDefaultTranslations(validator, translator)
case "zh":
zhTranslations.RegisterDefaultTranslations(validator, translator)
default:
enTranslations.RegisterDefaultTranslations(validator, translator)
}

return
}
return nil
}

Validator

validator是Gin框架的默认底层验证器,支持多种形式的验证。

package main

type RegisterInfo struct {
// 必填 最大15位 最小6位
Username string `json:"username" binding:"required,max=15,min=6"`
// 必填 最大20位 最小8位
Password string `json:"password" binding:"required,max=20,min=8"`
// 必填 大于等于18 小于等于80
Age uint8 `json:"age" binding:"required,gte=18,lte=80"`
// 必填 邮箱格式
Email string `json:"email" binding:"required,email"`
}

func main() {
r := gin.Default()

if err := createTranslator("en"); err != nil {
fmt.Println("Error")
return
}

r.POST("/register", func(ctx *gin.Context) {
var registerInfo RegisterInfo

err := ctx.ShouldBindJSON(&registerInfo)

if err != nil {
errs, ok := err.(validator.ValidationErrors)

if !ok {
ctx.JSON(http.StatusOK, gin.H{
"code": 1,
"msg": err.Error(),
})
return
}

ctx.JSON(http.StatusOK, gin.H{
"code": 1001,
// 翻译错误验证消息
"msg": removeStructName(errs.Translate(translator)),
})

return
}

ctx.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "ok",
"data": registerInfo,
})
})

r.Run()
}

/**
Request:
{
"username": "tom",
"password": "12345678",
"age": 19,
"email": "tom@qq.com1"
}
Response:
{
"code": 1001,
"msg": {
"email": "email must be a valid email address",
"username": "username must be at least 6 characters in length"
}
}
*/

func removeStructName(msg map[string]string) map[string]string {
m := map[string]string{}

for k, v := range msg {
key := k[strings.Index(k, ".")+1:]
m[key] = v
}

return m
}