# BackendReviewer — VR票务补充文档审查报告

> 审查时间：2026-05-14
> 审查人：BackendReviewer（后端架构师视角）
> 文档：`vr-ticket-uniapp-supplement.md`

---

## 文档评分：7/10

文档结构完整，覆盖了核心数据结构、API 接口、交互规范。但存在若干后端实现与文档描述不一致的问题，以及缺少关键实现细节。

---

## 一、发现的问题

### 问题 1：QR payload 结构错误（P0 高优先级）

**文档描述**（第 6.1 节）：
```json
{ "id": 482815, "g": 118, "iat": 1745286000, "exp": 1745287800 }
```

**实际后端**（`TicketService.php:191-197`）：
```php
$qr_payload = BaseService::signQrPayload([
    'id'   => $ticket_id,
    'g'    => $og['goods_id'],
    'code' => $ticket_code,   // ← 文档漏掉了这个字段！
    'iat'  => $now,
    'exp'  => $now + 1800,
]);
```

**影响**：前端按文档解析 QR payload 会缺少 `code` 字段，导致：
- 无法通过 QR 拿到 ticket_code（核销时可能需要）
- QR 验签逻辑可能失败（后端用 `code` 生成签名）

**修复建议**：文档第 6.1 节 QR payload 应补充 `code` 字段：
```json
{
  "id": 482815,
  "g": 118,
  "code": "uuid格式-xxx-xxx",
  "iat": 1745286000,
  "exp": 1745287800
}
```

---

### 问题 2：Auth 鉴权机制描述不准确（P1）

**文档描述**（第 2.3 节）：
> 无参数（依赖 C 端 session）

**实际后端**（`api/Ticket.php:24-57`）：
```php
// 支持三种方式：
// 1. X-Token header
// 2. Authorization: Bearer xxx header
// 3. cookie user_info
// 4. ShopXO 标准 session
```

**影响**：前端需要明确使用哪个 Auth 方式。UniApp 中推荐使用 `X-Token` header。

**修复建议**：文档第 2.3 节补充鉴权说明：
```
请求 Header: X-Token: {user_token}
响应 401: 未登录，跳转登录页
```

---

### 问题 3：CartSave extension_data 存储链路不清晰（P1）

**文档描述**（第 2.2 节）：
```json
"extension_data": "{\"attendee\":{...},\"seat\":{...}}"
```

**实际后端**：
1. `CartSave` 将 `extension_data` 存入订单主表 `order.extension_data`
2. `onOrderPaid` Hook 从 `order.extension_data` 读取 `attendee` 信息
3. **但**观演人信息实际上是多座位时每座一个，而不是整个订单共用一个

**问题**：当用户选多个座位时，每个座位的 `extension_data` 独立存储在各自的 `order_detail` 中，而不是订单主表。后端需要知道每个座位对应哪个观演人。

**实际实现**（待确认）：ShopXO BuyService 是否支持将 `extension_data` 写入 `order_detail` 表？

**修复建议**：文档需要明确：
- `extension_data` 写入位置（order 主表 vs order_detail 行）
- 多座位场景下，每个 `goods_data` 条目携带各自的 `extension_data`
- 后端 TicketService 如何从 `order_detail` 获取每座的 attendee 信息

---

### 问题 4：seatSpecMap 获取方式未确定（P1）

**文档**（第 4.5 节）：
> 待确认：seatSpecMap 获取方式
> - 方案 A（推荐）：后端在商品详情 API 中直接返回
> - 方案 B：前端 JS 重建（不推荐）
> - 方案 C：新增独立接口

**实际情况**：
- `SeatSkuService::GetGoodsViewData()` 是 PHP 方法，UniApp 无法直接调用
- 商品详情 API `/api/goods/detail` 默认不返回 `seatSpecMap`
- 后端需要改造：在商品详情响应中嵌入 `seatSpecMap`

**修复建议**：文档应明确推荐方案 A，并列出后端改造任务：
```
后端任务：在 GoodsService 或插件 hook 中，
当 goods.is_vr_ticket=1 时，在商品详情响应中注入 seatSpecMap
```

---

### 问题 5：票夹列表响应字段不完整（P2）

**文档描述**（第 2.3 节）：
```json
{
  "id": 482815,
  "goods_id": 118,
  "goods_title": "VR演唱会",
  "seat_info": "主要展厅 A区 1排1座",
  "session_time": "15:00-16:59",
  "venue_name": "测试场馆",
  "real_name": "张三",
  "verify_status": 0,
  "issued_at": "2026-05-01 12:00:00",
  "short_code": "003a2hgmgety"
}
```

