Golang中web參數(shù)校驗(yàn)的實(shí)現(xiàn)
參數(shù)校驗(yàn)
確保我們的應(yīng)用接收到的數(shù)據(jù)是格式正確并且安全的
基本用法
JSON
假設(shè)我們正在開發(fā)一個(gè)登錄接口,希望用戶必須提供用戶名和密碼。在 Gin 中,我們首先會(huì)定義一個(gè) Go 的 struct 來(lái)描述這個(gè)數(shù)據(jù)結(jié)構(gòu)。
type Login struct {
User string `json:"user" binding:"required"`
Password string `json:"password" binding:"required"`
}
- json:“…” 標(biāo)簽: 這個(gè)標(biāo)簽告訴 Gin,當(dāng)它解析傳入的 JSON 數(shù)據(jù)時(shí),JSON 中的 user 字段應(yīng)該映射到我們結(jié)構(gòu)體中的 User 字段。
- binding:“required” 標(biāo)簽: 這是最核心的部分!binding 就是用來(lái)做數(shù)據(jù)校驗(yàn)的。required 是一個(gè)校驗(yàn)規(guī)則,意思是“這個(gè)字段是必填的,不能為空”。
func loginHandler(c *gin.Context) {
var login Login
// 嘗試將請(qǐng)求的 JSON body 綁定到 login 結(jié)構(gòu)體上
// 在綁定的同時(shí),Gin 會(huì)根據(jù) "binding" 標(biāo)簽進(jìn)行校驗(yàn)
err := c.ShouldBindJSON(&login)
if err != nil {
// 如果 err 不是 nil,說明校驗(yàn)失敗了!
// 比如,某個(gè) "required" 的字段沒有被提供。
// Gin 通常會(huì)自動(dòng)返回一個(gè) 400 Bad Request 錯(cuò)誤。
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 如果 err 是 nil,說明所有校驗(yàn)都通過了!
c.JSON(200, gin.H{"status": "you are logged in"})
}

URL
除了處理 JSON,我們經(jīng)常還需要處理來(lái)自 URL 查詢(Query)和 HTML 表單(Form)的數(shù)據(jù)。
Gin 的設(shè)計(jì)非常統(tǒng)一。我們的 struct 定義方式幾乎不變,只需要更換一個(gè)綁定方法就可以了。
如何校驗(yàn) URL 查詢參數(shù),比如這樣一個(gè)請(qǐng)求:GET /search?keyword=gin&page=1
我們希望 keyword 是必填的,并且 page 必須是一個(gè)大于 0 的數(shù)字。
首先,還是定義我們的 struct:
type SearchQuery struct {
Keyword string `form:"keyword" binding:"required"`
Page int `form:"page" binding:"required,gt=0"`
}
- 我們用了 form:“…” 標(biāo)簽,而不是 json:“…”。這個(gè) form 標(biāo)簽既可以用于 URL 查詢參數(shù),也可以用于表單數(shù)據(jù)。
- 我在 Page 字段的 binding 里加了一個(gè)新規(guī)則 gt=0,它的意思是 “greater than 0”(必須大于0)。
func searchHandler(c *gin.Context) {
var query SearchQuery
// 從 URL query 中綁定并校驗(yàn)參數(shù)
// 注意這里換成了 ShouldBindQuery
err := c.ShouldBindQuery(&query)
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 校驗(yàn)通過!
c.JSON(200, gin.H{
"message": "search parameters are valid",
"keyword": query.Keyword,
"page": query.Page,
})
}

常用的校驗(yàn)規(guī)則
我們通過一個(gè)用戶注冊(cè)的例子來(lái)看看:
type RegisterUser struct {
// 用戶名:必填,長(zhǎng)度在 4 到 20 個(gè)字符之間
Username string `json:"username" binding:"required,min=4,max=20"`
// 郵箱:必填,且必須是合法的郵箱格式
Email string `json:"email" binding:"required,email"`
// 年齡:選填,但如果提供了,必須大于等于 18 歲,小于等于 100 歲
Age int `json:"age" binding:"gte=18,lte=100"`
// 用戶類型:必填,并且值必須是 'user' 或 'admin' 中的一個(gè)
UserType string `json:"user_type" binding:"required,oneof=user admin"`
}
- min=4,max=20:用于字符串、數(shù)組等,限制其最小和最大長(zhǎng)度。多個(gè)規(guī)則用逗號(hào) , 分隔。
- email:檢查字符串是否符合標(biāo)準(zhǔn)的 email 格式。
- gte=18:意思是 “Greater Than or Equal To”,即大于或等于 18。
- lte=100:意思是 “Less Than or Equal To”,即小于或等于 100。
- oneof=user admin:表示這個(gè)字段的值必須是后面列出的值之一(用空格分隔)。
func registerUserHandler(c *gin.Context) {
var register RegisterUser
err := c.ShouldBindJSON(®ister)
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{
"username": register.Username,
"email": register.Email,
})
}

