# Phase B：B端核销开发计划

> 创建时间：2026-04-25 10:26 CST
> 创建人：西莉雅
> 状态：✅ Council 执行 + 审阅完成（2026-04-25 13:13 CST）
> 分支：`feat/phase-b-verification`
> 审阅：3步 subagent 审阅 + 发现4处问题全部修完推送

---

## 一、背景

C端票夹 + 出票链路已完成（Phase 4.1-4.3）。
下一步：B端核销管理后台 + 安全修复。

**已知安全债务（Issue #7）：**
- M-01：verifyTicket TOCTOU → ✅ 已修复（FOR UPDATE + 事务）
- M-02：手动核销无鉴权 → 🔴 B端开发前必须修复
- M-03：ALTER TABLE 条件永不成立 → 🟢 快速修复（2行）
- M-04：loadSoldSeats 未实现 → ✅ 已实现
- M-05：verifier_id 客户端伪造 → 🔴 B端开发前必须修复
- M-06：Admin 控制器无权限校验 → 🔴 B端安全基线
- M-07：QR 明文暴露 ticket_code → 🟢 低风险，可后置
- M-08：issueTicket 二次写入 → ✅ 已修复

---

## 二、分阶段计划

### Phase B0 — 安全基线 ✅ 完成

> B0-1 + B0-2 + B0-3 由 subagent B0-SecurityFix 完成（commit: `f3d102e7a`）

**目标**：修复 M-06、M-02、M-05、M-03，建立 B端安全防线。

#### B0-1：Admin.php 权限校验（M-06）

在 Admin.php 所有操作方法（TicketVerify/TicketList/TicketExport/VerifierSave/VerifierDelete 等）开头加：

```php
public function TicketVerify()
{
    // M-06: 强制管理员登录校验
    if (empty(session('admin_id'))) {
        return DataReturn('无权限访问，请先登录后台', -1);
    }
    // ...原有逻辑
}
```

#### B0-2：verifier_id 改为 session 来源（M-05）

**现状**：`TicketVerify` 接收客户端传入的 `$verifier_id`，攻击者可伪造任意身份。

**修复**：改为从 session 获取当前 admin 对应的 ShopXO 用户ID，查 `vr_verifiers` 表获取 verifier_id。

```php
// 错误（可伪造）：
$verifier_id = input('verifier_id', 0, 'intval');

// 正确（从 session 获取）：
$admin_user_id = session('admin_user_info.id');  // ShopXO 后台登录用户ID
$verifier = \think\facade\Db::name('vr_verifiers')
    ->where('user_id', $admin_user_id)
    ->where('status', 1)
    ->find();
if (empty($verifier)) {
    return DataReturn('你不是核销员，无权核销', -1);
}
$verifier_id = $verifier['id'];
```

#### B0-3：ALTER TABLE bug 修复（M-03）

EventListener.php 中 `empty($result)` 对 PDOStatement 永远返回 false，ALTER TABLE 永不执行。

**文件**：`shopxo/app/plugins/vr_ticket/Event.php`（或 EventListener.php）

**修复**：改用 `count($result) === 0` 或 `empty($result->fetchAll())`。

---

### Phase B1 — 核心 B端核销页 ✅ 完成

> B1-1 + B1-2 + B1-3 由 subagent B1-CorePages 完成（commit: `d8c45fbb8`）

**目标**：建一个可用的扫码核销页面，优先满足现场核销需求。

#### B1-1：admin/view/ticket/verify.html（扫码核销主页面）

**功能**：
- 扫码按钮（HTML5 MediaDevices API，兼容 PC 摄像头）
- 手动输入短码/票码文本框
- 调用 `PluginsAdminUrl('vr_ticket', 'admin', 'TicketVerify')` API
- 展示核销结果（座位号、观演人姓名、商品名）
- 统计栏：今日核销数 / 待核销数 / 已核销总数

**注意**：需要先在 Hook.php 的 `AdminSidebarInit` 注册菜单项 `ticketVerify`。

#### B1-2：admin/view/ticket/list.html（电子票列表）

- 搜索：订单号/票码/观演人姓名/手机号
- 状态筛选：全部/未核销/已核销/已退款
- 商品筛选
- 每行显示：票码、观演人、座位信息、商品名、状态、时间
- 操作：查看详情

