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

8.7 KiB
Raw Permalink Blame History

幽灵 Spec 问题 — Council 调研汇总报告

日期2026-04-20 | AgentFrontendDev + BackendArchitect + SecurityEngineer 版本v2.1 | 基于 main 分支 11fdf0309


一、问题定义

**「场馆删除后编辑商品出现规格重复错误」**的技术描述:

  1. 商品关联场馆模板 Avr_goods_config 中存储 template_idtemplate_snapshotspec_base_id_map
  2. 场馆 A 被硬删除,vr_seat_templates 表中无记录
  3. 编辑商品时前端检测到模板不存在,自动置空场馆选择
  4. 但旧的幽灵 spec来自已删除场馆的配置仍混入表单
  5. 提交时触发「规格不允许重复」

二、Agent 调研成果

2.1 FrontendDev — 前端调研(reviews/council-ghost-spec-FrontendDev.md

关键发现

ticket_detail.html 是 C 端购票页,不是后台编辑页

文件 行号 结论
ticket_detail.html:186-187 前端接收 seatMap/specBaseIdMap 来自 GetGoodsViewData()
ticket_detail.html:202-213 renderSessions() 渲染场次选择器 仅渲染场次,非 ShopXO 规格
ticket_detail.html:375 loadSoldSeats()未实现,仅有 TODO P2 缺陷:已售座位无法标记
SeatSkuService.php:383-394 模板不存在 fallback 后端已正确置 null 并写 DB

幽灵 spec 不在前端产生

当前端购票页检测到模板不存在时,GetGoodsViewData() 会将 template_id=nulltemplate_snapshot=null 写入 DB前端收到空数据渲染空白购票页。

「规格不允许重复」触发点不在前端

该错误触发在 GoodsService.php:1859/1889/1925ShopXO 后台服务层),不在 ticket_detail.html

前端根因

