Api版本管理规范
URL 版本 / Header 版本策略、向后兼容
[Requirement] API 版本使用 v{主版本号}(如 v1、v2),仅在不兼容变更时递增主版本,不使用语义化版本的次版本/修订号。
[Standard] 版本号只增不减,已发布版本永不删除(但可标记为废弃)。
[Default] 版本号放在 URL 路径中:/api/v1/users,可读性最好、调试最方便。
[Suggestion] 对需要”同一资源多版本并存且网关透明路由”的场景,可使用 Header 版本:X-API-Version: 2026-01-01(日期型版本)。
| 策略 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| URL 路径(默认) | GET /api/v1/users | 直观、缓存友好、工具链支持好 | URL 变更需改客户端 |
| Header | GET /api/users, X-API-Version: 1 | URL 稳定 | 调试不便、缓存需带 Header |
| 查询参数 | GET /api/users?version=1 | 实现简单 | 不规范、易与业务参数冲突 |
[Caution] 禁止在同一系统内混用多种版本策略,选定后全局统一。
[Standard] 新版本发布后,旧版本至少保留 6 个月或 2 个大版本周期,确保调用方有足够迁移时间。
[Requirement] 同一时刻最多维护 2 个活跃版本(当前版本 + 上一版本),超出的强制废弃。
以下变更属于向后兼容,可在当前版本内直接发布:
| 变更类型 | 示例 |
|---|---|
| 新增可选的请求字段 | POST /users 新增可选 nickname |
| 新增响应字段 | 响应体新增 created_at |
| 新增接口/资源 | 新增 GET /users/{id}/orders |
| 新增枚举值 | status 新增 refunded(但客户端需能忽略未知值) |
| 放宽字段约束 | name 长度限制从 50 放宽到 100 |
[Standard] 新增响应字段时,调用方必须能忽略不认识的字段(客户端需做容错)。
以下变更属于不兼容,必须发布新版本:
| 变更类型 | 示例 |
|---|---|
| 删除字段/接口 | 移除响应中的 old_field |
| 修改字段类型 | amount 由 int 改为 string |
| 重命名字段 | user_name 改为 username |
| 收紧字段约束 | name 从可选改为必填 |
| 修改枚举语义 | status=3 含义改变 |
| 修改请求方法/路径 | GET /users 改为 POST /users/query |
| 修改成功状态码 | 200 改为 201 |
[Standard] 设计接口时预留扩展空间:响应使用对象而非数组、枚举值用字符串而非数字、避免在响应中暴露内部实现。
[Caution] 禁止在已有字段上”悄悄”改变语义(如 amount 从”分”改为”元”),必须新建字段。
[Requirement] 废弃接口必须在响应头中返回 Deprecation: true 和 Sunset: <RFC 7231 日期>,告知调用方下线时间。
HTTP/1.1 200 OK
Deprecation: true
Sunset: Wed, 01 Apr 2026 00:00:00 GMT
Link: </api/v2/users>; rel="successor-version"[Standard] OpenAPI 文档中使用 deprecated: true 标记,并在描述中写明替代接口与下线日期。
- 公告:在 OpenAPI 文档、变更日志中声明废弃,给出替代方案。
- 过渡期:至少 6 个月,期间接口仍可用但返回
Deprecation头。 - 监测:通过日志统计仍在调用旧版本的调用方,主动通知。
- 下线:到达
Sunset日期后,返回410 Gone,响应体说明替代接口。
[Requirement] 下线前必须确认无生产调用方(调用量为 0 持续 7 天),否则需延长过渡期。
HTTP/1.1 410 Gone
Content-Type: application/json
{
"code": 404010,
"message": "该接口已废弃,请使用 GET /api/v2/users",
"data": null,
"trace_id": "..."
}[Standard] 每次版本升级提供《迁移指南》,列出:变更清单、字段映射表、代码示例、常见问题。
[Standard] 多版本共存时,Controller 层按版本分发,Service 层尽量复用,仅在差异点做适配。
# 路由按版本分发
@router.post("/v1/users")
async def create_user_v1(req: UserCreateV1):
return await user_service.create(req.to_v2())
@router.post("/v2/users")
async def create_user_v2(req: UserCreateV2):
return await user_service.create(req)[Caution] 禁止在代码中通过 if version == "v1" 散布版本判断,应集中在适配层。
# FastAPI 示例:版本路由前缀
app.include_router(v1_router, prefix="/api/v1")
app.include_router(v2_router, prefix="/api/v2")# v1(旧版,即将废弃)
GET /api/v1/users/123
Deprecation: true
Sunset: Wed, 01 Apr 2026 00:00:00 GMT
{
"code": 0,
"data": { "id": 123, "user_name": "张三" }
}
# v2(新版)
GET /api/v2/users/123
{
"code": 0,
"data": { "id": 123, "username": "张三", "created_at": "2026-01-01T00:00:00Z" }
}v1 发布 ──► v2 发布 ──► v1 标记废弃 ──► v1 下线 │ │ │ │ └─ 长期维护 ─┘ └── 过渡期 ≥6 个月 ──┘| 场景 | 是否升版本 | 处理方式 |
|---|---|---|
POST /users 新增可选字段 age | 否 | v1 内直接加 |
GET /users 响应 name 改名为 username | 是 | 发布 v2,v1 保留 name |
amount 从分改为元 | 是 | 发布 v2,新增 amount_yuan 字段 |
新增 GET /users/{id}/orders 接口 | 否 | v1 内直接加 |
status 枚举新增 archived | 否 | v1 内直接加,客户端忽略未知值 |