# 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 开发前解决,否则前端无法正确实现购票和票夹功能。