Gin框架中參數(shù)校驗(yàn)優(yōu)化詳解
原始方式
gin使用的是 github.com/go-playground/validator 該組件進(jìn)行入?yún)⑿r?yàn),如下是gin中常用的參數(shù)校驗(yàn)方式:
type AccountCreateForm struct {
Id uint64 `json:"id"`
Name string `json:"name" binding:"required,max=16"` // 使用required和max限制入?yún)楸靥铐?xiàng)與最大長(zhǎng)度不能超過(guò)16字符
Username string `json:"username" binding:"required"`
Password string `json:"password"`
}該種方式有如下幾個(gè)不好使的地方:
錯(cuò)誤提示不友好,如果不做任何處理,默認(rèn)參數(shù)校驗(yàn)不通過(guò)會(huì)返回如下錯(cuò)誤提示

不支持正則表達(dá)式
不支持自定義錯(cuò)誤描述
改進(jìn)
自定義validatorx(validator擴(kuò)展工具包)
注冊(cè)翻譯器,新增對(duì)校驗(yàn)錯(cuò)誤進(jìn)行轉(zhuǎn)譯方法
package validatorx
import (
"mayfly-go/pkg/utils/stringx"
"mayfly-go/pkg/utils/structx"
"reflect"
"strings"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zh_trans "github.com/go-playground/validator/v10/translations/zh"
)
const CustomMsgTagName = "msg"
var (
trans ut.Translator
)
func Init() {
// 獲取gin的校驗(yàn)器
validate, ok := binding.Validator.Engine().(*validator.Validate)
if !ok {
return
}
// 修改返回字段key的格式
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
// 如果存在校驗(yàn)錯(cuò)誤提示消息,則使用字段名,后續(xù)需要通過(guò)該字段名獲取相應(yīng)錯(cuò)誤消息
if _, ok := fld.Tag.Lookup(CustomMsgTagName); ok {
return fld.Name
}
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})
// 注冊(cè)翻譯器
zh := zh.New()
uni := ut.New(zh, zh)
trans, _ = uni.GetTranslator("zh")
// 注冊(cè)翻譯器
zh_trans.RegisterDefaultTranslations(validate, trans)
// 注冊(cè)自定義正則表達(dá)式校驗(yàn)器
validate.RegisterValidation(CustomPatternTagName, patternValidFunc)
// 注冊(cè)自定義正則校驗(yàn)規(guī)則
RegisterCustomPatterns()
}
// Translate 翻譯錯(cuò)誤信息
func Translate(data any, err error) map[string][]string {
var result = make(map[string][]string)
errors := err.(validator.ValidationErrors)
for _, err := range errors {
fieldName := err.Field()
// 判斷該字段是否設(shè)置了自定義的錯(cuò)誤描述信息,存在則使用自定義錯(cuò)誤信息進(jìn)行提示
if field, ok := structx.IndirectType(reflect.TypeOf(data)).FieldByName(fieldName); ok {
if errMsg, ok := field.Tag.Lookup(CustomMsgTagName); ok {
customMsg := getCustomErrMsg(err.Tag(), errMsg)
if customMsg != "" {
result[fieldName] = append(result[fieldName], customMsg)
continue
}
}
}
// 如果是自定義正則校驗(yàn)規(guī)則,則使用自定義的錯(cuò)誤描述信息
if err.Tag() == CustomPatternTagName {
result[fieldName] = append(result[fieldName], fieldName+patternErrMsg[err.Param()])
continue
}
result[fieldName] = append(result[fieldName], err.Translate(trans))
}
return result
}
// 獲取自定義的錯(cuò)誤提示消息
//
// @param validTag 校驗(yàn)標(biāo)簽,如required等
// @param customMsg 自定義錯(cuò)誤消息
func getCustomErrMsg(validTag, customMsg string) string {
// 解析 msg:"required=用戶名不能為空,min=用戶名長(zhǎng)度不能小于8位"
msgs := strings.Split(customMsg, ",")
for _, msg := range msgs {
tagAndMsg := strings.Split(stringx.Trim(msg), "=")
if len(tagAndMsg) > 1 && validTag == stringx.Trim(tagAndMsg[0]) {
// 獲取valid tag對(duì)應(yīng)的錯(cuò)誤消息
return stringx.Trim(tagAndMsg[1])
}
}
return customMsg
}
// Translate 翻譯錯(cuò)誤信息為字符串
func Translate2Str(data any, err error) string {
res := Translate(data, err)
errMsgs := make([]string, 0)
for _, v := range res {
errMsgs = append(errMsgs, v...)
}
return strings.Join(errMsgs, ", ")
}自定義正則表達(dá)式校驗(yàn)方式
package validatorx
import (
"mayfly-go/pkg/global"
"regexp"
"github.com/go-playground/validator/v10"
)
const CustomPatternTagName = "pattern"
var (
regexpMap map[string]*regexp.Regexp // key:正則表達(dá)式名稱 value:正則表達(dá)式
patternErrMsg map[string]string // key:正則表達(dá)式名稱 value:校驗(yàn)不通過(guò)時(shí)的錯(cuò)誤消息提示
)
// 注冊(cè)自定義正則表達(dá)式校驗(yàn)規(guī)則
func RegisterCustomPatterns() {
// 賬號(hào)用戶名校驗(yàn),使用該種方式可以復(fù)用正則表達(dá)式以及錯(cuò)誤提示
// 使用方式如:Username string `json:"username" binding:"pattern=account_username"`
RegisterPattern("account_username", "^[a-zA-Z0-9_]{5,20}$", "只允許輸入5-20位大小寫(xiě)字母、數(shù)字、下劃線")
}
// 注冊(cè)自定義正則表達(dá)式
func RegisterPattern(patternName string, regexpStr string, errMsg string) {
if regexpMap == nil {
regexpMap = make(map[string]*regexp.Regexp, 0)
patternErrMsg = make(map[string]string)
}
regexpMap[patternName] = regexp.MustCompile(regexpStr)
patternErrMsg[patternName] = errMsg
}
// 自定義正則表達(dá)式校驗(yàn)器函數(shù)
func patternValidFunc(f validator.FieldLevel) bool {
reg := regexpMap[f.Param()]
if reg == nil {
global.Log.Warnf("%s的正則校驗(yàn)規(guī)則不存在!", f.Param())
return false
}
return reg.MatchString(f.Field().String())
}錯(cuò)誤轉(zhuǎn)譯
對(duì)入?yún)⑦M(jìn)行校驗(yàn),檢驗(yàn)不通過(guò)時(shí)將錯(cuò)誤進(jìn)行轉(zhuǎn)譯,轉(zhuǎn)譯為漢字或自定義的錯(cuò)誤描述等。
// 綁定并校驗(yàn)請(qǐng)求結(jié)構(gòu)體參數(shù)
func BindJsonAndValid[T any](g *gin.Context, data T) T {
if err := g.ShouldBindJSON(data); err != nil {
// 統(tǒng)一recover處理
panic(ConvBindValidationError(data, err))
} else {
return data
}
}
// 綁定請(qǐng)求體中的json至form結(jié)構(gòu)體,并拷貝至另一結(jié)構(gòu)體
func BindJsonAndCopyTo[T any](g *gin.Context, form any, toStruct T) T {
BindJsonAndValid(g, form)
structx.Copy(toStruct, form)
return toStruct
}
// 轉(zhuǎn)譯參數(shù)校驗(yàn)錯(cuò)誤,并將參數(shù)校驗(yàn)錯(cuò)誤為業(yè)務(wù)異常錯(cuò)誤(統(tǒng)一recover處理)
func ConvBindValidationError(data any, err error) error {
if e, ok := err.(validator.ValidationErrors); ok {
// 調(diào)用validatorx.Translate2Str方法進(jìn)行校驗(yàn)錯(cuò)誤轉(zhuǎn)譯
return biz.NewBizErrCode(403, validatorx.Translate2Str(data, e))
}
return err
}
// 返回失敗結(jié)果集
func ErrorRes(g *gin.Context, err any) {
switch t := err.(type) {
case biz.BizError:
g.JSON(http.StatusOK, model.Error(t))
case error:
g.JSON(http.StatusOK, model.ServerError())
global.Log.Errorf("%s\n%s", t.Error(), string(debug.Stack()))
case string:
g.JSON(http.StatusOK, model.ServerError())
global.Log.Errorf("%s\n%s", t, string(debug.Stack()))
default:
global.Log.Error(t)
}
}初始化校驗(yàn)器
項(xiàng)目啟動(dòng)時(shí),在合適的時(shí)機(jī)初始化校驗(yàn)器
// 參數(shù)校驗(yàn)器初始化、如錯(cuò)誤提示中文轉(zhuǎn)譯、注冊(cè)自定義校驗(yàn)器等 validatorx.Init()
統(tǒng)一使用方式
入?yún)⒆侄蝨ag綁定
type AccountCreateForm struct {
Id uint64 `json:"id"`
// msg tag里對(duì)應(yīng)的required max即為binding里的校驗(yàn)類型
Name string `json:"name" binding:"required,max=16" msg:"required=姓名不能為空,max=姓名最大長(zhǎng)度不能超過(guò)16位"`
// account_name為validatorx.RegisterPattern("account_username", "^[a-zA-Z0-9_]{5,20}$", "只允許輸入5-20位大小寫(xiě)字母、數(shù)字、下劃線")
Username string `json:"username" binding:"pattern=account_username"`
Password string `json:"password" binding:"required"`
}form := &form.AccountCreateForm{}
// 校驗(yàn)不通過(guò)會(huì)自行panic統(tǒng)一recover處理
var account *entity.Account = ginx.BindJsonAndCopyTo(rc.GinCtx, form, new(entity.Account)) 效果

