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

247 lines
7.1 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.

# VR Tree API 实现计划
> 创建时间2026-05-15
> 负责人:大头(代码实施)
> 参考文档:`docs/14_TREE_API_DESIGN.md`、`docs/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`(新建)
**核心方法**:
```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 示例):
```php
[
'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
```
**实现**:
```php
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 缓存:
```php
// 清除所有 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 后)
```bash
docker exec shopxo-php bash -c "php -r '
// 读取 goods_id=118 的 spec_key 样本
// 验证排序是否正确
'"
```
### 4.2 API 测试Step 3 后)
```bash
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 中包含已售座位
---
*文档状态:规划完成,待实施*