路由分组与 API 版本化实践
结论先行
RouterGroup 提供“路径前缀 + 中间件”的组合单元:v1 := r.Group("/api/v1", mw1) 创建分组,组内再 v1.Use(mw2)、v1.GET(...),子组会继承父组的中间件与前缀。API 版本化最常用 URL 路径前缀(/api/v1、/api/v2),直观、便于网关分流与缓存隔离;老接口保留、新接口另起一组即可平滑演进。
分组规则速记
| 用法 | 生效范围 |
|---|---|
| engine.Use(mw) | 全局:后续注册的所有路由(含所有分组) |
| group.Use(mw) / Group(prefix, mw) | 仅本组及嵌套子组 |
| 分组前缀 | 自动拼接:api.Group("/v1") → /api/v1 |
| 组内路由 | 注册在组上,路径自动带前缀 |
全局中间件先执行,随后才是组中间件、路由内联 handler(洋葱顺序见第 04/05 章)。
版本化实践
- URL 前缀:
/api/v1/users,最简单、最常用,适合绝大多数团队。 - 内容协商/Header 版本:同一 URL 按 Accept/版本头分发,资源路径干净但网关与缓存配置复杂,较少用。
- 演进:v1 冻结,只修 bug;新行为开 v2 新组;两组可同时存在,各自注册函数便于阅读。
- 清理:废弃版本返回 410 或保留一个版本周期,配合文档通告。
代码示例
func setupRoutes(r *gin.Engine) {
api := r.Group("/api") // 公共前缀
{
v1 := api.Group("/v1", Auth("v1"))
v1.GET("/users", listUsers)
v1.POST("/users", createUser)
v2 := api.Group("/v2", Auth("v2"))
v2.GET("/users", listUsersV2) // 新行为,旧客户端仍走 v1
}
}
func Auth(_ string) gin.HandlerFunc {
return func(c *gin.Context) { /* 校验,失败 Abort */ c.Next() }
}
常见追问 / 记忆点
- 追问:组内中间件和全局中间件的顺序?答:全局在前、组内居中、路由 handler 最内;外层中间件 Next 之后的收尾最后执行。
- 追问:版本放 URL 还是 Header?答:多数场景 URL 前缀;追求“同一资源多版本由客户端协商”才考虑 Header/内容协商,代价是复杂度。
- 追问:多个版本代码怎么组织不膨胀?答:每个版本一个注册函数 + 独立的 handler 集合,公共逻辑抽 service 层。
- 记忆点:Group = 前缀 + 中间件 + 继承;版本化首选 /api/vN 前缀,冻结旧版、另起新版。