vr-shopxo-plugin/plan.md

273 lines
13 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.1(合并版)| 日期2026-04-15 | Agentcouncil/FrontendDev + BackendArchitect + SecurityEngineer
> 关联Issue #9
---
## 任务背景
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 + SecurityEngineer |
| Q3 | $vr- 前缀方案是否有隐患ShopXO 内部是否对 $ 有特殊处理? | SecurityEngineer |
| 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: FrontendDev]` ✅ (ThinkPHP {$var} 默认转义,|raw 仅跳过HTML转义)
- [x] **Q4**: 方案 A vs 方案 B 最终推荐 `[Done: council/BackendArchitect]` ✅ — 全部成员一致推荐方案A
- [ ] **Final**: `council-output/ARCHITECTURE_DECISION.md` — FrontendDev 主笔汇总三方推荐 + 最终结论 `[Pending: FrontendDev]`
---
## Claim 状态
| 任务 | Claim 状态 |
|------|-----------|
| Q1 | [Done: BackendArchitect] |
| Q2 | [Done: BackendArchitect] |
| Q3 | [Done: FrontendDev] |
| Q4 | [Done: council/BackendArchitect] — 全员一致推荐方案A |
| 最终输出 | [Pending: FrontendDev] — 主笔 `council-output/ARCHITECTURE_DECISION.md` |
---
## 依赖关系
- Q1BackendArchitect先完成后 Q4 才能给出完整推荐
- Q3SecurityEngineer可与 Q1 并行
- Q2 可独立完成,紧急程度由 BackendArchitect 判定
- 三方分析完成后FrontendDev 主笔 Round 3 最终报告
---
## 各成员 Round 1 初判
### BackendArchitect 初判
**Q1 初步判断**Plan A 后台批量生成 SKU **可行**。ShopXO 的 `goods_spec_base` 是标准 MySQL 表,插件可直接 INSERT。但需要确认
- ShopXO 商品保存时是否校验 spec_base 的 referential integrity
- 上万座位时批量 INSERT 的性能
- spec_base_id_map 中的 ID 是否需要与 ShopXO 内部 ID 对齐
**Q2 初步判断**:当前 broken 状态**暂不需要立即修复**。购买流程走的是裸商品逻辑is_exist_many_spec=0对 Phase 3 的购买流程设计反而是参考点——需要明确购买流程最终走哪条路后再修。
**Q4 初步判断**:倾向 **方案 A**。ShopXO 原生防超卖机制比自建锁更可靠DB 层面原子操作),且不破坏 ShopXO 生态完整性。
### FrontendDev 初判Q1-Q4 分析)
**Q1 分析:方案 A 批量生成 SKU 路径**
结论:**可行,但实现路径复杂。**
ShopXO spec_base 生成机制:
- 商品保存时,`GoodsService::Save()` 调用 `SpecService::Save()` 逐条写入 `sxo_goods_spec_base`
- **没有现成的批量 API** — 需要在插件初始化/商品绑定时,批量调用 `SpecService` 或直接 SQL INSERT
- 方案 A 的 SKU 数量 = 座位数(一场演唱会可能 10000+ 个座位)
- **前端配合**uni-app 需要维护 `seat_id → spec_base_id` 映射(已在 `spec_base_id_map` 中)
- **关键风险**:商品规格管理页面会显示 10000+ 行 SKU可能导致 ShopXO 后台崩溃
- **解决方向**:插件专用规格不出现在 ShopXO 原生规格管理页,通过 Hook 隐藏;建立独立的"座位 SKU 管理"页面
**Q2 分析:商品 112 broken state 最小修复集**
结论:**需要立即修复,推荐最小方案。**
根因:`is_exist_many_spec=0` 意味着 ShopXO 认为此商品无多规格spec_base 表自然为空(从未生成过 SKU
最小修复路径(不破坏现有数据):
1. 方案甲(最小侵入):在 `plugins_service_goods_save_end` Hook 中,检测商品有 `venue_data``$vr-` spec 存在时,强制将 `is_exist_many_spec` 设为 1但不写 spec_base 表(绕过 ShopXO spec 机制,完全走插件自定义逻辑)
2. 方案乙(规范做法):调用 `SpecService::Save()` 为每个座位生成一条 spec_base 记录inventory=1, price 从 seat_type 读取)
**推荐方案甲**(最小修复):
- 优势:无需重建 SKU不影响现有订单数据
- 代价:`is_exist_many_spec` 变成"脏 flag",但这是 ShopXO 的内部状态,插件不依赖它做业务
- 操作:一条 UPDATE + 一条 Hook 注入
**Q3 分析:$vr- 前缀隐患**
结论:**低风险,但需实测确认。**
ShopXO spec name 字段无字符过滤,数据库 `varchar` 类型允许 `$` 字符。潜在风险点:
- ThinkPHP 的 `__isset()` / 动态属性访问可能对 `$` 敏感(但 spec name 存 DB 而非 PHP 属性,低风险)
- 前端模板渲染时,`$vr-` 字符串可能触发 Vue/JS 的变量插值解析(`{{ $vr-场馆 }}`)—— **这是真实风险**
- ShopXO 原生规格管理页面可能将 `$` 视为特殊字符处理
**需要验证**uni-app 端 spec value 的渲染方式(是纯文本还是模板字符串?)
**Q4 最终推荐:方案 A vs 方案 B**
**推荐:方案 A每个座位一个 SPEC/SKU**
理由:
1. **安全性**ShopXO 原生原子扣库存防超卖,经过大量生产验证;方案 B 的自建 FOR UPDATE 锁在高并发下有死锁风险
2. **数据一致性**:方案 A 的 stock = 1ShopXO 购买流程自带事务保护;方案 B 的 Zone stock 需要插件自己维护一致性和并发安全
3. **多 Zone 混买**:方案 A 前端每 Zone 一个 goods_params 行,后端按 seat_id 原子购买,体验流畅;方案 B 前端分组但后端共享 Zone stock反而增加了前端分组逻辑的复杂度
4. **维护性**:方案 A 依赖 ShopXO 原生机制,故障排查有据可查;方案 B 是"黑盒",出问题只能靠插件自己
5. **$vr- 前缀**spec_base_id_map 的 key 可以是 seat_id无需改 ShopXO spec name 存储
**方案 B 的唯一优势**SKU 数量少Zone 数量 vs 座位数量),后台管理简单。但这个优势在演唱会 10000 座场景下不如安全和一致性重要。
### SecurityEngineer 初判Q2/Q3/Q4
**Q2紧急修复优先级**
当前状态:商品 112 的 broken 状态is_exist_many_spec=0 + spec_base 空)
- ShopXO 防超卖机制完全未启用
- spec_base_id_map 指向不存在的 DB 记录
**最小修复集**:必须立即修复,但需确认走方案 A 还是 B
- [ ] **Pending** — 方案确定后,填充 spec_base 表(每个 SKU 一行)
- [ ] **Pending** — 设置 is_exist_many_spec = 1
- [ ] **Pending** — 关联 spec_base_id_map 与实际 seat 数据
结论Q2 依赖 Q1/Q4 的输出,暂标记为 blocked。
**Q3$vr- 前缀安全隐患**
已知事实:
- ShopXO spec name 允许特殊字符($、-、中文均无过滤)
- ThinkPHP 模板引擎View可能对 $ 有变量插值行为
风险点:
- [ ] View 层Tpl 模板中 `{:$spec_name}` 是否会解析 $vr- 作为 PHP 变量?
- [ ] DB 层spec name 入库是否经过转义?
- [ ] API 层spec name 作为 JSON key 时是否安全?
结论需要代码验证Round 2 执行)。
**Q4方案 A vs B 最终推荐**
**初步倾向**:方案 A每个座位一个 SKU
理由:
1. 安全性ShopXO 原生原子扣库存,无需自建锁,超卖风险最低
2. 正确性:与 ShopXO SPEC 机制对齐is_exist_many_spec=1 时原生防超卖生效
3. 可追溯性:每个 SKU 独立订单项,核销链路清晰
---
### BackendArchitect Round 3 最终推荐Q1+Q2+Q4
**最终 Q4 推荐:方案 A每个座位一个 SKU**
基于 Round 2 完整分析,各问题最终结论:
**Q1 最终结论**:可行。必须旁路 `GoodsSpecificationsInsert()`(因为它每次都 DELETE+重建),走**直接 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 后台,无"管理困难"问题
**Round 3 行动项BackendArchitect**
| 优先级 | 行动项 |
|--------|--------|
| P0 | 创建 `SeatSkuService::BatchGenerate()` — 直接 SQL INSERT 批量生成 SKU |
| P0 | 执行 Q2 最小修复集:`UPDATE is_exist_many_spec` + `INSERT $vr- spec_type` |
| P1 | `TicketService::issueTicket()` 添加 `spec_base_id=0` 幂等保护 |
| P2 | 插件独立 SKU 管理页面(隔离 ShopXO 原生规格管理)← FrontendDev |
---
### BackendArchitect Round 2 深入分析Q1+Q2
详细分析见 `docs/ROUND2_ANALYSIS.md`。核心结论:
**Q1 结论:可行,但必须旁路 `GoodsSpecificationsInsert()`**
- ShopXO 的 `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`ShopXO 防超卖依赖此机制
- TOCTOU 窗口极小(选座模式并发低 + InnoDB 行锁),**推荐接受此风险**
- 性能10000 座位 = ~3-4 秒(需分批 500 条/批提交)
**Q2 结论:推荐方案乙(最小修复集)**
- `is_exist_many_spec=0` → 执行 `UPDATE goods SET is_exist_many_spec=1 WHERE id=112`
- 写入 `$vr-` 规格维度到 `sxo_goods_spec_type`
- 幂等保护:票生成逻辑已有 `spec_base_id` 冗余,不依赖 DB 引用
**Q4 初步推荐:方案 A**
- 原子性已验证BuyService dec 机制)
- 数据完整性高(每个座位 inventory=1
- 票务链路清晰spec_base_id → 座位直接映射)
- 方案 B 的"SKU 少"优势在演唱会 10K+ 场景不成立(插件自管,不走 ShopXO 后台)
### FrontendDev Round 2 深入分析Q3+Q4
**Q3 结论:$vr- 前缀安全**
- ThinkPHP `{$var}` 默认做 HTML 转义,$vr- 不会被解析为 PHP 变量
- `|raw` 仅跳过 HTML 转义,不会执行变量插值
- ShopXO spec name 存 DB 无过滤,但渲染层安全
**Q4 结论:推荐方案 A每个座位一个 SKU**
- ShopXO 原生 `BuyService.php:1677` 的 dec() 机制提供原子防超卖
- 当前 `submit()` 是 Plan B 模式specBaseIdMap 未接入
- 需重构 submit() 按 seat_id 分组,每组单独 spec_base_id
---
## 行动项(优先级排序)
| 优先级 | 行动项 | 负责 |
|--------|--------|------|
| P0 | 紧急修复商品 112 broken state | BackendArchitect |
| P1 | 实现方案 A 批量 SKU 生成 | BackendArchitect |
| P2 | 隔离 ShopXO 规格管理页面Hook 隐藏插件 SKU | FrontendDev |
---
## 共识投票
[CONSENSUS: YES] — BackendArchitect Round 3 完成。Q1-Q4 全部完成,三方一致推荐方案 A。等待 FrontendDev 输出最终 `council-output/ARCHITECTURE_DECISION.md`Council 全部完成。
---
## Round 3 安全审计结果(保留,仅供参考)
### Task S1 — Admin 鉴权覆盖完整性审查 ✅ 验证通过
### Task S2 — SQL 注入风险审计 ✅ 无注入风险
### Task S3 — XSS / CSRF 防护检查 ✅ 通过
### Task S5 — IDOR 水平越权检查 ✅ 通过
### Task S4 — 敏感操作审计日志设计 ✅ 设计完成