API 版本管理策略:URL vs Header vs Content Negotiation

James Park | 2026-08-26T23:03:15 | Java, Spring Boot

对比 API 版本管理的三种主流方案,分析各自的优劣和适用场景,并给出 Spring Boot 实现示例。

# API 版本管理策略 ## 为什么需要版本管理? API 上线后,如果直接修改接口签名或响应结构,会导致已有客户端崩溃。版本管理让新旧版本并存,平滑过渡。 ## 方案一:URL Path 版本 ```java @RestController @RequestMapping("/api/v1/users") public class UserControllerV1 { @GetMapping("/{id}") public UserV1 getUser(@PathVariable Long id) { return userService.getUserV1(id); } } @RestController @RequestMapping("/api/v2/users") public class UserControllerV2 { @GetMapping("/{id}") public UserV2 getUser(@PathVariable Long id) { // V2 返回更丰富的数据 return userService.getUserV2(id); } } ``` **优点**:直观、缓存友好、方便测试 **缺点**:URL 膨胀、不符合 REST 语义(资源没变,只是表示变了) ## 方案二:Header 版本 ```java @RestController @RequestMapping("/api/users") public class UserController { @GetMapping(value = "/{id}", headers = "X-API-Version=1") public UserV1 getUserV1(@PathVariable Long id) { return userService.getUserV1(id); } @GetMapping(value = "/{id}", headers = "X-API-Version=2") public UserV2 getUserV2(@PathVariable Long id) { return userService.getUserV2(id); } } ``` 客户端请求: ``` GET /api/users/123 X-API-Version: 2 ``` **优点**:URL 干净、符合 REST **缺点**:不够直观、浏览器测试不便 ## 方案三:Content Negotiation ```java @GetMapping(value = "/{id}", produces = "application/vnd.myapp.v1+json") public UserV1 getUserV1(@PathVariable Long id) { return userService.getUserV1(id); } @GetMapping(value = "/{id}", produces = "application/vnd.myapp.v2+json") public UserV2 getUserV2(@PathVariable Long id) { return userService.getUserV2(id); } ``` 客户端请求: ``` GET /api/users/123 Accept: application/vnd.myapp.v2+json ``` **优点**:最符合 HTTP 标准 **缺点**:实现复杂、调试困难 ## 综合对比 | 维度 | URL Path | Header | Content Negotiation | |------|----------|--------|-------------------| | 直观性 | 高 | 中 | 低 | | REST 合规 | 低 | 中 | 高 | | 缓存友好 | 是 | 需配置 | 需配置 | | 浏览器测试 | 方便 | 不便 | 不便 | | 主流采用 | GitHub、Google | Stripe | 少见 | ## 推荐做法 1. **公开 API** -> URL Path 版本(直观,文档友好) 2. **内部微服务** -> Header 版本(简洁) 3. 无论哪种方案,都需要制定版本废弃策略(sunset policy) 4. 最多维护 2-3 个版本,老版本给出明确的废弃时间表

← Back to Blog