vr-shopxo-plugin/docs/PLAN_TREE_API_IMPLEMENTATIO...

7.1 KiB
Raw Permalink Blame History

VR Tree API 实现计划

创建时间2026-05-15 负责人:大头(代码实施) 参考文档:docs/14_TREE_API_DESIGN.mddocs/TASK_TREE_API_IMPLEMENTATION.md


一、设计确认

1.1 设计决策(已与大头确认)

决策项 结论 原因
spec_key 排序 字母顺序 未来扩展新维度时自动融入正确位置,无需修改排序逻辑
tree 分组维度 4 层(不含座位号) 座位号是最底层扁平元素,与 SKU/库存绑定,通过 spec_key 前缀匹配查找
inventory=0 保留并返回 便于前端座位图标记已售状态,与座位模板一一对应

1.2 spec_key 格式(已确定)

$vr-场次=15:00-16:59|$vr-场馆=鸟巢|$vr-演播室=主厅|$vr-分区=A|$vr-座位号=1排1座
   (按字母顺序排序)

重要:前端生成 spec_key 时必须使用相同的字母排序规则。


二、实现任务清单

Task 1: 创建 QueryManager 服务

文件: service/QueryManager.php(新建)

核心方法:

class QueryManager
{
    /**
     * 主入口:生成层级树
     * @param int   $goodsId
     * @param array $groupBy  e.g. ['venue', 'session', 'room', 'section']
     * @param array $seatSpecMap
     * @return array ['tree' => [...], 'template_keys' => [...]]
     */
    public static function buildTree(int $goodsId, array $groupBy, array $seatSpecMap): array

    /**
     * 构建扁平 SKU 列表
     * @param array $seatSpecMap
     * @return array flat_inventory
     */
    public static function buildFlatInventory(array $seatSpecMap): array

    /**
     * 构建模板去重池
     * @param array $templateKeys
     * @return array seat_templates_flat
     */
    public static function buildTemplatePool(array $templateKeys): array
}

分组维度映射:

group_by 值 spec_key 前缀 对应维度
venue $vr-场馆= venueName
session $vr-场次= sessionName
room $vr-演播室= roomName
section $vr-分区= sectionName

层级树结构venue-first 示例):

[
    'tree' => [
        '鸟巢' => [
            'name' => '鸟巢',
            'min_price' => 280,
            'has_available' => true,
            'rooms' => [
                '主厅' => [
                    'name' => '主厅',
                    'min_price' => 380,
                    'has_available' => true,
                    'sections' => [
                        'A' => [
                            'template_key' => '鸟巢_主厅_A',
                            'price' => 680,
                            'inventory' => 12,
                            'has_available' => true
                        ],
                        // ...
                    ]
                ]
            ],
            'sessions' => [
                '15:00-16:59' => ['min_price' => 380, 'has_available' => true],
                '20:00-21:59' => ['min_price' => 280, 'has_available' => false]
            ]
        ]
    ]
]

Task 2: 实现 tree() API 接口

文件: api/Goods.php

路由:

GET /api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=goods&pluginsaction=tree&goods_id=118&group_by=venue,session,room,section

实现:

public function tree()
{
    $goodsId = input('goods_id', 0, 'intval');
    $groupBy = input('group_by', 'venue,session,room,section', 'trim');
    $groupBy = array_filter(array_map('trim', explode(',', $groupBy)));

    if ($goodsId <= 0) {
        return self::error('goods_id 无效');
    }

    // 1. 缓存检查
    $cacheKey = 'vr_tree_' . $goodsId . '_' . md5(implode(',', $groupBy));
    $cached = \think\facade\Cache::get($cacheKey);
    if ($cached !== null) {
        $cached['meta']['cache_hit'] = true;
        return self::success($cached);
    }

    // 2. 读取数据源
    $seatMapData = SeatMapService::GetSeatMap($goodsId);
    $seatSpecMap = $seatMapData['seatSpecMap'] ?? [];

    // 3. 调用 QueryManager
    $treeData = QueryManager::buildTree($goodsId, $groupBy, $seatSpecMap);
    $flatInventory = QueryManager::buildFlatInventory($seatSpecMap);
    $templates = QueryManager::buildTemplatePool($treeData['template_keys'] ?? []);

    // 4. 组装响应
    $result = [
        'goods_id' => $goodsId,
        'group_by' => $groupBy,
        'tree' => $treeData['tree'],
        'seat_templates_flat' => $templates,
        'flat_inventory' => $flatInventory,
        'meta' => [
            'flat_count' => count($flatInventory),
            'template_count' => count($templates),
            'cache_hit' => false,
            'computed_at' => time(),
        ],
    ];

    // 5. 写入缓存TTL = 60s
    \think\facade\Cache::set($cacheKey, $result, 60);

    return self::success($result);
}

Task 3: 处理缓存失效

位置: 订单支付成功回调

SeatMapService::ClearCache() 被调用时,同时清除 tree 缓存:

// 清除所有 group_by 组合的 tree 缓存(可以存储一个 set 记录 key
// 或简单清除带前缀的缓存
\think\facade\Cache::delete('vr_tree_' . $goodsId . '_');

三、实现顺序

Step 任务 文件 优先级
1 创建 QueryManager QueryManager.php 🔴 核心
2 实现 tree() API Goods.php 🔴 核心
3 处理缓存失效 订单回调处 🟡 后续
4 前端适配 ticket_detail.html 🟡 后续

四、测试验证

4.1 修复验证Step 1 后)

docker exec shopxo-php bash -c "php -r '
// 读取 goods_id=118 的 spec_key 样本
// 验证排序是否正确
'"

4.2 API 测试Step 3 后)

curl "http://localhost:10000/api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=goods&pluginsaction=tree&goods_id=118&group_by=venue,session,room,section" \
  -H "X-Requested-With: XMLHttpRequest" | python3 -c "
import json,sys
d = json.load(sys.stdin)
if d['code'] == 0:
    data = d['data']
    print('✅ flat_count:', data['meta']['flat_count'])
    print('✅ template_count:', data['meta']['template_count'])
    print('✅ venues:', list(data['tree'].keys()))
    print('✅ cache_hit:', data['meta']['cache_hit'])
else:
    print('❌ error:', d['msg'])
"

预期结果:

  • flat_count > 0
  • template_count << flat_count(模板去重生效)
  • cache_hit: false(首次),true(后续请求)

五、风险评估

风险 概率 影响 缓解
前端 spec_key 生成逻辑不一致 前端必须使用与后端相同的字母排序规则
缓存失效时机 先实现缓存清除逻辑
大数据量性能 缓存 TTL=60s定期失效

六、后续工作

  1. 前端适配: 修改 ticket_detail.html 的 spec_key 生成逻辑
  2. 多模板支持: 当前 design 支持多场馆×多模板,需要扩展
  3. inventory=0 处理: 确认是否在 flat_inventory 中包含已售座位

文档状态:规划完成,待实施