council(finalize): Issue #9 final ARCHITECTURE_DECISION.md — Plan A adopted unanimously
- Q1: Batch SKU via direct SQL INSERT (bypass GoodsSpecificationsInsert) - Q2: Minimal fix (UPDATE is_exist_many_spec + INSERT \$vr- spec_type + idempotency) - Q3: \$vr- prefix LOW risk confirmed - Q4: All 3 members recommend Plan A (one SKU per seat) - Action items assigned: P0 BatchGenerate, P0 Q2 fix, P1 idempotency, P2 isolation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>refactor/vr-ticket-20260416
parent
fe457eee23
commit
0eb8adbf71
|
|
@ -0,0 +1,130 @@
|
|||
# vr-shopxo-plugin 架构决策报告
|
||||
|
||||
> 关联:Issue #9
|
||||
> 日期:2026-04-15
|
||||
> 参与:council/BackendArchitect + SecurityEngineer + FrontendDev
|
||||
> 轮次:Round 3(最终)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
vr-shopxo-plugin 是 ShopXO 票务插件,核心场景:VR 演唱会票务小程序,用户选座→下单→QR 核销。
|
||||
|
||||
Phase 0/1/2 已完成基础骨架,暴露 P0 架构问题:ShopXO SPEC 与 SKU 的绑定方案。
|
||||
|
||||
**当前商品 112 实测状态(问题根因):**
|
||||
```sql
|
||||
is_exist_many_spec = 0 -- ShopXO 认为无多规格
|
||||
spec_base 表 = 空 -- 没有任何 SKU
|
||||
spec_base_id_map → {A:1001, B:1002, C:1003} -- 这些 ID 在 DB 里不存在!
|
||||
```
|
||||
ShopXO 防超卖机制完全未启用,购买走裸商品逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 核心问题与结论
|
||||
|
||||
### Q1:方案 A 后台批量生成 SKU 路径是否可行?
|
||||
|
||||
**结论:可行,但必须旁路 GoodsSpecificationsInsert()**
|
||||
|
||||
- ShopXO GoodsService::GoodsSpecificationsInsert() 每次商品保存时 DELETE 所有现有 spec 后重建,10K+ 座位场景不可用
|
||||
- 可行路径:直接 SQL INSERT 到 sxo_goods_spec_type、sxo_goods_spec_base、sxo_goods_spec_value 三表
|
||||
- 性能:10000 座位约 3-4 秒(分批 500 条/批提交)
|
||||
- 关键:spec_base_id_map[seat_id] → actual_db_id 映射必须在 INSERT 后即时重建
|
||||
|
||||
### Q2:商品 112 broken 状态是否需要紧急修复?
|
||||
|
||||
**结论:推荐方案乙(最小修复集)**
|
||||
|
||||
1. UPDATE sxo_goods SET is_exist_many_spec = 1 WHERE id = 112
|
||||
2. INSERT $vr- spec_type(场馆/分区、时段三个维度)
|
||||
3. TicketService::issueTicket() 添加 spec_base_id = 0 的幂等保护 fallback
|
||||
|
||||
紧急程度中等,不影响当前票务逻辑运行,但应在 Phase 3 批量 SKU 生成前完成。
|
||||
|
||||
### Q3:$vr- 前缀方案是否有隐患?
|
||||
|
||||
**结论:低风险(SecurityEngineer + FrontendDev 确认)**
|
||||
|
||||
- ThinkPHP {$var} 默认做 HTML 转义,$vr- 不会被解析为 PHP 变量
|
||||
- |raw 仅跳过 HTML 转义,不会执行变量插值
|
||||
- ThinkPHP parseVar 正则对连字符 - 的处理会阻断 $vr- 的完整解析
|
||||
- ShopXO spec name 数据库字段无字符过滤,存储层安全
|
||||
|
||||
### Q4:方案 A vs 方案 B 最终推荐
|
||||
|
||||
**结论:一致推荐方案 A(每个座位一个 SKU)**
|
||||
|
||||
三方一致理由汇总:
|
||||
|
||||
| 维度 | 方案 A(每座位一个 SKU) | 方案 B(每个 Zone 一个 SKU) |
|
||||
|------|--------------------------|------------------------------|
|
||||
| 防超卖 | ShopXO 原生 dec() 原子扣减 | 自建 FOR UPDATE 锁(死锁风险) |
|
||||
| 数据一致性 | 每个座位 inventory=1,事务保护 | Zone 库存需插件自己维护 |
|
||||
| 购买流程 | ShopXO 原生流程完整走通 | 需 Hook 旁路购买逻辑 |
|
||||
| 票务链路 | spec_base_id 直接映射座位 | 需反向解析 seat_id |
|
||||
| SKU 数量 | 10000+(插件自管,ShopXO 后台隐藏) | 10-50(Zone 数量) |
|
||||
| 并发安全 | InnoDB 行锁 + 选座低并发窗口 | 自建锁,高并发风险 |
|
||||
| 可维护性 | 依赖 ShopXO 原生机制,有据可查 | 插件黑盒,故障排查困难 |
|
||||
|
||||
方案 B 的"SKU 少"优势在演唱会 10K+ 场景不成立:插件自管 SKU,通过 Hook 隐藏插件专用规格,不走 ShopXO 原生规格管理页面。
|
||||
|
||||
---
|
||||
|
||||
## 最终推荐
|
||||
|
||||
### 采纳方案:方案 A — 每个座位一个 ShopXO SKU(stock=1)
|
||||
|
||||
### 实现路径
|
||||
|
||||
1. Phase 3 批量 SKU 生成(BackendArchitect 负责)
|
||||
- 创建 SeatSkuService::BatchGenerate():直接 SQL INSERT 批量生成 SKU
|
||||
- 旁路 GoodsSpecificationsInsert(),避免每次商品保存清空重建
|
||||
- 分批提交(500 条/批),初始化一次
|
||||
|
||||
2. 紧急修复(BackendArchitect 负责)
|
||||
- UPDATE is_exist_many_spec = 1
|
||||
- INSERT $vr- spec_type(场馆/分区/时段)
|
||||
- TicketService::issueTicket() 的 spec_base_id=0 幂等保护
|
||||
|
||||
3. 插件规格隔离(FrontendDev 负责)
|
||||
- 通过 Hook 隐藏插件专用 SKU(不出现在 ShopXO 规格管理页)
|
||||
- 建立独立"座位 SKU 管理"页面
|
||||
|
||||
### 技术细节:ShopXO 原生防超卖机制
|
||||
|
||||
BuyService.php:1677-1681:
|
||||
```php
|
||||
$where = [
|
||||
['id', '=', $base['data']['spec_base']['id']],
|
||||
['goods_id', '=', $v['goods_id']],
|
||||
['inventory', '>=', $v['buy_number']],
|
||||
];
|
||||
Db::name('GoodsSpecBase')->where($where)->dec('inventory', $v['buy_number'])->update();
|
||||
```
|
||||
翻译为 SQL:UPDATE goods_spec_base SET inventory = inventory - N WHERE inventory >= N
|
||||
|
||||
这是 MySQL 层面的条件原子扣减,TOCTOU 窗口极小(选座模式已在前端锁定具体座位,并发窗口远低于总库存),推荐接受此风险。
|
||||
|
||||
---
|
||||
|
||||
## 行动项
|
||||
|
||||
| 优先级 | 行动项 | 负责 |
|
||||
|--------|--------|------|
|
||||
| P0 | 创建 SeatSkuService::BatchGenerate() — 直接 SQL INSERT 批量生成 SKU | BackendArchitect |
|
||||
| P0 | 执行 Q2 最小修复集:UPDATE is_exist_many_spec + INSERT $vr- spec_type | BackendArchitect |
|
||||
| P1 | TicketService::issueTicket() 添加 spec_base_id=0 幂等保护 | BackendArchitect |
|
||||
| P2 | 通过 Hook 隐藏插件专用 SKU,建立独立座位 SKU 管理页面 | FrontendDev |
|
||||
|
||||
---
|
||||
|
||||
## 共识
|
||||
|
||||
- BackendArchitect: CONSENSUS: YES — 推荐方案 A,Round 2/3 分析完成
|
||||
- SecurityEngineer: CONSENSUS: YES — $vr- 前缀低风险,方案 A 推荐
|
||||
- FrontendDev: CONSENSUS: YES — 方案 A 推荐,前端配合方案清晰
|
||||
|
||||
全票通过:采纳方案 A
|
||||
Loading…
Reference in New Issue