项目文件夹

文件
2026-07-13 21:36:01 +08:00

394 行
7.6 KiB
Markdown

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
---
# API 版本控制策略
## 为什么要对 API 进行版本控制?
API 版本控制允许你在演进 API 的同时,保持对现有客户端的向后兼容性。破坏性变更需要发布新版本。
### 破坏性变更
需要发布新版本的变更:
- 删除或重命名字段
- 更改字段类型(如字符串改为整数)
- 在请求中新增必填字段
- 更改响应结构
- 删除端点
- 更改同一场景下的 HTTP 状态码
- 更改认证机制
### 非破坏性变更
不需要发布新版本的安全变更:
- 新增端点
- 新增可选请求字段
- 在响应中新增字段(客户端应忽略未知字段)
- 修复 Bug
- 性能优化
- 为已有资源新增 HTTP 方法
## 版本控制策略
### 1. URI 版本控制
最常见且最直观的方式。版本号作为 URL 路径的一部分。
```http
GET /v1/users/123
GET /v2/users/123
```
**优点:**
- 在 URL 中清晰可见
- 易于理解与实现
- 路由和缓存简单
- 可同时运行多个版本
**缺点:**
- 违反 REST 原则(同一资源对应不同 URI)
- 修改版本号需要更新客户端代码
- 可能导致 URI 泛滥
**实现方式:**
```
/v1/users
/v1/products
/v2/users # 包含破坏性变更的新版本
/v2/products
```
### 2. 标头版本控制
版本号通过 HTTP 标头(Accept 标头或自定义标头)指定。
**Accept 标头:**
```http
GET /users/123
Accept: application/vnd.myapi.v1+json
GET /users/123
Accept: application/vnd.myapi.v2+json
```
**自定义标头:**
```http
GET /users/123
API-Version: 1
GET /users/123
API-Version: 2
```
**优点:**
- URI 保持稳定
- 更符合 REST 风格(同一资源对应同一 URI)
- 将版本控制与资源标识分离
**缺点:**
- 不够直观(调试难度更大)
- 路由更复杂
- 在浏览器中测试困难
- 缓存更复杂
### 3. 查询参数版本控制
版本号通过查询参数指定。
```http
GET /users/123?version=1
GET /users/123?version=2
# 或
GET /users/123?api-version=1
GET /users/123?api-version=2
```
**优点:**
- 实现简单
- 易于测试
- 在 URL 中可见
**缺点:**
- 污染查询字符串
- 不够语义化(版本不是筛选条件)
- 可能与其他查询参数冲突
### 4. 内容协商
客户端通过内容协商指定所需的版本。
```http
GET /users/123
Accept: application/vnd.myapi+json; version=1
GET /users/123
Accept: application/vnd.myapi+json; version=2
```
**优点:**
- 非常符合 REST 风格
- 灵活的内容类型协商
- URI 稳定
**缺点:**
- 实现复杂
- 对开发者不够直观
- 测试难度更大
## 推荐方案
**推荐大多数 API 使用 URI 版本控制**,原因如下:
- 最明确、最易于发现
- 易于理解和调试
- 实现和维护简单
- 版本之间界限清晰
```
/v1/users
/v2/users
/v3/users
```
## 版本格式
### 仅使用主版本号
公开 API 使用简单的主版本号(v1、v2、v3):
```
/v1/users
/v2/users
```
**优点:**
- 简单明了
- 易于沟通
- 促使开发者慎重考虑破坏性变更
### 基于日期的版本号
部分 API 使用日期作为版本号:
```
/2024-01-01/users
/2024-06-15/users
```
**使用者:** Stripe、GitHub API
**优点:**
- 版本发布时间一目了然
- 时间线清晰易懂
- 不会混淆主版本号与次版本号
**缺点:**
- 对客户端不够直观
- 难以了解具体变更内容
## 版本生命周期
### 1. 引入阶段
新版本与旧版本同时发布:
```
/v1/users # 仍受支持
/v2/users # 新版本可用
```
发布新版本时需公告:
- 解释变更的博客文章
- 迁移指南
- 破坏性变更列表
- v1 版本的弃用时间表
### 2. 弃用阶段
将旧版本标记为已弃用,但保持其继续运行:
```http
GET /v1/users/123
响应:
Deprecation: true
Sunset: Wed, 15 Jan 2025 00:00:00 GMT
Link: </v2/users/123>; rel="successor-version"
{
"id": 123,
"name": "John Doe"
}
```
**弃用标头:**
- `Deprecation: true` — 表示该版本已弃用
- `Sunset: <date>` — 该版本将于何时移除(RFC 8594)
- `Link: <url>; rel="successor-version"` — 指向新版本
### 3. 退役阶段
旧版本在公布的日期正式下线。
对已弃用的端点返回 410 Gone:
```http
GET /v1/users/123
响应:410 Gone
{
"error": {
"code": "VERSION_SUNSET",
"message": "API v1 已于 2025-01-15 退役。请使用 v2。",
"documentation_url": "https://api.example.com/docs/migration-v1-to-v2"
}
}
```
## 弃用策略
### 推荐时间线
1. **宣布弃用** — 至少在退役前 6 个月
2. **支持期** — 两个版本同时运行 6-12 个月
3. **退役日期** — 提前明确告知具体日期
4. **宽限期** — 在完全关闭前,提供 30 天的 410 Gone 响应
### 沟通渠道
- API 响应标头
- 发送给注册开发者的邮件
- 博客文章和更新日志
- 控制台通知
- 文档更新
- 状态页面公告
## 迁移策略
### 提供迁移指南
```markdown
# 从 v1 迁移至 v2
## 破坏性变更
### 用户资源变更
**v1**
```json
{
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}
```
**v2**
```json
{
"id": 123,
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com"
}
```
**迁移步骤:**
-`name` 字段拆分为 `first_name``last_name`
- 更新客户端代码以使用新字段
```
### 提供辅助工具
- 迁移脚本
- SDK 更新
- API 差异对比工具
- 兼容层(临时)
## 版本发现
### 根端点
```http
GET /
响应:
{
"versions": {
"v1": {
"status": "deprecated",
"sunset_date": "2025-01-15",
"documentation_url": "https://api.example.com/docs/v1"
},
"v2": {
"status": "current",
"documentation_url": "https://api.example.com/docs/v2"
},
"v3": {
"status": "beta",
"documentation_url": "https://api.example.com/docs/v3"
}
}
}
```
### 版本信息端点
```http
GET /v2/version
响应:
{
"version": "v2",
"released": "2024-01-15",
"status": "stable",
"sunset_date": null
}
```
## OpenAPI 版本控制
### 每个版本独立规范文件
```
openapi-v1.yaml
openapi-v2.yaml
openapi-v3.yaml
```
每个规范文件都是完整且独立的。
### 单一规范多服务器
```yaml
openapi: 3.1.0
info:
title: My API
version: 2.0.0
servers:
- url: https://api.example.com/v1
description: 版本 1(已弃用)
- url: https://api.example.com/v2
description: 版本 2(当前版本)
```
## 最佳实践
1. **从第一天起就做版本控制** — 从 /v1 开始,而不是 /api
2. **仅使用主版本号** — 使用 v1、v2、v3(而不是 v1.1、v1.2
3. **提供较长的弃用期** — 给客户端留出迁移时间(6-12 个月)
4. **沟通要清晰** — 使用标头、文档、邮件
5. **维护旧版本** — 至少同时支持两个版本
6. **记录变更** — 提供详细的迁移指南
7. **使用语义化版本控制** — 用于内部/SDK 版本控制
8. **永远不要在没有警告的情况下破坏兼容性** — 始终提前公告破坏性变更
9. **提供辅助工具** — 迁移脚本、更新后的 SDK
10. **监控使用情况** — 追踪哪些版本正在被使用
## 反模式
避免以下错误:
- **破坏性变更但不升级版本** — 破坏现有客户端
- **版本过多** — 维护噩梦(最多保持 2-3 个活跃版本)
- **弃用期过短** — 让开发者感到困扰
- **没有迁移路径** — 让升级变得痛苦
- **突然退役** — 毫无预警地破坏生产应用
- **版本控制策略不一致** — 不同端点使用不同策略
- **对单个端点进行版本控制** — 应在整个 API 中使用一致的版本