路径前缀式版本路由(/v1、/v2)是唯一能兼顾调试性、缓存友好性与监控准确性的方案;必须禁用Accept头或query参数做主路由,严格隔离DTO结构体、中间件及路由实例,并通过显式转换确保API契约稳定。

路径前缀是唯一能落地的路由方式
别用 Accept 头或 version 查询参数做主路由——它们会让 CDN 缓存失效、Prometheus 指标混杂、Nginx 日志无法按版本聚合、Postman 测试总得反复确认 header 是否传对。真实线上故障里,70% 的版本问题源于 header 解析逻辑分散在多个中间件中,而 /v1/users 一眼就能从 access log 和 Grafana 中定位到流量归属。
必须统一用小写路径前缀:/v1、/v2,禁止混用 /api/v1 和 /v1/api——否则 chi.Mount() 会挂载失败,路由树结构直接乱掉。
分组注册时中间件和 handler 必须物理隔离
常见错误是把所有 handler 写在一个 router 里:r.Get("/v1/users", ...) 和 r.Get("/v2/users", ...) 并列注册。这会让路由树扁平化,失去分组能力,中间件也无法按版本启用。
- 每个版本调用
chi.NewRouter()或gin.Group()新建独立实例 - 用
r.Mount("/v1", v1Router)或v1 := r.Group("/v1")挂载,而非手动拼路径 -
v1.Use(authV1Middleware)和v2.Use(authV2Middleware)必须分开注册;v2 新增双因素校验,不能复用 v1 的中间件 - handler 函数名带版本标识:
v1GetUsersHandler、v2GetUsersHandler,避免误调用
DTO 结构体必须按版本完全隔离
共用一个 User struct 是最隐蔽的兼容性炸弹。Go 的 JSON 序列化没有运行时 schema 校验,v2 加了 Nickname string `json:"nickname"`,如果 v1 handler 不小心用了 User{} 返回,v1 客户端解析就会 panic。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
立即学习“go语言免费学习笔记(深入)”;
- 哪怕字段完全一样,也要定义
UserV1和UserV2两个 struct - 别用指针“模拟可选字段”来凑合老版本:
v1.Name是string就该是string;v2.Name是*string是它的契约,不能反向污染 - 数据库模型(如
GORMUserModel)保持稳定,只负责存储;所有 API 层响应都走显式转换:return c.JSON(200, UserV1{})
错误码语义和字段变更必须跨版本锁定
很多团队在 v2 加新校验后返回新 error code(比如 422 表示“邮箱格式过期”),但老客户端没处理,直接 crash。这不是功能问题,是契约破坏。
- HTTP status code 含义不能变:
404永远表示“资源不存在”,不能某天改成400 - 新增字段只能是 optional 或 repeated,且 Protobuf tag 编号不能复用旧字段
- Swagger 文档必须按版本生成独立
openapi.yaml,避免v1接口注释里引用v2字段导致文档错乱
最容易被忽略的是:路径前缀看似简单,但一旦允许嵌套路由(如 v1.Group("/admin").GET("/users", ...))或在 handler 里用 strings.HasPrefix(c.Request().URL.Path, "/v1") 手动判断版本,就等于放弃分组隔离的收益,让版本边界再次模糊。


















