vr-shopxo-plugin/reviews/council-ghost-spec-summary.md

163 lines
8.2 KiB
Markdown
Raw Normal View History

# 幽灵 Spec 问题 — 三方调研汇总报告(终版)
**版本**: v2.0
**日期**: 2026-04-20
**汇总人**: SecurityEngineer
**来源报告**: SecurityEngineer-GHOST_SPEC_SECURITY.md + council-ghost-spec-FrontendDev.md + council-ghost-spec-BackendArchitect.md
---
## 一、问题概述
当票务商品关联的场馆模板被硬删除后,编辑商品时出现「规格不允许重复」错误。
**注意**`ticket_detail.html` 是 **C 端购票页面**(用于用户选座下单),不是后台商品编辑页面。「规格不允许重复」错误的真正触发点在 ShopXO 后台服务层 `GoodsService.php:1859/1889/1925`
### 问题触发路径
```
1. 商品选择场馆 A → vr_goods_config 存储 template_id=A、template_snapshot
2. 场馆 A 被硬删除 → vr_seat_templates 表中无记录
3. 编辑商品 → GetGoodsViewData() 发现 template_id 无效
→ 将 template_id 置 null、template_snapshot 置 null
→ 写回 DB自愈行为
→ 前端收到 template_id=null选单为空
4. 若 template_id 未被及时清理 → 保存时 BatchGenerate 返回 "座位模板 N 不存在"
5. 若 template_id 已清理 → 保存成功,但原规格数据丢失
```
---
## 二、各 Agent 调研结论
### 2.1 FrontendDev 调研结论(来源:`council-ghost-spec-FrontendDev.md`
| 问题 | 文件:行号 | 严重度 |
|------|---------|--------|
| `loadSoldSeats()` 未实现TODO 空函数) | ticket_detail.html:375-383 | P2 |
| 模板不存在时 fallback 行为正确 | SeatSkuService.php:383 | — |
| 「规格不允许重复」不在前端触发 | GoodsService.php:1859 | — |
| config 块残留(硬删除后未移除) | AdminGoodsSaveHandle.php | P2 |
| `spec_base_id_map` 不影响前端 | ticket_detail.html:417 | P3 |
**前端关键发现**
- `ticket_detail.html` 本身不构建 ShopXO 规格表格,其规格项仅为场次选择器
- 模板不存在时前端展示空白购票页(符合业务预期)
- `loadSoldSeats()` 是 TODO 注释,未发送 HTTP 请求,已售座位无法灰显
**修复建议**
- P2: 实现 `loadSoldSeats()` 从后端加载已售座位数据
- P2: AdminGoodsSaveHandle 硬删除后移除整个 config 块而非仅置 null
### 2.2 BackendArchitect 调研结论(来源:`council-ghost-spec-BackendArchitect.md`
| 优先级 | 根因描述 | 文件:行号 | 影响 |
|--------|----------|-----------|------|
| **P1** | 无效 config 块未从数组移除,`continue` 后脏数据写回 DB | AdminGoodsSaveHandle.php:88-89 + 148-150 | 幽灵 config 累积,无效 template_id 持续残留 |
| **P2** | GetGoodsViewData 单模板模式处理,多模板场景会覆盖有效配置块 | SeatSkuService.php:368 + 386-388 | 多模板商品中一个模板删除后整体数据损坏 |
| P3 | BatchGenerate 对无效 template_id 返回 code=-2阻断整个保存 | AdminGoodsSaveHandle.php:164-170 | 用户看到"座位模板不存在"错误 |
| P4 | 前端过滤后 configs 为空时,用户无声失去所有配置 | AdminGoodsSave.php:196-229 | 体验问题:用户不知道配置被过滤 |
| P5 | `loadSoldSeats()` 未实现 | ticket_detail.html:375-383 | 顾客可选已售座位,可能超卖 |
**后端关键发现**
1. **P1 根因**Critical`AdminGoodsSaveHandle.php:88-89` 中 `continue` 不删除 config 块,导致含无效 `template_id` 的脏配置被写回 DB第 148-150 行)。无效模板的 config 块在每次保存后持续累积。
2. **`spec_base_id_map` 不是幽灵 spec 来源**:该字段存储在 `vr_seat_templates` 表,模板硬删除后自然消失,不会在 goods 表的 `vr_goods_config` 中残留。
3. **`spec_base_id_map` 数据流**:存储在模板表 → `GetGoodsViewData` 读取解码SeatSkuService.php:404-409→ 前端 JS 接收。删除后前端 fallback 到 `sessionSpecId`
4. **多模板模式 P2 缺陷**GetGoodsViewData 第 368 行只取 `$vrGoodsConfig[0]`,多模板模式下第 2、3... 个配置块被完全忽略。第 386-388 行写回 DB 时只写 `[$config]`(单元素),会覆盖其他有效配置块。
**修复方案**
- **P1 Fix**: `AdminGoodsSaveHandle.php:88-89``continue` 改为 `unset($configs[$i])`,第 145 行后加 `$configs = array_values($configs)` 重排索引,第 148-150 行前加判空。
- **P2 Fix**: `SeatSkuService.php:368-393` 改为遍历所有有效配置块,写回时使用 `$validConfigs` 而非单元素数组。
### 2.3 SecurityEngineer 安全审计结论(来源:`SecurityEngineer-GHOST_SPEC_SECURITY.md`
| ID | 问题 | 严重性 |
|----|------|--------|
| S-1 | 场馆硬删除后保存失败,错误信息不友好 | P2 |
| S-2 | GetGoodsViewData 静默修改 DB | P2 |
| S-3 | `loadSoldSeats()` 空实现,前端无法标记已售座位 | P2 |
| S-4 | `template_snapshot` 无大小限制 | P3 |
**P1 安全漏洞发现0 个**
| 维度 | 评估 |
|------|------|
| 脏数据注入 | **安全** — 无注入路径 |
| 规格覆盖 | **安全** — 先删后建BatchGenerate 是唯一来源 |
| XSS 风险 | **安全** — 无渲染点 |
| 权限绕过 | **安全** — 依赖 ShopXO 内核 |
| DoS 风险 | **低** — 建议 DB 层加字段大小限制 |
**安全评估**:幽灵 spec 问题经审计后确认不是安全漏洞(无 P1
1. `spec_base_id_map` 不可控:不在表单提交范围内,不在 `vr_goods_config`
2. `template_snapshot` 保存时由后端重建,前端传入值被覆盖
3. `BatchGenerate` 有保护:模板不存在时返回错误阻断保存
---
## 三、综合结论
### 问题定性
| 维度 | 结论 |
|------|------|
| **安全评级** | 无漏洞0 P1 安全漏洞) |
| **功能评级** | **P1** — 无效 config 块未被移除,脏数据写回 DB |
| **其他功能缺陷** | P2 — 错误信息不友好、自愈行为副作用、超卖风险 |
**重要区分**SecurityEngineer 的 P1 定义是「安全漏洞」BackendArchitect 的 P1 定义是「功能性高优先级缺陷」。两者都正确:
- 从安全角度:无 P1 安全漏洞0 个)
- 从功能角度:无效 config 块残留是 P1 优先级缺陷(需立即修复)
### 根因链
```
1. 场馆硬删除 → vr_seat_templates 表记录消失
2. 商品 vr_goods_config.template_id 仍为已删除场馆的 ID
3. AdminGoodsSaveHandle.php:88-89 执行 continue不删除 config 块)
4. 第 148-150 行将含无效 template_id 的脏 config 写回 DB
5. 幽灵 config 块在 DB 中持续累积
6. 下次保存时 BatchGenerate 检测到无效模板 → 返回 code=-2 → 保存阻断
7. 用户看到不友好的错误信息「座位模板 N 不存在」
```
### 关键保护机制
- `BatchGenerate` 模板存在性检查SeatSkuService.php:52-57是最后防线模板不存在时保存被阻断无脏数据写入规格表
- 前端 `AdminGoodsSave.php:202` 过滤硬删除模板的 config 块(有效)
---
## 四、修复建议(优先级排序)
| 优先级 | 修复项 | 涉及文件 | Agent 归属 |
|--------|--------|---------|-----------|
| **P1** | 无效 config 块移除(`unset` + `array_values` + 判空) | AdminGoodsSaveHandle.php:88-145 + 148-150 | BackendArchitect |
| **P2-高** | GetGoodsViewData 多模板模式修复 | SeatSkuService.php:368-393 | BackendArchitect |
| **P2-中** | 改善 BatchGenerate 错误信息,引导用户重新选择场馆 | SeatSkuService.php:55-57 | BackendArchitect |
| **P2-中** | 改善前端过滤无效配置后的用户体验提示 | AdminGoodsSave.php:196-229 | FrontendDev |
| **P2-中** | 实现 `loadSoldSeats()` 标记已售座位 | ticket_detail.html:375-383 | FrontendDev |
| **P3-低** | `vr_goods_config` 字段加 TEXT 限制 | DB migration | BackendArchitect |
---
## 五、各 Agent 报告位置
| Agent | 报告文件 |
|-------|---------|
| SecurityEngineer | `reviews/SecurityEngineer-GHOST_SPEC_SECURITY.md` |
| FrontendDev | `.worktrees/FrontendDev/reviews/council-ghost-spec-FrontendDev.md` |
| BackendArchitect | `.worktrees/BackendArchitect/reviews/council-ghost-spec-BackendArchitect.md` |
---
## 六、后续行动
1. **BackendArchitect** 实施 P1 FixAdminGoodsSaveHandle 无效 config 块移除)
2. **FrontendDev** 实施 P2-中修复loadSoldSeats 实现 + 前端提示)
3. 优先处理 P1无效 config 块移除)和 P2-高(多模板模式)修复