Golang GinWeb框架4-請求參數(shù)綁定和驗證
簡介
本文接著上文(Golang GinWeb框架3-自定義日志格式和輸出方式/啟禁日志顏色)繼續(xù)探索GinWeb框架

模型綁定和驗證
使用模型綁定來綁定請求體到一個Go類型上. 目前支持JSON,XML,YAML以及標準表單(如foo=bar&boo=baz)的綁定.
Gin使用go-playground/validator/v10包來驗證請求, 關(guān)于tags在驗證中使用詳見(https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags)
注意:綁定前請確認結(jié)構(gòu)體中需要綁定的字段標簽與綁定類型一致,比如綁定JSON,設(shè)置標簽: json:"fieldname"
Gin提供兩種方式(類型)來完成綁定:
Must bind
1. 方法: Bind, BindJSON, BindXML, BindQuery, BindYAML, BindHeader
2. 特點: 這些方法底層使用MustBindWith方法. 如果出現(xiàn)綁定錯誤, 請求將以狀態(tài)碼400返回失敗信息:c.AbortWithError(400, err).SetType(ErrorTypeBind), 響應(yīng)中設(shè)置Content-Type頭為text/plain; charset=utf-8.如果手動設(shè)置響應(yīng)碼,會警告響應(yīng)頭已經(jīng)設(shè)置,比如提示: [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422, 如果想要更好的控制這些行為,建議使用下面對應(yīng)的ShoudBind方法.
Should bind
1. 方法: ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML, ShouldBindHeader
2. 特點: 這些方法底層使用ShouldBindWith. 如果出現(xiàn)綁定錯誤, 會返回錯誤, 開發(fā)者可以控制和恰當?shù)奶幚磉@些錯誤.
當使用綁定方法時, Gin嘗試根據(jù)類型頭Content-Type header自動推斷要使用的綁定器. 如果你已經(jīng)確認需要綁定的類型,可以直接使用底層的MustBindWith或ShouldBindWith方法.
你也可以針對特殊的字段指定required標簽值, 如果某個字段指定了:binding:"required"標簽, 但是在綁定時該字段為空會返回錯誤.
如以下代碼綁定JSON:
- package main
- import (
- "github.com/gin-gonic/gin"
- "net/http"
- )
- // Binding from JSON
- type Login struct {
- User string `form:"user" json:"user" xml:"user" binding:"required"` //分別定義form,json,xml,binding等標簽
- //Password string `form:"password" json:"password" xml:"password" binding:"required"`
- Password string `form:"password" json:"password" xml:"password" binding:"-"`
- }
- func main() {
- router := gin.Default()
- // Example for binding JSON ({"user": "manu", "password": "123"})
- router.POST("/loginJSON", func(c *gin.Context) {
- var json Login
- if err := c.ShouldBindJSON(&json); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
- if json.User != "manu" || json.Password != "123" {
- c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
- return
- }
- c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
- })
- // Example for binding XML (
- // <?xml version="1.0" encoding="UTF-8"?>
- // <root>
- // <user>user</user>
- // <password>123</password>
- // </root>)
- router.POST("/loginXML", func(c *gin.Context) {
- var xml Login
- if err := c.ShouldBindXML(&xml); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
- if xml.User != "manu" || xml.Password != "123" {
- c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
- return
- }
- c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
- })
- // Example for binding a HTML form (user=manu&password=123)
- router.POST("/loginForm", func(c *gin.Context) {
- var form Login
- // This will infer what binder to use depending on the content-type header.
- if err := c.ShouldBind(&form); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
- if form.User != "manu" || form.Password != "123" {
- c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
- return
- }
- c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
- })
- // Listen and serve on 0.0.0.0:8080
- router.Run(":8080")
- }
- //模擬請求: curl -v -X POST http://localhost:8080/loginJSON -H 'content-type: application/json' -d '{ "user": "manu", "password": "123" }'
跳過驗證: 與binding:"required"標簽對應(yīng)的是binding:"-", 表示該字段不做綁定, 所以綁定時該字段為空不會報錯.
自定義驗證器
你也可以自己注冊一個自定義驗證器, 示例代碼請參考(https://github.com/gin-gonic/examples/blob/master/custom-validation/server.go)
- package main
- import (
- "net/http"
- "time"
- "github.com/gin-gonic/gin"
- "github.com/gin-gonic/gin/binding"
- "github.com/go-playground/validator/v10"
- )
- // Booking contains binded and validated data.
- // Booking結(jié)構(gòu)中定義了包含綁定器和日期驗證器標簽
- type Booking struct {
- CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` //登記時間
- CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` //gtfield=CheckIn表示結(jié)賬時間必須大于登記時間
- }
- // 定義日期驗證器
- var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
- date, ok := fl.Field().Interface().(time.Time) //利用反射獲取到字段值 -> 轉(zhuǎn)為接口 -> 類型斷言(時間類型)
- if ok {
- today := time.Now()
- if today.After(date) { //如果當前時間在checkIn字段時間之后,返回false,即登記時間不能早于當前的時間
- return false
- }
- }
- return true
- }
- func main() {
- route := gin.Default()
- //對binding.Validator.Engine()接口進行類型斷言,斷言類型為Validate結(jié)構(gòu),如果斷言成功,就將自定義的驗證器注冊到Gin內(nèi)部
- if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
- // - if the key already exists, the previous validation function will be replaced. 該注冊方法會將已經(jīng)存在的驗證器替換
- // - this method is not thread-safe it is intended that these all be registered prior to any validation
- // 注冊方法不是線程安全的, 在驗證開始前,需要保證所有的驗證器都注冊成功
- v.RegisterValidation("bookabledate", bookableDate)
- }
- route.GET("/bookable", getBookable)
- route.Run(":8085")
- }
- func getBookable(c *gin.Context) {
- var b Booking
- if err := c.ShouldBindWith(&b, binding.Query); err == nil {
- c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
- } else {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- }
- }
- //模擬請求:
- // 登記時間和結(jié)賬時間符合條件
- //$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17"
- //{"message":"Booking dates are valid!"}
- //
- // 登記時間在結(jié)賬時間之后, 不滿足gtfield校驗規(guī)則
- //$ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09"
- //{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
- //
- // 登記時間在當前時間之前,不滿足自定義的驗證器
- //$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10"
- //{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}%
另外結(jié)構(gòu)體級別的驗證采用如下的方式注冊, v.RegisterStructValidation(UserStructLevelValidation, User{}), 請參考struct-lvl-validation example(https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations)
參考文檔
Gin官方倉庫:https://github.com/gin-gonic/gin