# Council 评估报告 — BackendArchitect(Round 4 现场核查) > 评估日期:2026-05-26 | 角色:后端架构师 | Git: `0d6d20062` → 提交中 --- ## 一、现状评估(Round 4 现场核查) ### 1.1 Phase 4 Tree API 设计 **状态:📋 设计文档存在,代码为零** | 组件 | 状态 | 说明 | |------|------|------| | `docs/PHASE_4_API.md` | ✅ 存在 | Tree API 设计文档 | | `docs/PLAN_TREE_API_IMPLEMENTATION.md` | ✅ 存在 | 实现计划 | | `SeatMapService.php`(服务类) | ✅ **存在且完整** | 333行,含 `GetSeatMap()` + `buildSeatSpecMap()` + `buildGoodsSpecData()` | | `api/Goods.php::seatmap()` | ✅ **存在且正确** | 第241行调用 `SeatMapService::GetSeatMap($goodsId)` | | `SeatSkuService.php` | ✅ 存在 | 独立服务,含 `BatchGenerate()` + `GetGoodsViewData()` + `buildSeatSpecMap()` | | Tree API `buildTree()` | ❌ 代码为零 | Phase 4 设计中的核心方法未实现 | **Round 4 修正**:设计文档中提到的 `SeatMapService` 类**在父仓库已存在且完整**,`api/Goods.php::seatmap()` 路由已正确调用它。Round 1-3 的"P0 崩溃"分析是误判。 ### 1.2 SeatMapService + seatmap API **状态:✅ 已完整实现** | 组件 | 状态 | 位置 | |------|------|------| | `SeatMapService.php` | ✅ **完整** | 333行,`GetSeatMap()` + 缓存 + `buildSeatSpecMap()` + `buildGoodsSpecData()` | | `api/Goods.php::seatmap()` | ✅ **正确** | 第233-246行,路由注册正常,调用 `SeatMapService::GetSeatMap()` | | `SeatSkuService::buildSeatSpecMap()` | ✅ 存在 | 第533行(私有方法) | | `SeatSkuService::GetGoodsViewData()` | ✅ 存在 | 第370行(H5 模板专用) | | `SeatSkuService::getSoldSeats()` | ⚠️ 方法不存在 | `GetSeatMap()` 已含库存信息,可替代 | | `index/Index.php::soldSeats` | ❌ **不存在** | `Index.php` 只有 `wallet()` 方法,无 `soldSeats` | **Round 4 修正**: - Round 3 报告称"Index.php:43 调用 getSoldSeats()"——**这是误判**。`Index.php` 只有 `wallet()` 方法,无 `soldSeats` action。 - `SeatMapService::GetSeatMap()` 已完整实现,含实时 `inventory` 字段(0=已售),可替代 `getSoldSeats()`。 - **无运行时崩溃**,seatmap API 工作正常。 ### 1.3 seatSpecMap 注入商品详情 API **状态:⚠️ Gap 1 成立,但有变通方案** | 组件 | 状态 | 说明 | |------|------|------| | `SeatSkuService::GetGoodsViewData()` | ✅ 存在 | 第370行,H5 模板专用 | | `Hook.php::plugins_service_goods_data` | ❌ **未注册** | Hook.php 无此 case | | `api/Goods.php::detail()` | ⚠️ **不包含 seatSpecMap** | 第278-299行,formatGoodsDetail 不注入 VR 数据 | | H5 `ticket_detail.html` | ✅ **工作正常** | 直接调用 `GetGoodsViewData()` | | UniApp `api/goods/detail` | ❌ **Gap 1 成立** | 无 Hook 注入,无 VR 数据 | **Gap 1 分析修正**: - **Gap 1 对 UniApp 仍然成立**(Hooks 未注册) - 但 `api/Goods.php::seatmap()`(第233行)已完整提供 seatSpecMap + goods_spec_data - **UniApp 可以绕过 Gap 1**:先调用 `/seatmap` API 获取座位图,再调用标准 `/detail` API 获取商品基础信息 - **最优解仍为 Hook 注册**:减少前端调用次数(一次 `/detail` 获取所有数据) ### 1.4 CartSave extension_data 多座位链路 **状态:✅ H5 已验证,后端无需改动** | 组件 | 状态 | 说明 | |------|------|------| | `ticket_detail.html:762` 订单提交 | ✅ 已实现 | `extension_data` 嵌套在 `order_base` | | `TicketService::onOrderPaid()` | ✅ 已实现 | 逐行生成票(多座位支持) | | Gap 2 状态 | ✅ **已消除** | 后端链路完整,UniApp 复刻 JSON 格式即可 | --- ## 二、发现问题(Round 4 修正) ### P0(重新评估) | # | 问题 | 严重度 | Round 3 对比 | |---|------|--------|-------------| | P0-1 `getSoldSeats()` 方法缺失 | ❌ **已消除** | `SeatMapService::GetSeatMap()` 已含库存,`Index.php` 无 soldSeats action | | P0-2 `plugins_service_goods_data` Hook 未注册 | ⚠️ **降级为 P1** | Gap 1 成立,但 UniApp 可用 `/seatmap` 变通绕过 | | P0-3 `Index.php:soldSeats` 触发 Fatal Error | ❌ **已消除** | Index.php 无 soldSeats action,无崩溃 | **重新分类**: | # | 问题 | 严重度 | 说明 | |---|------|--------|------| | P1-A | `api/Goods.php::detail()` 不包含 seatSpecMap | **高** | UniApp `/detail` API 缺少 VR 数据注入 | | P1-B | `plugins_service_goods_data` Hook 未注册 | **中** | UniApp detail API 最佳入口缺失 | | P2-A | Phase 4 Tree API `buildTree()` 未实现 | **中** | 设计完整,代码为零 | | P2-B | `api/Goods.php::seatmap()` 命名不一致 | **低** | seatmap vs seatMap(大小写) | --- ## 三、技术方案建议 ### 方案 A(推荐):Hook 注册(最小改动) **文件**:`Hook.php` 追加 case: ```php case 'plugins_service_goods_data': $goodsId = $params['goods_id'] ?? 0; if ($goodsId > 0) { TicketService::InjectGoodsDetailData($params['data'], $goodsId); } break; ``` **新增方法**:`TicketService.php`: ```php public static function InjectGoodsDetailData(array &$data, int $goodsId): void { if ($goodsId <= 0) return; $vrConfig = \think\facade\Db::name('goods') ->where('id', $goodsId) ->value('vr_goods_config'); if (empty($vrConfig)) return; $viewData = SeatSkuService::GetGoodsViewData($goodsId); if (empty($viewData['seatSpecMap'])) return; $data['seatSpecMap'] = $viewData['seatSpecMap']; $data['goods_spec_data'] = $viewData['goods_spec_data']; $data['specTypeList'] = $viewData['specTypeList'] ?? []; $data['seatMap'] = $viewData['vr_seat_template']['seat_map'] ?? null; $data['goods_config'] = $viewData['goods_config'] ?? null; } ``` **代码量**:约 30 行。效果:UniApp 调用 ShopXO 标准 `/goods/detail` API 时自动获得 VR 数据。 ### 方案 B(备选):UniApp 变通绕过(无需后端改动) UniApp 端可在调用商品详情后,再调用 `/seatmap` API 补充 VR 数据。 - **优点**:无需后端改动,立即可用 - **缺点**:前端多一次 API 调用(可接受) ### 方案 C:Phase 4 完整实现(独立任务) `buildTree()` 实现 + Tree VR 体验,作为 Phase 4 独立里程碑。 --- ## 四、优先级建议 | 优先级 | 任务 | 预计工时 | 收益 | |--------|------|---------|------| | **P1-A** | Hook 注册 + `InjectGoodsDetailData()` | 30min | 解锁 UniApp 完整票务链路 | | **P1-B** | `api/Goods.php::detail()` 命名规范化 | 10min | API 契约一致性 | | **P2** | Phase 4 Tree API 实现 | 待定 | Tree VR 体验 | | **P3** | Phase 4 完整 Tree 体验 | 待定 | VR 差异化功能 | --- ## 五、投票(Round 4) **议题:下一步主攻方向** **投票:A — 后端优先** **理由**: 1. **Hook 注册是最低成本最高收益**:约 30 行代码,一次性解决 UniApp 商品详情 API 的 VR 数据注入问题。无需前端变通,减少 API 调用次数。 2. **Round 4 重新评估确认**:后端 seatmap API 已完整实现(P0-1/P0-3 误判已消除),核心剩余问题是 Hook 注入这一处。 3. **Gap 2 已消除**:后端票务链路(CartSave → onOrderPaid → 票生成)已完整,多座位支持已验证。 4. **UniApp 可用方案 B 变通立即推进**:即使 Hook 暂未注册,UniApp 仍可通过"先 /seatmap 后 /detail"的方式绕过 Gap 1 立即启动开发。 5. **Phase 4 不应前置**:Tree API 是体验增强,在核心票务链路(P1)稳定前启动 Phase 4 资源浪费。 **补充:对其他提案的评估** - **B(前端优先)**:可接受——UniApp 确实可以先用方案 B 变通绕过 Gap 1 立即开发。但变通方案不如 Hook 注册简洁。 - **C(双线并行)**:可接受,但需明确分工。后端修 Hook,前端用方案 B 变通同时推进。 - **D(Phase 4 优先)**:不建议。Phase 4 是锦上添花,不是票务购买的基础设施。 --- *报告人:BackendArchitect | 2026-05-26 | Round 4*