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為必填字段"}}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM