From dbd62f5658155f7a1a1de30e49b57d6c38807a62 Mon Sep 17 00:00:00 2001 From: Council Date: Mon, 20 Apr 2026 22:43:01 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=BF=BD=E5=8A=A0=E5=B9=BD=E7=81=B5=20?= =?UTF-8?q?spec=20=E4=BF=AE=E5=A4=8D=E8=AE=B0=E5=BD=95=20(DEVELOPMENT=5FLO?= =?UTF-8?q?G.md=20=E6=9B=B4=E6=96=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/DEVELOPMENT_LOG.md | 58 ++++++++++++++ docs/PLAN_GHOST_SPEC_FIX.md | 150 ++++++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 docs/PLAN_GHOST_SPEC_FIX.md diff --git a/docs/DEVELOPMENT_LOG.md b/docs/DEVELOPMENT_LOG.md index c032fbb..f2c1497 100644 --- a/docs/DEVELOPMENT_LOG.md +++ b/docs/DEVELOPMENT_LOG.md @@ -720,3 +720,61 @@ if (empty($room['id'])) { - 大头明确说了"不用了"、"可以 git 提交了"之后,西莉雅因为读到了 subagent 的报告,误以为还需要继续工作,额外 apply 了补丁 - **行动准则**:当大头说"可以提交了",意味着他认为工作已完成,此时不应再基于其他报告引入新改动——除非他明确说"还有问题" - 本次修复的 `is_delete` → `is_delete_time` 是正确且必要的,但触发点是错误的(源于对大头的意图误判) + +--- + +## 2026-04-20 晚 — 幽灵 Spec 问题修复(Issue #15 + #16) + +### 问题现象 + +编辑票务商品时,若商品关联的场馆模板已被硬删除,提交保存时触发「规格不允许重复」错误。幽灵 spec 块累积,无法自动清除。 + +### 调研过程 + +1. **Council 调研**(BackendArchitect + FrontendDev + SecurityEngineer 并行) + - 根因:`AdminGoodsSaveHandle.php:89` 的 `continue` 跳过 snapshot 重建但不移除无效 config 块 + - Council 修复:`unset($configs[$i])` + 写回前判空 + +2. **大头 antigravity 独立验证**(`reports/GHOST_SPEC_INVESTIGATION_REPORT.md`) + - 确认 Council 结论正确 + - 关键发现:`save_thing_end` 从 DB 读旧数据(`$goodsRow['vr_goods_config']`),前端过滤后的数据(`$data['vr_goods_config']`)只是 fallback + - **补充修复**:调换读取优先级(`$data` 优先,DB 兜底) + +3. **西莉雅 Plan 审查**(`docs/PLAN_GHOST_SPEC_FIX.md`) + - 认可报告结论 + - 确认 Plan 的两层修复方案:主要修复(读取优先级)+ 防御层(unset + 判空) + - Issue #15 + #16 方案确认 + +### 修复内容 + +**Issue #15 — AdminGoodsSaveHandle.php(三步)** + +| 步骤 | 行号 | 修改内容 | +|------|------|---------| +| 读取优先级调换 | 61-65 | `$data['vr_goods_config']` 优先,DB 兜底 | +| 无效 config 块移除 | 89 | `unset($configs[$i])` | +| 重排索引 + 写回判空 | 145-150 | `array_values` 重排 + `if (!empty($configs))` | + +**Issue #16 — SeatSkuService.php GetGoodsViewData(两步)** + +| 步骤 | 行号 | 修改内容 | +|------|------|---------| +| 多模板过滤 | 368-383 | 遍历所有配置块过滤有效块;若全部无效返回 null | +| 模板不存在时清理 | 394-415 | 清理无效块并写回有效配置(而非覆盖) | + +### Git Commit + +``` +2311f17b9 fix(vr_ticket): 修复幽灵 spec 问题 (Issue #15 + #16) +``` + +### Issue 关闭 + +- **Issue #15** → closed(save_thing_end 脏数据写回) +- **Issue #16** → closed(GetGoodsViewData 单模板模式) + +### 验收状态 + +- ✅ antigravity 测试通过(基本没问题) +- ✅ 西莉雅代码审查通过(读取优先级 + 防御层双重保障) +- ✅ 多模板模式修复验证 diff --git a/docs/PLAN_GHOST_SPEC_FIX.md b/docs/PLAN_GHOST_SPEC_FIX.md new file mode 100644 index 0000000..3a00e49 --- /dev/null +++ b/docs/PLAN_GHOST_SPEC_FIX.md @@ -0,0 +1,150 @@ +# Plan: 幽灵 Spec 问题修复 + +> 日期:2026-04-20 | Issue: #15 + #16 | 状态:待实施 + +--- + +## 问题概览 + +| Issue | 问题 | 优先级 | 根因文件 | +|--------|------|--------|---------| +| #15 | save_thing_end 脏数据写回 | P1 | AdminGoodsSaveHandle.php | +| #16 | GetGoodsViewData 单模板模式 | P1 | SeatSkuService.php | + +--- + +## Issue #15 — save_thing_end 脏数据写回 + +### 根因 + +`AdminGoodsSaveHandle.php` 第 60-65 行:DB 值优先(`$goodsRow['vr_goods_config']`),前端过滤后的值(`$data['vr_goods_config']`)只是 fallback。脏 config 块(含已删除场馆的 template_id)被写回 DB,幽灵 spec 累积。 + +### 修复计划 + +#### 步骤 1:调换读取优先级(主要修复) + +**文件**:`shopxo/app/plugins/vr_ticket/hook/AdminGoodsSaveHandle.php` +**行号**:第 60-65 行 + +```php +// 修改前 +$rawConfig = is_array($goodsRow) ? ($goodsRow['vr_goods_config'] ?? '') : ''; +if (empty($rawConfig)) { + $rawConfig = $data['vr_goods_config'] ?? ''; +} + +// 修改后(主要修复) +// 前端已过滤无效 template_id,优先使用 data。若无前端数据再 fallback 到 DB +if (!empty($data['vr_goods_config'])) { + $rawConfig = $data['vr_goods_config']; +} else { + $rawConfig = is_array($goodsRow) ? ($goodsRow['vr_goods_config'] ?? '') : ''; +} +``` + +**验证**: +- 提交后 `git diff` 确认读取顺序调换 +- `git status` 确认只有 AdminGoodsSaveHandle.php 被修改 + +#### 步骤 2:无效 config 块移除(防御层) + +**文件**:同上 +**行号**:第 88-89 行 + +```diff + if (empty($template)) { +- continue; ++ unset($configs[$i]); // 移除无效 config 块 ++ continue; + } +``` + +**行号**:第 145 行(`unset($config);` 之后) + +```php +$configs = array_values($configs); // 重排数组索引 +``` + +**验证**: +- grep 确认 `unset($configs[$i])` 在第 89 行附近 +- grep 确认 `array_values` 在 `unset($config)` 之后 + +#### 步骤 3:写回前判空(防御层) + +**行号**:第 148 行之前 + +```diff ++ if (!empty($configs)) { + Db::name('Goods')->where('id', $goodsId)->update([ + 'vr_goods_config' => json_encode($configs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + ]); ++ } +``` + +--- + +## Issue #16 — GetGoodsViewData 单模板模式 + +### 根因 + +`SeatSkuService.php` 第 368 行:只取 `$vrGoodsConfig[0]`(第一个配置块)。多模板商品时其余配置块被完全忽略;写回时只写 `[$config]` 单元素,可能覆盖其他有效配置块。 + +### 修复计划 + +#### 步骤 1:过滤有效配置块 + +**文件**:`shopxo/app/plugins/vr_ticket/service/SeatSkuService.php` +**行号**:第 365-373 行(在 `$config = $vrGoodsConfig[0];` 之前) + +```php +// 过滤有效配置块 +$validConfigs = []; +foreach ($vrGoodsConfig as $cfg) { + $tid = intval($cfg['template_id'] ?? 0); + if ($tid <= 0) continue; + $tpl = Db::name(self::table('seat_templates'))->where('id', $tid)->find(); + if (!empty($tpl)) { + $validConfigs[] = $cfg; + } +} +if (empty($validConfigs)) { + return ['vr_seat_template' => null, 'goods_spec_data' => [], 'goods_config' => null]; +} +$config = $validConfigs[0]; // 取第一个有效配置块用于前端展示 +``` + +#### 步骤 2:修改写回逻辑 + +**行号**:第 386-388 行(原写回 `[$config]`) + +```diff +- Db::name('Goods')->where('id', $goodsId)->update([ +- 'vr_goods_config' => json_encode([$config], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), +- ]); ++ Db::name('Goods')->where('id', $goodsId)->update([ ++ 'vr_goods_config' => json_encode($validConfigs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ++ ]); +``` + +**验证**: +- grep 确认 `validConfigs` 被用于写回 +- grep 确认不再有 `[$config]` 写回模式 + +--- + +## 实施顺序 + +``` +1. Issue #15(AdminGoodsSaveHandle.php)— 三步修改 +2. Issue #16(SeatSkuService.php)— 两步修改 +3. 验证:grep + git diff 确认修改正确 +4. commit + push +``` + +--- + +## 禁止事项 + +- 不改其他文件(尤其是 Admin.php) +- 不改 Hook.php 或其他无关文件 +- commit 前执行 `git status` 确认只有目标文件 \ No newline at end of file