2.1 路由核心名词
- 路由(Route)
- 「HTTP 方法 + 路径模式」到处理函数的一条映射,如
GET /users/:id → getUser。 - 路径参数(Path Param)
- 用
:name声明的动态段,匹配单个路径片段,通过c.Param("name")读取。 - 通配参数(Wildcard / Catch-all)
- 用
*name声明,匹配其后所有路径(含斜杠),只能放在路由末尾,常用于静态文件与代理。 - 查询参数(Query Param)
- URL 问号之后的键值对,如
?page=2&size=10,用c.Query("page")读取。 - 路由分组(RouterGroup)
- 共享路径前缀和中间件的一组路由,用
r.Group("/api/v1")创建,可嵌套。 - 静态文件服务
- 把某个磁盘目录映射到 URL 前缀,让 Gin 直接返回文件内容(图片、CSS、下载)。
- Any / NoRoute
Any注册全部方法;NoRoute处理未匹配的 404 请求。
2.2 HTTP 方法注册
r := gin.Default()
r.GET("/users", listUsers) // 查询列表
r.POST("/users", createUser) // 创建
r.GET("/users/:id", getUser) // 详情
r.PUT("/users/:id", updateUser) // 全量更新
r.PATCH("/users/:id", patchUser) // 部分更新
r.DELETE("/users/:id", delUser) // 删除
// 一次注册多个方法
r.Any("/health", healthCheck)
r.Match([]string{"GET", "POST"}, "/form", handleForm)
2.3 路径参数 :id
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id") // string
c.JSON(200, gin.H{"user_id": id})
})
// 多个参数
r.GET("/users/:id/posts/:pid", func(c *gin.Context) {
c.JSON(200, gin.H{
"user": c.Param("id"),
"post": c.Param("pid"),
})
})
参数是 string
c.Param 永远返回 string。要转 int 请用 strconv.Atoi(id) 并处理错误;第 3 章会介绍用 ShouldBindUri 直接绑定到结构体字段并校验。
2.4 通配参数 *action
// 匹配 /files/ 之后的任意路径(含斜杠)
r.GET("/files/*filepath", func(c *gin.Context) {
// 请求 /files/img/a/b.png -> "/img/a/b.png"
fp := c.Param("filepath")
c.String(200, "requested: %s", fp)
})
通配段以 / 开头返回,且只能出现在路径末尾。同一前缀下 :param 与 *wildcard 不能冲突(radix 树会在启动时 panic 报冲突)。
2.5 查询参数与默认值
r.GET("/search", func(c *gin.Context) {
q := c.Query("q") // 不存在返回 ""
page := c.DefaultQuery("page", "1") // 带默认值
size, ok := c.GetQuery("size") // ok 判断是否存在
// 数组:?tag=go&tag=web
tags := c.QueryArray("tag") // []string{"go","web"}
// map:?filter[name]=alice&filter[age]=20
filters := c.QueryMap("filter")
c.JSON(200, gin.H{"q": q, "page": page, "size": size, "hasSize": ok, "tags": tags, "filters": filters})
})
2.6 路由分组 Group
分组让相同前缀 + 相同中间件的路由聚在一起,是组织大型 API 的关键手段:
r := gin.Default()
// v1 版本 API
v1 := r.Group("/api/v1")
{
v1.GET("/users", listUsers) // -> /api/v1/users
v1.POST("/users", createUser)
// 嵌套分组 + 局部中间件
admin := v1.Group("/admin", AuthRequired())
{
admin.DELETE("/users/:id", delUser) // -> /api/v1/admin/users/:id
}
}
// v2 独立演进
v2 := r.Group("/api/v2")
v2.GET("/users", listUsersV2)
花括号是习惯写法
分组后的 { ... } 只是 Go 的代码块,用来视觉上圈定「这些路由属于该组」,没有语法必要性,但强烈推荐——大型项目里一眼就能看清层级。
2.7 静态文件与模板
// 把 ./public 目录映射到 /static
r.Static("/static", "./public")
// 单个文件
r.StaticFile("/favicon.ico", "./resources/favicon.ico")
// 用 embed 打包进二进制(Go 1.16+)
// r.StaticFS("/assets", http.FS(embeddedFiles))
// HTML 模板渲染
r.LoadHTMLGlob("templates/*")
r.GET("/", func(c *gin.Context) {
c.HTML(200, "index.tmpl", gin.H{"title": "古法编程"})
})
2.8 404 与方法不允许
r.NoRoute(func(c *gin.Context) {
c.JSON(404, gin.H{"error": "resource not found"})
})
// 开启 405 支持后可捕获方法不匹配
r.HandleMethodNotAllowed = true
r.NoMethod(func(c *gin.Context) {
c.JSON(405, gin.H{"error": "method not allowed"})
})
2.9 路由设计小结
- 用 RESTful 风格:资源用名词复数,动作用 HTTP 方法表达。
- 版本放前缀
/api/v1,用 Group 隔离,方便平滑升级。 - 路径参数取出即 string,需要类型/校验时优先绑定到结构体(第 3 章)。
- 通配
*只能收尾;静态资源用Static或 embed 打包。 - 下一章进入 请求处理与绑定——把 JSON/Query/表单一键映射到结构体并校验。