Gin 参数验证(支持中英文翻译)

Gin 参数验证(支持中英文翻译)

1. 请求效果图

  • 返回中文翻译( header中添加 locale=zh )

    image

  • 返回英文翻译 ( header中添加 locale=en )

    image

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个字符"
    }
}
*/

posted @ 2022-05-15 16:32  郭赫伟  阅读(656)  评论(0编辑  收藏  举报