Yii框架RESTful API维护关键在于版本隔离、行为可插拔、响应契约稳定:通过urlManager按命名空间路由(如v1/user、v2/user),模型fields()显式声明字段,废弃字段移入extraFields()标注弃用,认证升级采用JWT与session双轨并行,findModel()需适配软删除、租户隔离及主库查询。

Yii 框架的 RESTful 接口不是写完就扔的“一次性代码”,维护和迭代的关键在于**版本隔离、行为可插拔、响应契约稳定**。硬改 v1/UserController 里的 actions() 或直接删字段,90% 的概率会让前端炸锅。
怎么给 API 加版本前缀并保证老接口不挂
Yii 的 urlManager 支持按控制器命名空间自动映射版本路径,不需要手动拼 /v1/users 或写一堆 if ($version === 'v2') 分支:
- 把控制器按版本分目录:比如
controllers/v1/UserController.php和controllers/v2/UserController.php - 在
config/web.php的urlManager中注册规则时,用数组指定控制器全名:['class' => 'yii\rest\UrlRule', 'controller' => ['v1/user', 'v2/user']] - 确保每个版本控制器都继承
yii\rest\ActiveController,且$modelClass指向对应版本的数据模型(如app\models\v1\User),避免跨版本共享模型导致字段冲突 - 不要用
$_GET['v']或请求头做动态版本路由——这会让缓存、文档、测试全部失效
字段变更时如何不破坏下游消费方
删除或重命名 API 返回字段是高频雷区。Yii 本身不强制字段契约,得靠主动约束:
- 在模型的
fields()方法里显式声明对外暴露的字段,而不是依赖toArray()默认行为:return ['id', 'username', 'created_at']; - 废弃字段别直接删,先在
extraFields()里保留,并加注释说明“deprecated since v2.1”;等所有调用方完成迁移后再移除 - 新增字段默认不返回,除非显式调用
?expand=profile—— 这需要在控制器中配置Serializer并启用ExpandableBehavior - 避免在
rules()里改验证逻辑影响已有字段语义(比如把'email'的'required'改成'safe')
怎么安全地替换认证方式(比如从 session 切到 JWT)
认证机制升级最容易引发大面积 401,核心是「双轨并行 + 请求头识别」:
- 不要停掉旧认证中间件,而是新增一个
JwtAuthFilter,在behaviors()中按请求头判断走哪条路:if (isset($_SERVER['HTTP_AUTHORIZATION']) && strpos($_SERVER['HTTP_AUTHORIZATION'], 'Bearer ') === 0) - JWT 解析失败时,降级回 session 认证(仅限过渡期),但记录日志标记“fallback_to_session”便于监控
- 所有新接口默认只支持 JWT,老接口保持双模式,通过 Nginx 或负载均衡器逐步切流
- 禁用
yii\filters\auth\HttpBasicAuth类直接替换,它不兼容无状态场景,容易引发会话竞争
为什么改个 ActiveController::findModel() 就崩了
这个方法被 view、update、delete 多个动作共用,看似只是查一条记录,实则牵扯权限、软删除、租户隔离:
- 默认实现是
$modelClass::findOne($id),如果模型启用了软删除(is_deleted = 1),必须重写该方法过滤掉已删记录 - 多租户场景下,不能只查
$id,还得加andWhere(['tenant_id' => \Yii::$app->user->identity->tenant_id]) - 别在
findModel()里抛NotFoundHttpException——应由控制器动作自己处理,否则异常堆栈无法区分是查不到还是没权限 - 如果用了数据库读写分离,
findModel()必须走主库(Yii::$app->db->master),否则可能读到延迟数据导致更新冲突
最常被忽略的是:API 维护不是改代码,而是管住「谁在什么时候调用了什么字段」。上线前用 curl -I 检查响应头中的 X-API-Version 和 Content-Type 是否一致,比跑一遍单元测试更能提前发现契约断裂。











