115 lines
4.0 KiB
Markdown
115 lines
4.0 KiB
Markdown
# Plan — DebugAgent: "Undefined array key 'id'" 调试计划
|
||
|
||
> 版本:v1.0 | 日期:2026-04-20 | Agent:council/DebugAgent
|
||
|
||
---
|
||
|
||
## 任务概述
|
||
|
||
调查 ShopXO 后台编辑票务商品(goods_id=118)保存时报错:
|
||
```
|
||
Undefined array key "id"
|
||
```
|
||
|
||
---
|
||
|
||
## 根因分析摘要(Round 1 快速结论)
|
||
|
||
### 1. 最可能触发点
|
||
|
||
**AdminGoodsSaveHandle.php 第 71 行**:
|
||
```php
|
||
$template = Db::name('vr_seat_templates')->find($templateId);
|
||
$seatMap = json_decode($template['seat_map'] ?? '{}', true); // ← 危险
|
||
```
|
||
|
||
- `find()` 查不到记录时返回 `null`
|
||
- `??` 操作符只防御 `$template` 为 `null`,**不防御** `$template` 存在但键 `'seat_map'` 缺失
|
||
- PHP 8+ 会报 `Undefined array key "seat_map"`,而非 "id"
|
||
|
||
**真正报 "id" 的位置**——第 77 行 `array_filter` 回调内:
|
||
```php
|
||
return in_array($r['id'], $config['selected_rooms'] ?? []);
|
||
// ^^^^^^ 如果 $r(room 对象)缺少 'id' 键则触发
|
||
```
|
||
|
||
但如果 room 数据正常(有 id),则第 71 行是实际断路点。
|
||
|
||
### 2. 表前缀问题(核心根因)
|
||
|
||
| 代码 | 表名方法 | 实际 SQL 表 |
|
||
|------|---------|------------|
|
||
| `BaseService::table('seat_templates')` | `'vr_' + 'seat_templates'` | `vr_seat_templates` ✅ |
|
||
| `Db::name('vr_seat_templates')` | ThinkPHP `Db::name()` | 取决于 `database.php` 配置前缀 |
|
||
| `Db::name('Goods')` | ThinkPHP `Db::name()` | `sxo_goods` ✅(ShopXO 系统表) |
|
||
|
||
**ShopXO 数据库配置**(需确认 `shopxo/config/database.php`):
|
||
- 若 `prefix` = `'sxo_'`:则 `Db::name('vr_seat_templates')` → 查表 `sxo_vr_seat_templates`(不存在)
|
||
- 若 `prefix` = `'vrt_'`:则 `Db::name('vr_seat_templates')` → 查表 `vrt_vr_seat_templates`(不存在)
|
||
- 正确表名应来自 ThinkPHP 原始前缀(ShopXO 插件表一般不带前缀或用独立前缀)
|
||
|
||
**SeatSkuService 使用 `BaseService::table()` → `vr_seat_templates`(正确)**
|
||
**AdminGoodsSaveHandle 使用 `Db::name('vr_seat_templates')` → 可能查错表(错误)**
|
||
|
||
### 3. 如果 `find($templateId)` 返回 null
|
||
|
||
第 71 行:`$template['seat_map']` → `Undefined array key 'seat_map'`(不是 'id')
|
||
第 72 行:`$allRooms = $seatMap['rooms'] ?? [];` → 此行安全(`??` 防御)
|
||
|
||
### 4. vr_goods_config JSON 解码
|
||
|
||
```php
|
||
$configs = json_decode($rawConfig, true); // → array|null
|
||
if (is_array($configs) && !empty($configs)) { ... } // 防御正确
|
||
```
|
||
`$configs` 是数组时 `$config['template_id']` 访问安全(不会触发 "id" 错误)。
|
||
|
||
### 5. selected_rooms 数据类型
|
||
|
||
- `selected_rooms`: `string[]`(room id 数组),e.g. `["room_id_xxx"]`
|
||
- `$r['id']`: 来自 `seat_map.rooms[].id`,通常是字符串
|
||
- 类型匹配:无类型强制问题,但若 `room.id` 为 `null` 或缺失则触发 "id"
|
||
|
||
### 6. $data['item_type'] 访问安全
|
||
|
||
第 59 行:`($data['item_type'] ?? '') === 'ticket'` — 有 `??` 防御,安全。
|
||
|
||
---
|
||
|
||
## 任务清单(Round 2 执行)
|
||
|
||
- [ ] **Task 1**: 读取 `shopxo/config/database.php`,确认 `prefix` 配置值
|
||
- [ ] **Task 2**: 读取 `AdminGoodsSaveHandle.php` 第 70-72 行,确认 `$template` 为 null 时实际报错信息
|
||
- [ ] **Task 3**: 确认 ShopXO `Db::name()` 表前缀行为(查 ShopXO 源码或文档)
|
||
- [ ] **Task 4**: 编写根因报告 `reports/DebugAgent-ROOT_CAUSE.md`
|
||
- [ ] **Task 5**: 给出修复建议(用 `BaseService::table()` 替代 `Db::name()`)
|
||
|
||
---
|
||
|
||
## 阶段划分
|
||
|
||
| 阶段 | 内容 | 状态 |
|
||
|------|------|------|
|
||
| **Draft** | Round 1:代码静态分析,定位可疑行 | ✅ 完成 |
|
||
| **Review** | Round 2:读取配置文件确认表前缀,输出根因报告 | 待做 |
|
||
| **Finalize** | Round 3:合并报告到 main,提交调试结论 | 待做 |
|
||
|
||
---
|
||
|
||
## 依赖
|
||
|
||
- Task 1-3 必须按顺序执行(需读取配置文件)
|
||
- 不需要 BackendArchitect / SecurityEngineer 配合,可独立完成
|
||
|
||
---
|
||
|
||
## 执行顺序
|
||
|
||
Task 1 → Task 2 → Task 3(串行,每步确认后立即 commit)→ Task 4 → Task 5
|
||
|
||
---
|
||
|
||
## 声称
|
||
|
||
- [Claimed: council/DebugAgent] — Task 1-5 全部
|