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 的技能)
This commit is contained in:
augushong
2026-07-19 12:03:22 +08:00
parent 98dd244e61
commit 4a4155070d
3 changed files with 396 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
# 控制器响应是 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 响应格式与触发条件