必须用 r.group() 创建版本分组,因其支持路径前缀统一管理、中间件按组隔离、openapi文档准确生成、prometheus指标按版本聚合,且避免cdn缓存串、nginx代理失效及路由匹配混乱。

直接用 r.Group("/v1") 和 r.Group("/v2") 创建分组,是最简单、最不容易出错的方案。别在 handler 里写 if version == "v2" 分支,也别靠 ?version=v2 或 Accept 头做主路由——调试难、监控糊、CDN 缓存会串,线上踩坑概率高。
为什么必须用 r.Group() 而不是拼路径
硬编码 r.GET("/users/v1", handler) 看似省事,实际埋了三颗雷:
-
router.Walk()扫不到真实分组结构,OpenAPI 工具生成的文档里版本信息全乱,Prometheus metrics 也没法按/v1、/v2聚合 - 中间件没法按版本启停:比如 v2 需要新鉴权逻辑,你只能在每个 handler 里重复判断,没法只给
/v2加中间件 - Nginx 的
location /v1/规则会失效,因为实际路径是/users/v1,前缀对不上,反向代理配置一改就炸
/v1/users 还是 /api/v1/users?路径层级怎么定
版本段必须是路径第一级,且不能套娃。常见错误包括:
-
/api/v1/users—— 如果整个服务只跑 API,/api是冗余前缀;只有当你同时暴露/health、/metrics等非 API 路由时才需要 -
/v1/api/users或/users/v1—— 破坏 REST 资源语义:/users应该稳定,版本是访问维度,不是资源属性 -
/v1/users?version=v2—— 查询参数不参与路由匹配,中间件拦不住,CDN 会把 v1/v2 响应缓存成同一个 key
真正有效的结构只有两种:/v1/users(推荐)或 /v1/users/:id/orders(嵌套资源走子 Group,不是拼字符串)
handler 怎么复用又不混 DTO
v1 和 v2 行为一致但字段不同,硬拷贝 handler 代码会失控,共用一个 User struct 又会导致 JSON 序列化错乱。正确做法是:
- 每个版本定义独立 DTO:
UserV1、UserV2,即使字段名一样也分开声明 - handler 只调领域层获取原始数据,比如
userDomain := svc.GetUser(id) - 响应交由版本专属转换函数处理:
c.JSON(200, toResponseV1(userDomain))或c.JSON(200, toResponseV2(userDomain)) - 别用
json:"name,omitempty"模拟可选字段——v1 的Name是必填,就该是string,不是*string
中间件挂错位置是高频事故点
Gin 的中间件作用域严格绑定 Group,挂错地方就会漏掉或误加:
- 跨版本通用中间件(如日志、trace ID 注入)必须注册在根
r上,或显式传入每个 Group - 如果只把鉴权中间件挂到
v2 := r.Group("/v2")里,/v1就裸奔了 - 路由注册顺序影响匹配:如果
r.GET("/v1/:id", handler)写在v1 := r.Group("/v1")之前,它会截获所有/v1/xxx请求,导致v1.Group("/users")下的子路由完全失效
最稳妥的节奏是:先声明 Group,再在其内部注册子路由,中间件按需挂载——这个顺序一旦写反,404 和权限漏洞就一起来了。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











