用路由前缀区分版本最稳妥,如/api/v1/users和/api/v2/users;适配器应仅负责显式字段映射,不处理校验与权限;废弃版本需冻结adapter并添加弃用响应头,而非直接删除。

用路由前缀区分版本比在代码里判断更可靠
直接在 $_GET 或请求头里解析 version 参数,容易漏掉边界情况(比如没传、传错格式、缓存污染),也难做统一鉴权和日志归类。实际项目中,把版本号固化进 URL 路径最稳妥,例如 /api/v1/users 和 /api/v2/users。Nginx 或 PHP 路由器能天然隔离,后续加中间件、限流、监控都基于路径做,不会跨版本错乱。
不推荐用 Accept: application/vnd.myapp.v2+json 这类内容协商方式——调试困难,前端发请求时容易忘记设 header,而且多数网关、CDN、日志系统不识别自定义 MIME 类型。
适配器模式真正起作用的地方不在路由层,而在控制器之后、业务逻辑之前:
- 每个版本的 Controller 只负责接收请求、调用对应版本的
UserServiceAdapter - Adapter 封装了字段映射、参数转换、默认值填充等逻辑,不碰数据库或领域模型
- 核心服务(如
UserRepository)保持稳定,v1 和 v2 Adapter 都调它
v1 到 v2 的字段适配必须显式声明,别依赖自动反射
常见错误是写个通用 array_map 或用 __get 动态转发,结果 v2 新增字段没透出、v1 废弃字段还在返回、嵌套结构扁平化失败。适配器必须逐字段写清楚映射关系,哪怕重复。
比如 v1 返回 ["user_id" => 123, "full_name" => "Alice"],v2 要变成 ["id" => 123, "name" => "Alice", "status" => "active"]:
class V2UserAdapter
{
public function fromV1(array $v1Data): array
{
return [
'id' => $v1Data['user_id'] ?? 0,
'name' => $v1Data['full_name'] ?? '',
'status' => 'active', // v2 强制新增字段
];
}
}
这种写法看似啰嗦,但可测、可 debug、上线前能用单元测试覆盖所有字段组合。用魔术方法或配置表驱动,一出问题连报错位置都难定位。
适配器不处理权限和校验,那是中间件和 Request 类的事
有人把参数校验、用户权限检查塞进 Adapter,导致 v1 和 v2 的安全逻辑不一致,或者改一个版本的权限就不得不动两个 Adapter。这是职责错位。
正确分工是:
- 路由匹配后,先走统一中间件:验证 API Token、检查用户 scope(如
read:users_v2) - Request 对象(如
V2UserCreateRequest)负责验证字段格式、必填项、长度限制 - Adapter 只做数据形状转换,输入一定是已通过校验的数组或 DTO
这样 v2 加了手机号格式校验,不影响 v1;v1 删除某个字段,Adapter 里删一行映射就行,不用动校验规则。
版本废弃不是删代码,而是冻结 Adapter 并记录降级路径
线上还有 5% 的 v1 流量?别急着删 V1UserAdapter。先在路由层加日志埋点,统计调用量和客户端 User-Agent;再给 v1 接口响应头加 X-API-Deprecated: true 和 Deprecation: Sat, 01 Jan 2025 00:00:00 GMT;最后在 Adapter 构造函数里抛出 new DeprecatedVersionException(),由全局异常处理器返回带迁移指引的 JSON。
真正的删除动作,要等监控确认 v1 流量归零、且所有 SDK 已升级。否则某天凌晨三点,老安卓 App 崩溃,你翻 Git 历史找旧 Adapter 会比修 bug 还累。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











