[架构决策] SPEC 设计方向:Zone 级 vs 每个座位一个 SPEC + ShopXO 原生防超卖 #9
Loading…
Reference in New Issue
There is no content yet.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may exist for a short time before cleaning up, in most cases it CANNOT be undone. Continue?
架构决策:SPEC 设计方向
一、背景与问题来源
本次 P0-2 价格验证修复讨论中,暴露了一个根本性的架构认知偏差:在讨论 ShopXO SPEC 机制时,发现当前 Phase 0-2 的实现与原始设计文档(
docs/06_SEAT_MAP_INTEGRATIONATION.md)存在重大不一致。二、ShopXO SPEC 机制(代码确认)
2.1 核心机制
ShopXO 的商品有多个 SPEC 维度,每个维度有多个 VALUE。
ShopXO 自动计算所有维度的笛卡尔积,生成 SKU:
2场馆 × 2分区 × 2时段 × N座位 = 大量 SKU关键数据库表:
goods_spec_base:每行 = 一个完整 SKU(spec_base_id = SKU ID)inventory(库存)、price、goods_idgoods_spec_value:(spec_base_id, value) 对2.2 购买流程(代码确认)
ShopXO
BuyService::BuyGoods()处理购买:ShopXO 原生防超卖:购买时直接用
spec_base_id原子扣goods_spec_base.inventory,无需自己写 FOR UPDATE 锁。三、原始设计文档(
docs/06_SEAT_MAP_INTEGRATIONATION.md第 1.5 节)3.1 spec_base_id_map 设计
3.2 原始设计的购买流程
3.3 文档中的购买流程(关键注释)
四、当前代码实际实现
4.1 submit() 函数(ticket_detail.html:384-421)
所有选中座位(不管在哪个 Zone)共用一个
spec_base_id(zone 级别)。4.2 实际问题
4.3 关键漏洞
当前代码绕过了 ShopXO 的 spec 验证:
多 Zone 混买场景下(当前代码的 bug):
用户选了:
前端发给 ShopXO:
ShopXO 只会检查 Zone A 的库存(A区够不够 3 张),完全不知道 Zone B 座位的存在。
五、两种架构方向
方案 A:每个座位一个 SPEC(原始设计)
核心思路: Zone 只是 UI 展示用的样式/价格区;每个具体座位是独立的 SPEC in ShopXO,stock=1。
实现要求:
spec_base_id_map每个座位一个 key(如{"1_1": {spec_base_id: 10001}})submit():按 spec_base_id 分组,每个 spec_base_id 单独一行 goods_params优点:
缺点:
方案 B:每个 Zone 一个 SPEC(当前实现)
核心思路: 每个 Zone(A区、B区)= 一个 SPEC,stock = 该 Zone 座位总数。
实现要求:
spec_base_id_map每个 Zone 一个 key(如{A: id_A, B: id_B})onOrderPaid校验:seats[] 数量 = 该 Zone 的 stock优点:
缺点:
六、待确认的关键问题
问题 1(最高优先):spec_base_id_map 实际存储格式
数据库里
spec_base_id_map到底是 Zone 级还是座位级?{"A": {spec_base_id: id_A}, "B": {spec_base_id: id_B}}{"1_1": {spec_base_id: id_1_1}, "1_2": {spec_base_id: id_1_2}, ...}调查方法:
问题 2:ShopXO SPEC 数量上限
问题 3:Zone 级 SPEC 的合法性
submit()把所有 Zone 座位塞进一个 goods_params 行是否合法?七、行动项
八、关联 Issue
补充评论 1(2026-04-15 18:55 CST)
数据库实际状态确认
问题严重程度比原 Issue 描述的还要高
当前商品 112 的状态:
is_exist_many_spec=0→ ShopXO 关闭多规格校验spec_base_id_map引用的 1001/1002/1003 → 不存在现在的购买流程:ShopXO 直接走裸商品逻辑,库存校验和扣减完全绕过。防超卖全靠
onOrderPaid里的自建 FOR UPDATE 锁。昨天讨论(spec_value per-goods COPY)与 Issue #9 的关系
昨天大头补充的结论(spec_value 是 per-goods COPY,不是全局可复用)影响的是「绑定 vr_venues 到 ShopXO 规格」的匹配逻辑。
对方案 A 的影响:不大。
对方案 B 的影响:反而更糟糕。
行动建议
必须决策:选方案 A 还是方案 B。
无论选哪个,当前
is_exist_many_spec=0+ spec_base=空的局面必须立即修正。现在的状态是「ShopXO 层面完全裸奔」。建议流程:
补充评论 2(2026-04-15 19:10 CST)
解决方案:$vr- 命名空间前缀(已确认方案)
为避免用户手动添加的普通规格与插件专用规格冲突,采用
$vr-前缀做命名空间隔离:为什么不冲突:插件票务商品使用自定义模板
ticket_detail.html,前端 UI 不走 ShopXO 默认规格选择器。用户无法通过默认界面触碰到$vr-规格。特殊字符支持确认 ✅
ShopXO spec name 无字符过滤,从源码确认:
前端(goods/spec.html):
只有 required 验证,无 regex 无字符限制。
后端(GoodsService::GoodsSpecificationsInsert):
结论:
$vr-场馆完全合法。$、-、中文均可用。两天讨论综合结论
$vr-前缀隔离当前唯一待决策:方案 A vs 方案 B
方案 A(每个座位一个 SPEC):
方案 B(每个 Zone 一个 SPEC):
需要 Council 评议的核心问题:方案 A 的「后台批量生成 SKU」实现路径是否可行?以及当前 broken 状态下的紧急修复优先级。
补充评论 3(2026-04-15 19:38 CST)
甲方新需求已整理
完整需求文档:
docs/10_NEW_REQUIREMENTS_20260415.md4 项新需求摘要:
ShopXO 多 spec_base_id 同单购买验证结果 ✅
源码证据(BuyService.php):
路径:
BuyGoods()→ 每个 goods_params 条目 → 一个$data[]元素OrderSplitService::Run()→ 按 warehouse 分组(非 goods_id 合并),不同 spec_base_id 保留为不同 goods_items[]OrderInsert()→foreach($v["goods_items"] as $vs)→ 每个条目 → 一行 order_goods结论:ShopXO 原生支持同一 goods_id + 不同 spec_base_id 各买 1 个,各生成独立 order_goods 行。
方案 A 完全兼容甲方全部新需求
当前数据库 broken 状态(需紧急修复)
ShopXO 防超卖机制完全未启用。
需要 Council 补充评议:
补充评论 4(2026-04-15 19:49 CST)— Council 评议结果
Council 最终结论(全票通过:方案 A)
完整报告:
council-output/ARCHITECTURE_DECISION.mdQ1:批量 SKU 生成路径
结论:✅ 可行,但必须旁路 GoodsSpecificationsInsert()
GoodsSpecificationsInsert()每次保存时 DELETE 所有 spec 后重建,1 万座会删崩goods_spec_type/goods_spec_base/goods_spec_value)Q2:商品 112 broken 状态修复
结论:推荐方案乙(最小修复集),紧急程度中等
Q3:$vr- 前缀安全性
结论:✅ 低风险,确认安全
{$var}默认 HTML 转义,不作为 PHP 变量解析parseVar正则\$["']*[a-zA-Z_]在$vr-场馆中无法匹配有效变量名varchar类型允许$字符Q4:最终推荐
结论:✅ 方案 A 全票通过(三方一致)
每个座位 = 一个 ShopXO SKU(inventory=1),ShopXO 原生原子扣库存防超卖。
行动项
补充评论 5(2026-04-15 20:15 CST)— P0/P1 实现确认 + 设计分歧讨论
P0/P1 实现状态
已推送 commit:
5e9c11137(BackendArchitect) +93b70d4d5(FrontendDev)当前 admin 管理端现状
现有管理页面(Phase 2 已实现):
目前没有场馆管理页面。
当前 vr_seat_templates 表结构 vs 你的描述
当前表结构:
当前 BatchGenerate() 中的 $vr- spec 维度:
$vr-场馆→ 硬编码为 "国家体育馆"(无独立 venue 表)$vr-分区→ 从 seat_map.zones 读取(Zone A/B/C 区)$vr-时段→ placeholder,后续 UpdateSessionSku 替换$vr-座位号→ seatId(如 "A_1")你的描述 vs 当前设计 — 对比分歧
关键问题需要讨论
vr_seat_templates的name字段就够用?补充评论 6(2026-04-15 21:13 CST)— Phase 3 完整设计方案
完整方案文档:
docs/11_EDITOR_AND_INJECTION_DESIGN.md核心结论
后台编辑器:在场馆配置管理页面做表单可视化编辑器(layui + Vue3 CDN),不碰 ShopXO 原生 Spec 界面。
商品发布注入:利用
plugins_view_admin_goods_save钩子,在商品发布页注入票务配置面板(选场馆 + 分区多选)。商户点发布 → 插件静默生成海量 Spec。整体流程
关键设计决策
实施步骤
Phase 3-1:后台场馆配置管理(admin 页面)
Phase 3-2:商品发布页注入(钩子 + 表单面板)
Phase 3-3:Spec 自动生成接入 BatchGenerate()
Phase 3-4:商品编辑回显(低优先级)
补充评论 7(2026-04-15 21:45 CST)— v3.0 设计确认
文档:
docs/11_EDITOR_AND_INJECTION_DESIGN.md(v3.0)三个核心决策(已确认)
seat_map.venue顶层,不独立建表BatchGenerate(enabledZones)sxo_goods.vr_ticket_configJSON 字段,老商品 NULL 兼容新增内容
BatchGenerate(int $goodsId, int $templateId, array enabledZones=[])PM Auditor 10 项纠错(v2.0)
下一步
Phase 3-1 开工 → 新建 Venue.php + 表单编辑器 + seat_map 升级迁移