vr-shopxo-plugin/plan.md

491 lines
21 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 Phase 2 — 后台管理开发计划
> 版本v2.0(合并版)| 制定日期2026-04-15 | Agentcouncil/FrontendDev + council/SecurityEngineer + council/BackendArchitect
## 概述
Phase 2 目标:完成后台管理页面开发,涵盖座位模板管理、电子票管理、核销员管理、核销记录查询,以及 Admin 控制器鉴权修复。
---
## 阶段划分
| 阶段 | 内容 | 负责 |
|------|------|------|
| Draft | 资料收集、研究方向确认 | 所有成员 |
| Review | 代码实现审查、安全评审 | SecurityEngineer + Council |
| Finalize | 合并到 main、文档整理 | 所有成员 |
---
## Agent 任务分配
| Agent | 主要任务 |
|-------|---------|
| BackendArchitect | API 设计、权限模型、数据库查询 |
| SecurityEngineer | Admin 鉴权、注入/XSS、审计、IDOR |
| FrontendDev | UI 框架选型、ShopXO admin 风格适配 |
---
## 任务清单
### 座位模板管理
- [x] 座位模板列表页(`seat_template/list.html``[Done: council/FrontendDev]`
- [x] 座位模板新增/编辑页(`seat_template/save.html``[Done: council/FrontendDev]`
- [ ] 座位图可视化编辑器集成
- [x] 分类绑定功能category_id 字段已在 save.html 中实现)`[Done: council/FrontendDev]`
### 电子票管理
- [x] 电子票列表页(`ticket/list.html``[Done: council/FrontendDev]`
- [x] 票详情页(`ticket/detail.html``[Done: council/FrontendDev]`
- [x] 批量导出功能CSV— 修复:导出按钮 GET→POST form `⚠️ Fixed Round 4`
- [x] 票状态筛选(未核销/已核销/已退款)`[Done: council/FrontendDev]`
### 核销员管理
- [x] 核销员列表页(`verifier/list.html``[Done: council/FrontendDev]`
- [x] 核销员新增/编辑/删除(`verifier/save.html``[Done: council/FrontendDev]`
- [ ] 核销员绑定店铺/场次
### 核销记录
- [x] 核销记录列表页(`verification/list.html``[Done: council/FrontendDev]`
- [x] 多条件查询(时间/核销员/场次)`[Done: council/FrontendDev]`
- [ ] 核销统计看板
### Admin 鉴权P1 安全)
- [x] 所有 Admin 控制器继承 Base controller `✓ Base extends Common (BackendArchitect)`
- [x] 鉴权中间件验证 `✓ SecurityEngineer S1 验证通过`
- [x] 敏感操作日志审计Task S4
### 后端 API 任务
- [x] **Task B1** — 座位模板管理 CRUD `[Done: council/BackendArchitect]`
- [x] **Task B2** — 电子票列表 / 详情 / 导出 `[Done: council/BackendArchitect]`
- [x] **Task B3** — 核销员管理(增删改查)`[Done: council/BackendArchitect]`
- [x] **Task B4** — 核销记录查询 `[Done: council/BackendArchitect]`
### 安全任务
- [x] **Task S1** — Admin 鉴权覆盖完整性 `[Done: council/SecurityEngineer]`
- [x] **Task S2** — SQL 注入风险审计 `[Done: council/SecurityEngineer]`
- [x] **Task S3** — XSS / CSRF 防护检查 `[Done: council/SecurityEngineer]`
- [x] **Task S4** — 敏感操作审计日志设计 `[Done: council/BackendArchitect]`
- [x] **Task S5** — IDOR / 水平越权测试用例编写 `[Done: council/SecurityEngineer]`
---
## Research Direction List合并版
### 安全研究方向SecurityEngineer
#### R1: Admin 控制器鉴权风险
**背景**Council 安全审议发现 P1 问题 —— Admin 控制器缺少统一鉴权机制Phase 2 所有后台页面均受影响。
Key Questions
- [ ] ShopXO 后台 admin 控制器统一鉴权入口在哪里?`AdminBaseController` 或中间件?
- [ ] Phase 2 新增的 Base 控制器(`app/common.php` 的 Base 类)是否已正确调用鉴权?
- [ ] 各子控制器SeatTemplate / Ticket / Verifier是否有遗漏的 public 方法暴露未授权访问?
- [ ] 鉴权失败后的跳转逻辑是否正确?是否存在认证绕过风险?
- [ ] 后台操作是否需要二次验证(如删除、核销等敏感操作)?
**优先级**P0
#### R2: SQL 注入风险评估
**背景**:电子票导出、核销记录查询等涉及复杂 SQL必须防范注入。
Key Questions
- [ ] ShopXO 原生查询构造器Db::table / Db::name的参数绑定机制是否覆盖所有输入点
- [ ] 日期范围查询、模糊搜索等动态构造场景是否存在拼接风险?
- [ ] IN() 子句的数组参数是否经过安全处理?
- [ ] 关联查询join中是否有注入向量
- [ ] 是否有 ORM 之外的原始 SQL 执行需要审查?
**优先级**P1
#### R3: XSS / CSRF 风险
**背景**:后台管理页面输出到管理端,但仍需防范存储型 XSS 和 CSRF。
Key Questions
- [ ] 后台页面渲染时是否统一使用 htmlspecialchars 或框架转义?
- [ ] 富文本编辑(如座位模板名称备注)是否存在存储型 XSS
- [ ] POST 请求是否均携带 CSRF TokenShopXO 的 CSRF 保护机制是什么?
- [ ] JSON API 响应是否可能携带恶意脚本?
- [ ] 删除/核销等关键操作是否有 CSRF Token 保护?
**优先级**P1
#### R4: 敏感操作审计日志
**背景**:核销操作、票务导出等属于高敏感操作,必须可追溯。
Key Questions
- [ ] 是否需要新建 `vr_audit_log` 表记录关键操作?
- [ ] 核销记录是否需要记录操作人 IP、UA、设备指纹
- [ ] 导出操作是否需要记录?谁、何时、导出内容摘要?
- [ ] 审计日志是否需要防篡改设计(如 hash chain 或 append-only 表)?
- [ ] ShopXO 是否有现有审计日志机制可以复用?
**优先级**P2
#### R5: 水平越权风险IDOR
**背景**:核销员管理、电子票详情等场景存在横向越权风险。
Key Questions
- [ ] 核销员详情/编辑接口是否校验当前用户所属机构/场馆?
- [ ] 电子票详情接口是否校验持票人身份?
- [ ] 核销记录查询是否仅返回当前核销员/机构的记录?
- [ ] 删除/编辑操作是否有归属校验owner check
- [ ] 是否有测试用例覆盖常见的 IDOR 攻击向量?
**优先级**P1
---
### 前端研究方向FrontendDev
#### FR-1: ShopXO Admin UI 框架选型
**背景**ShopXO 后台使用 Layui需确认是否继续使用还是迁移 Vue。
Key Questions:
- ShopXO 官方后台v6.8.0)使用的是什么 UI 版本?
- Layui 是否支持 Vue 3如果不支持混用 Vue + Layui 是否会导致冲突?
- 票务插件是否应保持与 ShopXO 原生风格一致,还是可以独立升级?
- 是否有 ShopXO 插件使用 Vue 3 的先例?
#### FR-2: 现有 ShopXO Admin 页面风格适配
**背景**:保持与 ShopXO 原生后台风格一致可降低学习成本。
Key Questions:
- ShopXO 后台使用的是什么设计系统(颜色/字体/间距规范)?
- 表格组件(数据列表)用的是 Layui table 还是自建?
- 分页、筛选、搜索的通用组件封装在哪里?
- 弹窗/表单布局的规范是什么?
#### FR-3: 座位图编辑器集成方案
**背景**:座位模板需要可视化编辑,复杂度高。
Key Questions:
- 是否有开源的 Vue 座位图编辑器可以集成?
- Canvas vs SVG vs CSS Grid哪个方案最适合票务座位图
- 座位图编辑后如何序列化存储到 seat_map JSON
- 编辑器是否需要支持拖拽、分区着色、座位类型标注?
#### FR-4: 数据导出方案CSV/Excel
**背景**:电子票列表需要支持批量导出。
Key Questions:
- ShopXO 后台是否有现成的导出组件?
- 大量数据10000+ 条)导出的处理策略是什么(流式导出 vs 后台队列)?
- 是否需要支持 Excel 格式(.xlsx还是只需 CSV
- 导出字段如何与 vr_tickets 表字段对应?
#### FR-5: 响应式与权限控制
**背景**:后台页面需要同时支持不同屏幕和权限级别。
Key Questions:
- ShopXO 后台的权限体系是如何设计的RBAC按钮级字段级
- 票务管理员是否需要独立的角色?与 ShopXO 管理员如何隔离?
- 后台页面是否需要支持移动端PAD 核销场景)?
- 操作日志记录哪些字段(用户/时间/操作/IP/变更前后)?
---
### 后端研究方向BackendArchitect
#### BR-1: 后台 API 设计 — ThinkPHP 8 控制器层规范
**背景**ShopXO 基于 ThinkPHP 8后台控制器需遵循其 Request/Response 约定,否则无法与现有 auth middleware 配合。
Key Questions:
- [ ] ShopXO Admin 控制器基类是什么?是否有统一的 `BaseAdmin``AdminController`
- [ ] ThinkPHP 8 的 `Request` 对象如何获取当前登录 admin id / role
- [ ] API 返回格式是统一 JSON 还是沿用 ShopXO 的 `Json` 渲染器?
- [ ] 分页参数命名规范ShopXO 默认用 `page`/`limit` 还是 `page`/`pageSize`
- [ ] 新增 controller 是否需要在某个注册处声明(路由/菜单)?
#### BR-2: 权限模型 — 多角色鉴权设计
**背景**:核销员(vr_verifiers)与超级管理员属于不同角色,后台功能需要细粒度权限控制。
Key Questions:
- [ ] ShopXO admin 用户表(`sx_admin`)的 role 字段结构是怎样的?是 RBAC 吗?
- [ ] 核销员是否复用 admin 表,还是独立表(`vr_verifiers`)?各自权限如何隔离?
- [ ] 座位模板编辑 / 电子票导出 / 核销记录查看是否需要分开授权?
- [ ] 是否有现成的权限中间件可以复用,还是需要自行实现?
#### BR-3: 数据库查询优化 — 大数据量导出场景
**背景**:电子票列表 + 核销记录在数据量大时有分页和内存压力。
Key Questions:
- [ ] `vr_tickets` / `vr_verifications` 当前预估数据规模?是否需要游标分页?
- [ ] 导出功能CSV/ExcelPHP 侧是否需要流式输出避免 OOM
- [ ] `vr_verifications``verified_at` 字段是否建索引?
- [ ] 是否需要读写分离ShopXO 数据层是否支持?
- [ ] 是否有批量查询 N+1 问题(如查询票时重复查 holder info
#### BR-4: 事务一致性 — 核销操作的原子性
**背景**:核销涉及两张表(`vr_verifications` 写入 + `vr_tickets.status` 更新),若并发核销同一票会导致重复核销。
Key Questions:
- [ ] ThinkPHP 8 Db facade 的事务写法(`Db::transaction()`)如何与 Model 层配合?
- [ ] 是否需要悲观锁(`SELECT ... FOR UPDATE`)防止并发核销?
- [ ] 乐观锁version 字段)是否适用于高并发核销场景?
- [ ] 核销失败后的补偿逻辑如何设计?
#### BR-5: 存储过程 / 事件驱动 — 核销通知异步化
**背景**:核销操作可能触发通知(站内信/微信),同步执行会阻塞核销响应。
Key Questions:
- [ ] ShopXO 是否有事件/队列机制Redis/DB queue
- [ ] ThinkPHP 8 的 `Queue::later()``event()` 是否可用?
- [ ] 若无队列,核销通知是否可以降级为同步日志+后台任务补偿?
- [ ] 是否需要记录核销操作的 event log 供后续审计?
---
## 依赖关系
- **FR-1、FR-2** 优先完成,决定前端技术栈选型
- **FR-3** 依赖 FR-1 的选型结论
- **FR-4** 可在 Phase 3 后端 API 确定后并行进行
- **FR-5** 与 SecurityEngineer 协同,需要等 BackendArchitect 输出权限模型
- **Task S1**(鉴权审查)需在 BackendArchitect 完成 API 设计初稿后进行交叉评审
- **Task S2**SQL 审计)可与 BackendArchitect 的数据库设计并行
- **Task S4**(审计日志)依赖最终的数据模型设计
- **BR-1**ThinkPHP 控制器规范)与 **FR-1** 交叉确认 API 返回格式
- **BR-2**(权限模型)与 **FR-5** 协同,需 BackendArchitect 和 SecurityEngineer 共同输出
---
## 共识投票
[CONSENSUS: NO] — 本轮仅完成研究讨论,实际执行待后续阶段
---
## Round 3 安全审计结果SecurityEngineer
### Task S1 — Admin 鉴权覆盖完整性审查 ✅ 验证通过
**审查方法**:读取 main 合并结果 + ShopXO Common.php 源码
#### 鉴权链分析
```
ThinkPHP 路由 → admin.php (module=admin)
→ Common::__construct()
1. AdminService::LoginInfo() ← 填充 $this->admin从 session
2. AdminPowerService::PowerMenuInit()
3. ViewInit()
→ 插件控制器(如 Ticket/list
→ Base extends Common → parent::__construct()
→ 完整继承上述 3 步
```
**结论**
-`Base extends Common` — 登录检查和权限菜单已正确初始化
- ✅ 所有子控制器SeatTemplate / Ticket / Verifier / Verification通过 `extends Base` 自动获得鉴权
- ✅ BackendArchitect 的 P0 修复Base extends Common已合并到 main**Task S1 验证通过**
**Defense-in-Depth 建议**(非阻塞):
> 在 `Base::__construct()` 末尾显式调用 `$this->IsLogin()`,确保即使未来有人重写 `__construct()` 也不会绕过鉴权
---
### 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 执行