- ulthon-controller-response-throw 规则:揭露 success/error/result/redirect 都是 throw HttpResponseException 的陷阱,含两种正确范式(try 外 / 放行 HttpResponseException)与 grep 自查方法 - ulthon-system-config 规则:sysconfig 完整机制(存储/读取/保存/视图扩展),含新增配置项两种方式(加入已有组 / 新建独立 Tab) - AGENTS.md 索引同步:零散规则表 +2 行,工作流列表 +2 行(含上一 commit 的技能)
4.6 KiB
控制器响应是 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 外)
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 链最前面放行响应异常:
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,禁止)
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 内安全:
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 之一。
排查命令(找所有受影响的文件):
# 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:success/error 的 JSON 响应格式与触发条件