自定義錯(cuò)誤信息
核心思路是:捕獲到校驗(yàn)錯(cuò)誤后,我們不再直接把它整個(gè)返回,而是逐一檢查是哪個(gè)字段的哪條規(guī)則出錯(cuò)了,然后根據(jù)這些信息,手動(dòng)“翻譯”成我們想展示的話。
func registerUserHandler(c *gin.Context) {
var register RegisterUser
err := c.ShouldBindJSON(®ister)
if err != nil {
var verrs validator.ValidationErrors
ok := errors.As(err, &verrs)
if !ok {
// 如果不是 ValidationErrors 類型的錯(cuò)誤,直接返回原始錯(cuò)誤
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 創(chuàng)建一個(gè) map 來(lái)存放我們“翻譯”后的錯(cuò)誤信息
// key 是字段名,value 是錯(cuò)誤信息
// 比如:{"Username": "長(zhǎng)度不能小于 4 個(gè)字符"}
translatedErrors := make(map[string]string)
for _, fe := range verrs {
// fe.Field() 獲取出錯(cuò)的字段名,比如 "Username"
translatedErrors[fe.Field()] = getErrorMsg(fe)
}
c.JSON(400, gin.H{"errors": translatedErrors})
return
}
c.JSON(200, gin.H{
"username": register.Username,
"email": register.Email,
})
}
// 這個(gè)函數(shù)專門用來(lái)“翻譯”錯(cuò)誤信息
func getErrorMsg(fe validator.FieldError) string {
// fe.Tag() 能獲取到是哪個(gè)校驗(yàn)規(guī)則出錯(cuò)了,比如 "required", "min", "email"
switch fe.Tag() {
case "required":
return "這是必填項(xiàng)"
case "min":
// fe.Param() 能獲取到規(guī)則后面的參數(shù),比如 "min=4" 中的 "4"
return "長(zhǎng)度不能小于 " + fe.Param() + " 個(gè)字符"
case "max":
return "長(zhǎng)度不能超過 " + fe.Param() + " 個(gè)字符"
case "email":
return "請(qǐng)輸入正確的郵箱地址"
case "oneof":
return "必須是 " + fe.Param() + " 中的一個(gè)"
default:
return "未知錯(cuò)誤"
}
}

自定義校驗(yàn)規(guī)則
當(dāng)內(nèi)置的規(guī)則(像 required, email, min, max 等)不夠用時(shí),我們就需要自定義規(guī)則。比如,我們想強(qiáng)制要求“密碼必須同時(shí)包含字母和數(shù)字”。
整個(gè)過程分為三步,我們一步一步來(lái):
- 編寫一個(gè)自定義校驗(yàn)函數(shù)
這個(gè)函數(shù)需要一個(gè)特定的格式。它接收一個(gè) validator.FieldLevel 類型的參數(shù),并返回一個(gè) bool 值。返回 true 表示校驗(yàn)通過,false 表示失敗。
// isPasswordStrong 就是我們的自定義校驗(yàn)函數(shù)
func isPasswordStrong(fl validator.FieldLevel) bool {
// fl.Field().String() 可以獲取到字段的字符串值
password := fl.Field().String()
hasLetter := false
hasDigit := false
// 遍歷密碼字符串,檢查是否同時(shí)包含字母和數(shù)字
for _, char := range password {
if unicode.IsLetter(char) {
hasLetter = true
}
if unicode.IsDigit(char) {
hasDigit = true
}
}
return hasLetter && hasDigit
}
將自定義函數(shù)“注冊(cè)”到 Gin 的校驗(yàn)器里
光寫好函數(shù)還不行,我們得告訴 Gin 的校驗(yàn)器:“嘿,我這里有一個(gè)新的校驗(yàn)規(guī)則,它的名字叫 strong_password,對(duì)應(yīng)的處理函數(shù)是 isPasswordStrong。”這個(gè)注冊(cè)過程通常在你的程序啟動(dòng)時(shí)(比如 main 函數(shù)里)完成。
// 從 Gin 的 binding 中獲取底層的 validator 引擎
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 注冊(cè)我們的自定義校驗(yàn)函數(shù)
// 第一個(gè)參數(shù)是這個(gè)規(guī)則在 struct tag 中使用的名字
// 第二個(gè)參數(shù)是我們的函數(shù)
v.RegisterValidation("strong_password", isPasswordStrong)
}
- 在 Struct Tag 中使用新規(guī)則
一旦注冊(cè)成功,我們就可以像使用任何內(nèi)置規(guī)則一樣,在 struct tag 中使用 strong_password 了。

到此這篇關(guān)于Golang中web參數(shù)校驗(yàn)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Golang web參數(shù)校驗(yàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言對(duì)JSON數(shù)據(jù)進(jìn)行序列化和反序列化
這篇文章介紹了Go語(yǔ)言對(duì)JSON數(shù)據(jù)進(jìn)行序列化和反序列化的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07
Golang學(xué)習(xí)筆記(一):簡(jiǎn)介
這篇文章主要介紹了Golang學(xué)習(xí)筆記(一):簡(jiǎn)介,本文講解了Go語(yǔ)言最主要的特性、安裝、環(huán)境變量設(shè)置、整體目錄結(jié)構(gòu)、Helloworld、go命令、調(diào)試、編輯器設(shè)置等內(nèi)容,需要的朋友可以參考下2015-05-05
golang http使用踩過的坑與應(yīng)對(duì)方式
這篇文章主要介紹了golang http使用踩過的坑與應(yīng)對(duì)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
詳解如何在golang項(xiàng)目開發(fā)中創(chuàng)建自己的Module
既然我們使用了很多開源的 module為我們的日常開發(fā)提供了很多的便捷性,那我們?cè)撊绾螌?shí)現(xiàn)自己的 module 來(lái)提供給團(tuán)隊(duì)中使用,接下小編就給大家介紹一下在golang項(xiàng)目開發(fā)如何創(chuàng)建自己的Module,需要的朋友可以參考下2023-09-09
淺析Go項(xiàng)目中的依賴包管理與Go?Module常規(guī)操作
這篇文章主要為大家詳細(xì)介紹了Go項(xiàng)目中的依賴包管理與Go?Module常規(guī)操作,文中的示例代碼講解詳細(xì),對(duì)我們深入了解Go語(yǔ)言有一定的幫助,需要的可以跟隨小編一起學(xué)習(xí)一下2023-10-10