问题 严重度 位置
loadSoldSeats() 未实现 P2 ticket_detail.html:375
前端对已删除场馆无特殊处理 P2 ticket_detail.html(整体正确 fallback

前端修复建议

loadSoldSeats() 实现(ticket_detail.html:375

loadSoldSeats: function() {
    if (!this.goodsId || !this.sessionSpecId) return;
    var self = this;
    $.get(this.requestUrl + '?s=plugins/vr_ticket/index/sold_seats', {
        goods_id: this.goodsId,
        spec_base_id: this.sessionSpecId
    }, function(res) {
        if (res.code === 0 && res.data) {
            self.soldSeats = res.data;
            self.markSoldSeats();
        }
    });
},

2.2 BackendArchitect — 后端调研(reviews/council-ghost-spec-BackendArchitect.md

关键发现(逐行验证)

根因 1Critical无效 config 块未被移除,脏数据写回 DB

AdminGoodsSaveHandle.php:83-90continue 跳过 snapshot 重建但不删除 config 块,第 148-150 行将脏 config 无条件写回 goods 表。

根因 2HighGetGoodsViewData 仅处理单模板模式,多模板时无效块不清理

SeatSkuService.php:368 — 只取 $vrGoodsConfig[0],多模板场景下其余配置块被完全忽略;第 386-388 行写回 DB 时只写 [$config] 单元素。

根因 3MediumBatchGenerate 对无效 template_id 返回 code=-2阻断保存

AdminGoodsSaveHandle.php:164-170 — 无效 config 块的 templateId 仍为原值BatchGenerate 内部检测到模板不存在后返回错误码,阻断整个保存流程。

根因 4Medium前端过滤无法防御 DB 层污染

AdminGoodsSave.php:196-229 — 前端 JS 通过 validTemplateIds.has(c.template_id) 过滤无效块,但无法保证 DB 层 config 块被正确清理。

后端根因

幽灵 spec 在 AdminGoodsSaveHandle.php:88continue 处产生:当模板不存在时,continue 跳过 snapshot 重建,但 config 块本身未被移除,残存在 vr_goods_config 中。

后端修复建议(已合并)

  1. AdminGoodsSaveHandle.php:88continue 改为 unset($configs[$i]),第 145 行后加 $configs = array_values($configs);
  2. AdminGoodsSaveHandle.php:148-150 — 写回前加 if (!empty($configs))
  3. SeatSkuService.php:368 — 遍历所有配置块而非只处理第一个
  4. SeatSkuService.php:386-388 — 写回 validConfigs 而非 [$config]

2.3 SecurityEngineer — 安全审计(reviews/SecurityEngineer-AUDIT.md

审计报告来源

  • reviews/SecurityEngineer-AUDIT.mdAdminGoodsSaveHandle.php 根因分析 + 修复建议
  • reviews/council-ghost-spec-BackendArchitect.md — "幽灵 spec" 全链路根因分析4 个根因)

审计结论来源SecurityEngineer-AUDIT.md

级别 位置 问题 结论
P1 AdminGoodsSaveHandle.php:77 array_filter 回调内直接访问 $r['id'],无空安全保护 → Primary 错误源 已修复main
P1 AdminGoodsSaveHandle.php:71 模板不存在时 $template['seat_map'] null 访问PHP 8.0+ 已修复main
P2 AdminGoodsSaveHandle.php:88 硬删除后 continue 跳过config 块残留于 vr_goods_config 已修复main
P2 AdminGoodsSaveHandle.php:29-35 管理员可通过 vr_goods_config_base64 注入任意配置 ⚠️ 需评估
P2 ticket_detail.html:375 loadSoldSeats() 未实现,已售座位无法标记 ⚠️ 待实现
P3 AdminGoodsSaveHandle.php:91-93 json_encode 失败无捕获 低优先级

安全评估

根因分类P1安全缺陷 + 功能缺陷)

  • P1-1:模板不存在时,continue 跳过 snapshot 重建,但 config 块未被移除 → 残留于 vr_goods_config
  • P1-2AdminGoodsSaveHandle.php:77 直接访问 $r['id'] 无空安全保护 → "Undefined array key 'id'" 崩溃
  • 幽灵 spec 注入路径:硬删除后 continue 跳过AdminGoodsSaveHandle.php:88但 config 块残留于 vr_goods_config 数组,最终被写回 DBAdminGoodsSaveHandle.php:148-150
  • template_snapshot 可信度:来源是 vr_seat_templates 表,硬删除后被 GetGoodsViewData() 置 null可信
  • 无直接 XSS:后端输出均有编码,seatMapspecBaseIdMap 来自 DB 合规数据

ShopXO 入口安全AdminGoodsSave.php 入口有 ThinkPHP 参数绑定保护,无注入风险。


三、根因总结

技术根因链路

1. 场馆硬删除
   ↓ vr_seat_templates 表中记录消失
2. AdminGoodsSaveHandle:88 — continue 跳过 snapshot 重建
   ↓ 但 config 块未被移除(残留 template_id=null + spec_base_id_map
3. GetGoodsViewData:383 — 模板不存在,置 null 并写 DB
   ↓ 但如果有多个 config 块,其余块仍携带旧 snapshot
4. 商品编辑时 — vr_goods_config 中的旧数据被读取
   ↓ 前端 fallback 正确(展示空白购票页)
5. 后端保存时 — AdminGoodsSaveHandle:77 访问 $r['id'] 崩溃
   ↓ 或触发「规格不允许重复」GoodsService.php:1859

根因分级

级别 描述 状态
P0 AdminGoodsSaveHandle.php:77$r['id'] 无空安全 已修复main
P1 AdminGoodsSaveHandle.php:71 — 模板不存在时 null 访问 已修复main
P2 AdminGoodsSaveHandle.php:88 — 硬删除后 config 块残留 已修复main
P2 ticket_detail.html:375loadSoldSeats() 未实现 ⚠️ 待实现
P3 AdminGoodsSaveHandle.php:91-93json_encode 失败无捕获 低优先级

修复已合并到 main 的 commit来源fix/venue-hard-delete-p0 分支)

df8353a69 feat: 真删除功能 + 三按钮布局 + seat_template 视图补全
95346206d fix: 移除不存在的座位模板菜单 + 调整删除提示文案
9f3a46e5a fix(vr_ticket): 修复硬删除按钮 + 清理残留代码
f1173e3c8 docs: 补充硬删除修复记录 + Issue #13 关闭说明

四、待处理项

# 问题 优先级 负责人
1 loadSoldSeats() 未实现(ticket_detail.html:375 P2 FrontendDev
2 vr_goods_config 多 config 块场景需测试验证 P2 BackendArchitect
3 AdminGoodsSaveHandle 表前缀风格不统一(Db::name() vs BaseService::table() P3 BackendArchitect

五、报告文件索引

报告 路径
FrontendDev 前端调研 reviews/council-ghost-spec-FrontendDev.md
BackendArchitect 后端调研 reviews/council-ghost-spec-BackendArchitect.md
SecurityEngineer 安全审计 reviews/SecurityEngineer-AUDIT.md
BackendArchitect 幽灵 spec 调研 reviews/council-ghost-spec-BackendArchitect.md
本汇总报告 reviews/council-ghost-spec-summary.md