路径参数::name 与 *path 通配
本章解决什么问题:/users/42 这类“同一条路由、动态取值”是 REST 服务的核心需求。本章讲 :name 单段参数、*path 通配参数与 c.Param 取值,以及路由冲突与优先级规则。
冒号参数 :name
用 :name 声明路径中的占位段,它只匹配一个非空路径段(不含 /)。处理函数用 c.Param("name") 取回字符串:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{"id": id})
})
r.GET("/users/:id/orders/:oid", func(c *gin.Context) {
c.JSON(200, gin.H{
"id": c.Param("id"),
"oid": c.Param("oid"),
})
})
r.GET("/files/*path", func(c *gin.Context) {
// *path 会包含开头的斜杠,例如 /files/a/b.txt 得到 "/a/b.txt"
c.JSON(200, gin.H{"path": c.Param("path")})
})
r.Run(":8080")
}
第二段路由演示同一条路径可以有多个冒号参数,只要所处段位不同即可。
通配参数 *path
以 * 开头的段是“抓取剩余一切”的通配符:必须放在路径最后,且匹配结果包含起始的斜杠。典型用途是静态文件、下载、前端路由回退这类“前缀固定、后面任意”的场景。
与 :name 的区别::name 至少要有一个字符,*path 可能匹配到空内容或只有斜杠的片段,取值后通常需要再处理。
取值与类型转换
- c.Param 永远返回字符串,且只来自 URL 路径本身,与查询串无关;
- 需要数值时自己用 strconv 转换,或直接做结构体绑定:字段 tag 写 binding:"uri",用 c.ShouldBindUri(&s),路径参数会按名填入字段(绑定体系在第 7 章统一展开)。
冲突与优先级
Gin 的路由是 radix tree,非法注册会在启动期直接 panic,常见情形:
- /users/new 与 /users/:name:静态段优先级更高,两者可以共存;
- /users/:name 与 /users/:id:同一位置的参数名不同,冲突 panic;
- /users/:id 与 /users/:id/orders:参数名一致且后续结构可延伸,可以共存。
一句话规则:同深度下静态节点优先于参数节点,参数节点之间靠“同名 + 不同后继结构”区分。
关键点
- :name 匹配单个非空段;*path 抓取剩余全部并带前导斜杠,只能出现在末尾。
- 路径参数与查询参数是两回事:前者在路径里,后者在 ? 后面的键值对里。
- 路由冲突属于“启动即失败”的保护机制,应在开发期暴露,而不是靠 catch 绕过。
小结
动态路径段让少量路由覆盖大量资源::name 负责单段取值,*path 负责前缀通配。下一章看 URL 中另一类参数——Query 与表单参数。