**实际后端**（`WalletService::getUserTickets`）：
- `seat_info` 格式：`场次|场馆|演播室|分区|座位号`（5段，用竖线分隔）
- 需要确认返回字段是否与文档一致

**修复建议**：补充 `qr_data` 字段说明（可选，用于直接展示 QR 图）

---

## 二、关键 Gap（前端开发前必须确认）

### Gap 1：seatSpecMap 返回方式（P0）

**问题**：前端无法获取座位规格映射表，导致选座功能无法实现。

**必须解决**：后端需要在商品详情 API 响应中返回 `seatSpecMap`，或在插件中新增专用接口。

**截止时间**：Phase 2 开发前必须完成

---

### Gap 2：CartSave extension_data 传递链路（P0）

**问题**：多座位下单时，每个座位的观演人信息如何传递到后端？

**必须解决**：确认 ShopXO BuyService 如何处理 `extension_data`：
- 写入 `order.extension_data` 还是 `order_detail`？
- 多座位场景下每座独立的 extension_data 如何存储？

**截止时间**：Phase 3 开发前必须完成

---

### Gap 3：QR payload 验签逻辑（P1）

**问题**：前端需要验证 QR payload 的有效性（含 `code` 字段的签名验证）

**必须解决**：
1. 文档更新 QR payload 结构（补充 `code` 字段）
2. 后端提供签名字段 `sig` 的生成方式（HMAC-SHA256）
3. 前端需要知道 secret 从哪里获取（后端提供接口？）

**截止时间**：票详情页开发前必须完成

---

## 三、改进建议（具体的、可操作的）

### 建议 1：更新 QR payload 文档（第 6.1 节）

```markdown
### 6.1 QR 票数据结构

```json
// QR payload（Base64 编码）
{
  "id": 482815,
  "g": 118,
  "code": "uuid格式-xxx-xxx",
  "iat": 1745286000,
  "exp": 1745287800
}

// 签名：sig = HMAC-SHA256("code.id.g.iat.exp", secret) 取前8位
```
```

### 建议 2：补充 Auth 鉴权说明（第 2.3 节）

```markdown
### 票夹 API

**请求 Header**：
```
X-Token: {user_token}
```
（也支持 Authorization: Bearer xxx，ShopXO session cookie）

**响应 401**：未登录，跳转登录页
```

### 建议 3：明确后端待配合事项（第 9 节）

```markdown
## 九、后端待配合事项

1. **seatSpecMap 返回**：在商品详情 API 响应中嵌入 `seatSpecMap`
   - 触发条件：`goods.is_vr_ticket = 1`
   - 数据来源：`SeatSkuService::buildSeatSpecMap()`
   - 预计工作量：2h

2. **CartSave 链路确认**：
   - 确认 extension_data 是否写入 order_detail
   - 多座位场景下每座独立的 attendee 信息如何存储

3. **QR 签名字段说明**：
   - 提供前端验签所需的 secret 获取方式
   - 或提供后端验签接口
```

---

## 四、对前端开发者的建议（3条）

### 1. 优先确认 seatSpecMap 接口

在开始选座功能开发前，必须与后端确认：
- seatSpecMap 通过哪个 API 获取？
- 返回的数据结构是否与文档一致？

**建议**：先让后端在商品详情 API 中返回 seatSpecMap，再开始前端开发。

---

### 2. Auth 统一使用 X-Token

UniApp 中统一使用 `X-Token` header 进行用户鉴权：
```javascript
uni.request({
  url: '...',
  header: {
    'X-Token': uni.getStorageSync('token')
  }
})
```

避免混用 Authorization 或 cookie。

---

### 3. QR 验签先做本地过期检查

前端先做本地过期检查，再考虑签名验证：
```javascript
const payload = JSON.parse(atob(qr_data.split('|')[1]));
if (payload.exp < Date.now() / 1000) {
  // 已过期，调用 refreshQr 接口获取新 QR
}
```

签名验证（sig 字段）可延后实现，先确保核心功能可用。

---

## 五、审查总结

| 维度 | 评分 | 说明 |
|------|------|------|
| 数据结构 | 9/10 | 描述清晰，与后端一致 |
| API 接口 | 6/10 | 存在不一致和缺失（QR payload、Auth） |
| 交互规范 | 8/10 | 详细，可直接参考 |
| 实现细节 | 5/10 | 缺少关键链路说明（extension_data） |

**综合评分：7/10**

文档整体质量良好，但 API 部分需要后端确认和修正，特别是：
1. QR payload 结构（缺少 code 字段）
2. Auth 鉴权机制（需明确 X-Token 方式）
3. extension_data 存储链路（多座位场景）

这三个问题必须在 Phase 2 开发前解决，否则前端无法正确实现购票和票夹功能。