模型绑定

模型绑定是把请求参数自动映射到结构体的过程。不用再手写一堆解析代码了。

绑定方法

Gin 提供了两类绑定方法:

ShouldBind 系列

绑定失败返回错误,不会自动返回响应:

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

r.POST("/users", func(c *gin.Context) {
    var user User
    if err := c.ShouldBind(&user); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, user)
})

Bind 系列

绑定失败自动返回 400 响应:

r.POST("/users", func(c *gin.Context) {
    var user User
    if err := c.Bind(&user); err != nil {
        return
    }

    c.JSON(200, user)
})

推荐使用 ShouldBind 系列,可以自定义错误响应。

绑定来源

ShouldBind 会根据 Content-Type 自动选择绑定方式:

Content-Type绑定方法
application/jsonShouldBindJSON
application/xmlShouldBindXML
application/x-www-form-urlencodedShouldBind
multipart/form-dataShouldBind

显式绑定

可以显式指定绑定来源:

var user User

c.ShouldBindJSON(&user)
c.ShouldBindXML(&user)
c.ShouldBindQuery(&user)
c.ShouldBindUri(&user)
c.ShouldBindHeader(&user)

绑定标签

结构体标签告诉 Gin 如何映射参数:

type User struct {
    Name  string `json:"name" form:"name" xml:"name"`
    Email string `json:"email" form:"email" xml:"email"`
    Age   int    `json:"age" form:"age" xml:"age"`
}
标签用途
jsonJSON 绑定
form表单绑定
xmlXML 绑定
binding验证规则
uriURI 参数绑定

绑定查询参数

type SearchParams struct {
    Query string `form:"q"`
    Page  int    `form:"page"`
    Size  int    `form:"size"`
}

r.GET("/search", func(c *gin.Context) {
    var params SearchParams
    if err := c.ShouldBindQuery(&params); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, params)
})

访问 /search?q=golang&page=1&size=10,参数会自动绑定。

绑定路径参数

type UserParams struct {
    ID string `uri:"id" binding:"required"`
}

r.GET("/users/:id", func(c *gin.Context) {
    var params UserParams
    if err := c.ShouldBindUri(&params); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, gin.H{"user_id": params.ID})
})

绑定请求头

type Headers struct {
    Token    string `header:"Authorization"`
    Language string `header:"Accept-Language"`
}

r.GET("/info", func(c *gin.Context) {
    var headers Headers
    if err := c.ShouldBindHeader(&headers); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, headers)
})

完整示例

package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

type CreateUserRequest struct {
    Name  string `json:"name" form:"name" binding:"required"`
    Email string `json:"email" form:"email" binding:"required,email"`
    Age   int    `json:"age" form:"age" binding:"gte=0,lte=150"`
}

type GetUserParams struct {
    ID string `uri:"id" binding:"required"`
}

type SearchQuery struct {
    Query string `form:"q" binding:"required"`
    Page  int    `form:"page" binding:"gte=1"`
    Size  int    `form:"size" binding:"gte=1,lte=100"`
}

func main() {
    r := gin.Default()

    r.POST("/users", createUser)
    r.GET("/users/:id", getUser)
    r.GET("/search", search)

    r.Run(":8080")
}

func createUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBind(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, gin.H{
        "message": "用户创建成功",
        "user":    req,
    })
}

func getUser(c *gin.Context) {
    var params GetUserParams
    if err := c.ShouldBindUri(&params); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "id":   params.ID,
        "name": "User " + params.ID,
    })
}

func search(c *gin.Context) {
    var query SearchQuery
    if err := c.ShouldBindQuery(&query); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "query": query.Query,
        "page":  query.Page,
        "size":  query.Size,
    })
}

小结

这一章学习了:

  • ShouldBindBind 的区别
  • 各种绑定来源
  • 绑定标签的使用
  • 绑定查询参数、路径参数、请求头