gin验证表单时 怎么实现类似laravel表单验证的attribute替换?

当前实现的结果

{
    code: 422,
    errors: {
        password: "password为必填字段",
        username: "username为必填字段"
    },
    message: "The given data was invalid"
}

当前代码

func InitTrans(locale string) (err error) {
    // 修改gin框架中的Validator引擎属性,实现自定制
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {

        // 注册一个获取json tag的自定义方法
        v.RegisterTagNameFunc(func(fld reflect.StructField) string {
            //label := strings.SplitN(fld.Tag.Get("label"), ",", 2)[0]
            //fmt.Println(label)
            //if label != "" {
            //    return label
            //}

            name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
            if name == "-" {
                return ""
            }
            return name
        })

        zhT := zh.New() // 中文翻译器
        enT := en.New() // 英文翻译器

        // 第一个参数是备用(fallback)的语言环境
        // 后面的参数是应该支持的语言环境(支持多个)
        // uni := ut.New(zhT, zhT) 也是可以的
        uni := ut.New(enT, zhT)

        // locale 通常取决于 http 请求头的 'Accept-Language'
        var ok bool
        // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
        Trans, ok = uni.GetTranslator(locale)
        if !ok {
            return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
        }

        // 注册翻译器
        switch locale {
        case "en":
            err = enTranslations.RegisterDefaultTranslations(v, Trans)
        case "zh":
            fmt.Println("注册了中文翻译器")
            err = zhTranslations.RegisterDefaultTranslations(v, Trans)
        default:
            err = enTranslations.RegisterDefaultTranslations(v, Trans)
        }
        return
    }
    return
}

func Abort(c *gin.Context) {
    var req loginRequest

    if err := c.ShouldBind(&req); err != nil {
        // 获取validator.ValidationErrors 类型的errors
        errs, ok := err.(validator.ValidationErrors)
        if !ok {
            // 非validator.ValidationErrors类型错误直接返回
            c.JSON(http.StatusOK, gin.H{
                "msg": err.Error(),
            })
            return
        }
        // validator.ValidationErrors 类型错误则进行翻译
        c.JSON(http.StatusOK, gin.H{
            "code":    422,
            "message": "The given data was invalid",
            "errors":  lang.RemoveTopStruct(errs.Translate(lang.Trans)),
        })
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "code": 200,
    })
}

我尝试在结构体中加一个label标签,替换tag,如上面注释的代码,但是发现字段名一起变了

{
    code: 422,
    errors: {
        password: "password为必填字段",
        用户名: "用户名为必填字段"
    },
    message: "The given data was invalid"
}

我只想把消息内容(laravel里的message里的attribute)里的username替换为用户名,不想替换前面的key,该怎么做?

:) wink
唐章明
讨论数量: 3

laravel 的也是自定义报错的吧,go 的话是自己封装一次,像这种 github.com/deatil/lakego-admin/blo...

2个月前 评论

自定义一个Translate方法吧

func Translate(ve validator.ValidationErrors, ut ut.Translator) validator.ValidationErrorsTranslations {
    trans := make(validator.ValidationErrorsTranslations)
    for _, e := range ve {
        trans[e.StructField()] = e.Translate(ut)
    }
    return trans
}

// 调用
c.JSON(http.StatusOK, gin.H{
    "code":    422,
    "message": "The given data was invalid",
    "errors":  Translate(errs, lang.Trans),
})
2个月前 评论

这个好像不太行,我之前也研究过,后来放弃了。errs 里面的 key 是用 Namespace 填充的, RegisterTagNameFunc 修改 tagName 后,Namespace 也会同时改变

现在用自定义实现的

package validation

import (
    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "github.com/go-playground/locales/zh"
    ut "github.com/go-playground/universal-translator"
    "github.com/go-playground/validator/v10"
    zhTrans "github.com/go-playground/validator/v10/translations/zh"
    "net/http"
    "reflect"
    "runtime"
    "strings"
    "xingxingduanju/app/rules"
)

var (
    uni      *ut.UniversalTranslator
    trans    ut.Translator
    validate *validator.Validate
)

func init() {
    locale := zh.New()
    uni = ut.New(locale, locale)
    trans, _ = uni.GetTranslator("zh")
    // 获取gin的校验器
    validate = binding.Validator.Engine().(*validator.Validate)
    // 注册翻译器
    if err := zhTrans.RegisterDefaultTranslations(validate, trans); err != nil {
        return
    }
    // 注册一个函数,获取 struct tag 里自定义的 label 作为字段名
    validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
        // 获取结构体字段的名称
        name := strings.SplitN(fld.Tag.Get("label"), ",", 2)[0]

        switch name {
        case "-":
            return ""
        case "":
            name = fld.Tag.Get("json")
            if name == "" {
                name = fld.Tag.Get("form")
            }
        }

        return name
    })

    _ = validate.RegisterValidation("phone", rules.ValidatePhone)
    _ = validate.RegisterValidation("not_virtual_segment", rules.ValidateNotVirtualSegment)
    _ = validate.RegisterValidation("captcha", rules.ValidateCaptcha)
    _ = validate.RegisterValidation("ajcaptcha", rules.ValidateAjCaptcha)
    _ = validate.RegisterValidation("nickname", rules.ValidateNickname)
    _ = validate.RegisterValidation("realname", rules.ValidateRealname)
    _ = validate.RegisterValidation("card_no", rules.ValidateCardNo)
}

func Validate(c *gin.Context, dest any) {
    defer func() {
        if err := recover(); err != nil {
            switch err.(type) {
            case *runtime.TypeAssertionError:
                panic(&Error{Message: "请求参数格式错误。"})
            default:
                panic(err)
            }
        }
    }()

    var err error

    // json 是通过 c.Request.Body 方法绑定数据,多次绑定需要使用 ShouldBindBodyWith 方法
    if c.Request.Method != http.MethodGet && c.ContentType() == binding.MIMEJSON {
        err = c.ShouldBindBodyWith(dest, binding.JSON)
    } else {
        err = c.ShouldBind(dest)
    }

    if err != nil {
        message, errors := Translate(err.(validator.ValidationErrors), reflect.TypeOf(dest).Elem())
        panic(&Error{
            Message: message,
            Errors:  errors,
            Err:     err,
        })
    }
}

// Translate 翻译错误信息
func Translate(errs validator.ValidationErrors, dv reflect.Type) (message string, errors map[string][]string) {
    errors = map[string][]string{}

    messages := map[string]string{
        "phone":               "手机号码格式不正确。",
        "not_virtual_segment": "暂不支持虚拟号码。",
    }

    for i, err := range errs {
        var msg string
        var tagName string

        if field, ok := dv.FieldByName(err.StructField()); ok {
            if msg = field.Tag.Get("message"); msg == "" {
                msg = messages[err.Tag()]
            }
            if tagName = field.Tag.Get("json"); tagName == "" {
                tagName = field.Tag.Get("form")
            }
        }

        if msg == "" {
            msg = err.Translate(trans)
        }

        if tagName == "" {
            tagName = err.Field()
        }

        if i == 0 {
            message = msg
        }

        errors[tagName] = append(errors[tagName], msg)
    }

    return
}
2个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!