:param 与 *path 的区别与使用注意
结论先行
:name 匹配单个路径段(不含 /);*name(catch-all)匹配剩余的全部路径(含 /),且必须是路由的最后一段。取值统一用 c.Param("name"),catch-all 拿到的值会保留前导斜杠。
对比表
| 维度 | :param(参数段) | *path(通配段/catch-all) |
|---|---|---|
| 匹配范围 | 一个段,不含 / | 剩余全部内容,含 / |
| 示例 | /user/:id | /files/*filepath |
| 请求 /user/123 | id=“123” | 不适用 |
| 请求 /files/a/b/c | 不适用 | filepath=“/a/b/c” |
| 约束 | 段名在同一位置唯一;不与冲突模式共存 | 必须最后一段;同层其他子路由受限 |
| 典型用途 | 资源 ID、slug | 静态文件路径、前端路由回退 |
使用注意
- 路径里出现
/必须用*path,:param永远截断在下一个/。 *filepath的值带前导/,做磁盘路径前常用 strings.TrimPrefix 去掉。- 路由注册顺序无关紧要,匹配靠树结构而不是“先注册先匹配”。
- Gin 默认开启尾斜杠自动重定向(RedirectTrailingSlash)与固定路径纠正(RedirectFixedPath 默认关),设计 API 时尽量别依赖 301 纠正。
代码示例
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id") // "/users/123" -> "123"
c.String(200, id)
})
r.GET("/files/*filepath", func(c *gin.Context) {
p := c.Param("filepath") // "/files/a/b.txt" -> "/a/b.txt"
name := strings.TrimPrefix(p, "/")
c.String(200, name)
})
常见追问 / 记忆点
- 追问:/user/:id 和 /user/new 会不会冲突?答:不会冲突且静态段优先命中(/user/new 走 new 的 handler);只有同位置通配符互相冲突才 panic,细节以官方文档为准。
- 追问:什么时候用 *path?答:需要吞掉“任意多段”时,例如文件下载、SPA history 路由回退(配合 NoRoute 也可)。
- 记忆点:
:一段,*到底;*必须垫底,取值带前导斜杠。