路由版本化
在现代 API 开发中,版本化是一个非常重要的话题。当你的 API 需要进行不兼容的更改时,版本化可以确保现有的客户端不会受到影响,同时允许新的客户端使用新的功能。
Midway 提供了灵活的路由版本化方案,支持多种版本化策略,让你可以轻松地管理 API 的不同版本。
支持的框架
路由版本化功能在以下框架中可用:
框架 | 支持状态 |
---|---|
@midwayjs/web-koa | ✅ |
@midwayjs/web | ✅ |
@midwayjs/web-express | ✅ |
Serverless | ❌ |
快速开始
启用版本化
首先,你需要 在框架配置中启用版本化:
// src/config/config.default.ts
export default {
// Koa 框架
koa: {
versioning: {
enabled: true,
type: 'URI', // 版本化类型
prefix: 'v', // 版本前缀
}
},
// Express 框架
express: {
versioning: {
enabled: true,
type: 'URI',
prefix: 'v',
}
},
// Web (Egg) 框架
egg: {
versioning: {
enabled: true,
type: 'URI',
prefix: 'v',
}
}
};
在控制器中使用版本
然后在控制器装饰器中指定版本:
import { Controller, Get, Post } from '@midwayjs/core';
@Controller('/users', {
version: '1', // 指定版本
description: 'Users API v1'
})
export class UsersV1Controller {
@Get('/')
async getUsers() {
return {
version: 'v1',
users: [
{ id: 1, name: 'John', email: '[email protected]' }
]
};
}
@Post('/')
async createUser(@Body() user: any) {
return {
version: 'v1',
user: { id: 2, ...user }
};
}
}
@Controller('/users', {
version: '2', // 新版本
description: 'Users API v2'
})
export class UsersV2Controller {
@Get('/')
async getUsers() {
return {
version: 'v2',
data: {
users: [
{ id: 1, name: 'John', email: '[email protected]', profile: { avatar: 'avatar.jpg' } }
],
meta: { total: 1, page: 1 }
}
};
}
@Post('/')
async createUser(@Body() user: any) {
return {
version: 'v2',
data: { user: { id: 2, ...user, profile: {} } },
meta: { created: new Date().toISOString() }
};
}
}
现在你可以通过不同的 URL 访问不同版本的 API:
# 访问 v1 版本
curl http://localhost:7001/v1/users
# 访问 v2 版本
curl http://localhost:7001/v2/users
版本化策略
Midway 支持多种版本化策略,你可以根据需要选择合适的方式。
URI 版本化(推荐)
这是最常见和直观的版本化方式,版本信息包含在 URL 路径中。
// 配置
export default {
koa: {
versioning: {
enabled: true,
type: 'URI',
prefix: 'v', // 可自定义前缀
}
}
};
# 请求示例
GET /v1/users
GET /v2/users
POST /v1/users
Header 版本化
通过 HTTP Header 指定版本信息。
// 配置
export default {
koa: {
versioning: {
enabled: true,
type: 'HEADER',
header: 'x-api-version', // 可自定义 header 名称
}
}
};
# 请求示例
curl -H "x-api-version: 1" http://localhost:7001/users
curl -H "x-api-version: 2" http://localhost:7001/users
Media Type 版本化
通过 Accept
header 的参数指定版本。
// 配置
export default {
koa: {
versioning: {
enabled: true,
type: 'MEDIA_TYPE',
mediaTypeParam: 'version', // 参数名称
}
}
};
# 请求示例
curl -H "Accept: application/json;version=1" http://localhost:7001/users
curl -H "Accept: application/json;version=2" http://localhost:7001/users
自定义版本化
你还可以提供自定义的版本提取函数:
// 配置
export default {
koa: {
versioning: {
enabled: true,
type: 'CUSTOM',
extractVersionFn: (ctx) => {
// 从查询参数中提取版本
return ctx.query.version;
// 或者从其他地方提取版本
// return ctx.headers['custom-version'];
}
}
}
};
# 请求示例
curl "http://localhost:7001/users?version=1"
curl "http://localhost:7001/users?version=2"
高级用法
多版本支持
一个控制器可以支持多个版本:
@Controller('/api', {
version: ['1', '2'], // 支持 v1 和 v2
})
export class ApiController {
@Get('/info')
async getInfo(ctx) {
// 可以通过 ctx.apiVersion 获取当前请求的版本
return {
version: ctx.apiVersion,
message: `This is API version ${ctx.apiVersion}`
};
}
}
默认版本
你可以设置默认版本,当没有指定版本时使用:
export default {
koa: {
versioning: {
enabled: true,
type: 'URI',
prefix: 'v',
defaultVersion: '1', // 默认版本
}
}
};
获取版本信息
在控制器中,你可以通过上下文获取当前请求的版本信息:
@Controller('/api')
export class ApiController {
@Get('/version')
async getVersion(ctx) {
return {
currentVersion: ctx.apiVersion, // 当前版本
originalPath: ctx.originalPath, // 原始路径(版本化之前)
};
}
}
配置选项
完整的配置选项说明:
interface VersioningConfig {
// 是否启用版本化
enabled: boolean;
// 版本化类型
type?: 'URI' | 'HEADER' | 'MEDIA_TYPE' | 'CUSTOM';
// 默认版本(当无法提取版本时使用)
defaultVersion?: string;
// URI 版本化的前缀(默认:'v')
prefix?: string;
// Header 版本化的 header 名称(默认:'x-api-version')
header?: string;
// Media Type 版本化的参数名称(默认:'version')
mediaTypeParam?: string;
// 自定义版本提取函数
extractVersionFn?: (ctx) => string | undefined;
}
最佳实践
版本命名
建议使用简单的数字版本号:
- ✅ 推荐:
'1'
,'2'
,'3'
- ❌ 不推荐:
'v1.0'
,'1.2.3'
,'latest'
版本策略选择
- URI 版本化:最直观,便于缓存,推荐用于公开 API
- Header 版本化:URL 保持简洁,适合内部 API
- Media Type 版本化:符合 REST 原则,但实现复杂
- 自定义版本化:最灵活,但需要额外的文档说明
向后兼容
在设计新版本时,尽量保持向后兼容:
// 好的版本化设计
@Controller('/users', { version: '1' })
export class UsersV1Controller {
@Get('/')
async getUsers() {
return { users: [] }; // 简单格式
}
}
@Controller('/users', { version: '2' })
export class UsersV2Controller {
@Get('/')
async getUsers() {
return {
data: { users: [] }, // 添加了 data 包装
meta: { total: 0 } // 添加了元信息
};
}
}
版本废弃
当需要废弃旧版本时,建议:
- 在响应头中添加废弃警告
- 提供迁移指南
- 设置合理的废弃时间表
@Controller('/users', { version: '1' })
export class UsersV1Controller {
@Get('/')
async getUsers(ctx) {
// 添加废弃警告
ctx.set('X-API-Deprecated', 'true');
ctx.set('X-API-Deprecation-Date', '2024-01-01');
ctx.set('X-API-Sunset-Date', '2024-06-01');
return { users: [] };
}
}
注意事项
- 版本前缀:URI 版本化会在路由前添加版本前缀,确保控制器的路由路径设计合理
- 中间件影响:版本化中间件会修改请求路径,可能影响其他依赖路径的中间件
- 性能考虑:版本化会增加路由匹配的复杂度,但影响通常很小
- 测试覆盖:确保为每个版本的 API 编写充分的测试
通过 Midway 的版本化功能,你可以优雅地管理 API 的演进,为用户提供稳定可靠的服务。