revel 中应通过 url 路径(如 /api/v1/ 和 /api/v2/)区分 api 版本,即在 app/controllers 下按版本建子目录、在 conf/routes 中显式声明带前缀路由,并为 swagger 注释精确匹配路径;避免用请求头实现版本路由,因其破坏路由语义且难以维护;废弃旧版时须通过拦截器返回 410 gone 并设置 deprecation 和 link 响应头,明确传达退役信号。

Revel 中如何用 URL 路径区分 API 版本
Revel 本身不提供原生的 API 版本路由机制,但你可以直接复用其 routes 文件 + controllers 命名空间的约定来实现路径版本控制,效果等同于 Gin 的 r.Group("/v1")。关键不是改框架,而是组织好路由和控制器层级。
常见错误是把所有版本逻辑堆在同一个控制器里,靠 if/else 切换版本分支——这会导致测试难、维护乱、文档生成失效。
- 在
app/controllers下按版本建子目录:v1、v2,每个目录放对应版本的 Controller(如v1/UserController.go、v2/UserController.go) - 在
conf/routes中显式声明带前缀的路由:GET /api/v1/users v1.UserController.List GET /api/v2/users v2.UserController.List POST /api/v1/users v1.UserController.Create
- 确保两个版本的 Controller 不共享方法签名(比如
v1.UserController.Show(id int)和v2.UserController.Show(id string)可以共存,不会冲突)
如何让 Swagger 文档正确识别多版本接口
Revel 没有内置 Swagger 支持,但用 swaggo/swag 生成文档时,若不加干预,它会把所有控制器方法混在一起,无法体现版本隔离。问题根源在于注释扫描范围太宽,且没有自动绑定到对应路由前缀。
必须手动在每个版本控制器的方法上补全 @Router 注释,并严格匹配 routes 文件中的路径:
-
v1.UserController.List的注释里写:// @Router /api/v1/users [get] -
v2.UserController.List的注释里写:// @Router /api/v2/users [get] - 执行
swag init -g app/init.go前,确认所有版本控制器都已 import 到app/init.go的import _ "your-app/app/controllers/v1"和import _ "your-app/app/controllers/v2" - 生成后检查
docs/swagger.json中paths字段是否确实分出了/api/v1/users和/api/v2/users两个独立条目
为什么不要用请求头(Accept)做 Revel 的版本路由
理论上可行,但 Revel 的拦截器(interceptor)和过滤器(filter)链对请求头解析的支持较弱,不像 Gin 或 Spring Boot 那样有成熟的 Content-Type 解析中间件。硬要实现,你会陷入两个麻烦:
- 必须在每个需要版本判断的 Controller 方法开头手动解析
c.Request.Header.Get("Accept"),重复代码多,且容易漏掉; - Revel 的
routes是静态定义的,无法根据请求头动态切换 handler,最终还是得靠 if/else 分流,失去路由层面的语义清晰性; - Swagger 注释里的
@Accept字段只影响请求体格式说明,不参与路由生成,文档和实际行为会脱节。
除非你有强合规要求(如必须遵循 RFC 7231 的 vendor media type),否则在 Revel 里优先选路径版本——它更可控、调试直观、Nginx 日志可直接过滤、CDN 缓存策略也更容易配置。
废弃旧版本接口时最容易被忽略的一件事
很多团队只记得在代码里删掉 v1 的路由或返回 404,却忘了 HTTP 层面的契约信号。Revel 允许你在拦截器中统一注入响应头,这是提醒调用方升级的最轻量方式。
- 在
app/controllers/versioncheck.go中写一个 BEFORE 拦截器,检查请求路径是否含/v1/且当前已是 v2 主力期: - 匹配成功时调用
c.Response.Header().Set("Deprecation", "true")和c.Response.Header().Set("Link", "<https:>; rel=\"latest-version\"")</https:> - 同时返回 410 Gone(而非 404),明确表示该资源已被永久移除,客户端应停止调用
- 这个拦截器必须放在所有其他 BEFORE 拦截器之前,否则可能被后续逻辑覆盖响应头
真正起作用的不是代码删没删,而是下游系统是否收到了足够明确的退役信号。Header + 状态码组合,比文档公告或邮件更可靠。











