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

117 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 控制器响应是 throwsuccess/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 误吞响应异常。
## 四、正确范式 Bcatch 前放行 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 响应格式与触发条件