您好,登錄后才能下訂單哦!
本篇內容介紹了“Go高效率開發Web參數校驗的方式有哪些”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
web開發中,你肯定見到過各種各樣的表單或接口數據校驗:
客戶端參數校驗:在數據提交到服務器之前,發生在瀏覽器端或者app應用端,相比服務器端校驗,用戶體驗更好,能實時反饋用戶的輸入校驗結果。
服務器端參數校驗:發生在客戶端提交數據并被服務器端程序接收之后,通常服務器端校驗都是發生在將數據寫入數據庫之前,如果數據沒通過校驗,則會直接從服務器端返回錯誤消息,并且告訴客戶端發生錯誤的具體位置和原因,服務器端校驗不像客戶端校驗那樣有好的用戶體驗,因為它直到整個表單都提交后才能返回錯誤信息。但是服務器端校驗是應用對抗錯誤,惡意數據的最后防線,在這之后,數據將被持久化至數據庫。當今所有的服務端框架都提供了數據校驗與過濾功能(讓數據更安全)。
本文主要討論服務器端參數校驗
確保用戶以正確格式輸入數據,提交的數據能使后端應用程序正常工作,同時在一切用戶的輸入都是不可信的前提下(比如xss跨域腳本攻擊,sql注入),參數驗證是不可或缺的一環,也是很繁瑣效率不高的一環,在對接表單提交或者api接口數據提交,程序里充斥著大量重復驗證邏輯和if else語句,本文分析參數校驗的三種方式,找出最優解,從而提高參數驗證程序代碼的開發效率。
常見的網站登陸場景
接口一: 場景:輸入手機號,獲取短信驗證碼 校驗需求:判斷手機號非空,手機號格式是否正確 接口二: 場景:手機收到短信驗證碼,輸入驗證碼,點擊登陸 校驗需求:1、判斷手機號非空,手機號格式是否正確;2、驗證碼非空,驗證碼格式是否正確
技術選型:web框架gin
package main func main() { engine := gin.New() engine := gin.New() ctrUser := controller.NewUser() engine.POST("/user/login", ctrUser.Login) ctrCaptcha := controller.NewCaptcha() engine.POST("/captcha/send", ctrCaptcha.Send) engine.Run() } -------------------------------------------------------------------------------- package controller type Captcha struct {} func (ctr *Captcha) Send(c *gin.Context) { mobile := c.PostForm("mobile") // 校驗手機號邏輯 if mobile == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "手機號不能為空"}) return } matched, _ := regexp.MatchString(`^(1[3-9][0-9]\d{8})$`, mobile) if !matched { c.JSON(http.StatusBadRequest, gin.H{"error": "手機號格式不正確"}) return } c.JSON(http.StatusBadRequest, gin.H{"mobile": mobile}) } type User struct {} func (ctr *User) Login(c *gin.Context) { mobile := c.PostForm("mobile") code := c.PostForm("code") // 校驗手機號邏輯 if mobile == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "手機號不能為空"}) return } matched, _ := regexp.MatchString(`^(1[3-9][0-9]\d{8})$`, mobile) if !matched { c.JSON(http.StatusBadRequest, gin.H{"error": "手機號格式不正確"}) return } // 校驗手機號邏輯 if code == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "驗證碼不能為空"}) return } if len(code) != 4 { c.JSON(http.StatusBadRequest, gin.H{"error": "驗證碼為4位"}) return } c.JSON(http.StatusBadRequest, gin.H{"mobile": mobile, "code": code}) }
源碼鏈接
代碼分析:
參數驗證函數放在Controller層;
這是一種比較初級也是最樸素的實現方式,在現實代碼review中經常遇到,這樣實現會有什么問題?
1、手機號碼驗證邏輯重復;
2、違背了controller層的職責,controller層充斥著大量的驗證函數(Controller層職責:從HTTP請求中獲得信息,提取參數,并分發給不同的處理服務);
重復代碼是軟件質量下降的重大來源!!!
1、重復代碼會造成維護成本的成倍增加;
2、需求的變動導致需要修改重復代碼,如果遺漏某處重復的邏輯,就會產生bug(例如手機號碼增加12開頭的驗證規則);
3、重復代碼會導致項目代碼體積變得臃腫;
聰明的開發者肯定第一時間想到一個解決辦法:提取出驗證邏輯,工具包util實現IsMobile函數
package util func IsMobile(mobile string) bool { matched, _ := regexp.MatchString(`^(1[3-9][0-9]\d{8})$`, mobile) return matched } 代碼分析: 問題:代碼會大量出現util.IsMobile、util.IsEmail等校驗代碼
思考:從面向對象的思想出發,IsMobile屬于util的動作或行為嗎?
技術選型:web框架gin自帶的模型驗證器中文提示不是很好用,這里使用govalidator 模型綁定校驗是目前參數校驗最主流的驗證方式,每個編程語言的web框架基本都支持這種模式,模型綁定時將Http請求中的數據映射到模型對應的參數,參數可以是簡單類型,如整形,字符串等,也可以是復雜類型,如Json,Json數組,對各種數據類型進行驗證,然后拋出相應的錯誤信息。
源碼鏈接
package request func init() { validator.TagMap["IsMobile"] = func(value string) bool { return IsMobile(value) } } func IsMobile(value string) bool { matched, _ := regexp.MatchString(`^(1[1-9][0-9]\d{8})$`, value) return matched } type Captcha struct { Mobile string `form:"mobile" valid:"required~手機號不能為空,numeric~手機號碼應該為數字型,IsMobile~手機號碼格式錯誤"` } type User struct { Mobile string `form:"mobile" valid:"required~手機號不能為空,numeric~手機號碼應該為數字型,IsMobile~手機號碼格式錯誤"` Code string `form:"code" valid:"required~驗證碼不能為空,numeric~驗證碼應該為數字型"` } ------------------------------------------------------------------------------- package controller type Captcha struct {} func (ctr *Captcha) Send(c *gin.Context) { request := new(request.Captcha) if err := c.ShouldBind(request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if _, err := validator.ValidateStruct(request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusBadRequest, gin.H{"data": request}) } type User struct {} func (ctr *User) Login(c *gin.Context) { request := new(request.User) if err := c.ShouldBind(request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if _, err := validator.ValidateStruct(request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusBadRequest, gin.H{"data": request}) }
代碼分析:
1、mobile校驗邏輯同樣重復(注釋實現校驗的邏輯重復,如錯誤提示"手機號不能為空"修改為"請填寫手機號",需要修改兩個地方)
2、validator.ValidateStruct函數會驗證結構體所有屬性
對于2問題不太好理解,舉例解釋 業務場景:用戶注冊功能,需要校驗手機號、短信驗證碼、密碼、昵稱、生日 type User struct { Mobile string `form:"mobile" valid:"required~手機號不能為空,numeric~手機號碼應該為數字型,IsMobile~手機號碼格式錯誤"` Code string `form:"code" valid:"required~驗證碼不能為空,numeric~驗證碼應該為數字型"` Password string `form:"password" valid:"required~密碼不能為空,stringlength(6|18)~密碼6-18個字符"` Nickname string `form:"nickname" valid:"required~昵稱不能為空,stringlength(2|10)~昵稱2-10個字符"` Birthday time.Time `form:"birthday" valid:"required~生日不能為空" time_format:"2006-01-02"` }
代碼分析:
登陸功能需要校驗Mobile、Code屬性;
注冊功能需要校驗Mobile、Code、Password、Nickname、Birthday屬性;
如果代碼校驗共用User結構體,就產生了一個矛盾點,有兩種方法可以解決這一問題:
修改validator.ValidateStruct函數,增加校驗白名單或黑名單,實現可以設置部分屬性校驗或者忽略校驗部分屬性;
// 只做Mobile、Code屬性校驗或者忽略Mobile、Code屬性校驗 validator.ValidateStruct(user, "Mobile", "Code") 這種也是一種不錯的解決方式,但是在項目實踐中會遇到點小問題: 1、一個校驗結構體有20個屬性,只需要校驗其中10個字段,不管用白名單還是黑名單都需要傳10個字段; 2、手寫字段名容易出錯;
新建不同的結構體,對應相應的接口綁定校驗
type UserLogin struct { Mobile string `form:"mobile" valid:"required~手機號不能為空,numeric~手機號碼應該為數字型,IsMobile~手機號碼格式錯誤"` Code string `form:"code" valid:"required~驗證碼不能為空,numeric~驗證碼應該為數字型"` } type UserRegister struct { Mobile string `form:"mobile" valid:"required~手機號不能為空,numeric~手機號碼應該為數字型,IsMobile~手機號碼格式錯誤"` Code string `form:"code" valid:"required~驗證碼不能為空,numeric~驗證碼應該為數字型"` Password string `form:"password" valid:"required~密碼不能為空,stringlength(6|18)~密碼6-18個字符"` Nickname string `form:"nickname" valid:"required~昵稱不能為空,stringlength(2|10)~昵稱2-10個字符"` Birthday time.Time `form:"birthday" valid:"required~生日不能為空" time_format:"2006-01-02"` } 代碼解析: 用戶登陸接口對應:UserLogin結構體 用戶注冊接口對應:UserRegister結構體
同樣問題再次出現,Mobile、Code屬性校驗邏輯重復。
再介紹第三種參數校驗方式之前,先審視一下剛才的一段代碼:
if err := c.ShouldBind(&request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if _, err := validator.ValidateStruct(request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return }
參數綁定校驗的地方都需要出現這幾行代碼,我們可以修改gin源碼,把govalidator庫集成在gin中;
如何修改第三方庫源代碼參照項目 源碼鏈接
在gin根目錄增加context_validator.go文件,代碼如下: package gin import ( "github.com/asaskevich/govalidator" ) type Validator interface { Validate() error } func (c *Context) ShouldB(data interface{}) error { if err := c.ShouldBind(data); err != nil { return err } if _, err := govalidator.ValidateStruct(data); err != nil { return err } var v Validator var ok bool if v, ok = data.(Validator); !ok { return nil } return v.Validate() }
controller層的參數綁定校驗代碼如下:
type User struct {} func (ctr *User) Register(c *gin.Context) { request := new(request.UserRegister) if err := c.ShouldB(request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusBadRequest, gin.H{"data": request}) }
代碼分析:
增加了Validator接口,校驗模型實現Validator接口,可以完成更為復雜的多參數聯合校驗檢查邏輯,如檢查密碼和重復密碼是否相等
type UserRegister struct { Mobile string `form:"mobile" valid:"required~手機號不能為空,numeric~手機號碼應該為數字型,IsMobile~手機號碼格式錯誤"` Code string `form:"code" valid:"required~驗證碼不能為空,numeric~驗證碼應該為數字型"` Password string `form:"password" valid:"required~密碼不能為空,stringlength(6|18)~密碼6-18個字符"` RePassword string `form:"rePassword" valid:"required~重復密碼不能為空,stringlength(6|18)~重復密碼6-18個字符"` Nickname string `form:"nickname" valid:"required~昵稱不能為空,stringlength(2|10)~昵稱2-10個字符"` Birthday time.Time `form:"birthday" valid:"required~生日不能為空" time_format:"2006-01-02"` } func (req *UserRegister) Validate() error { if req.Password != req.RePassword { return errors.New("兩次密碼不一致") } return nil }
模型校驗是通過反射機制來實現,眾所周知反射的效率都不高,現在gin框架集成govalidator,gin原有的校驗功能就顯得多余,小伙伴們可以從ShouldBind函數從下追,把自帶的校驗功能屏蔽,提高框架效率。
解決字段校驗邏輯重復的最終方法就是拆解字段為獨立結構體,通過多個字段結構體的不同組合為所需的校驗結構體,代碼如下:
源碼鏈接
package captcha type CodeS struct { Code string `form:"code" valid:"required~驗證碼不能為空,numeric~驗證碼應該為數字型"` } package user type PasswordS struct { Password string `form:"password" valid:"required~密碼不能為空,stringlength(6|18)~密碼6-18個字符"` } type RePasswordS struct { RePassword string `form:"rePassword" valid:"required~重復密碼不能為空,stringlength(6|18)~重復密碼6-18個字符"` } type NicknameS struct { Nickname string `form:"nickname" valid:"required~昵稱不能為空,stringlength(2|10)~昵稱2-10個字符"` } type BirthdayS struct { Birthday time.Time `form:"birthday" valid:"required~生日不能為空" time_format:"2006-01-02"` } type UserLogin struct { MobileS captcha.CodeS } type UserRegister struct { MobileS captcha.CodeS user.PasswordS user.RePasswordS user.NicknameS user.BirthdayS } func (req *UserRegister) Validate() error { if req.Password() != req.RePassword() { return errors.New("兩次密碼不一致") } return nil } 代碼解析: 為什么字段結構體都加了S? 1、結構體包含匿名結構體不能調用匿名結構體同名屬性,匿名結構體加S標識為結構體 示例代碼不能很好的展示項目結構,可以查看源代碼
代碼分析:
獨立的字段結構體通常以表名為包名定義范圍,比如商品名稱和分類名稱字段名都為Name,但是所需定義的校驗邏輯(字符長度等)很有可能不同;
每一個接口建立對應的驗證結構體:
接口user/login: 對應請求結構體UserLogin 接口user/register: 對應請求結構體UserRegister 接口captcha/send: 對應請求結構體CaptchaSend
公用的字段結構體例如ID、Mobile建立單獨的文件;
總結:
一、驗證邏輯封裝在各自的實體中,由request層實體負責驗證邏輯,驗證邏輯不會散落在項目代碼的各個地方,當驗證邏輯改變時,找到對應的實體修改就可以了,這就是代碼的高內聚;
二、通過不同實體的嵌套組合就可以實現多樣的驗證需求,使得代碼的可重用性大大增強,這就是代碼的低耦合
獨立字段結構體組合成不同的校驗結構體,這種方式在實際項目開發中有很大的靈活性,可以滿足參數校驗比較多變復雜的需求場景,小伙伴可以在項目開發中慢慢體會。
源碼鏈接1、需要提交參數為json或json數組如何校驗綁定?
type ColumnCreateArticle struct { IDS article.TitleS } type ColumnCreate struct { column.TitleS Article *ColumnCreateArticle `form:"article"` Articles []ColumnCreateArticle `form:"articles"` }
2、嚴格遵循一個接口對應一個校驗結構體
func (ctr *Column) Detail(c *gin.Context) { request := new(request.IDS) if err := c.ShouldB(request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusBadRequest, gin.H{"data": request}) }
示例代碼獲取文章專欄詳情的接口,參數為專欄id,因為只有一個id參數,如果剛開始圖省事,沒有建立對應獨立的ColumnDetail校驗結構體,后期接口增加參數(例如來源等),還是要改動這一塊代碼,增加代碼的不確定性
3、布爾參數的三種狀態
type ColumnDetail struct { IDS // 為真顯示重點文章,為否顯示非重點文章,為nil都顯示 ArticleIsImportant *bool `form:"articleIsImportant"` } column?id=1&articleIsImportant=true ArticleIsImportant為true column?id=1&articleIsImportant=false ArticleIsImportant為false column?id=1 ArticleIsIm
“Go高效率開發Web參數校驗的方式有哪些”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。