#### B1-3：admin/view/ticket/detail.html（票详情）

- 显示票完整信息（座位/观演人/QR码/发放时间）
- 显示核销记录（如已核销）

---

### Phase B2 — 辅助管理页 ✅ 完成

> B2-1 + B2-2 + B2-3 由 subagent B2-SupportPages 完成（commit: `a104f16f0`）

#### B2-1：核销员管理

- `admin/view/verifier/list.html`：核销员列表（关联 ShopXO 用户）
- `admin/view/verifier/save.html`：添加/编辑核销员（选择 ShopXO 用户作为关联）

#### B2-2：核销记录

- `admin/view/verification/list.html`：核销历史记录，支持按核销员/日期筛选

#### B2-3：座位模板管理

- `admin/view/seat_template/list.html`：座位模板列表
- `admin/view/seat_template/save.html`：座位模板编辑（参考现有的 view/venue/save.html 风格）

---

## 三、实施顺序

```
B0-1 → B0-2 → B0-3  （安全基线，并行）
    ↓
B1-1 → B1-2 → B1-3   （核心核销，并行或串行）
    ↓
B2-1 → B2-2 → B2-3   （辅助管理，并行）
```

---

## 四、技术约束（来自 EXPERIENCES.md）

1. **视图路径**：`MyView('../../../plugins/vr_ticket/admin/view/ticket/list', [...])`
2. **public_host**：控制器必须显式传递 `public_host` 给模板
3. **header/footer**：所有 admin 页面必须有 `{{:ModuleInclude('public/header')}}` 和 `{{:ModuleInclude('public/footer')}}`
4. **Vue 3 textarea**：禁止 `<textarea>[[ value ]]</textarea>`，用隐藏 input
5. **CDN**：禁止 unpkg/jsdelivr，用 `cdn.staticfile.net` 或 ShopXO 自带文件
6. **双目录陷阱**：修改 `app/plugins/vr_ticket/static/` 后必须同步到 `public/plugins/vr_ticket/static/`
7. **AmazeUI 类名**：列表页用 `am-table am-table-striped am-table-hover am-text-middle`
8. **PHP 注释**：确保所有 `/* ... */` 闭合，避免语法错误

---

## 五、验收标准（代码层）

- [x] M-06：无 session('admin_id') 无法调用任何 Admin.php 接口（审阅通过）
- [x] M-05：verifier_id 来自 session，不可伪造（审阅通过）
- [x] M-03：ALTER TABLE bug 修复（审阅通过）
- [x] TicketStats API 存在且权限校验正确（审阅通过）
- [x] B1-1：verify.html 有 stats AJAX + loadStats() + 扫码+核销功能（代码审阅通过，含修复）
- [x] B1-2：list.html 搜索/筛选/分页/操作按钮（审阅通过）
- [x] B1-3：detail.html 基础信息+条形码+核销记录（审阅通过，标签已修）
- [x] B2-1：verifier/list.html + save.html（审阅通过）
- [x] B2-2：verification/list.html（审阅通过）
- [x] B2-3：seat_template/list.html + save.html（审阅通过）
- [x] 所有页面 header/footer 完整（审阅通过）

> ⚠️ 以上均为代码层审阅通过。实际功能需在 ShopXO 后台部署后测试验证。（待实际测试）

---

## 六、待确认 & 待测试

1. **ShopXO 后台 session key**：需确认 `session('admin_user_info.id')` 是正确的 ShopXO 后台登录用户 ID key。
2. **Hook.php 菜单注册**：`ticket/verify.html` 已注册扫码核销菜单项，需在 ShopXO 后台确认菜单显示。
3. **TicketStats API**：`verify.html` 引用了 `PluginsAdminUrl('vr_ticket', 'admin', 'TicketStats')` 获取统计数据，该方法尚未在 Admin.php 中实现，需补充。
4. **QR 码展示**：`detail.html` 需确认 QR 码图片 URL 路径（`TicketService::getQrCodeUrl` 是否可被 admin 模板调用）。

## 七、Commit 记录

```
f3d102e7a feat(B0): M-06/M-05/M-03 security fixes
d8c45fbb8 feat(B1): ticket/verify + list + detail admin views
a104f16f0 feat(B2): verifier + verification + seat_template admin views
```
