i18n+validator,对GIN框架结构体的binding标签的验证错误进行翻译

相信很多用过GIN框架的小伙伴们,肯定都知道如何对结构体进行验证, 比如验证字段不能为空,最小值是多少, 最大值是多少。但我们通过调用c.ShouldBindJson方法进行验证的时候,返回的错误信息却是很有友好.

先来看下,返回的错误信息
Key: 'CreateData.Name' Error:Field validation for 'Name' failed on the 'required' tag
Key: 'CreateData.Mobile' Error:Field validation for 'Mobile' failed on the 'required' tag
Key: 'CreateData.IdCardNo' Error:Field validation for 'IdCardNo' failed on the 'required' tag
相信很多小伙伴都会觉得,这样错误提示很不友好,肯定不能直接将错误信息返回给前端小伙伴。
那今天我们就来实现一个功能,当name字段为空时, 直接返回错误信息”姓名不能为空”, 我们来一步一步实现。我们用到的工具主要是i18n,通过这种形式来完成。
不过网上也有类似的教程,我就看过一篇李文周老师写的一篇文章 validator库参数校验若干实用技巧 https://www.liwenzhou.com/posts/Go/validator_usages/
不过我看了这个之后, 感觉有点麻烦了,所以抽工作之余,用另一种我觉得比较方便的方法实现。
先把前置条件亮出来
i18n仓库路径: “github.com/nicksnyder/go-i18n/i18n”
validator仓库路径:”gopkg.in/go-playground/validator.v8″
结构体定义:
type CreateData struct {
   Name                string `form:"name" json:"name" binding:"required,min=3,max=10"`                      //姓名
   Mobile              string `form:"mobile" json:"mobile" binding:"required"`                               //手机号
   IdCardNo            string `form:"id_card_no" json:"id_card_no" binding:"required"`                       //身份证号
}

 

现在,来看一下具体的实现过程。
第一步:先来分析一下返回的错误信息
  • 对ShouldBindJson返回的错误进行断言,断言的类型是 validator.ValidationErrors
  • 我们进行追踪,可以看于这个类型的定义 type ValidationErrors map[string]*FieldError
  • FieldError的结构体定义如下
// FieldError contains a single field's validation error along
// with other properties that may be needed for error message creation
type FieldError struct {
	Field     string
	Name      string
	Tag       string
	ActualTag string
	Kind      reflect.Kind
	Type      reflect.Type
	Param     string
	Value     interface{}
}

 

经过以上的分析, 我们就可以翻译成这样【姓名最大值不能超过2个字符…】, 那i18n的翻译模板就会长成这个样子。
- id: lte
  translation: "{{.field}}为{{.value}},不能大于{{.sValue}}"

- id: required
  translation: "{{.field}}不能为空"

- id: min
  translation: "{{.field}}最小值不能小于{{.sValue}}个字符"

- id: max
  translation: "{{.field}}最大值不能超过{{.sValue}}个字符"
id: 代表binding内的关键字,
translation: 代表要翻译的内容。
field 代表字段
sValue 代表限定的值
value 代表我们传递的值
{{.field}}这种形式是i18n采用了内置的template引擎,进行内容替换。template使用方法,请转到这里 https://www.cnblogs.com/f-ck-need-u/p/10053124.html
那我们如何得到字段【Name】的中文翻译=【姓名】呢?我想到的是通过定义一个Map来实现,比如:
map[string]string{ "Name": "姓名", }
第二步:接口实现
  • 定义一个interface,里面定义一个方法,返回值是一个map[string]string结构

type FieldMap interface {
	GetFieldMap() map[string]string
}

  • 定义一个结构体,来实现FieldMap接口

type certificateApply struct {
}

func NewCertificateApply() *certificateApply {
	return &certificateApply{}
}
func (c *certificateApply) GetFieldMap() map[string]string {
	return map[string]string{
		"Name":                "名称",
		"Mobile":              "手机号",
		"IdCardNo":            "身份证号",
		"CertificateName":     "证书名",
		"CertificateRecordId": "证书DI",
		"Data":                "数据",
		"OnboardTemplateId":   "模板Id",
	}
}

 

  • 我们还用c.ShouldBindJson来获取错误信息,只不过不同的是, 我们需要对返回的错误进行类型断言,然后进行处理。
var data CreateData
if err := c.BindJSON(&data); err != nil {
	utils.ValidStructTrans(c, err, types.NewCertificateApply())
	return
}
3、来看下ValidStructTrans这个函数
//c.ShouldBindJson返回的错误,进行翻译成容易理解的表达方式
//参数列表: 
// c *gin.Context 
// err error ShouldBindJson 返回的错误
// fieldMapInterface FieldMap,传的是一个接口类型, 
// 只要实现了这个接口类型的结构体, 都允许传入,很实用
func ValidStructTrans(c *gin.Context, err error, fieldMapInterface FieldMap) {
   //类型断言
   errs := err.(validator.ValidationErrors)

   //翻译文件所在位置
   i18n.MustLoadTranslationFile("/your path/i18n/zh-cn.yaml")
   T, _ := i18n.Tfunc("zh-cn")

   fieldErrs := []string{}
   //得到字段名与翻译的Map
   fieldMap := fieldMapInterface.GetFieldMap()
   
   //因为断言后, 是map类型,可以直接进行遍历
      for _, v := range errs {
      mf := map[string]interface{}{}
      mf["field"] = v.Field //兼容处理,如果没有定义字段的中文翻译,则默认=字段名
      if v, ok := fieldMap[v.Field]; ok {
         mf["field"] = v //字段是什么
      }
      mf["value"] = v.Value    //现在的值是什么
      mf["sValue"] = v.Param   //限制在什么范围的值 , min=100, v.param=100
      fieldErr := T(v.Tag, mf) //返回的提示错误, 如:  姓名不能为空

      fieldErrs = append(fieldErrs, fieldErr)
   }
   c.JSON(http.StatusOK,gin.H{
       "msg":strings.Join(fieldErrs,"\n"),
    })
   c.Abort()
}
最后, 我们来看下结果
只传一个空json
不传身份证号
参数全部合法

0

发表评论

邮箱地址不会被公开。