Files
ulthon_admin/.agents/rules/ulthon-controller-response-throw.md
augushong 4a4155070d docs(agents): 新增控制器响应陷阱与 sysconfig 机制规则
- ulthon-controller-response-throw 规则:揭露 success/error/result/redirect 都是 throw HttpResponseException 的陷阱,含两种正确范式(try 外 / 放行 HttpResponseException)与 grep 自查方法
- ulthon-system-config 规则:sysconfig 完整机制(存储/读取/保存/视图扩展),含新增配置项两种方式(加入已有组 / 新建独立 Tab)
- AGENTS.md 索引同步:零散规则表 +2 行,工作流列表 +2 行(含上一 commit 的技能)
2026-07-19 12:03:22 +08:00

4.6 KiB
Raw Blame History

控制器响应是 throwsuccess/error/result/redirect 陷阱)

来源框架内置ulthon- 作用域所有控制器admin / tools 等全部模块) 触发条件:在控制器里写 try-catch、调用 $this->success/error/result/redirect 时加载

一、陷阱本质

框架的 JumpTraitBaseextend/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失败"(冒号后为空),因为 HttpResponseExceptiongetMessage() 默认为空字符串
  • 但数据库实际已变更成功(事务已在抛异常前提交)
  • 用户表现:"提示失败,但刷新页面发现操作其实成功了"

这是最难排查的一类 bug表象是失败实际是成功数据已经落库。

三、正确范式 A推荐响应调用放在 try 外)

try {
    $result = SomeService::do($id);
} catch (\Throwable $e) {
    return $this->error('失败:' . $e->getMessage());
}
return $this->success('成功', $result);   // ← 在 try 外

适用场景:大多数控制器流程。结构清晰,避免 catch 误吞响应异常。

四、正确范式 Bcatch 前放行 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

相关技能