vr-shopxo-plugin/docs/council-eval-backendarchite...

242 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Council 评估报告 — BackendArchitectRound 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`(追加到文件末尾)
```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
```php
case 'plugins_service_goods_data':
$goodsId = $params['goods_id'] ?? 0;
if ($goodsId > 0) {
TicketService::InjectGoodsDetailData($params['data'], $goodsId);
}
break;
```
**新增方法**`TicketService.php`(追加到文件末尾):
```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'];
// seatSpecMapseat_key → 完整规格(含 row/col/room/section/price
$data['seatSpecMap'] = $viewData['seatSpecMap'] ?? [];
// goods_spec_data场次列表含价格
$data['goods_spec_data'] = $viewData['goods_spec_data'] ?? [];
// specTypeList5维规格维度定义
$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 功能完成 |
**决策路径**
1. 先修 P0-1 + P0-22h 内可完成)
2. 再推进 `api/` 目录规范化
3. Phase 4 在 P0/P1 稳定后作为独立任务启动
---
## 五、投票Round 3
**议题:下一步主攻方向**
**投票A — 后端优先**
**理由**
1. **最小改动最大收益**`getSoldSeats()` 实现30 行)+ Hook 注册10 行)= 约 40 行代码,修复后 UniApp 票务链路全部解锁。这是最低成本的最高收益修复。
2. **运行时崩溃是根本阻塞**Round 2 的"H5 已绕过"分析正确,但忽略了 `Index.php:soldSeats` 对所有直接调用 API 的客户端UniApp / 第三方)都会触发 PHP 致命错误。必须修复。
3. **Hook 注册是 UniApp 的唯一入口**Gap 1 对 H5 无影响H5 走模板层直接调用),但对 UniApp 来说是唯一入口。没有 Hook 注册UniApp 永远无法通过标准 API 获取 seatSpecMap。
4. **Gap 2CartSave已消除**Round 2 确认 H5 已验证 extension_data 链路正确UniApp 只需复刻同样的 JSON 结构,不存在后端缺口。
5. **Phase 4 不应前置**Tree API 是体验增强不是购买流程的基础设施在核心票务链路P0未稳定前启动 Phase 4 是资源浪费。
**补充:对其他提案的评估**
- **B前端优先**不可行。UniApp 选座组件需要 seatSpecMap 数据,但 Gap 1 不修则数据不可得。前端等 Hook 注册后再开发效率更高。
- **C双线并行**:在 P0 明确可修复的前提下,"双线并行"是浪费:前端等待期间无所事事,不如后端一次性修完再解锁前端。
- **DPhase 4 优先)**Phase 4 是锦上添花不是基础设施。Tree API 失败不影响用户购票核心流程。
---
*报告人BackendArchitect | 2026-05-26 | Round 3*