绑定多个参数

实际开发中,一个请求可能包含多种参数:路径参数、查询参数、请求体。Gin 支持同时绑定多个来源的参数。

分开绑定

最简单的方式是分别绑定:

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

type UserQuery struct {
    Fields string `form:"fields"`
}

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

r.PUT("/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
    }

    var query UserQuery
    if err := c.ShouldBindQuery(&query); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    var body UserBody
    if err := c.ShouldBindJSON(&body); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, gin.H{
        "id":     params.ID,
        "fields": query.Fields,
        "name":   body.Name,
        "email":  body.Email,
    })
})

合并结构体

可以把多个来源的参数合并到一个结构体:

type UpdateUserRequest struct {
    ID     string `uri:"id" binding:"required"`
    Fields string `form:"fields"`
    Name   string `json:"name"`
    Email  string `json:"email"`
}

r.PUT("/users/:id", func(c *gin.Context) {
    var req UpdateUserRequest

    if err := c.ShouldBindUri(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    if err := c.ShouldBindQuery(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, req)
})

绑定请求头

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

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

    c.JSON(200, gin.H{
        "token":    req.Token,
        "language": req.Language,
    })
})

完整请求绑定

package main

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

type FullRequest struct {
    ID       string `uri:"id" binding:"required"`
    Version  string `form:"version"`
    Token    string `header:"Authorization" binding:"required"`
    Name     string `json:"name"`
    Email    string `json:"email"`
}

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

    r.PUT("/users/:id", updateUser)

    r.Run(":8080")
}

func updateUser(c *gin.Context) {
    var req FullRequest

    if err := c.ShouldBindUri(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
        return
    }

    if err := c.ShouldBindQuery(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无效的查询参数"})
        return
    }

    if err := c.ShouldBindHeader(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "缺少认证信息"})
        return
    }

    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求体"})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "id":      req.ID,
        "version": req.Version,
        "token":   req.Token,
        "name":    req.Name,
        "email":   req.Email,
    })
}

绑定辅助函数

可以封装一个辅助函数简化绑定:

func bindAll(c *gin.Context, req interface{}) error {
    if err := c.ShouldBindUri(req); err != nil {
        return err
    }
    if err := c.ShouldBindQuery(req); err != nil {
        return err
    }
    if err := c.ShouldBindHeader(req); err != nil {
        return err
    }
    if err := c.ShouldBindJSON(req); err != nil {
        return err
    }
    return nil
}

r.PUT("/users/:id", func(c *gin.Context) {
    var req FullRequest
    if err := bindAll(c, &req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, req)
})

小结

这一章学习了:

  • 分开绑定多个参数来源
  • 合并结构体绑定
  • 绑定请求头
  • 封装绑定辅助函数