# Council 评估报告 — BackendArchitect > 评估日期:2026-05-26 | 角色:后端架构师 --- ## 一、现状评估 ### 1.1 Phase 4 Tree API 设计 **状态:仅有 commit,无实现代码** - `docs/` 中无 Phase 4 专属设计文档(无 `07/08/09/10_PHASE4*.md`) - GitNexus 索引显示 `Goods.php` 中有 `GetSeatMap`、`buildTree`、`buildTemplatePool`,但 `shopxo/app/plugins/vr_ticket/api/` 目录不存在 - **结论:Tree API 设计处于概念阶段,无可执行代码** ### 1.2 SeatMapService + seatmap API **状态:半完成,API 层断裂** | 组件 | 状态 | 说明 | |------|------|------| | `index/Index.php::soldSeats` | ⚠️ 存在但断裂 | 路由存在,调用 `SeatSkuService::getSoldSeats()` | | `SeatSkuService::getSoldSeats()` | ❌ **缺失** | 方法被引用但未实现 | | 完整 `/seatmap` API 端点 | ❌ 不存在 | 只有已售座位子集,无座位图数据 | | `SeatSkuService::BatchGenerate` | ✅ 已完成 | 座位 SKU 批量生成逻辑完整 | | `SeatSkuService::GetGoodsViewData` | ✅ 已完成 | 模板+场次数据读取 | ### 1.3 seatSpecMap 注入商品详情 API **状态:❌ Gap 存在** - ShopXO 商品详情 API 路由:`/?s=api/goods/detail&goods_id=X` - 插件 Hook `plugins_service_goods_data` 在 `Hook.php` 中**未注册** - `vr_goods_config` 字段已存在于 `sxo_goods` 表(`Event.php::Install()` 中通过 ALTER TABLE 追加) - 但商品详情返回数据中**无 seatSpecMap 结构** ### 1.4 CartSave extension_data 多座位链路 **状态:⚠️ Gap 存在** - `GoodsCartService.php` 中**无 `extension_data` 相关代码** - ShopXO 购物车数据模型:`sxo_cart` 表有 `goods_id`, `spec_id`, `stock`, 无扩展字段 - 多座位下单链路需要扩展购物车字段才能在购物车→订单流程中保留座位信息 --- ## 二、发现问题 ### P0(阻塞前端) | # | 问题 | 位置 | 严重度 | |---|------|------|--------| | P0-1 | `SeatSkuService::getSoldSeats()` 方法缺失 | `SeatSkuService.php` | 致命 — soldSeats API 不可用 | | P0-2 | seatSpecMap 未注入商品详情 | 无 Hook 注入点 | 致命 — 前端无法获取座位→SKU 映射 | | P0-3 | CartSave 无法传递座位信息 | `GoodsCartService.php` | 致命 — 多座位流程断裂 | ### P1(影响体验) | # | 问题 | 位置 | 严重度 | |---|------|------|--------| | P1-1 | `/seatmap` 完整 API 未实现 | `index/Index.php` | 高 — 实时座位状态轮询无法工作 | | P1-2 | Phase 4 Tree API 无代码 | 无文件 | 中 — 功能规划停留在设计阶段 | | P1-3 | `vr_goods_config` JSON 结构与 ShopXO spec 系统对齐问题 | `SeatSkuService.php:29` | 中 — SPEC_DIMS 硬编码维度名 | --- ## 三、技术方案建议 ### 方案 A:修复 P0 Gap(推荐) **优先级最高,解锁前端开发** #### A1: 实现 `getSoldSeats()` 方法 ```php // SeatSkuService.php 新增 public static function getSoldSeats(int $goodsId, int $specBaseId = 0): array { $query = Db::name('goods_spec_base') ->where('goods_id', $goodsId) ->where('inventory', 0); // 已售=库存为0 if ($specBaseId > 0) { $query->where('id', $specBaseId); } $sold = $query->select()->toArray(); // 通过 GoodsSpecValue 反查 seat_id $soldSeats = []; foreach ($sold as $spec) { $seatValue = Db::name('goods_spec_value') ->where('goods_spec_base_id', $spec['id']) ->where('value', 'LIKE', '%-%-%-' /* 座位号格式: venue-room-char-rowcol */) ->find(); if ($seatValue) { $soldSeats[] = self::extractSeatKey($seatValue['value']); } } return $soldSeats; } ``` #### A2: 注册商品详情 Hook,注入 seatSpecMap ```php // Hook.php plugins_service_goods_data 分支 case 'plugins_service_goods_data': $ret = TicketService::InjectGoodsDetailData($params); break; ``` ```php // TicketService.php 新增 public static function InjectGoodsDetailData(&$data, $goodsId) { if (!self::isTicketGoods($goodsId)) return; $viewData = SeatSkuService::GetGoodsViewData($goodsId); $seatTemplate = $viewData['vr_seat_template'] ?? null; if ($seatTemplate && !empty($seatTemplate['spec_base_id_map'])) { // seatSpecMap: seat_id → spec_base_id 映射 $data['seatSpecMap'] = $seatTemplate['spec_base_id_map']; // seatMap: 座位图渲染数据 $data['seatMap'] = $seatTemplate['seat_map'] ?? null; // sessions: 场次列表 $data['sessions'] = $viewData['goods_spec_data'] ?? []; } } ``` #### A3: 扩展 CartSave 支持座位 extension_data 两种路径: **路径 1(ShopXO 原生扩展)**:修改 `sxo_cart` 表加 `extension_data` 字段,通过 Hook 拦截保存 ```php // Hook.php 新增 case 'plugins_service_goods_cart_save_handle': TicketService::CartSaveWithSeats($params); break; ``` **路径 2(绕过购物车)**:票务商品不走购物车,直接从选座→订单(参考大麦/猫眼模式) > **建议**:票务场景走路径 2(绕过购物车),因为: > - 每个座位唯一库存(inventory=1),购物车没有意义 > - 用户选座→立即下单→支付,更符合票务直觉 > - 避免购物车超卖复杂性 --- ### 方案 B:实现完整 `/seatmap` API 在 `index/Index.php` 中扩展: ```php public function seatmap(array $params = []) { $goodsId = intval($params['goods_id'] ?? 0); if ($goodsId <= 0) return DataReturn('goods_id invalid', -1); $viewData = SeatSkuService::GetGoodsViewData($goodsId); $soldSeats = SeatSkuService::getSoldSeats($goodsId); return DataReturn('success', 0, [ 'seat_map' => $viewData['vr_seat_template']['seat_map'] ?? null, 'sold_seats' => $soldSeats, 'sessions' => $viewData['goods_spec_data'] ?? [], 'spec_base_id_map' => $viewData['vr_seat_template']['spec_base_id_map'] ?? [], ]); } ``` --- ## 四、优先级建议 ### 立即执行(P0) 1. **实现 `SeatSkuService::getSoldSeats()`** — 修复 soldSeats API 2. **注册 `plugins_service_goods_data` Hook** — 解锁 seatSpecMap 注入 3. **决策 CartSave 路径**:票务商品走绕过购物车的直购模式 ### 短期执行(P1) 4. **实现完整 `/seatmap` API** — 统一座位数据接口 5. **完善 Phase 4 Tree API 设计文档** — 补充 API 契约和路由设计 ### 可延后(P2) 6. **Phase 4 Tree API 实现** — 依赖 Phase 2/3 稳定后启动 --- ## 五、投票 **议题:下一步主攻方向** **投票:C — 双线并行** **理由**: 1. 后端 P0 Gap(`getSoldSeats` 缺失、Hook 未注册)属于"修完就能用"的阻断性问题,工作量小但价值高,完全修复预计 2-3 小时 2. 前端当前有 H5 票务页(`ticket_detail.html`)作为保底,可以独立推进 uniapp 选座组件开发,无需等待全部 API 3. Tree API(Phase 4)设计尚未完成,过早投入实现是浪费,应在 P0/P1 修复后作为第二阶段启动 4. "双线并行"中的分工建议: - 后端:修复 P0 Gap + seatmap API + Hook 注册 - 前端:基于现有 `GetGoodsViewData` 数据模型开发选座组件,等 Hook 注册后接入 seatSpecMap **补充:对其他提案的评估** - **A(后端优先)**:合理,但"后端优先"意味着前端完全等待 — 这会浪费前端已有的 H5 票务页保底能力 - **B(前端优先)**:不可行,前端开发被 P0 Gap 阻断,seatSpecMap 缺失时选座组件无法正确映射 SKU - **D(Phase 4 优先)**:Phase 4 是锦上添花,不是基础功能,Tree API 失败不影响票务核心购买流程 --- *报告人:BackendArchitect | 2026-05-26*