您好,登錄后才能下訂單哦!
這篇“golang validator庫(kù)參數(shù)校驗(yàn)實(shí)用技巧有哪些”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來(lái)看看這篇“golang validator庫(kù)參數(shù)校驗(yàn)實(shí)用技巧有哪些”文章吧。
在web開(kāi)發(fā)中一個(gè)不可避免的環(huán)節(jié)就是對(duì)請(qǐng)求參數(shù)進(jìn)行校驗(yàn),通常我們會(huì)在代碼中定義與請(qǐng)求參數(shù)相對(duì)應(yīng)的模型(結(jié)構(gòu)體),借助模型綁定快捷地解析請(qǐng)求中的參數(shù),例如 gin 框架中的Bind
和ShouldBind
系列方法。
gin框架使用github.com/go-playground/validator進(jìn)行參數(shù)校驗(yàn),目前已經(jīng)支持github.com/go-playground/validator/v10了,我們需要在定義結(jié)構(gòu)體時(shí)使用 binding
tag標(biāo)識(shí)相關(guān)校驗(yàn)規(guī)則,可以查看validator文檔查看支持的所有 tag。
首先來(lái)看gin框架內(nèi)置使用validator
做參數(shù)校驗(yàn)的基本示例。
package main import ( "net/http" "github.com/gin-gonic/gin" ) type SignUpParam struct { Age uint8 `json:"age" binding:"gte=1,lte=130"` Name string `json:"name" binding:"required"` Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required"` RePassword string `json:"re_password" binding:"required,eqfield=Password"` } func main() { r := gin.Default() r.POST("/signup", func(c *gin.Context) { var u SignUpParam if err := c.ShouldBind(&u); err != nil { c.JSON(http.StatusOK, gin.H{ "msg": err.Error(), }) return } // 保存入庫(kù)等業(yè)務(wù)邏輯代碼... c.JSON(http.StatusOK, "success") }) _ = r.Run(":8999") }
我們使用curl發(fā)送一個(gè)POST請(qǐng)求測(cè)試下:
curl -H "Content-type: application/json" -X POST -d '{"name":"q1mi","age":18,"email":"123.com"}' http://127.0.0.1:8999/signup
輸出結(jié)果:
{"msg":"Key: 'SignUpParam.Email' Error:Field validation for 'Email' failed on the 'email' tag\nKey: 'SignUpParam.Password' Error:Field validation for 'Password' failed on the 'required' tag\nKey: 'SignUpParam.RePassword' Error:Field validation for 'RePassword' failed on the 'required' tag"}
從最終的輸出結(jié)果可以看到 validator
的檢驗(yàn)生效了,但是錯(cuò)誤提示的字段不是特別友好,我們可能需要將它翻譯成中文。
validator
庫(kù)本身是支持國(guó)際化的,借助相應(yīng)的語(yǔ)言包可以實(shí)現(xiàn)校驗(yàn)錯(cuò)誤提示信息的自動(dòng)翻譯。下面的示例代碼演示了如何將錯(cuò)誤提示信息翻譯成中文,翻譯成其他語(yǔ)言的方法類似。
package main import ( "fmt" "net/http" "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" enTranslations "github.com/go-playground/validator/v10/translations/en" zhTranslations "github.com/go-playground/validator/v10/translations/zh" ) // 定義一個(gè)全局翻譯器T var trans ut.Translator // InitTrans 初始化翻譯器 func InitTrans(locale string) (err error) { // 修改gin框架中的Validator引擎屬性,實(shí)現(xiàn)自定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { zhT := zh.New() // 中文翻譯器 enT := en.New() // 英文翻譯器 // 第一個(gè)參數(shù)是備用(fallback)的語(yǔ)言環(huán)境 // 后面的參數(shù)是應(yīng)該支持的語(yǔ)言環(huán)境(支持多個(gè)) // uni := ut.New(zhT, zhT) 也是可以的 uni := ut.New(enT, zhT, enT) // locale 通常取決于 http 請(qǐng)求頭的 'Accept-Language' var ok bool // 也可以使用 uni.FindTranslator(...) 傳入多個(gè)locale進(jìn)行查找 trans, ok = uni.GetTranslator(locale) if !ok { return fmt.Errorf("uni.GetTranslator(%s) failed", locale) } // 注冊(cè)翻譯器 switch locale { case "en": err = enTranslations.RegisterDefaultTranslations(v, trans) case "zh": err = zhTranslations.RegisterDefaultTranslations(v, trans) default: err = enTranslations.RegisterDefaultTranslations(v, trans) } return } return } type SignUpParam struct { Age uint8 `json:"age" binding:"gte=1,lte=130"` Name string `json:"name" binding:"required"` Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required"` RePassword string `json:"re_password" binding:"required,eqfield=Password"` } func main() { if err := InitTrans("zh"); err != nil { fmt.Printf("init trans failed, err:%v\n", err) return } r := gin.Default() r.POST("/signup", func(c *gin.Context) { var u SignUpParam if err := c.ShouldBind(&u); err != nil { // 獲取validator.ValidationErrors類型的errors errs, ok := err.(validator.ValidationErrors) if !ok { // 非validator.ValidationErrors類型錯(cuò)誤直接返回 c.JSON(http.StatusOK, gin.H{ "msg": err.Error(), }) return } // validator.ValidationErrors類型錯(cuò)誤則進(jìn)行翻譯 c.JSON(http.StatusOK, gin.H{ "msg":errs.Translate(trans), }) return } // 保存入庫(kù)等具體業(yè)務(wù)邏輯代碼... c.JSON(http.StatusOK, "success") }) _ = r.Run(":8999") }
同樣的請(qǐng)求再來(lái)一次:
curl -H "Content-type: application/json" -X POST -d '{"name":"q1mi","age":18,"email":"123.com"}' http://127.0.0.1:8999/signup
這一次的輸出結(jié)果如下:
{"msg":{"SignUpParam.Email":"Email必須是一個(gè)有效的郵箱","SignUpParam.Password":"Password為必填字段","SignUpParam.RePassword":"RePassword為必填字段"}}
上面的錯(cuò)誤提示看起來(lái)是可以了,但是還是差點(diǎn)意思,首先是錯(cuò)誤提示中的字段并不是請(qǐng)求中使用的字段,例如:RePassword
是我們后端定義的結(jié)構(gòu)體中的字段名,而請(qǐng)求中使用的是re_password
字段。如何是錯(cuò)誤提示中的字段使用自定義的名稱,例如json
tag指定的值呢?
只需要在初始化翻譯器的時(shí)候像下面一樣添加一個(gè)獲取json
tag的自定義方法即可。
// InitTrans 初始化翻譯器 func InitTrans(locale string) (err error) { // 修改gin框架中的Validator引擎屬性,實(shí)現(xiàn)自定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { // 注冊(cè)一個(gè)獲取json tag的自定義方法 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() // 英文翻譯器 // 第一個(gè)參數(shù)是備用(fallback)的語(yǔ)言環(huán)境 // 后面的參數(shù)是應(yīng)該支持的語(yǔ)言環(huán)境(支持多個(gè)) // uni := ut.New(zhT, zhT) 也是可以的 uni := ut.New(enT, zhT, enT) // ... liwenzhou.com ... }
再嘗試發(fā)請(qǐng)求,看一下效果:
{"msg":{"SignUpParam.email":"email必須是一個(gè)有效的郵箱","SignUpParam.password":"password為必填字段","SignUpParam.re_password":"re_password為必填字段"}}
可以看到現(xiàn)在錯(cuò)誤提示信息中使用的就是我們結(jié)構(gòu)體中json
tag設(shè)置的名稱了。
但是還是有點(diǎn)瑕疵,那就是最終的錯(cuò)誤提示信息中心還是有我們后端定義的結(jié)構(gòu)體名稱——SignUpParam
,這個(gè)名稱其實(shí)是不需要隨錯(cuò)誤提示返回給前端的,前端并不需要這個(gè)值。我們需要想辦法把它去掉。
這里參考https://github.com/go-playground/validator/issues/633#issuecomment-654382345提供的方法,定義一個(gè)去掉結(jié)構(gòu)體名稱前綴的自定義方法:
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 }
我們?cè)诖a中使用上述函數(shù)將翻譯后的errors
做一下處理即可:
if err := c.ShouldBind(&u); err != nil { // 獲取validator.ValidationErrors類型的errors errs, ok := err.(validator.ValidationErrors) if !ok { // 非validator.ValidationErrors類型錯(cuò)誤直接返回 c.JSON(http.StatusOK, gin.H{ "msg": err.Error(), }) return } // validator.ValidationErrors類型錯(cuò)誤則進(jìn)行翻譯 // 并使用removeTopStruct函數(shù)去除字段名中的結(jié)構(gòu)體名稱標(biāo)識(shí) c.JSON(http.StatusOK, gin.H{ "msg": removeTopStruct(errs.Translate(trans)), }) return }
看一下最終的效果:
{"msg":{"email":"email必須是一個(gè)有效的郵箱","password":"password為必填字段","re_password":"re_password為必填字段"}}
這一次看起來(lái)就比較符合我們預(yù)期的標(biāo)準(zhǔn)了。
上面的校驗(yàn)還是有點(diǎn)小問(wèn)題,就是當(dāng)涉及到一些復(fù)雜的校驗(yàn)規(guī)則,比如re_password
字段需要與password
字段的值相等這樣的校驗(yàn)規(guī)則,我們的自定義錯(cuò)誤提示字段名稱方法就不能很好解決錯(cuò)誤提示信息中的其他字段名稱了。
curl -H "Content-type: application/json" -X POST -d '{"name":"q1mi","age":18,"email":"123.com","password":"123","re_password":"321"}' http://127.0.0.1:8999/signup
最后輸出的錯(cuò)誤提示信息如下:
{"msg":{"email":"email必須是一個(gè)有效的郵箱","re_password":"re_password必須等于Password"}}
可以看到re_password
字段的提示信息中還是出現(xiàn)了Password
這個(gè)結(jié)構(gòu)體字段名稱。這有點(diǎn)小小的遺憾,畢竟自定義字段名稱的方法不能影響被當(dāng)成param傳入的值。
此時(shí)如果想要追求更好的提示效果,將上面的Password字段也改為和json
tag一致的名稱,就需要我們自定義結(jié)構(gòu)體校驗(yàn)的方法。
例如,我們?yōu)?code>SignUpParam自定義一個(gè)校驗(yàn)方法如下:
// SignUpParamStructLevelValidation 自定義SignUpParam結(jié)構(gòu)體校驗(yàn)函數(shù) func SignUpParamStructLevelValidation(sl validator.StructLevel) { su := sl.Current().Interface().(SignUpParam) if su.Password != su.RePassword { // 輸出錯(cuò)誤提示信息,最后一個(gè)參數(shù)就是傳遞的param sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password") } }
然后在初始化校驗(yàn)器的函數(shù)中注冊(cè)該自定義校驗(yàn)方法即可:
func InitTrans(locale string) (err error) { // 修改gin框架中的Validator引擎屬性,實(shí)現(xiàn)自定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { // ... liwenzhou.com ... // 為SignUpParam注冊(cè)自定義校驗(yàn)方法 v.RegisterStructValidation(SignUpParamStructLevelValidation, SignUpParam{}) zhT := zh.New() // 中文翻譯器 enT := en.New() // 英文翻譯器 // ... liwenzhou.com ... }
最終再請(qǐng)求一次,看一下效果:
{"msg":{"email":"email必須是一個(gè)有效的郵箱","re_password":"re_password必須等于password"}}
這一次re_password
字段的錯(cuò)誤提示信息就符合我們預(yù)期了。
除了上面介紹到的自定義結(jié)構(gòu)體校驗(yàn)方法,validator
還支持為某個(gè)字段自定義校驗(yàn)方法,并使用RegisterValidation()
注冊(cè)到校驗(yàn)器實(shí)例中。
接下來(lái)我們來(lái)為SignUpParam
添加一個(gè)需要使用自定義校驗(yàn)方法checkDate
做參數(shù)校驗(yàn)的字段Date
。
type SignUpParam struct { Age uint8 `json:"age" binding:"gte=1,lte=130"` Name string `json:"name" binding:"required"` Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required"` RePassword string `json:"re_password" binding:"required,eqfield=Password"` // 需要使用自定義校驗(yàn)方法checkDate做參數(shù)校驗(yàn)的字段Date Date string `json:"date" binding:"required,datetime=2006-01-02,checkDate"` }
其中datetime=2006-01-02是內(nèi)置的用于校驗(yàn)日期類參數(shù)是否滿足指定格式要求的tag。 如果傳入的date
參數(shù)不滿足2006-01-02這種格式就會(huì)提示如下錯(cuò)誤:
{"msg":{"date":"date的格式必須是2006-01-02"}}
針對(duì)date字段除了內(nèi)置的datetime=2006-01-02提供的格式要求外,假設(shè)我們還要求該字段的時(shí)間必須是一個(gè)未來(lái)的時(shí)間(晚于當(dāng)前時(shí)間),像這樣針對(duì)某個(gè)字段的特殊校驗(yàn)需求就需要我們使用自定義字段校驗(yàn)方法了。
首先我們要在需要執(zhí)行自定義校驗(yàn)的字段后面添加自定義tag,這里使用的是checkDate
,注意使用英文分號(hào)分隔開(kāi)。
// customFunc 自定義字段級(jí)別校驗(yàn)方法 func customFunc(fl validator.FieldLevel) bool { date, err := time.Parse("2006-01-02", fl.Field().String()) if err != nil { return false } if date.Before(time.Now()) { return false } return true }
定義好了字段及其自定義校驗(yàn)方法后,就需要將它們聯(lián)系起來(lái)并注冊(cè)到我們的校驗(yàn)器實(shí)例中。
// 在校驗(yàn)器注冊(cè)自定義的校驗(yàn)方法 if err := v.RegisterValidation("checkDate", customFunc); err != nil { return err }
這樣,我們就可以對(duì)請(qǐng)求參數(shù)中date
字段執(zhí)行自定義的checkDate
進(jìn)行校驗(yàn)了。 我們發(fā)送如下請(qǐng)求測(cè)試一下:
curl -H "Content-type: application/json" -X POST -d '{"name":"q1mi","age":18,"email":"123@qq.com","password":"123", "re_password": "123", "date":"2020-01-02"}' http://127.0.0.1:8999/signup
此時(shí)得到的響應(yīng)結(jié)果是:
{"msg":{"date":"Key: 'SignUpParam.date' Error:Field validation for 'date' failed on the 'checkDate' tag"}}
這…自定義字段級(jí)別的校驗(yàn)方法的錯(cuò)誤提示信息很“簡(jiǎn)單粗暴”,和我們上面的中文提示風(fēng)格有出入,必須想辦法搞定它呀!
我們現(xiàn)在需要為自定義字段校驗(yàn)方法提供一個(gè)自定義的翻譯方法,從而實(shí)現(xiàn)該字段錯(cuò)誤提示信息的自定義顯示。
// registerTranslator 為自定義字段添加翻譯功能 func registerTranslator(tag string, msg string) validator.RegisterTranslationsFunc { return func(trans ut.Translator) error { if err := trans.Add(tag, msg, false); err != nil { return err } return nil } } // translate 自定義字段的翻譯方法 func translate(trans ut.Translator, fe validator.FieldError) string { msg, err := trans.T(fe.Tag(), fe.Field()) if err != nil { panic(fe.(error).Error()) } return msg }
定義好了相關(guān)翻譯方法之后,我們?cè)?code>InitTrans函數(shù)中通過(guò)調(diào)用RegisterTranslation()
方法來(lái)注冊(cè)我們自定義的翻譯方法。
// InitTrans 初始化翻譯器 func InitTrans(locale string) (err error) { // ...liwenzhou.com... // 注冊(cè)翻譯器 switch locale { case "en": err = enTranslations.RegisterDefaultTranslations(v, trans) case "zh": err = zhTranslations.RegisterDefaultTranslations(v, trans) default: err = enTranslations.RegisterDefaultTranslations(v, trans) } if err != nil { return err } // 注意!因?yàn)檫@里會(huì)使用到trans實(shí)例 // 所以這一步注冊(cè)要放到trans初始化的后面 if err := v.RegisterTranslation( "checkDate", trans, registerTranslator("checkDate", "{0}必須要晚于當(dāng)前日期"), translate, ); err != nil { return err } return } return }
這樣再次嘗試發(fā)送請(qǐng)求,就能得到想要的錯(cuò)誤提示信息了。
{"msg":{"date":"date必須要晚于當(dāng)前日期"}}
以上就是關(guān)于“golang validator庫(kù)參數(shù)校驗(yàn)實(shí)用技巧有哪些”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。