vr-shopxo-plugin/plan.md

305 lines
12 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.0 | 制定日期2026-04-15 | Agentcouncil/BackendArchitect
> 关联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 防超卖
---
## 阶段划分
| 阶段 | 内容 | 负责 |
|------|------|------|
| Round 1 | 独立评议 + plan.md 合并 | 所有成员 |
| Round 2 | 各成员深入分析(后台实现路径、安全评估、前端方案) | 所有成员 |
| Round 3 | 综合推荐 + 输出最终决策报告 | 所有成员 |
---
## Agent 任务分配
| Agent | 主要评议方向 |
|-------|------------|
| BackendArchitect | Q1Plan A 后台批量 SKU 生成可行性)+ Q4最终推荐 |
| SecurityEngineer | Q3$vr- 前缀安全风险)+ Q4安全性维度 |
| FrontendDev | 前端方案 A/B 的实现差异 + Q4前端实现成本 |
---
## 任务清单
### Q1 — Plan A 后台批量生成 SKU 路径评估 `[Pending: BackendArchitect]`
- [ ] 分析 ShopXO spec_base 表写入路径
- [ ] 确认是否需要修改 ShopXO 核心代码还是通过插件可完成
- [ ] 评估批量生成的性能(上万座位场景)
- [ ] 给出可行性结论
### Q2 — 商品 112 broken 状态紧急修复 `[Pending: BackendArchitect]`
- [ ] 评估 is_exist_many_spec=0 + spec_base 空的实际影响
- [ ] 确定最小修复集(是否需要立即修复)
- [ ] 制定修复方案
### Q3 — $vr- 前缀安全评估 `[Pending: SecurityEngineer]`
- [ ] 检查 ShopXO 对带 $ 字符 spec name 的处理逻辑
- [ ] 识别潜在冲突或注入风险
- [ ] 给出安全结论
### Q4 — 方案 A vs 方案 B 最终推荐 `[Pending: all]`
- [ ] BackendArchitect从实现成本、ShopXO 原生机制利用角度评议
- [ ] SecurityEngineer从防超卖安全性角度评议
- [ ] FrontendDev从前端复杂度角度评议
### 最终输出
- [ ] `council-output/ARCHITECTURE_DECISION.md` — 汇总三方推荐 + 最终结论Round 3
---
## 依赖关系
- Q1BackendArchitect先完成后 Q4 才能给出完整推荐
- Q3SecurityEngineer可与 Q1 并行
- Q2 可独立完成,紧急程度由 BackendArchitect 判定
---
## Claim 状态
| 任务 | Claim 状态 |
|------|-----------|
| Q1 | [Pending: BackendArchitect] |
| Q2 | [Pending: BackendArchitect] |
| Q3 | [Pending: SecurityEngineer] |
| Q4 | [Pending: all] |
| 最终输出 | [Pending: all] |
---
## 本轮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 生态完整性。
---
### Task S2 — SQL 注入风险审计 ✅ 无注入风险
**审查范围**Phase 2 所有控制器 + TicketService
| 控制器 | 查询点 | 输入处理 | 结论 |
|--------|--------|----------|------|
| SeatTemplate::list | `name` like, `status` | `null` + `intval()` | ✅ 安全 |
| Ticket::list | `keywords` multi-field like | `trim()` + 查询构造器绑定 | ✅ 安全 |
| Ticket::verify | `ticket_code`, `verifier_id` | `trim()` + `intval()` | ✅ 安全 |
| Verification::list | date range | `strtotime()` 绑定 | ✅ 安全 |
**无原始 SQL 执行,无字符串拼接注入。**
⚠️ **P1 Bug已修复**`Verifier.php:45` ThinkPHP `column()` 不支持直接传 CONCAT SQL已改用 `select()` + PHP 拼接
---
### Task S3 — XSS / CSRF 防护检查 ✅ 通过
| 方面 | 状态 |
|------|------|
| CSRF Token (POST) | ✅ ShopXO 保护 |
| XSS存储型 | ✅ 低风险admin 上下文) |
| 关键操作 guard | ✅ `delete()` / `verify()` / `export()` 均有 `IS_AJAX_POST` 检查 |
---
### Task S5 — IDOR 水平越权检查 ✅ 通过
Admin 上下文(所有控制器需登录 admin + 插件菜单权限)下访问控制正确。
---
### 安全任务更新
- [x] **Task S1** — Admin 鉴权覆盖完整性 — `[Done: council/SecurityEngineer]`
- [x] **Task S2** — SQL 注入风险审计 — `[Done: council/SecurityEngineer]`
- [x] **Task S3** — XSS / CSRF 防护检查 — `[Done: council/SecurityEngineer]`
- [x] **Task S5** — IDOR / 水平越权测试用例编写 — `[Done: council/SecurityEngineer]`
- [x] **Task S4** — 敏感操作审计日志设计 — `[Done: council/BackendArchitect]`
---
## Task S4 — 敏感操作审计日志设计 ✅ 设计完成
**表结构**`vr_audit_log` 已在 `EventListener.php` 中定义第99-121行字段如下
| 字段 | 类型 | 说明 |
|------|------|------|
| `action` | VARCHAR(60) | 操作类型verify/export/refund/disable/enable/delete |
| `operator_id` | BIGINT | 操作用户IDadmin |
| `operator_name` | VARCHAR(90) | 操作用户名(冗余) |
| `target_type` | VARCHAR(60) | 对象类型ticket/verifier/seat_template |
| `target_id` | BIGINT | 对象ID |
| `target_desc` | VARCHAR(255) | 对象描述(冗余,便于查询) |
| `client_ip` | VARCHAR(45) | 客户端IP支持IPv6 |
| `user_agent` | VARCHAR(512) | User-Agent |
| `request_id` | VARCHAR(64) | 请求追踪IDUUID |
| `extra` | LONGTEXT | 附加数据JSON变更前后快照 |
| `created_at` | INT UNSIGNED | 操作时间戳 |
**索引**`idx_action` / `idx_operator_id` / `idx_target(target_type,target_id)` / `idx_created_at`
**AuditService 接口设计**(待 Phase 3 实现):
```php
// service/AuditService.php
class AuditService
{
// 记录操作
public static function log($action, $target_type, $target_id, $extra = []);
// 自动从 Common 控制器获取 admin 上下文
private static function getAdminContext();
// 生成请求追踪ID
private static function makeRequestId();
}
```
**集成点**Phase 3 实现):
| 控制器 | 方法 | action 值 | extra 快照 |
|--------|------|-----------|-----------|
| Ticket | `verify()` | `verify` | verify_status=0→1, verifier_id |
| Ticket | `export()` | `export` | goods_id, count |
| Ticket | `refund()` | `refund` | verify_status=0→2 |
| Verifier | `delete()` | `disable_verifier` | verifier_id, name |
| Verifier | `save()` | `enable_verifier` | verifier_id, name |
| SeatTemplate | `save()` | `edit_template` | template_id, name |
| SeatTemplate | `delete()` | `disable_template` | template_id, name |
> **防篡改策略**:表为 append-only不提供 UPDATE/DELETE 接口;`operator_name` 冗余存储防止审计日志与 admin 表不同步时丢失身份。
---
## BackendArchitect Round 4 — P1 Bug Fix
### Verification.php:55 — `column()` 多字段映射 BugP1 已修复)
**问题**`ThinkPHP column()` 不支持多字段映射,`column('seat_info,real_name,goods_id', 'id')` 返回结构与代码预期不符,导致核销记录列表页 `seat_info` / `real_name` / `goods_id` 为空。
**修复**:改用 `select()` + PHP foreach 拼接为 `$tickets[id] => row` 关联数组。
**文件**`admin/controller/Verification.php` 第51-63行
---
## FrontendDev Round 4 — P1 Bug Fix
### ticket/list.html — 导出按钮 IS_AJAX_POST 不匹配 BugP1
**问题**`ticket/list.html:35` 导出按钮为 `<a>` 链接GET 请求),但 `Ticket.php:export()` 要求 `IS_AJAX_POST`,导致点击"导出CSV"按钮返回"非法请求"错误。
**修复**
- 视图:`ticket/list.html` 第35行 → `<a>` 链接改为 `<button type="button" id="export-btn">`JS 动态创建 `<form method="post">` 提交
- 控制器:`Ticket.php:export()` 保留 `IS_AJAX_POST` 检查不变(保持安全),注释更新说明 POST-only 设计
**文件**`admin/view/ticket/list.html` 第35行 + 第92-98行
---
## Issue #9 — 架构决策:票务 SKU 方案评议
> 核心问题VR 演唱会票务"每个座位一个 SKU"(方案 Avs "每个 Zone 一个 SKU"(方案 B
### 评议问题清单
| # | 问题 | 优先级 |
|---|------|--------|
| Q1 | 方案 A 后台批量生成 SKU 路径是否可行ShopXO 是否有批量 API | P0 |
| Q2 | 当前商品 112 的 broken 状态is_exist_many_spec=0 + spec_base 空)是否需要紧急修复?最小修复集? | P0 |
| Q3 | $vr- 前缀方案是否有隐患ShopXO 内部是否对 $ 有特殊处理? | P1 |
| Q4 | 方案 A vs 方案 B 最终推荐(实现成本 / 安全性 / 可维护性) | P0 |
### 任务分配
| Agent | 负责问题 | 行动项 |
|-------|---------|--------|
| SecurityEngineer | Q2/Q3/Q4 | 安全评估 + 最终推荐 |
| BackendArchitect | Q1 | SKU 生成路径分析 |
| FrontendDev | Q4 | 前端实现成本评估 |
### 阶段计划
- [ ] **Round 1**(本轮):各 Agent 独立分析 → 更新 plan.md → 合并到 main
- [ ] **Round 2**:执行研究(代码探索 + 分析)→ 输出分析报告到 plan.md
- [ ] **Round 3**Cross-review → 汇总 → 写入 `council-output/ARCHITECTURE_DECISION.md`
### 安全工程师分析SecurityEngineer
#### 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 独立订单项,核销链路清晰
**需 Round 2 验证**
- 方案 A 后台 SKU 批量生成是否可行BackendArchitect 输出)
- $vr- 前缀在 View 层是否安全SecurityEngineer 验证)
### 行动项(优先级排序)
1. **[Claimed: council/SecurityEngineer]** Q3 — 验证 $vr- 前缀在 ThinkPHP View 层是否安全Round 2
2. **[Claimed: council/SecurityEngineer]** Q2 — 方案确定后给出最小修复集Round 3
3. **[Pending]** Q4 — 综合输出最终推荐报告Round 3
---
## 共识投票
[CONSENSUS: NO] — Phase 2 收尾Issue #9 架构决策研究待 Round 2 执行