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(®isterInfo)
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
}