• 优化gin表单的错误提示信息


    相关链接

    gin官方例子

    文章的代码

    简单使用表单检验请求参数

    创建一个简单的登录例子,我们对username和password绑定了required标签,代表着请求login接口的参数中必须包含这两个字段。

    type User struct {
    	UserName string `json:"username" binding:"required"`
    	Password string `json:"password" binding:"required"`
    }
    
    func login(c *gin.Context) {
    
    	var user User
    	if err := c.ShouldBindJSON(&user); err != nil {
    		c.JSON(http.StatusOK, gin.H{"msg": err.Error()})
    		return
    	}
    
    	if user.UserName != "admin" || user.Password != "123456" {
    		c.JSON(http.StatusUnauthorized, gin.H{"msg": "unauthorized"})
    		return
    	}
    
    	c.JSON(http.StatusOK, gin.H{"msg": "you are logged in"})
    }
    
    func main() {
    	r := gin.Default()
    	r.POST("/login", login)
    	r.Run() // 监听并在 0.0.0.0:8080 上启动服务
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    我们使用仅带有username去请求login接口,会输出如下,提示我们Password校验失败了,因为required的标签导致的。但是这个提示并不友好,我们需要进行优化展示。

    {'msg': "Key: 'User.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
    
    • 1

    翻译

    我们需要对上面的提示信息进行一个翻译,并且可以支持各种语言的友好性提示。

    我们在global/global.go文件中创建一个全局变量,该全局变量在后面的表单翻译中需要使用到

    import ut "github.com/go-playground/universal-translator"
    
    var (
    	Trans ut.Translator
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5

    initialize/validator.go文件中编写内容如下,获取gin中的validate对象,然后给该对象绑定中文和英文的友好提示信息,我们可以通过locale来设置我们需要使用中文还是英文的信息。

    func InitTrans(locale string) (err error) {
    
    	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
    
    		// 翻译
    		zhT := zh.New()
    		enT := en.New()
    		uni := ut.New(enT, zhT, enT)
    
    		global.Trans, ok = uni.GetTranslator(locale)
    		if !ok {
    			return fmt.Errorf("uni.GetTranslator(%s) error", locale)
    		}
    
    		switch locale {
    		case "zh":
    			zh_translations.RegisterDefaultTranslations(v, global.Trans)
    		case "en":
    			en_translations.RegisterDefaultTranslations(v, global.Trans)
    		default:
    			en_translations.RegisterDefaultTranslations(v, global.Trans)
    		}
    
    		return
    	}
    	return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    最后在main.go中的main方法下调用上面的InitTrans方法来初始化翻译内容。

    再将login方法中ShouldBindJSON返回的error转成validator.ValidationErrors类型,该类型包含一个Translate方法,调用该方法,再将之前的全局变量Trans传入。

    func login(c *gin.Context) {
    
    	var user User
    	if err := c.ShouldBindJSON(&user); err != nil {
    
    		errs, ok := err.(validator.ValidationErrors)
    		if !ok {
    			// 非校验错误,其他错误直接返回
    			c.JSON(http.StatusOK, gin.H{"msg": err.Error()})
    			return
    		}
    
    		c.JSON(http.StatusOK, gin.H{"msg": errs.Translate(global.Trans)})
    		return
    	}
    
    	if user.UserName != "admin" || user.Password != "123456" {
    		c.JSON(http.StatusUnauthorized, gin.H{"msg": "unauthorized"})
    		return
    	}
    
    	c.JSON(http.StatusOK, gin.H{"msg": "you are logged in"})
    }
    
    func main() {
    	err := initialize.InitTrans("zh")
    	if err != nil {
    		fmt.Printf("初始化翻译器错误, err = %s", err.Error())
    		return
    	}
    
    	r := gin.Default()
    	r.POST("/login", login)
    	r.Run() // 监听并在 0.0.0.0:8080 上启动服务
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    我们再使用仅带有username字段去请求login接口,输出内容如下。

    {'msg': {'User.Password': 'Password为必填字段'}}
    
    • 1

    但是,发现提示信息的key是User.Password,是表单对象和其字段名称,我们应该想要的是:

    {'msg': {'password': 'Password为必填字段'}}
    
    • 1

    优化返回字段的key

    我们修改InitTrans方法,通过go-playground提供的方法RegisterTagNameFunc来将我们自定义的方法注册进去,该自定义方法的目的是修改上面的Password改为json中的password,可以改成json标签中的值作为返回。

    func InitTrans(locale string) (err error) {
    
    	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
    
    		//修改返回字段key的格式
    		v.RegisterTagNameFunc(func(fld reflect.StructField) string {
    			name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
    			if name == "-" {
    				return ""
    			}
    			return name
    		})
    
    		// 翻译
    		zhT := zh.New()
    		enT := en.New()
    		uni := ut.New(enT, zhT, enT)
    
    		global.Trans, ok = uni.GetTranslator(locale)
    		if !ok {
    			return fmt.Errorf("uni.GetTranslator(%s) error", locale)
    		}
    
    		switch locale {
    		case "zh":
    			zh_translations.RegisterDefaultTranslations(v, global.Trans)
    		case "en":
    			en_translations.RegisterDefaultTranslations(v, global.Trans)
    		default:
    			en_translations.RegisterDefaultTranslations(v, global.Trans)
    		}
    
    		return
    	}
    	return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    再请求,响应如下,发现password已经改好了,但是User也想删除。

    {'msg': {'User.password': 'password为必填字段'}}
    
    • 1

    我们在utils/validator.go文件中编写代码如下,该方法是用来删除User的。

    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
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    再在翻译返回的错误信息包上该方法。

    func login(c *gin.Context) {
    
    	var user User
    	if err := c.ShouldBindJSON(&user); err != nil {
    
    		errs, ok := err.(validator.ValidationErrors)
    		if !ok {
    			// 非校验错误,其他错误直接返回
    			c.JSON(http.StatusOK, gin.H{"msg": err.Error()})
    			return
    		}
    
    		c.JSON(http.StatusOK, gin.H{"msg": utils.RemoveTopStruct(errs.Translate(global.Trans))})
    		return
    	}
    
    	if user.UserName != "admin" || user.Password != "123456" {
    		c.JSON(http.StatusUnauthorized, gin.H{"msg": "unauthorized"})
    		return
    	}
    
    	c.JSON(http.StatusOK, gin.H{"msg": "you are logged in"})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    再执行,相应结果如下,这个就是我们想要的信息。

    {'msg': {'password': 'password为必填字段'}}
    
    • 1

    总结

    个人觉的虽然gin灵活小巧,但是功能真的很不完善。每次一次输出友好信息,我们都要手动调用Translate来翻译,并且还需要通过RemoveTopStruct方法来修改返回的信息,按简单的来说,应该由框架来做,我们只需要通过配置,就能自动输出我们想要的友好提示信息才对。

    欢迎关注,互相学习,共同进步~

    我的个人博客

    我的微信公众号:编程黑洞

  • 相关阅读:
    Expo项目 通过EAS update发布
    LeetCode1710——卡车上的最大单元数
    一分钟理解npm run dev 和 npm run serve
    【算法思想】贪心
    Postgresql聚合函数string_agg() 用法
    Mysql中的目录和文件详解
    CNP实现应用CD部署
    warning LNK4017: DESCRIPTION 语句不支持目标平台;已忽略
    全国职业技能大赛云计算--高职组赛题卷①(容器云)
    AI:07-基于卷积神经网络的海洋生物的识别
  • 原文地址:https://blog.csdn.net/qq_22918243/article/details/126828051