更多代碼詳見(jiàn):gitee.com/objs/mayfly-go一個(gè)web版 linux(終端[終端回放] 文件 腳本 進(jìn)程 計(jì)劃任務(wù))、數(shù)據(jù)庫(kù)(mysql postgres)、redis(單機(jī) 哨兵 集群)、mongo統(tǒng)一管理操作平臺(tái)
到此這篇關(guān)于Gin框架中參數(shù)校驗(yàn)優(yōu)化詳解的文章就介紹到這了,更多相關(guān)Gin參數(shù)校驗(yàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang中文字符串截取函數(shù)實(shí)現(xiàn)原理
在golang中可以通過(guò)切片截取一個(gè)數(shù)組或字符串,但是當(dāng)截取的字符串是中文時(shí),可能會(huì)出現(xiàn)問(wèn)題,下面我們來(lái)自定義個(gè)函數(shù)解決Golang中文字符串截取問(wèn)題2018-03-03
Go語(yǔ)言基礎(chǔ)學(xué)習(xí)之?dāng)?shù)組的使用詳解
數(shù)組相必大家都很熟悉,各大語(yǔ)言也都有數(shù)組的身影。Go 語(yǔ)言也提供了數(shù)組類型的數(shù)據(jù)結(jié)構(gòu)。本文就來(lái)通過(guò)一些簡(jiǎn)單的示例帶大家了解一下Go語(yǔ)言中數(shù)組的使用,希望對(duì)大家有所幫助2022-12-12
Golang基于泛化調(diào)用與Nacos實(shí)現(xiàn)Dubbo代理
這篇文章主要為大家詳細(xì)介紹了Golang如何基于泛化調(diào)用與Nacos實(shí)現(xiàn)Dubbo代理,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-04-04
Go語(yǔ)言之使用pprof工具查找goroutine(協(xié)程)泄漏
這篇文章主要介紹了Go語(yǔ)言之使用pprof工具查找goroutine(協(xié)程)泄漏,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
Go語(yǔ)言RPC Authorization進(jìn)行簡(jiǎn)單ip安全驗(yàn)證的方法
這篇文章主要介紹了Go語(yǔ)言RPC Authorization進(jìn)行簡(jiǎn)單ip安全驗(yàn)證的方法,實(shí)例分析了Go語(yǔ)言進(jìn)行ip驗(yàn)證的技巧,需要的朋友可以參考下2015-03-03
Golang開(kāi)發(fā)庫(kù)的集合及作用說(shuō)明
這篇文章主要為大家介紹了Golang開(kāi)發(fā)golang庫(kù)的集合及簡(jiǎn)單的作用說(shuō)明,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11
go使用Gin框架利用阿里云實(shí)現(xiàn)短信驗(yàn)證碼功能
這篇文章主要介紹了go使用Gin框架利用阿里云實(shí)現(xiàn)短信驗(yàn)證碼,使用json配置文件及配置文件解析,編寫(xiě)路由controller層,本文通過(guò)代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-08-08

