路由分组与 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 前缀,冻结旧版、另起新版。
笔记加载中…