用URL路径做版本控制最稳妥,如/v1/users和/v2/users隔离handler;应避免query或header传版本号,推荐按版本分组路由、分包管理handler、禁止跨版本复用struct。
绝大多数 Go Web 框架(gin、echo、chi)都默认支持路径前缀路由,这是实现版本控制最直观、兼容性最好、也最容易调试的方式。客户端请求 /v1/users 和 /v2/users 时,后端可完全隔离两套 handler 逻辑,互不干扰。
常见错误是把版本号塞进 query 参数(如 /users?version=v2)或 header(如 Accept: application/vnd.myapi.v2+json),这会导致缓存失效、CDN 难以识别、日志聚合困难,且 OpenAPI 文档生成和测试都更麻烦。
实操建议:
router.Group("/v1") 和 router.Group("/v2")
handlers/v1 和 handlers/v2),避免交叉引用gin,注意 Use() 中间件作用域:全局中间件对所有版本生效;而 group.Use() 只影响该版本当需要对少量用户渐进式上线 v2 接口(比如只对 user_id % 100 的用户启用新逻辑),又不想改 URL,可以用 Accept 或自定义 header(如 X-API-Version: v2)做运行时路由分发。但这不是标准 RESTful 版本控制推荐做法,属于灰度/AB 测试范畴。
容易踩的坑:
Accept 值应遵循
application/vnd.{vendor}.{api}+json 格式,例如 application/vnd.example.users+json; version=2,但很多前端库(如 axios、fetch)默认不设此 header,调试时容易漏掉Content-Type 判断版本,它描述的是请求体格式,不是 API 语义版本/v1/users)和 header 版本,必须明确定义优先级,否则逻辑混乱func versionRouter(c *gin.Context) {
accept := c.GetHeader("Accept")
if strings.Contains(accept, "version=2") {
handleV2User(c)
return
}
handleV1User(c)
}
无论用路径还是 header 控制版本,都应在入口处强制校验:非法版本号(如 /v999/users 或 X-API-Version: v3)必须返回 400 Bad Request 或 406 Not Acceptable,而不是静默降级到 v1 —— 这会让客户端误以为调用成功,埋下兼容隐患。
关键点:
const V1 = "v1"),避免硬编码字符串散落在各处c.Set("api_version", "v2"),后续 handler 可安全读取,无需重复解析API 版本升级常伴随数据结构变更(如 v2 新增 status_reason 字段),但线上不可能立刻停掉 v1。此时必须保证:
最易被忽略的一点:日志和监控指标必须带上 api_version 标签。否则当 v2 出现 5xx 错误率飙升时,你无法快速判断是新逻辑问题,还是旧版本流量误打到了新 handler 上。