URI 绑定

URI 绑定可以把路径参数直接绑定到结构体,代码更清晰。

基本用法

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})
})

访问 /users/123ID 的值是 123

多个路径参数

type PostParams struct {
    UserID string `uri:"user_id" binding:"required"`
    PostID string `uri:"post_id" binding:"required"`
}

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

    c.JSON(200, gin.H{
        "user_id": params.UserID,
        "post_id": params.PostID,
    })
})

访问 /users/123/posts/456,得到 user_id=123post_id=456

参数验证

import "strconv"

type UserParams struct {
    ID int `uri:"id" binding:"required,gt=0"`
}

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})
})

访问 /users/abc 会返回错误,因为 abc 无法转换为 int

通配符参数

type FileParams struct {
    Path string `uri:"path"`
}

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

    c.JSON(200, gin.H{"path": params.Path})
})

访问 /files/docs/report.pdfPath 的值是 /docs/report.pdf

组合绑定

URI 绑定可以和其他绑定组合使用:

type UpdateUserParams struct {
    ID   string `uri:"id" binding:"required"`
    Name string `json:"name" binding:"required"`
}

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

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

    c.JSON(200, gin.H{
        "id":   params.ID,
        "name": body.Name,
    })
})

完整示例

package main

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

type UserIDParams struct {
    ID int `uri:"id" binding:"required,gt=0"`
}

type PostIDParams struct {
    UserID int `uri:"user_id" binding:"required,gt=0"`
    PostID int `uri:"post_id" binding:"required,gt=0"`
}

type FilePathParams struct {
    Path string `uri:"path"`
}

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

    r.GET("/users/:id", getUser)
    r.GET("/users/:user_id/posts/:post_id", getPost)
    r.GET("/files/*path", getFile)

    r.Run(":8080")
}

func getUser(c *gin.Context) {
    var params UserIDParams
    if err := c.ShouldBindUri(&params); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户 ID"})
        return
    }

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

func getPost(c *gin.Context) {
    var params PostIDParams
    if err := c.ShouldBindUri(&params); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无效的参数"})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "user_id": params.UserID,
        "post_id": params.PostID,
        "title":   "Post Title",
    })
}

func getFile(c *gin.Context) {
    var params FilePathParams
    if err := c.ShouldBindUri(&params); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无效的文件路径"})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "path":    params.Path,
        "content": "文件内容",
    })
}

小结

这一章学习了:

  • URI 绑定的基本用法
  • 多个路径参数
  • 参数验证
  • 通配符参数
  • 组合绑定