golang validator参数校验 中文

96
darcyaf
0.1 2020.04.05 20:36 字数 886

今天在改后台页面,参数校验错误时输出全是英文,使用着很难看懂到底时什么错了
故而决定去做i18n前端国际化. 改的时候踩了很多坑,故而记录一下,顺便记录以下查问题的方式。

效果

从原来的Title is required变为标题为必填字段

完成后的代码:

这里主要定义了初始化了一个中文的trans和Validate的变量,并对其做初始化
初始化主要做了以下事情:

  1. 注册了TagName函数
    // RegisterTagNameFunc registers a function to get alternate names for StructFields.
    这个方法主要就是提供一个tag的解析器,返回一个Field替代的字符串
    我自己是定义了一个label的tag用于替换
  2. 注册了validate的翻译函数
    直接使用了原来提供的中文转换,对required等标签做对应的国际化
package service

import (
    zhongwen "github.com/go-playground/locales/zh"
    ut "github.com/go-playground/universal-translator"
    "github.com/go-playground/validator/v10"
    zh_translations "github.com/go-playground/validator/v10/translations/zh"
    "reflect"
    "strings"
)

var Validate *validator.Validate
var trans ut.Translator

func init() {
    zh := zhongwen.New()
    uni := ut.New(zh, zh)
    trans, _ = uni.GetTranslator("zh")

    Validate = validator.New()
    Validate.RegisterTagNameFunc(func(field reflect.StructField) string {
        label := field.Tag.Get("label")
        if label == "" {
            return field.Name
        }
        return label
    })
    zh_translations.RegisterDefaultTranslations(Validate, trans)
}
func Translate(errs validator.ValidationErrors) string {
    var errList []string
    for _, e := range errs {
        // can translate each error one at a time.
        errList = append(errList,e.Translate(trans))
    }
    return strings.Join(errList,"|")
}

调用方式

type ArticlesPost struct {
    Title           string  `json:"title" validate:"required,max=32,min=4" label:"标题"`
}
var ap ArticlePost
err = service.Validate.Struct(ap)
if err!=nil{
  errStr =Translate(errs)
  fmt.Sprintln(errStr)
}

思路

  1. 最刚开始去百度查了,无果
  2. 查了iris的文档,也无果
  3. 去看了validate的文档,找到了universal-translator 这个包,可以初步将is required等样式改为必填字段
  4. 还是没法将字段名映射成中文,google搜索到了How can I translate fieldName? #364这个issue,评论里给出了en.Add("MyField", "Field", false)的方式添加字段的映射,最后在alidate.RegisterTranslation注册required的时候,通过T方法转换成对应的中文fld, _ := ut.T(fe.Field()),考虑到要每次都注册Struct的字段,而且全局的同一个key肯定没法定义不同的值,弃用
  5. 第一次想着是不是校验本身已经提供了对应的位置,看了interface,有些英文半知半解,没找到结果,放弃
  6. 继续,想到是不是可以自定义tag,然后重写type TranslationFunc func(ut ut.Translator, fe FieldError) string 函数,想在这个翻译阶段,去动态过去struct中那个tag的值,这样就不会重复了.
  7. 研究了这个函数的传参,FieldError中已经只剩下字段对应的数据了,无法获取到tag信息,差点已经想放弃了
  8. 再次研究validator关于tag的函数
    第一个是设置一个新的tag来替换validate,另一个的说明是注册一个方法来为结构体字段获取替换的名字
    仔细看看说明,果然就是这个,在看看TagNameFunc的签名,参数是reflect.StructField,能够拿到tag等一系列信息
// TagNameFunc allows for adding of a custom tag name parser
type TagNameFunc func(field reflect.StructField) string
// SetTagName allows for changing of the default tag name of 'validate'
func (v *Validate) SetTagName(name string) {
    v.tagName = name
}

// RegisterTagNameFunc registers a function to get alternate names for StructFields.
//
// eg. to use the names which have been specified for JSON representations of structs, rather than normal Go field names:
//
//    validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
//        name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
//        if name == "-" {
//            return ""
//        }
//        return name
//    })
func (v *Validate) RegisterTagNameFunc(fn TagNameFunc) {
    v.tagNameFunc = fn
    v.hasTagNameFunc = true
}

至此,终于找到了正确的解决方案

总结

在这里发现为了解决这个问题走了很多弯路,查了一大堆资料才发现甚至原来就有提供该功能。
发现自己的几个问题:

  1. 英文不是很好,偶尔有些单词不认识,阻止了进一步发现问题,这里也突然想到,英语好一些确实可以在学编程这个路子上受益匪浅
  2. 看文档不是很仔细,鄙人觉得大部分的编程问题都不是很高深,能读得懂错误是什么意思,然后去查查文档或者搜索引擎就能解决,另一个是大部分的编程文档还是英文好一些,细节性的东西在翻译的时候可能会被略掉。
Golang