# 控制器响应是 throw(success/error/result/redirect 陷阱) > 来源:框架内置(ulthon-) > 作用域:所有控制器(admin / tools 等全部模块) > 触发条件:在控制器里写 try-catch、调用 `$this->success/error/result/redirect` 时加载 ## 一、陷阱本质 框架的 `JumpTraitBase`(`extend/base/common/traits/JumpTraitBase.php`)中,`success()` / `error()` / `result()` / `redirect()` 四个方法**全部是 `throw new HttpResponseException($response)`,不是 return**: | 方法 | 行号 | throw 位置 | |------|------|-----------| | `success()` | 第 23 行 | 第 49 行 | | `error()` | 第 62 行 | 第 86 行 | | `result()` | 第 99 行 | 第 110 行 | | `redirect()` | 第 122 行 | 第 130 行 | 因此它们一旦出现在 `try { ... } catch (\Throwable $e)` 块内,**成功响应的异常会被 catch 捕获吞掉**,随后走到 catch 里的 `$this->error(...)`,最终返回错误响应——而此时业务事务往往已经提交、数据已经变更。 ## 二、症状特征 - HTTP 返回 `code:500` - `msg` 形如 `"XX失败:"`(冒号后为空),因为 `HttpResponseException` 的 `getMessage()` 默认为空字符串 - 但数据库实际已变更成功(事务已在抛异常前提交) - 用户表现:**"提示失败,但刷新页面发现操作其实成功了"** 这是最难排查的一类 bug:表象是失败,实际是成功,数据已经落库。 ## 三、正确范式 A(推荐:响应调用放在 try 外) ```php try { $result = SomeService::do($id); } catch (\Throwable $e) { return $this->error('失败:' . $e->getMessage()); } return $this->success('成功', $result); // ← 在 try 外 ``` 适用场景:大多数控制器流程。结构清晰,避免 catch 误吞响应异常。 ## 四、正确范式 B(catch 前放行 HttpResponseException) 当 success 必须在 try 内调用时,在 catch 链最前面放行响应异常: ```php try { $result = SomeService::do($id); return $this->success('成功', $result); // try 内也可 } catch (\think\exception\HttpResponseException $e) { throw $e; // ← 放行响应异常,不吞 } catch (\Throwable $e) { return $this->error('失败:' . $e->getMessage()); } ``` 适用场景:success 调用必须紧贴业务逻辑(如需要复用 `$result`),无法移到 try 外。 ## 五、反例(会触发 bug,禁止) ```php try { $result = SomeService::do($id); return $this->success('成功', $result); // ← 成功响应被 catch 吞掉 } catch (\Throwable $e) { // ← 缺 HttpResponseException 放行 return $this->error('失败:' . $e->getMessage()); } ``` 特征: - try 内调用 `$this->success/error/result/redirect` - catch 用 `\Throwable` 或 `\Exception`(不区分响应异常) **结果**:用户看到"操作失败",但数据库已变更。常见于"绑定/解绑/状态流转"等需要事务的操作。 ## 六、特例:`return json()` 不受影响 `return json(...)` 是普通的 return,不抛异常,**放 try 内安全**: ```php try { $result = SomeService::do($id); return json($result); // ← 安全,不抛 HttpResponseException } catch (\Throwable $e) { return $this->error('失败:' . $e->getMessage()); } ``` 但通常推荐统一用 `$this->success()` 保持响应格式一致(`{code, msg, data}` 结构)。 ## 七、自查方法 新增/修改控制器后,凡 try 块内出现 `$this->success` / `$this->error` / `$this->result` / `$this->redirect` 的,必须满足范式 A 或 B 之一。 排查命令(找所有受影响的文件): ```bash # 1. 找所有调用响应方法的文件 grep -rn '\$this->success\|\$this->error\|\$this->result\|\$this->redirect' app/admin/controller # 2. 找所有 try-catch 的文件 grep -rn 'catch\s*(\s*\\Throwable\|catch\s*(\s*\\Exception' app/admin/controller ``` 两个结果取交集文件,逐个 Read 确认: - 抛异常型响应(success/error/result/redirect)是否在 try 内 - 在 try 内的话,是否有 `HttpResponseException` 放行(范式 B) ## 八、判定原则 - **不动框架内核**:`JumpTraitBase` 的 throw 是 ThinkPHP 的标准契约(success/error/result 均 throw),且框架维护原则为"稳定性优先/向下兼容",改它会破坏既有行为 - **靠开发者在 `app/` 层遵守本规则规避**:响应放 try 外(范式 A),或显式放行 HttpResponseException(范式 B) ## 相关技能 - [ulthon-page-api-dual-mode](../skills/ulthon-page-api-dual-mode/SKILL.md):success/error 的 JSON 响应格式与触发条件