導讀
在第二節,我們學習了Gin框架的路由定義與引數接收,今天應一位同學的要求,來講解一下引數的系結與校驗,
為什么校驗引數?
本不必拋出這個問題的,但顧及到初出茅廬的同學,這里解釋一下,

假設做一個注冊介面,傳過來的用戶名是不是不能太騷氣?比如一堆空格和符號之類的;密碼是不是不能太長也不能太短?手機號是不是要符合規則?性別是不是不能填人妖?

另外,登錄的時候我們也需要驗證賬號密碼是不是正確的,那么為了方便上手,咱就先來個簡單示例,做登錄驗證,
激情演示
做登錄之前得先想清楚需要對用戶名密碼做什么樣的限制,比如他們都不能為空、用戶名只能是字母或數字、密碼長度只能在6位到12位之間等,如果各位看官沒有異議,接下來我就拿上述的這幾個條件來演示了,

定義結構體與介面
首先得有個地方存接收到的用戶名、密碼引數,那就定一個名叫Login的結構體吧,
type Login struct {
User string
Password string
}
然后再定義一個登錄介面,這塊不知道啥意思的同學可以去看我的第二篇教程,
router.POST("/login", func(c *gin.Context) {
})
介面是定了,怎么才能把接收的引數放到Login結構體里去呢?

系結引數
我們去翻一下gin.Context下面都有些什么好東西可以拿來用,看到了一個叫做Bind的東東,官方說它可以自動根據Content-Type設定的值決議請求過來的引數,然后把引數設定到結構體里,
func (c *Context) Bind(obj interface{}) error {
b := binding.Default(c.Request.Method, c.ContentType())
return c.MustBindWith(obj, b)
}
哇塞,這就有意思了,呼叫一下試試,記得把Login的參考傳過去,畢竟人家還要賦值的,
router.POST("/login", func(c *gin.Context) {
var login Login
c.Bind(&login)
c.JSON(200, login)
})
果不其然,跑起來的同學應該可以發現,它失敗了,并沒有取到我想要的引數值,
curl -d "user=pingye&password=123" http://localhost:8080/login
{"User":"","Password":""}
這不對啊,不是說好做彼此的天使嗎?

不行,我要一層一層剝開gin框架是怎么處理的,

系結失敗,剖析原始碼
經過長達60分鐘的精心研究,我終于發現了終極奧義,先把我繪制的圖貼上,

事情是這樣的,我們呼叫的Bind方法實際呼叫了兩個方法binding.Default和c.MustBindWith,前者的主要作用是根據終端請求的Content-Type選擇處理器,沒辦法,gin太強,支持的型別太多了,我們剛才的請求方式被理所應當的分配給了formBinding,
var (
JSON = jsonBinding{}
XML = xmlBinding{}
Form = formBinding{}
Query = queryBinding{}
FormPost = formPostBinding{}
FormMultipart = formMultipartBinding{}
ProtoBuf = protobufBinding{}
MsgPack = msgpackBinding{}
YAML = yamlBinding{}
Uri = uriBinding{}
Header = headerBinding{}
)
然后呢?在拿到了formBinding之后,就來到了c.MustBindWith方法,它的作用就是呼叫formBinding的Bind方法,原來這哥們就是個中間商在賺差價,
func (formBinding) Bind(req *http.Request, obj interface{}) error {
if err := req.ParseForm(); err != nil {
return err
}
if err := req.ParseMultipartForm(defaultMemory); err != nil {
if err != http.ErrNotMultipart {
return err
}
}
if err := mapForm(obj, req.Form); err != nil {
return err
}
return validate(obj)
}
Bind方法主要就干了兩件事情,第一是決議傳過來的表單引數,第二是找到結構體里的tagform進行匹配賦值,看到這里我就明白了,原來只需要在Login后面加上一個tag,
繼續系結引數
那我們加上tag試一下,
type Login struct {
User string `form:"user"`
Password string `form:"password"`
}
跑起來果然很完美,
curl -d "user=pingye&password=123" http://localhost:8080/login
{"User":"pingye","Password":"123"}
看到了這里,聰明的你應該涌出了很多想法,剛才說支持那么多型別,前端傳的是json咋搞呢?同學們可以自己試一下,現有的這套代碼啥都不用改就可以決議json,因為jsonBinding并沒有去Login結構體找tag,所以不用在后面加上json:"user"的標識,
curl -H "Content-Type:application/json" -d '{"user":"pingye","password":"123455"}' http://localhost:8080/login
{"User":"pingye","Password":"123455"}
至于其他的型別,同學們可以自己去動手試驗一下,我們必須得到引數驗證環節了,
引數驗證
OK,這就到了激動人心的引數驗證時刻了,再回顧一下剛才的需求,用戶名和密碼不能為空,用戶名只能是英文和數字,密碼長度必須得在6到12位,
gin官方給出的示例是直接在tag中加校驗規則,比如不能為空,就加上binding:"required",
type Login struct {
User string `form:"user" binding:"required"`
Password string `form:"password" binding:"required"`
}
在驗證的地方也做一下處理,Bind會自動幫我們進行校驗,如果校驗失敗會回傳一個error,我們把它輸出即可,
router.POST("/login", func(c *gin.Context) {
var login Login
err := c.Bind(&login)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(200, login)
})
放心,引數驗證的演示不會就這么結束的,

鑒于官方檔案給的資訊太少了,我們還是通過原始碼去找更多線索吧,通過源代碼可以看到,gin的引數驗證實際上并不是自己實作的,而是使用了一個叫做go-playground/validator的庫,
.
├── LICENSE
├── Makefile
├── README.md
├── _examples
├── baked_in.go
├── benchmarks_test.go
├── cache.go
├── doc.go
├── errors.go
├── field_level.go
├── go.mod
├── go.sum
├── logo.png
├── non-standard
├── regexes.go
├── struct_level.go
├── testdata
├── translations
├── translations.go
├── util.go
├── validator.go
├── validator_instance.go
└── validator_test.go
4 directories, 19 files
里面有一個叫做doc.go的檔案,有非常多的示例與解釋,簡直找到寶藏了,去他的官方檔案,

我在里面找到了長度限制的demo,很簡單,min和max兩個標簽就搞定了,跑一下完全沒有問題,
type Login struct {
User string `form:"user" binding:"required"`
Password string `form:"password" binding:"required,min=6,max=12"`
}
curl -d "user=pingye&password=12345" http://localhost:8080/login
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'min' tag"}
curl -d "user=pingye&password=1234567890123" http://localhost:8080/login
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'max' tag"}
實作了兩個校驗了,還剩下用戶名的字母和數字限制,讓我震驚的是,我以為隨便說的這個限制要用多種組合來實作,竟然輕松就找到了一個對應的,簡直太棒了(強烈推薦這個庫,看來后面有必要出一期這個庫的介紹),很簡單,加上一個名叫alphanum的規則就可以實作了,
type Login struct {
User string `form:"user" binding:"required,alphanum"`
Password string `form:"password" binding:"required,min=6,max=12"`
}
今天我的任務結束了,各位是不是需要查看原始碼,來吧來吧,點擊查看原始碼,順便STAR一下我哈,
Go語言庫示例開源專案「golang-examples」歡迎star~
https://github.com/pingyeaa/golang-examples
感謝大家的觀看,如果覺得文章對你有所幫助,歡迎關注公眾號「平也」,聚焦Go語言與技術原理,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/27382.html
標籤:Go
上一篇:檔案基本操作
下一篇:go語言基礎(一)
