4. 路由参数:路径参数(:param)、通配符参数(*param)、查询字符串参数(Query)的获取与使用

在 Gin 路由设计中,参数传递是客户端与服务器交互的核心手段。Gin 提供了三种参数绑定方式:路径参数(Path Parameters)、通配符参数(Wildcard Parameters)和查询字符串参数(Query Parameters)。理解它们的获取方式、作用域和适用场景,是构建清晰 API 边界的基础。

4.1 三种参数类型对比

参数类型 路由定义示例 URL 示例 获取方式 适用场景
路径参数 :param /users/:id /users/42 c.Param("id") 资源标识、实体 ID
通配符参数 *param /files/*path /files/a/b/c.txt c.Param("path") 路径匹配、静态文件服务
查询字符串 ?key=value /search?q=go /search?q=go&page=1 c.Query("q") 过滤、分页、排序参数

4.2 路径参数(:param)—— 精确匹配资源

路径参数使用冒号 : 前缀定义,用于匹配 URL 路径中的单个片段。它不会匹配斜杠 /,因此适合定位单一资源。

核心规则:路径参数只匹配非空片段,且不包含斜杠。多个路径参数可以连续定义,如 /users/:uid/posts/:pid。
package main

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

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

    // 单个路径参数
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        c.JSON(http.StatusOK, gin.H{
            "user_id": id,
            "type":    "path_param",
        })
    })

    // 多个路径参数
    r.GET("/users/:uid/posts/:pid", func(c *gin.Context) {
        uid := c.Param("uid")
        pid := c.Param("pid")
        c.JSON(http.StatusOK, gin.H{
            "user_id": uid,
            "post_id": pid,
        })
    })

    r.Run(":8080")
}
最佳实践:路径参数应使用在 RESTful 资源路径中,如 /api/v1/products/:productId。避免在路径参数中包含特殊字符,如需传递编码后的值,应使用 URL 编码。

4.3 通配符参数(*param)—— 匹配剩余路径

通配符参数使用星号 * 前缀定义,它会匹配从当前位置到路径末尾的所有片段,包括斜杠。通配符参数必须定义在路由末尾,且一个路由中只能有一个通配符参数。

// 通配符参数示例
r.GET("/files/*filepath", func(c *gin.Context) {
    filepath := c.Param("filepath")
    // 请求 /files/notes/chapter1.txt → filepath = "/notes/chapter1.txt"
    // 请求 /files/              → filepath = "/"
    c.String(http.StatusOK, "Requested file: %s", filepath)
})

// 通配符参数与路径参数组合
r.GET("/projects/:project/assets/*assetPath", func(c *gin.Context) {
    project := c.Param("project")
    assetPath := c.Param("assetPath")
    c.JSON(http.StatusOK, gin.H{
        "project":    project,
        "asset_path": assetPath,
    })
})
注意:通配符参数会包含前导斜杠。例如 /files/notes/readme.md 匹配后,c.Param("filepath") 返回 /notes/readme.md(包含开头的 /)。处理文件路径时需注意拼接根目录。

4.4 查询字符串参数(Query)—— 过滤与分页

查询字符串参数通过 URL 中 ? 后的 key=value 对传递。Gin 提供了多种获取方式,支持默认值和数组参数。

// 基础查询参数获取
r.GET("/search", func(c *gin.Context) {
    // 获取单个参数,不存在返回空字符串
    query := c.Query("q")

    // 获取参数并设置默认值
    page := c.DefaultQuery("page", "1")
    size := c.DefaultQuery("size", "20")

    // 获取数组参数:?tags=go&tags=gin&tags=api
    tags := c.QueryArray("tags")

    // 获取 Map 参数:?filter[name]=john&filter[age]=30
    filter := c.QueryMap("filter")

    c.JSON(http.StatusOK, gin.H{
        "query":  query,
        "page":   page,
        "size":   size,
        "tags":   tags,
        "filter": filter,
    })
})
方法 签名 说明
Query c.Query(key string) string 获取参数值,不存在返回空字符串
DefaultQuery c.DefaultQuery(key, defaultValue string) string 获取参数值,不存在返回默认值
GetQuery c.GetQuery(key string) (string, bool) 获取参数值并判断是否存在
QueryArray c.QueryArray(key string) []string 获取重复 key 的数组值
QueryMap c.QueryMap(key string) map[string]string 获取嵌套 Map 参数

4.5 参数获取流程与边界

Gin 路由参数解析流程 HTTP 请求到达 路由树匹配(Radix Tree) 解析路由参数 路径参数 c.Param(":id") 通配符参数 c.Param("*path") 查询字符串参数 c.Query("key") 路由处理函数执行

4.6 参数验证与类型转换

获取到的参数默认都是字符串类型,在实际业务中通常需要转换为目标类型并进行验证。

r.GET("/products/:id", func(c *gin.Context) {
    // 1. 获取路径参数
    idStr := c.Param("id")

    // 2. 字符串转整数
    id, err := strconv.Atoi(idStr)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "invalid product id",
        })
        return
    }

    // 3. 获取查询参数并转换
    pageStr := c.DefaultQuery("page", "1")
    page, _ := strconv.Atoi(pageStr)

    // 4. 参数范围验证
    if page < 1 {
        page = 1
    }

    c.JSON(http.StatusOK, gin.H{
        "product_id": id,
        "page":       page,
    })
})
类型转换建议:使用 strconv 包进行基础类型转换。对于复杂验证逻辑,推荐使用 go-playground/validator 结合结构体绑定(将在后续章节详细讲解)。

4.7 参数优先级与冲突处理

当路径参数和查询参数同名时,Gin 不会自动覆盖或报错,需要开发者自行处理优先级逻辑。

r.GET("/items/:id", func(c *gin.Context) {
    // 路径参数优先
    id := c.Param("id")

    // 查询参数作为可选覆盖
    if queryID := c.Query("id"); queryID != "" {
        // 业务逻辑:是否允许查询参数覆盖路径参数
        id = queryID
    }

    c.JSON(http.StatusOK, gin.H{
        "final_id": id,
    })
})
场景 路由定义 请求 URL 获取结果 建议
路径参数唯一 /users/:id /users/100 id = "100" 标准 RESTful 设计
查询参数补充 /users/:id /users/100?fields=name,email id = "100", fields = "name,email" 路径参数标识资源,查询参数控制返回
参数同名冲突 /items/:id /items/100?id=200 c.Param("id") = "100", c.Query("id") = "200" 明确优先级规则,避免混淆

4.8 路由参数与中间件作用域

路由参数在中间件中同样可访问,这为参数级别的权限校验提供了基础。

// 中间件中获取路径参数
func UserAuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        userID := c.Param("uid")
        if userID == "" {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
                "error": "user id required",
            })
            return
        }
        // 将解析后的用户信息存入上下文
        c.Set("current_user_id", userID)
        c.Next()
    }
}

r.GET("/users/:uid/profile", UserAuthMiddleware(), func(c *gin.Context) {
    uid := c.GetString("current_user_id")
    c.JSON(http.StatusOK, gin.H{
        "user_id": uid,
        "profile": "user profile data",
    })
})
边界注意:通配符参数 *param 在中间件中获取时,如果路由尚未完全匹配,可能返回空字符串。确保在路由处理函数或其后执行的中间件中获取通配符参数。

4.9 参数获取的常见陷阱

  • 空字符串与不存在:c.Query("key") 在参数不存在时返回空字符串,无法区分“参数值为空”和“参数不存在”。使用 c.GetQuery("key") 可判断是否存在。
  • 路径参数编码:路径参数中的特殊字符(如 /、?)会被 URL 编码,Gin 自动解码。如需传递原始斜杠,应使用通配符参数。
  • 参数类型安全:所有参数均为字符串,类型转换失败时应返回 400 错误,而非 panic。
  • 路由冲突:/users/:id 和 /users/search 会冲突,因为 :id 会匹配 search。静态路由应定义在参数路由之前。

4.10 完整示例:综合参数处理

package main

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

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

    // 综合示例:文章列表 API
    r.GET("/api/v1/articles/:category/*subpath", func(c *gin.Context) {
        // 路径参数
        category := c.Param("category")

        // 通配符参数
        subpath := c.Param("subpath")

        // 查询参数
        keyword := c.Query("keyword")
        pageStr := c.DefaultQuery("page", "1")
        page, _ := strconv.Atoi(pageStr)
        tags := c.QueryArray("tag")

        // 参数验证
        if page < 1 {
            page = 1
        }

        c.JSON(http.StatusOK, gin.H{
            "category": category,
            "subpath":  subpath,
            "keyword":  keyword,
            "page":     page,
            "tags":     tags,
        })
    })

    r.Run(":8080")
}

// 测试请求:
// GET /api/v1/articles/tech/backend/go?keyword=gin&page=2&tag=web&tag=api
// 返回:
// {
//   "category": "tech",
//   "subpath": "/backend/go",
//   "keyword": "gin",
//   "page": 2,
//   "tags": ["web", "api"]
// }
设计原则总结:
  • 路径参数用于标识资源层级和唯一实体。
  • 通配符参数用于匹配动态路径片段,适合文件系统或嵌套资源。
  • 查询参数用于过滤、分页、排序等非资源标识的附加信息。
  • 三种参数类型可以组合使用,但需保持语义清晰,避免职责混淆。