4.5 KiB
4.5 KiB
扁平数据 + 查询管理器 方案
讨论日期:2026-05-15 参与:西莉雅、大头
一、核心思想
后端做计算,前端做渲染。
- 扁平数据层(Flat Inventory):所有 SKU 以 spec_key 为索引,不预设层级
- 查询管理器(Query Manager):按
group_by参数聚合,生成层级树 - 缓存层:结果缓存,避免重复计算
- 前端:通过
group_by参数指定想要的层级顺序,收到树后直接渲染
二、关键设计决策
2.1 模板去重
原则:同一 venue + room + section 的座位图模板在所有场次下共享,与 session 无关。
例如:鸟巢 + 主厅 + A区
→ 6 个场次 × 1 个模板 = 1 份模板数据(不是 6 份)
→ template_key = "鸟巢_主厅_A"
→ 在 seat_templates_flat 中只存 1 份
优势:
- 消除跨场次的模板数据冗余
- 前端只需按 key 引用,无需处理重复模板
2.2 层级顺序由前端控制
group_by=venue,session,room,section → 场馆优先(Joery 场景)
group_by=session,venue,room,section → 场次优先(当前实现)
后端不在意顺序,只负责聚合。
2.3 扁平数据全量返回
API 返回:
├── tree(层级,含 section 聚合)
├── seat_templates_flat(模板去重池)
└── flat_inventory(所有 SKU,前端本地筛选)
前端选座流程(全程零额外 API):
venue → session → room → section
↓
查 template_key → 查 seat_templates_flat → 渲染座位图
↓
spec_key 前缀匹配 flat_inventory → 标记可选座位
2.4 实时查询接口(复用查询管理器)
查询管理器同时支持实时查询:
GET /api/goods/inventory?goods_id=118&spec=venue:鸟巢,session:15:00-16:59
→ 返回该条件下的总库存
三、API 接口
GET /api/goods/tree
?goods_id=118
&group_by=venue,session,room,section
&cache_ttl=120
返回结构:
{
"goods_id": 118,
"title": "VR演唱会",
"group_by": ["venue", "session", "room", "section"],
"tree": {
"鸟巢": {
"name": "鸟巢",
"min_price": 280,
"has_available": true,
"rooms": { "主厅": { "sections": { "A": { "template_key": "鸟巢_主厅_A", "price": 680, "inventory": 12 } } } },
"sessions": { "15:00-16:59": { "min_price": 380, "has_available": true } }
}
},
"seat_templates_flat": {
"鸟巢_主厅_A": { "map": [...], "sections": [...], "seats": {...} }
},
"flat_inventory": [
{ "spec_key": "$vr-场次=15:00-16:59|...|$vr-座位号=1排1座", "price": 680, "inventory": 1, "seat_key": "room_0_A_1" }
],
"meta": { "flat_count": 120, "template_count": 4, "cache_hit": false }
}
四、与现有系统衔接
vr_goods_config
↓
SeatSkuService::buildSeatSpecMap() → 扁平 SKU(不预设层级)
↓
QueryManager(新增)
├── buildTree():按 group_by 聚合
├── buildTemplatePool():模板去重
└── buildFlatInventory():返回扁平 SKU 列表
↓
缓存层(TTL=60s)
↓
API Response
现有组件不变:
- SeatMapService / SeatSkuService:继续作为数据源
- vr_seat_templates 表:继续存储座位模板
- vr_goods_config:继续作为商品配置
五、与"层级 JSON 树方案"的关系
| 维度 | 层级 JSON 树(方案 A) | 扁平 + 查询管理器(方案 B,采纳) |
|---|---|---|
| 前端复杂度 | 低(直接用树) | 中(本地筛选 + 模板引用) |
| 后端复杂度 | 中(预计算固定结构) | 中(查询管理器,按参数聚合) |
| 灵活性 | 差(固定层级) | 好(前端指定 group_by) |
| 模板去重 | 支持(但实现复杂) | ✅ 原生支持 |
| 数据冗余 | 高(模板重复) | 低(去重池) |
| 实时查询 | 不支持 | ✅ 支持 |
结论:Joery 的方案更优,采纳方案 B。
六、注意事项
- template_key 生成算法:由后端统一生成,格式为
venue_room_section,前端不感知算法 - spec_key 排序:前端匹配时必须使用与后端相同的排序规则(固定顺序,用
|连接) - 缓存失效:订单支付成功后同时清除 SeatMapService 缓存和 tree 缓存
- inventory=0 的座位:flat_inventory 需要包含已售座位(用于前端标记"已售"状态)
- 缓存 TTL:默认 60s,可通过
cache_ttl参数调整