10 KiB
Council 评估报告 — BackendArchitect(Round 3 更新)
评估日期:2026-05-26 | 角色:后端架构师 | Git:
0d6d20062
一、现状评估(2026-05-26 现场核查)
1.1 Phase 4 Tree API 设计
状态:📋 设计文档已提交,代码为零
| 组件 | 状态 | 说明 |
|---|---|---|
docs/PHASE_4_API.md |
✅ 存在(父仓库 commit 40a9b0ad1) |
Tree API 设计文档 |
docs/PLAN_TREE_API_IMPLEMENTATION.md |
✅ 存在(父仓库 commit 40a9b0ad1) |
实现计划 |
SeatMapService.php(设计名) |
❌ 文件不存在 | 实际文件名为 SeatSkuService.php |
SeatSkuService.php(实际) |
✅ 存在 | 但没有 buildTree() 方法 |
api/ 目录(Worktree) |
❌ 未纳入 Git 追踪 | 父仓库有 api/Goods.php,Worktree 为空 |
api/Goods.php::seatmap() |
⚠️ 父仓库有,工作树无 | 调用不存在的 SeatMapService::GetSeatMap() |
Round 3 修正:api/Goods.php 在父仓库存在,有 seatmap() 方法,但父仓库 Worktree 隔离造成版本混淆。Tree API 设计完整,代码仅存在 SeatSkuService::GetGoodsViewData() 部分复用逻辑。
1.2 SeatMapService + seatmap API
状态:⚠️ API 断裂,带运行时错误
| 组件 | 状态 | 位置 |
|---|---|---|
index/Index.php::soldSeats |
⚠️ 存在 | index/Index.php:43 |
SeatSkuService::getSoldSeats() |
❌ 完全缺失 | 被 Index.php:43 调用但方法不存在 |
index/Index.php::soldSeats |
❌ 运行时崩溃 | PHP Fatal Error |
SeatSkuService::GetGoodsViewData() |
✅ 完整 | 返 |
回 seatSpecMap + specTypeList |
| SeatSkuService::buildSeatSpecMap() | ✅ 完整 | SeatSkuService.php:532 |
| SeatSkuService::BatchGenerate() | ✅ 完整 | 座位 SKU 批量生成 |
严重性澄清:Round 2 称 H5 绕过了 soldSeats API(正确),但忽略了 UniApp 的 soldSeats 端点调用会触发 PHP 致命错误。ticket_detail.html 自身正常,但所有直接调用 soldSeats API 的客户端都会崩溃。
新增发现:api/Goods.php 的 seatmap() 方法(父仓库)调用 SeatMapService::GetSeatMap(),但该类不存在(实际类名为 SeatSkuService,方法为 GetGoodsViewData)。这是 命名混淆 Bug。
1.3 seatSpecMap 注入商品详情 API
状态:❌ Gap 仍成立
| 组件 | 状态 | 说明 |
|---|---|---|
vr_goods_config 表字段 |
✅ 存在 | Event.php::Install() 创建 |
SeatSkuService::GetGoodsViewData() |
✅ 存在 | SeatSkuService.php:369 |
Hook.php::plugins_service_goods_data |
❌ 未注册 | Hook.php:13-28 无此 case |
H5 ticket_detail.html |
✅ 工作正常 | 直接调用 GetGoodsViewData() |
| UniApp 商品详情 API | ❌ Gap 1 成立 | Hook 不注册则无法触发 seatSpecMap 注入 |
确认:Gap 1 完全成立。H5 工作是因为 ticket_detail.html 是模板文件,在服务器端直接调用 SeatSkuService::GetGoodsViewData()。UniApp 走 API 层,plugins_service_goods_data Hook 不注册,则 ShopXO 的商品详情 API 不会触发 VR 座位数据注入。
1.4 CartSave extension_data 多座位链路
状态:✅ H5 已验证,UniApp 只需复刻
| 组件 | 状态 | 说明 |
|---|---|---|
ticket_detail.html:762 订单提交 |
✅ 已实现 | extension_data 嵌套在 order_base |
TicketService::onOrderPaid() |
✅ 已实现 | 逐行生成票(多座位支持) |
| UniApp CartSave | ✅ 与 H5 相同 JSON 格式 | Gap 2 已消除,只要后端提供端点即可 |
Round 2 修正确认:Gap 2 已验证可工作,核心问题变为"UniApp 需要相同的 API 端点格式"。
二、发现问题(Round 3 修正)
P0(运行时崩溃,直接阻塞)
| # | 问题 | 位置 | 严重度 | Round 2 对比 |
|---|---|---|---|---|
| P0-1 | SeatSkuService::getSoldSeats() 方法缺失 |
SeatSkuService.php(末行为 buildSeatSpecMap,无此方法) |
致命 — Index.php:43 触发 PHP Fatal Error |
确认:Round 2 误判为"H5 绕过" |
| P0-2 | plugins_service_goods_data Hook 未注册 |
Hook.php:13-28(无对应 case) |
致命 — UniApp 商品详情无法获取 seatSpecMap | 与 Round 2 一致 |
P1(影响扩展性)
| # | 问题 | 位置 | 严重度 | Round 2 对比 |
|---|---|---|---|---|
| P1-1 | api/Goods.php::seatmap() 引用不存在的 SeatMapService |
shopxo/app/plugins/vr_ticket/api/Goods.php:241 |
高 — 命名混淆,实际方法在 SeatSkuService::GetGoodsViewData |
新发现 |
| P1-2 | api/ 目录未纳入 Git 追踪(Worktree) |
工作树 shopxo/app/plugins/vr_ticket/api/ |
高 — 无法部署 / 无法协作 | 新发现 |
| P1-3 | Phase 4 Tree API 代码为零 | 无 buildTree() 方法 |
中 — 设计完备,代码未开始 | 与 Round 2 一致 |
三、技术方案建议
P0-1 Fix:getSoldSeats() 实现
文件:SeatSkuService.php(追加到文件末尾)
/**
* 获取已售座位列表(seatKey 数组)
*
* 已售座位定义:inventory = 0 的 GoodsSpecBase 记录
* seatKey 格式:roomId_rowLabel_colNum(如 "room_0_A_3")
*
* @param int $goodsId
* @param int $specBaseId 可选,限定查询
* @return string[]
*/
public static function getSoldSeats(int $goodsId, int $specBaseId = 0): array
{
if ($goodsId <= 0) return [];
$query = \think\facade\Db::name('goods_spec_base')
->where('goods_id', $goodsId)
->where('inventory', 0);
if ($specBaseId > 0) {
$query->where('id', $specBaseId);
}
$soldSpecs = $query->select()->toArray();
if (empty($soldSpecs)) return [];
// 从 extends.seat_key 提取
$seatKeys = [];
foreach ($soldSpecs as $spec) {
$extends = json_decode($spec['extends'] ?? '{}', true);
$seatKey = $extends['seat_key'] ?? '';
if (!empty($seatKey)) {
$seatKeys[] = $seatKey;
}
}
return $seatKeys;
}
触发点:index/Index.php:43 现有调用无需修改。
P0-2 Fix:plugins_service_goods_data Hook 注册
文件:Hook.php:13 追加 case:
case 'plugins_service_goods_data':
$goodsId = $params['goods_id'] ?? 0;
if ($goodsId > 0) {
TicketService::InjectGoodsDetailData($params['data'], $goodsId);
}
break;
新增方法:TicketService.php(追加到文件末尾):
/**
* 注入商品详情 VR 票务数据(通过 plugins_service_goods_data Hook)
*
* @param array &$data ShopXO 商品详情数据(引用传递)
* @param int $goodsId
*/
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['vr_seat_template'])) return;
$seatTemplate = $viewData['vr_seat_template'];
// seatSpecMap:seat_key → 完整规格(含 row/col/room/section/price)
$data['seatSpecMap'] = $viewData['seatSpecMap'] ?? [];
// goods_spec_data:场次列表(含价格)
$data['goods_spec_data'] = $viewData['goods_spec_data'] ?? [];
// specTypeList:5维规格维度定义
$data['specTypeList'] = $viewData['specTypeList'] ?? [];
// seatMap:座位图渲染数据
$data['seatMap'] = $seatTemplate['seat_map'] ?? null;
// goods_config:当前生效的配置块
$data['goods_config'] = $viewData['goods_config'] ?? null;
}
P1 Fix:命名统一(SeatSkuService vs SeatMapService)
| 当前引用 | 应改为 | 位置 |
|---|---|---|
use app\plugins\vr_ticket\service\SeatMapService |
use app\plugins\vr_ticket\service\SeatSkuService |
shopxo/app/plugins/vr_ticket/api/Goods.php:12 |
SeatMapService::GetSeatMap |
SeatSkuService::GetGoodsViewData |
shopxo/app/plugins/vr_ticket/api/Goods.php:241 |
注意:此修复应在 api/ 目录重新纳入 Git 追踪后执行。
四、优先级建议
| 优先级 | 任务 | 预计工时 | 收益 |
|---|---|---|---|
| P0-1 | 实现 getSoldSeats() |
30min | 修复 soldSeats API 崩溃 |
| P0-2 | Hook 注册 + InjectGoodsDetailData() |
1h | 解锁 UniApp 完整票务链路 |
| P1-A | api/ 目录纳入 Git 追踪 + 命名修复 |
30min | 可部署、可协作 |
| P1-B | Phase 4 Tree API 实现(buildTree()) |
待定 | Phase 4 功能完成 |
决策路径:
- 先修 P0-1 + P0-2(2h 内可完成)
- 再推进
api/目录规范化 - Phase 4 在 P0/P1 稳定后作为独立任务启动
五、投票(Round 3)
议题:下一步主攻方向
投票:A — 后端优先
理由:
-
最小改动最大收益:
getSoldSeats()实现(30 行)+ Hook 注册(10 行)= 约 40 行代码,修复后 UniApp 票务链路全部解锁。这是最低成本的最高收益修复。 -
运行时崩溃是根本阻塞:Round 2 的"H5 已绕过"分析正确,但忽略了
Index.php:soldSeats对所有直接调用 API 的客户端(UniApp / 第三方)都会触发 PHP 致命错误。必须修复。 -
Hook 注册是 UniApp 的唯一入口:Gap 1 对 H5 无影响(H5 走模板层直接调用),但对 UniApp 来说是唯一入口。没有 Hook 注册,UniApp 永远无法通过标准 API 获取 seatSpecMap。
-
Gap 2(CartSave)已消除:Round 2 确认 H5 已验证 extension_data 链路正确,UniApp 只需复刻同样的 JSON 结构,不存在后端缺口。
-
Phase 4 不应前置:Tree API 是体验增强,不是购买流程的基础设施,在核心票务链路(P0)未稳定前启动 Phase 4 是资源浪费。
补充:对其他提案的评估
- B(前端优先):不可行。UniApp 选座组件需要 seatSpecMap 数据,但 Gap 1 不修则数据不可得。前端等 Hook 注册后再开发效率更高。
- C(双线并行):在 P0 明确可修复的前提下,"双线并行"是浪费:前端等待期间无所事事,不如后端一次性修完再解锁前端。
- D(Phase 4 优先):Phase 4 是锦上添花,不是基础设施。Tree API 失败不影响用户购票核心流程。
报告人:BackendArchitect | 2026-05-26 | Round 3