路由参数

路由参数是 URL 路径中的动态部分,比如 /users/123 中的 123。Gin 提供了方便的方法来获取这些参数。

路径参数

使用 :param 语法定义路径参数:

r.GET("/users/:id", func(c *gin.Context) {
    id := c.Param("id")
    c.JSON(200, gin.H{"user_id": id})
})

访问 /users/123id 的值就是 123

多个参数

r.GET("/users/:userId/posts/:postId", func(c *gin.Context) {
    userId := c.Param("userId")
    postId := c.Param("postId")
    c.JSON(200, gin.H{
        "user_id":  userId,
        "post_id":  postId,
    })
})

访问 /users/123/posts/456,会得到 userId=123postId=456

通配符参数

通配符 * 可以匹配剩余的所有路径:

r.GET("/files/*filepath", func(c *gin.Context) {
    filepath := c.Param("filepath")
    c.JSON(200, gin.H{"path": filepath})
})

访问 /files/a/b/c.txtfilepath 的值是 /a/b/c.txt

混合使用

r.GET("/users/:id/files/*filepath", func(c *gin.Context) {
    id := c.Param("id")
    filepath := c.Param("filepath")
    c.JSON(200, gin.H{
        "user_id":  id,
        "filepath": filepath,
    })
})

访问 /users/123/files/docs/report.pdf,会得到 id=123filepath=/docs/report.pdf

参数验证

获取参数后通常需要验证:

r.GET("/users/:id", func(c *gin.Context) {
    id := c.Param("id")

    if id == "" {
        c.JSON(400, gin.H{"error": "用户 ID 不能为空"})
        return
    }

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

数字验证

import "strconv"

r.GET("/users/:id", func(c *gin.Context) {
    idStr := c.Param("id")
    
    id, err := strconv.Atoi(idStr)
    if err != nil {
        c.JSON(400, gin.H{"error": "无效的用户 ID"})
        return
    }

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

获取所有参数

Params() 返回所有路径参数:

r.GET("/users/:userId/posts/:postId", func(c *gin.Context) {
    params := c.Params()
    
    for _, param := range params {
        fmt.Printf("%s = %s\n", param.Key, param.Value)
    }

    c.JSON(200, params)
})

完整示例

package main

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

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

    r.GET("/users/:id", getUser)
    r.GET("/users/:userId/posts/:postId", getUserPost)
    r.GET("/files/*filepath", getFile)
    r.GET("/search/:category/*query", search)

    r.Run(":8080")
}

func getUser(c *gin.Context) {
    idStr := c.Param("id")

    id, err := strconv.Atoi(idStr)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "用户 ID 必须是数字",
        })
        return
    }

    if id <= 0 {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "用户 ID 必须大于 0",
        })
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "id":   id,
        "name":  fmt.Sprintf("User %d", id),
    })
}

func getUserPost(c *gin.Context) {
    userId := c.Param("userId")
    postId := c.Param("postId")

    c.JSON(http.StatusOK, gin.H{
        "user_id":  userId,
        "post_id":  postId,
    })
}

func getFile(c *gin.Context) {
    filepath := c.Param("filepath")

    if filepath == "" {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "文件路径不能为空",
        })
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "filepath": filepath,
        "message": "文件内容",
    })
}

func search(c *gin.Context) {
    category := c.Param("category")
    query := c.Param("query")

    c.JSON(http.StatusOK, gin.H{
        "category": category,
        "query":    query,
        "results":  []string{"result 1", "result 2"},
    })
}

参数匹配优先级

当有多个路由可能匹配时,Gin 会选择最具体的:

r.GET("/users/new", func(c *gin.Context) {
    c.JSON(200, gin.H{"action": "create new user"})
})

r.GET("/users/:id", func(c *gin.Context) {
    id := c.Param("id")
    c.JSON(200, gin.H{"user_id": id})
})

访问 /users/new 会匹配第一个路由,而不是第二个。

小结

这一章学习了:

  • 使用 :param 定义路径参数
  • 使用 *param 定义通配符参数
  • 获取和验证参数
  • 参数匹配优先级