vr-shopxo-plugin/plan.md

232 lines
10 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.

# vr-shopxo-plugin 架构决策评议 — plan.md
> 版本v1.2(最终合并版)| 日期2026-04-15 | Agentcouncil/FrontendDev + BackendArchitect + SecurityEngineer
> 关联Issue #9 | 状态FINAL
---
## 任务背景
Phase 0/1/2 已完成基础骨架,暴露了一个 P0 架构问题VR 演唱会票务商品中 ShopXO SPEC 与 SKU 的绑定方案。
**已知事实:**
- ShopXO `goods_spec_base`SKU表当前为空商品 112 的 `is_exist_many_spec=0`
- `spec_base_id_map` 中的 ID如 1001/1002/1003在 DB 中不存在
- ShopXO 防超卖机制(原子扣 inventory完全未启用
**两种架构方向:**
- **方案 A**:每个座位 = 一个 SKUstock=1ShopXO 原生防超卖
- **方案 B**:每个 Zone = 一个 SKUstock=Zone座位数自建 FOR UPDATE 防超卖
---
## 核心问题4问
| # | 问题 | 负责 |
|---|------|------|
| Q1 | 方案 A 后台批量生成 SKU 路径是否可行ShopXO 是否有批量 API | BackendArchitect |
| Q2 | 当前商品 112 的 broken 状态is_exist_many_spec=0 + spec_base 空)是否需要紧急修复?最小修复集? | BackendArchitect |
| Q3 | $vr- 前缀方案是否有隐患ShopXO 内部是否对 $ 有特殊处理? | SecurityEngineer + FrontendDev |
| Q4 | 方案 A vs 方案 B 最终推荐(实现成本 / 安全性 / 可维护性) | 所有成员 |
---
## 阶段划分
| 阶段 | 内容 | 负责 |
|------|------|------|
| Round 1 | 独立评议 + plan.md 合并 | 所有成员 |
| Round 2 | 各成员深入分析(后台实现路径、安全评估、前端方案) | 所有成员 |
| Round 3 | 综合推荐 + 输出最终决策报告 + `council-output/ARCHITECTURE_DECISION.md` | FrontendDev 主笔 |
---
## 任务清单
- [x] **Q1**: 方案 A 批量生成 SKU 路径 `[Done: BackendArchitect]`
- [x] **Q2**: 商品 112 broken 状态紧急修复 `[Done: BackendArchitect]`
- [x] **Q3**: $vr- 前缀安全评估 `[Done: SecurityEngineer + FrontendDev]`
- [x] **Q4**: 方案 A vs 方案 B 最终推荐 `[Done: 所有成员]` ✅ — 三方一致推荐方案 A
- [x] **Final**: `council-output/ARCHITECTURE_DECISION.md` `[Done: FrontendDev]`
---
## Claim 状态
| 任务 | Claim 状态 |
|------|-----------|
| Q1 | [Done: BackendArchitect] |
| Q2 | [Done: BackendArchitect] |
| Q3 | [Done: SecurityEngineer] + [Done: FrontendDev] |
| Q4 | [Done: BackendArchitect] + [Done: FrontendDev] + [Done: SecurityEngineer] |
| 最终输出 | [Done: FrontendDev] |
---
## 依赖关系
- Q1BackendArchitect先完成后 Q4 才能给出完整推荐
- Q3SecurityEngineer可与 Q1 并行
- Q2 可独立完成,紧急程度由 BackendArchitect 判定
- 三方分析完成后FrontendDev 主笔 Round 3 最终报告
---
## 各成员 Round 1 初判
### BackendArchitect 初判
**Q1 初步判断**Plan A 后台批量生成 SKU **可行**。ShopXO 的 `goods_spec_base` 是标准 MySQL 表,插件可直接 INSERT。
**Q2 初步判断**:当前 broken 状态**暂不需要立即修复**。购买流程走的是裸商品逻辑is_exist_many_spec=0需要明确购买流程最终走哪条路后再修。
**Q4 初步判断**:倾向 **方案 A**。ShopXO 原生防超卖机制比自建锁更可靠DB 层面原子操作)。
### FrontendDev 初判Q1-Q4 分析)
**Q1**:结论:**可行,但实现路径复杂。** 无现成批量 API需要插件自管Hook 隐藏。SKU 数量 = 座位数10000+)。
**Q2**:结论:**需要立即修复,推荐最小方案。**
**Q3**:结论:**低风险,但需实测确认。**
**Q4 推荐****方案 A每个座位一个 SPEC/SKU**。安全性+数据一致性优先。
### SecurityEngineer 初判Q2/Q3/Q4
**Q2**:依赖 Q1/Q4标记为 blocked。
**Q3**ThinkPHP View 层可能对 `$` 有变量插值行为需要代码验证Round 2 执行)。
**Q4**:初步倾向 **方案 A**。
---
## 各成员 Round 2 深入分析
### BackendArchitect Round 2 深入分析Q1+Q2
详细分析见 `docs/ROUND2_ANALYSIS.md`
**Q1 结论:可行,但必须旁路 `GoodsSpecificationsInsert()`**
- `GoodsSpecificationsInsert()` 每次商品保存时 DELETE 所有现有 spec 后重建10K+ 座位场景不可用
- 可行路径:**直接 SQL INSERT** 到 `sxo_goods_spec_type`、`sxo_goods_spec_base`、`sxo_goods_spec_value` 三表
- 关键代码:`BuyService.php:1677-1681` 的 `dec()` 机制 = MySQL 条件原子扣减 `UPDATE SET inventory = inventory - N WHERE inventory >= N`
- TOCTOU 窗口极小(选座模式并发低 + InnoDB 行锁),**推荐接受此风险**
- 性能10000 座位 ≈ 3-4 秒(需分批 500 条/批提交)
**Q2 结论:推荐方案乙(最小修复集)**
- `UPDATE goods SET is_exist_many_spec=1 WHERE id=112`
- 写入 `$vr-` 规格维度到 `sxo_goods_spec_type`
- 幂等保护:票生成逻辑已有 `spec_base_id` 冗余
**Q4 初步推荐:方案 A**
- 原子性已验证BuyService dec 机制)
- 数据完整性高(每个座位 inventory=1
- 票务链路清晰spec_base_id → 座位直接映射)
### SecurityEngineer Round 2 分析Q3
SecurityEngineer 在 Round 2 进行了 ThinkPHP View 层的 $vr- 前缀安全审计,结论:**无高危风险**。
### FrontendDev Round 2 深入分析Q3+Q4
**Q3 结论:$vr- 前缀安全**
- ThinkPHP `{$var}` 默认做 HTML 转义,$vr- 不会被解析为 PHP 变量
- `|raw` 仅跳过 HTML 转义,不会执行变量插值
- ThinkPHP parseVar 正则对连字符 `-` 的处理会阻断 $vr- 的完整解析
- ShopXO spec name 存 DB 无过滤,但渲染层安全
**Q4 最终推荐:方案 A每个座位一个 SPEC/SKU—— 明确推荐**
**核心发现**
1. 当前 `ticket_detail.html` submit() 是 Plan B 模式,`specBaseIdMap` 已声明但**未接入** submit 逻辑
2. ShopXO 购买流程从 `spec_base` 表读取库存并原子扣减
**方案 A vs B 最终对比**
| 维度 | 方案 A每座=SKU | 方案 B每 Zone=SKU |
|------|-------------------|---------------------|
| **防超卖** | ShopXO 原生原子扣库存stock=1DB 层保证 | 自建 FOR UPDATE 锁,需自己写并发逻辑 |
| **实现复杂度** | 后端需批量生成 1 万+ SKU前端 `submit()` 需改为逐座提交 | 后端简单;前端按 Zone 分组即可 |
| **多 Zone 混买** | 每座一行 goods_params后端原子处理 | 前端分组但后端共享 Zone 库存,复杂度高 |
| **后台可维护性** | 10000+ SKU 行,但可 Hook 隐藏 | Zone 数量少,后台友好 |
| **调试/故障排查** | 每个 SKU 独立,可追溯 | 共享库存,出问题难以定位 |
| **与 ShopXO 生态** | 完全对齐,无缝集成 | 绕过 spec 校验,部分 ShopXO 功能失效 |
**Plan A 前端实现路径**
关键修改:将 `submit()` 从"session-level 提交"改为"seat-level 逐座提交"
```javascript
// Plan A: 每座一行 goods_params逐座购买
this.selectedSeats.forEach(function(seat) {
var seatSpecBaseId = app.specBaseIdMap[seat.row + '_' + seat.col]?.spec_base_id;
// 如果 spec_base_id 存在,走 ShopXO 原生购买
// 否则走 Plan B 回退逻辑
});
```
`specBaseIdMap` 数据结构已就位(从后端 PHP 注入),前端只需接入即可。
---
## 各成员 Round 3 最终推荐
### BackendArchitect Round 3 最终推荐Q1+Q2+Q4
**Q1 最终结论**:可行。必须旁路 `GoodsSpecificationsInsert()`,走**直接 SQL INSERT** 路径。性能10000 座位 ≈ 3-4 秒(分批 500 条/批)。关键:`spec_base_id_map[seat_id] → actual_db_id` 映射必须在 INSERT 后即时重建。
**Q2 最终结论**:推荐**方案乙**(最小修复集):
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
**Q3 最终结论**(汇入 SecurityEngineer + FrontendDev 确认低风险。ThinkPHP `{$var}` 默认 HTML 转义,`$vr-` 不会触发变量解析。
**Q4 最终推荐:方案 A**,理由汇总:
1. **ShopXO 原生原子防超卖**`BuyService::dec()` = MySQL 条件原子扣减,无需自建锁
2. **TOCTOU 风险可接受**选座模式并发窗口极小InnoDB 行锁提供最后保护
3. **票务链路清晰**`spec_base_id` 直接映射座位,票生成无需反向解析
4. **方案 B 优势不成立**:插件自管 SKUHook 隐藏),不走 ShopXO 后台,无"管理困难"问题
### FrontendDev Round 3 最终推荐Q3+Q4
三方一致推荐 **方案 A每个座位一个 ShopXO SKU**。
最终决策报告:`council-output/ARCHITECTURE_DECISION.md`
---
## 行动项(优先级排序)
| 优先级 | 行动项 | 负责 |
|--------|--------|------|
| P0 | 创建 `SeatSkuService::BatchGenerate()` — 直接 SQL INSERT 批量生成 SKU分批 500 条) | BackendArchitect |
| P0 | 执行 Q2 最小修复集:`UPDATE is_exist_many_spec=1` + `INSERT $vr- spec_type` | BackendArchitect |
| P1 | `TicketService::issueTicket()` 添加 `spec_base_id=0` 幂等保护 | BackendArchitect |
| P1 | 重构 `ticket_detail.html` submit():接入 `specBaseIdMap`,改为 seat-level 逐座提交 | FrontendDev |
| P2 | 实现 `loadSoldSeats()`:查询各 seat spec_base 的库存状态 | FrontendDev |
| P2 | Hook 隐藏插件专用 SKU隔离 ShopXO 原生规格管理页) | FrontendDev |
| P3 | 设计插件独立 SKU 管理页面 | FrontendDev |
---
## 共识投票
| 成员 | CONSENSUS |
|------|-----------|
| BackendArchitect | `[CONSENSUS: YES]` — 推荐方案 ARound 2/3 分析完成 |
| SecurityEngineer | `[CONSENSUS: YES]` — $vr- 前缀低风险,方案 A 推荐 |
| FrontendDev | `[CONSENSUS: YES]` — 方案 A 推荐,前端配合方案清晰 |
**全票通过:采纳方案 A**
---
## Round 3 安全审计结果(保留,仅供参考)
### Task S1 — Admin 鉴权覆盖完整性审查 ✅ 验证通过
### Task S2 — SQL 注入风险审计 ✅ 无注入风险
### Task S3 — XSS / CSRF 防护检查 ✅ 通过
### Task S5 — IDOR 水平越权检查 ✅ 通过
### Task S4 — 敏感操作审计日志设计 ✅ 设计完成