vr-shopxo-plugin/docs/15_FLAT_INVENTORY_QUERY_MAN...

209 lines
6.2 KiB
Markdown
Raw Permalink Normal View History

# 扁平数据 + 查询管理器 方案
> **状态**: ✅ 已完成
> **讨论日期**2026-05-15
> **参与**:西莉雅、大头
---
## 一、核心思想
**后端做计算,前端做渲染。**
- **动态层级树**:根据 `group_by` 参数动态生成层级结构
- **查询管理器Query Manager**:按 `group_by` 参数聚合,生成层级树
- **自底向上聚合**:每个层级节点自动计算 `inventory`、`min_price`、`max_price`、`has_available`
- **座位嵌入**:座位数据直接嵌入树的最深层 `seats` 属性
- **模板去重池**:同一 `venue + room + section` 的模板只返回一份,格式为键值对
---
## 二、关键设计决策
### 2.1 模板去重
**原则**:同一 `venue + room + section` 的座位图模板在所有场次下共享,与 session 无关。
```
例如:测试场馆 + 老展厅 1 + A
→ 多个场次 × 1 个模板 = 1 份模板数据
→ template_key = "测试场馆_老展厅 1_A"
→ 在 seat_templates 中只存 1 份,格式为键值对
```
### 2.2 层级顺序由前端控制
```
group_by=venue,session,room,section → 场馆优先Joery 场景)
group_by=session,venue,room,section → 场次优先
group_by=section,venue,session,room → 自定义顺序
```
### 2.3 座位数据嵌入树
```
API 返回:
├── tree层级含 seats 嵌入在最深层)
├── seat_templates模板去重池键值对格式
└── meta元数据
前端选座流程(全程零额外 API
venue → session → room → section
直接从 tree 最深层获取 seats
查 template_key → 查 seat_templates → 渲染座位图
```
### 2.4 移除 flat_inventory
`flat_inventory` 已被移除,因为:
- 座位数据已嵌入 tree 最深层
- 前端直接从 `seats` 获取座位,无需额外遍历
---
## 三、API 接口
```
GET /api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=goods&pluginsaction=tree
?goods_id=118
&group_by=venue,session,room,section
```
返回结构(当前实现):
```json
{
"code": 0,
"data": {
"goods_id": 118,
"group_by": ["venue", "session", "room", "section"],
"tree": {
"venues": {
"测试场馆": {
"name": "测试场馆",
"min_price": 0,
"max_price": 0,
"has_available": true,
"inventory": 31,
"sessions": {
"07:00-09:59": {
"name": "07:00-09:59",
"inventory": 31,
"rooms": {
"老展厅 1": {
"name": "老展厅 1",
"inventory": 16,
"sections": {
"A": {
"name": "A",
"min_price": 0,
"max_price": 0,
"has_available": true,
"inventory": 10,
"template_key": "测试场馆_老展厅 1_A",
"price": 0,
"seats": {
"1排1座": {
"spec_key": "$vr-分区=A|...",
"venue": "测试场馆",
"session": "07:00-09:59",
"room": "老展厅 1",
"section": "A",
"seat": "1排1座",
"price": 0,
"inventory": 1
}
}
}
}
}
}
}
}
}
}
},
"seat_templates": {
"测试场馆_老展厅 1_A": {
"template_key": "测试场馆_老展厅 1_A",
"name": "测试场馆",
"room_name": "老展厅 1",
"section_name": "A",
"seat_map": { ... },
"layout_cols": 10,
"layout_rows": 10
}
},
"meta": {
"seat_count": 59,
"template_count": 6,
"cache_hit": false,
"computed_at": 1778861766
}
}
}
```
---
## 四、与现有系统衔接
```
vr_goods_config
SeatSkuService::buildSeatSpecMap() → 扁平 SKU
QueryManager新增
├── buildTree():按 group_by 聚合,嵌入 seats
├── buildTemplatePool():模板去重
└── transformTemplatePool():转换为键值对格式
缓存层TTL=60s
API Response
```
**现有组件不变**
- SeatMapService / SeatSkuService继续作为数据源
- vr_seat_templates 表:继续存储座位模板
- vr_goods_config继续作为商品配置
---
## 五、实现状态
| 任务 | 状态 | 说明 |
|------|------|------|
| QueryManager 核心逻辑 | ✅ 已完成 | buildTree, computeStatsRecursive |
| 动态层级树生成 | ✅ 已完成 | 根据 group_by 动态构建 |
| Seats 嵌入最深层 | ✅ 已完成 | seats 属性嵌入 tree 最深层 |
| 自底向上统计 | ✅ 已完成 | inventory, min_price, max_price, has_available |
| seat_templates 键值对 | ✅ 已完成 | transformTemplatePool() |
| 移除 flat_inventory | ✅ 已完成 | seats 已嵌入 tree |
| 缓存机制 | ✅ 已完成 | Cache::set/get + 标签失效 |
| API 文档 | ✅ 已完成 | `docs/api/VR_TICKET_TREE_API.md` |
| peer_goods 多场次关联 | ✅ 已完成 | 按 coding 关联同演出不同日期商品 |
| session_meta 场次元数据 | ✅ 已完成 | 从 SKU extends 提取停售时间戳 |
| SKU batch_expire_ts 写入 | ✅ 已完成 | BatchGenerate 时写入 extends |
| BuyCheck 停售验证钩子 | ✅ 已完成 | 开场前5分钟禁止下单 |
| 字段修正 | ✅ 已完成 | goods_code→coding, produce_date→batch_number_expire |
---
## 六、注意事项
1. **template_key 生成算法**`venue_room_section`,前端不感知算法
2. **缓存失效**:订单支付成功后同时清除 SeatMapService 缓存和 tree 缓存
3. **inventory=0 的座位**:仍保留在 seats 中,用于前端标记"已售"状态
4. **缓存 TTL**:默认 60s通过 `cache_ttl` 参数可调整
---
## 七、相关文档
| 文档 | 说明 |
|------|------|
| [api/VR_TICKET_TREE_API.md](./api/VR_TICKET_TREE_API.md) | 完整 API 文档 |
| [14_TREE_API_DESIGN.md](./14_TREE_API_DESIGN.md) | Tree API 设计文档 |