Gin 参数验证(支持中英文翻译)
Gin 参数验证(支持中英文翻译)
- 官方文档:https://pkg.go.dev/github.com/go-playground/validator/v10#pkg-overview
- 官方示例: https://github.com/go-playground/validator/blob/master/_examples/translations/main.go
1. 请求效果图
-
返回中文翻译( header中添加 locale=zh )
-
返回英文翻译 ( header中添加 locale=en )
2. 源码
go.mod 文件
module test_trans
go 1.16
require (
github.com/gin-gonic/gin v1.7.7
github.com/go-playground/locales v0.14.0
github.com/go-playground/universal-translator v0.18.0
github.com/go-playground/validator/v10 v10.11.0
)
main.go
package main
import (
"fmt"
"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"
"net/http"
"reflect"
"strings"
)
// 官方文档:https://pkg.go.dev/github.com/go-playground/validator/v10#pkg-overview
// 官方示例: https://github.com/go-playground/validator/blob/master/_examples/translations/main.go
// https://pkg.go.dev/github.com/go-playground/validator/v10
type Family struct {
Name string `json:"name" binding:"required,gte=3,lte=10"`
Member Member `json:"member" binding:"required"`
}
type Member struct {
Name string `json:"name" binding:"required,gte=3,lte=10"`
Age int `json:"age" binding:"required,gt=10"`
Email string `json:"email" binding:"required,email"`
}
var Uni *ut.UniversalTranslator
// InitTrans 初始化翻译器,修改gin框架中的 validator 引擎, 注册支持的翻译器
func InitTrans() error {
// 修改gin框架中的 validator 引擎属性, 实现自定制
if validate, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 注册一个获取json tag的自定义方法,返回错误字段使用 json tag 字段,而不是结构体字段名
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})
enT := en.New() // 英文翻译器
zhT := zh.New() // 中文翻译器
Uni = ut.New(enT, zhT, enT) // 第一个参数是备用语言环境,后面的参数是应该支持的语言环境
// 注册中文翻译器
if trans, ok := Uni.GetTranslator("zh"); ok {
if err := zhTranslations.RegisterDefaultTranslations(validate, trans); err != nil {
return err
}
} else {
return fmt.Errorf(`Uni.GetTranslator("%s") error`, "zh")
}
// 注册英文翻译器
if trans, ok := Uni.GetTranslator("en"); ok {
if err := enTranslations.RegisterDefaultTranslations(validate, trans); err != nil {
return err
}
} else {
return fmt.Errorf(`Uni.GetTranslator("%s") error`, "en")
}
return nil
} else {
return fmt.Errorf("")
}
}
// GetLocalTrans 根据入参语言类型,注册相应的翻译器
// 项目中通常会将该函数放到中间件中, locale 会从请求的 headers 中获取
func GetLocalTrans(locale string) (ut.Translator, error) {
// 根据不同语言获取相应的翻译器
if trans, ok := Uni.GetTranslator(locale); !ok {
return nil, fmt.Errorf(`Uni.GetTranslator("%s") error`, locale)
} else {
return trans, nil
}
}
/*
removeTopStruct 移除验证错误字段最前名的结构体名称
移除前
{
"error": {
"Family.member.email": "email必须是一个有效的邮箱",
"Family.member.name": "name长度必须至少为3个字符",
"Family.name": "name长度必须至少为3个字符"
}
}
移除后
{
"error": {
"member.email": "email必须是一个有效的邮箱",
"member.name": "name长度必须至少为3个字符",
"name": "name长度必须至少为3个字符"
}
}
*/
func removeTopStruct(fields map[string]string) map[string]string {
res := map[string]string{}
for field, err := range fields {
res[field[strings.Index(field, ".")+1:]] = err
}
return res
}
func main() {
err := InitTrans()
if err != nil {
panic(err)
}
r := gin.Default()
r.POST("/testing", testing)
err = r.Run()
if err != nil {
panic(err)
}
}
func testing(c *gin.Context) {
// 根据header中的 locale 字段获取翻译器
locale := c.GetHeader("locale")
if locale == "" {
locale = "zh"
}
tarns, err := GetLocalTrans(locale)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("cannot get translator: %v", err)})
c.Abort()
return
}
// binding json 数据
var family Family
if err := c.ShouldBindJSON(&family); err != nil {
errs, ok := err.(validator.ValidationErrors)
if !ok {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
c.Abort()
return
} else {
c.JSON(http.StatusOK, gin.H{
"error": removeTopStruct(errs.Translate(tarns)), // 翻译验证错误
})
c.Abort()
return
}
}
c.JSON(http.StatusOK, family)
}
/*
curl --request POST 'http://127.0.0.1:8080/testing' \
--header 'locale: zh' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "伐木",
"member": {
"name": "小军",
"age": 18,
"email": "123"
}
}'
返回:
{
"error": {
"member.email": "email必须是一个有效的邮箱",
"member.name": "name长度必须至少为3个字符",
"name": "name长度必须至少为3个字符"
}
}
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)