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

163 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 幽灵 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-高(多模板模式)修复