跳转到内容

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 变更需改客户端
HeaderGET /api/users, X-API-Version: 1URL 稳定调试不便、缓存需带 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 标记,并在描述中写明替代接口与下线日期。

  1. 公告:在 OpenAPI 文档、变更日志中声明废弃,给出替代方案。
  2. 过渡期:至少 6 个月,期间接口仍可用但返回 Deprecation 头。
  3. 监测:通过日志统计仍在调用旧版本的调用方,主动通知。
  4. 下线:到达 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 内直接加,客户端忽略未知值