首页 > 其他分享 >优化gin表单的错误提示信息

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

时间:2023-04-14 10:32:43浏览次数:39  
标签:return err global 表单 ok 提示信息 msg 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 上启动服务
}

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

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

翻译

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

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

import ut "github.com/go-playground/universal-translator"

var (
	Trans ut.Translator
)

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
}

最后在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 上启动服务
}

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

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

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

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

优化返回字段的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
}

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

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

我们在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
}

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

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"})
}

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

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

总结

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

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

我的个人博客

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


标签:return,err,global,表单,ok,提示信息,msg,gin
From: https://blog.51cto.com/zhengwenfeng/6189581

相关文章

  • JS阻止表单提交
    阻止表单提交方法一:<inputtype=submitname=t_buttonοnclick="aaa();returnfalse">方法二:<inputtype=submitname=t_buttonοnclick="returnaaa()">functionaaa(){...returnfalse;}......
  • nginx location配置的优先级
        location正则写法location=/{#精确匹配/,主机名后面不能带任何字符串[configurationA]}location/{#因为所有的地址都以/开头,所以这条规则将匹配到所有请求#但是正则和最长字符串会优先匹配[configurationB]}location/documents......
  • docker启动nginx
    1》首先需要随便启动个nginx容器并进入容器,把nginx的配置项(conf、html、log)拷贝到服务器。命令如下:#htmldockercp容器id:/usr/share/nginx/html服务器路径#logsdockercp容器id:/var/log/nginx服务器路径#confdockercp容器id:/etc/nginx服务器路径注意:配置......
  • Nginx的安装和卸载
    一.安装(centos)1,安装一些依赖包,因为nginx是通过C语言来写成的:yum-yinstallgccpcre-develzlib-developensslopenssl-devel2,下载Nginx安装包yuminstallwget&&wgethttps://nginx.org/download/nginx-1.16.1.tar.gz3,解压缩包tar-zxvfnginx-1.16.1.tar.gzn......
  • nginx报错504 Geteway Timeout的处理
    后端开发同事反馈从页面导入大数据量表格,有时候响应正常,有时候接口返回504 GetewayTimeout报错,通过查看nginx日志报错基本上都是60s左右,原因是:问了开发同事说有时候后端程序处理导入的数据然后落库操作会很慢,即程序执行时间过长,由于proxy_read_timeout参数默认60s,由于后端服......
  • docker 容器操作、应用部署、mysql,redis,nginx、迁移与备份、Dockerfile
    容器操作#启动容器dockerstart容器id#停止容器dockerstop容器id#文件拷贝先创建文件mkdir:文件夹vivimtouch:文件#容器的文件copy到宿主机上(不是在容器内部操作)dockercp容器名称:容器目录需要拷贝的文件或目录dockercp......
  • 【element-ui】element ui from表单手机号座机号验证
    //手机号验证rules:{ phone:[ {required:true,min:11,max:11,message:"请输入11位手机号码",trigger:"blur"},{pattern:/^1[3456789]\d{9}$/,message:"请输入正确的手机号码"}]}//座机号......
  • Nginx 常用配置
    一、rewrite在/usr/share/nginx/html/下创建abc.html文件[root@localhosthtml]#cat/usr/share/nginx/html/81/abc.htmlabcnginx配置如下server{listen81;server_namelocalhost;#access_log/var/log/nginx/host.access.logmain;lo......
  • 【Docker】容器操作 mysql部署 redis部署 nginx部署 迁移与备份 Dockerfile
    目录上节回顾今日内容1容器操作2应用部署2.1mysql部署2.2redis2.3nginx3迁移与备份4Dockerfile练习上节回顾#1docker启动,停止,查看状态命令 systemctlstartdockersystemctlstopdockersystemctlstatusdockerdockerinfo#查看详情systemc......
  • go语言学习-gin框架会话控制
    cookie介绍HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出Cookie就是解决HTTP协议无状态的方案之一,中文是小甜饼的意思Cookie实际上就是服务器保存在浏览器上的一段信息。浏览器有了Cookie之后,每次向服务器发送请求时都会同......