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

7.6 KiB
Raw Blame History

Council 评估报告 — BackendArchitect

评估日期2026-05-26 | 角色:后端架构师


一、现状评估

1.1 Phase 4 Tree API 设计

状态:仅有 commit无实现代码

  • docs/ 中无 Phase 4 专属设计文档(无 07/08/09/10_PHASE4*.md
  • GitNexus 索引显示 Goods.php 中有 GetSeatMapbuildTreebuildTemplatePool,但 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_dataHook.php未注册
  • vr_goods_config 字段已存在于 sxo_goods 表(Event.php::Install() 中通过 ALTER TABLE 追加)
  • 但商品详情返回数据中无 seatSpecMap 结构

1.4 CartSave extension_data 多座位链路

状态:⚠️ Gap 存在

  • GoodsCartService.phpextension_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() 方法

// 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

// Hook.php plugins_service_goods_data 分支
case 'plugins_service_goods_data':
    $ret = TicketService::InjectGoodsDetailData($params);
    break;
// 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

两种路径:

路径 1ShopXO 原生扩展):修改 sxo_cart 表加 extension_data 字段,通过 Hook 拦截保存

// Hook.php 新增
case 'plugins_service_goods_cart_save_handle':
    TicketService::CartSaveWithSeats($params);
    break;

路径 2绕过购物车:票务商品不走购物车,直接从选座→订单(参考大麦/猫眼模式)

建议:票务场景走路径 2绕过购物车因为

  • 每个座位唯一库存inventory=1购物车没有意义
  • 用户选座→立即下单→支付,更符合票务直觉
  • 避免购物车超卖复杂性

方案 B实现完整 /seatmap API

index/Index.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

  1. 实现完整 /seatmap API — 统一座位数据接口
  2. 完善 Phase 4 Tree API 设计文档 — 补充 API 契约和路由设计

可延后P2

  1. Phase 4 Tree API 实现 — 依赖 Phase 2/3 稳定后启动

五、投票

议题:下一步主攻方向

投票C — 双线并行

理由

  1. 后端 P0 GapgetSoldSeats 缺失、Hook 未注册)属于"修完就能用"的阻断性问题,工作量小但价值高,完全修复预计 2-3 小时
  2. 前端当前有 H5 票务页(ticket_detail.html)作为保底,可以独立推进 uniapp 选座组件开发,无需等待全部 API
  3. Tree APIPhase 4设计尚未完成过早投入实现是浪费应在 P0/P1 修复后作为第二阶段启动
  4. "双线并行"中的分工建议:
    • 后端:修复 P0 Gap + seatmap API + Hook 注册
    • 前端:基于现有 GetGoodsViewData 数据模型开发选座组件,等 Hook 注册后接入 seatSpecMap

补充:对其他提案的评估

  • A后端优先:合理,但"后端优先"意味着前端完全等待 — 这会浪费前端已有的 H5 票务页保底能力
  • B前端优先:不可行,前端开发被 P0 Gap 阻断seatSpecMap 缺失时选座组件无法正确映射 SKU
  • DPhase 4 优先)Phase 4 是锦上添花不是基础功能Tree API 失败不影响票务核心购买流程

报告人BackendArchitect | 2026-05-26