Gin框架系列之表单验证
一、表单基本校验
Gin是一个Web框架,提供Web服务,所以很多功能是通过第三方插件集成进去的,这里使用了https://github.com/go-playground/validator来处理的。它实现了结构体值验证以及基于标签的单个字段。所以可以将请求体绑定到结构体模型上。
需要在绑定的字段上设置tag,比如,绑定格式为json,需要这样设置 json:"fieldname" 。
此外,Gin还提供了两套绑定方法:
1、Must bind
- Methods - Bind, BindJSON, BindXML, BindQuery, BindYAML
- Behavior - 这些方法底层使用 MustBindWith,如果存在绑定错误,请求将被以下指令中止 c.AbortWithError(400, err).SetType(ErrorTypeBind),响应状态代码会被设置为400,请求头Content-Type被设置为text/plain; charset=utf-8。注意,如果你试图在此之后设置响应代码,将会发出一个警告 [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422,如果你希望更好地控制行为,请使用ShouldBind相关的方法
2、Should bind
- Methods - ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML
- Behavior - 这些方法底层使用 ShouldBindWith,如果存在绑定错误,则返回错误,开发人员可以正确处理请求和错误。
当我们使用绑定方法时,Gin会根据Content-Type推断出使用哪种绑定器,如果你确定你绑定的是什么,你可以使用MustBindWith或者BindingWith。
你还可以给字段指定特定规则的修饰符,如果一个字段用binding:"required"修饰,并且在绑定时该字段的值为空,那么将返回一个错误。
package main import ( "github.com/gin-gonic/gin" "net/http" ) type LoginForm struct { UserName string `form:"username" json:"username" binding:"required"` Password string `form:"password" json:"password" binding:"required"` } func main() { router := gin.Default() router.POST("/login", func(c *gin.Context) { var loginForm LoginForm if err := c.ShouldBind(&loginForm); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "msg": "登录成功", }) }) router.Run(":8000") }
然后可以通过客户端发送请求:
import requests res = requests.post(url="http://127.0.0.1:8000/login", data={ "username": "bily", "password": "123456" }) print(res.text)
但是假如输入的请求体不符合后台验证规则,会出现下面的错误:
{"error":"Key: 'LoginForm.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
那么怎么把上述的英文转成中文呢?
二、表单验证错误处理
可以看到上面的错误提示都是一些后端定义的字段名以及英文提示,对于用户来说不是很友好,所以需要将其转成中文提示。
package main import ( "fmt" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "github.com/go-playground/locales/en" "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" en_translations "github.com/go-playground/validator/v10/translations/en" zh_translations "github.com/go-playground/validator/v10/translations/zh" "net/http" "reflect" "strings" ) var trans ut.Translator // 初始化一个翻译器函数 func InitTrans(locale string) (err error) { // 修改gin框架中的validator引擎属性,实现定制 v, ok := binding.Validator.Engine().(*validator.Validate) if ok { // 注册一个获取json的tag自定义方法 v.RegisterTagNameFunc(func(field reflect.StructField) string { name := strings.SplitN(field.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name }) } zhT := zh.New() //中文翻译器 enT := en.New() //英文翻译器 uni := ut.New(enT, zhT, enT) // 配置默认翻译器、以及可支持的翻译器 trans, ok = uni.GetTranslator(locale) if !ok { return fmt.Errorf("uni.GetTranslator(%s) error", locale) } switch locale { case "en": _ = en_translations.RegisterDefaultTranslations(v, trans) case "zh": _ = zh_translations.RegisterDefaultTranslations(v, trans) default: _ = en_translations.RegisterDefaultTranslations(v, trans) } return } type LoginForm struct { UserName string `form:"username" json:"username" binding:"required"` Password string `form:"password" json:"password" binding:"required"` } func main() { router := gin.Default() router.POST("/login", func(c *gin.Context) { // 调用翻译器函数 if err := InitTrans("zh"); err != nil { fmt.Println("翻译器错误!") return } var loginForm LoginForm if err := c.ShouldBind(&loginForm); err != nil { if errors, ok := err.(validator.ValidationErrors); !ok { // 如果错误不能转化,不能进行翻译,就返回错误信息 c.JSON(http.StatusOK, gin.H{ "error": errors.Error(), }) } else { // 如果错误能进行翻译,就返回翻译后的错误信息 c.JSON(http.StatusBadRequest, gin.H{ "error": errors.Translate(trans), }) } return } c.JSON(http.StatusOK, gin.H{ "msg": "登录成功", }) }) router.Run(":8000") }
假如进行一个错误请求:
import requests res = requests.post(url="http://127.0.0.1:8000/login", data={ "password": "123456" }) print(res.text)
如下为错误信息:
{"error":{"LoginForm.username":"username为必填字段"}}
着只解决了部分问题,但是字段没有翻译过来,所以还需要进行优化:
... func removeTopStruct(fields map[string]string) map[string]string { rsp := map[string]string{} for key, value := range fields { rsp[key[strings.Index(key, ".")+1:]] = value } return rsp } ...
在之前的错误处理main函数中引入该函数:
... else { // 如果错误能进行翻译,就返回翻译后的错误信息 c.JSON(http.StatusBadRequest, gin.H{ "error": removeTopStruct(errors.Translate(trans)), }) } ...
错误处理的效果为:
{"error":{"username":"username为必填字段"}}
作者:iveBoy
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。