Files
cmr-mini/backend/docs/资源对象与目录方案.md

623 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 资源对象与目录方案
> 文档版本v1.3
> 最后更新2026-04-07 13:13:19
本文档用于把“地图复用、KML 复用、内容资源复用、配置发布”统一收成一套后端可执行方案。
目标:
- 不再把所有资源都塞进单个 `event/*.json`
- 让地图、KML、内容模板、主题资源都能独立复用
-`event` 只负责“组合与覆盖”,不拥有底层资源本体
-`release` 能稳定追溯当时到底用了哪一份地图、哪一份 KML、哪一套资源包
- 让同一套资源对象既能服务小程序,也能服务未来 APP
当前补充约束:
- 正式资源目录只认 `OSS / CDN`
- 本地 `tmp/` 仅作为临时收件箱,不参与正式发布源
- backend 当前已开始提供运维入口第一期:
- `POST /admin/ops/tile-releases/import`
- `POST /admin/ops/course-sets/import-kml-batch`
- backend 当前也已开始提供运维入口第二期:
- `POST /admin/assets/upload`
- `POST /admin/assets/register-link`
- `GET /admin/assets`
- `GET /admin/assets/{assetPublicID}`
- 当前目标是把“上传文件”和“登记外链”统一收口到同一套资源模型,不要求运维自己关心底层存储实现。
---
## 1. 设计结论
后端后续不要按“一个活动一个完整资源目录”来设计,而要按“资源对象库 + Event 组合 + Release 固化”来设计。
建议统一拆成这 5 类对象:
1. `Map`
2. `Playfield`
3. `GameMode`
4. `ResourcePack`
5. `Event`
它们的关系是:
- `Map`:地图底座
- `Playfield`:空间对象 / KML / 控制点集
- `GameMode`:玩法默认规则
- `ResourcePack`:内容、主题、音频等资源档
- `Event`:业务活动对象,只做引用与少量覆盖
最终客户端吃的仍然不是这些编辑态对象,而是:
- `Release`
- `manifest.json`
补充约束:
- manifest 必须保持终端中立
- 不要在资源对象层把目录或字段设计成“小程序专用资源包”
- APP 与小程序应共享同一套资源对象和 release 记录
---
## 2. 为什么必须这么拆
你现在遇到的核心问题不是“目录怎么摆”,而是“哪些资源会复用”。
当前最典型的复用场景:
- 同一张地图会被多个活动复用
- 同一份 KML / 控制点集会被多个活动复用
- 同一套 H5 内容模板会被多个活动复用
- 同一套主题和音频资源会被多个活动复用
如果继续按 `event -> 自己拥有所有文件` 设计,后面会出现:
- 地图重复拷贝
- KML 重复上传
- 同一资源多个活动版本不一致
- 一次资源修复需要改很多 event
- 历史 session 无法明确追溯当时使用的是哪一版资源
所以正确做法不是“每个 event 一套全量文件”,而是:
`共享资源对象 -> event 引用 -> release 固化版本`
---
## 3. 五类核心对象
## 3.1 Map
作用:
- 表示一张可复用地图底座
最少应包含:
- `code`
- `name`
- `status`
- `tiles root`
- `mapmeta`
- 可选边界、缩放、投影信息
典型场景:
- 一个公园底图
- 一张校园底图
- 一张城区底图
注意:
- `Map` 不等于某场活动
- `Map` 是共享资产
## 3.2 Playfield
作用:
- 表示一份可复用场地对象数据
最常见的承载就是:
- `KML`
- `GeoJSON`
- 控制点集
最少应包含:
- `code`
- `name`
- `kind`
- `sourceType`
- `sourceFile`
- 可选提取元数据
- 控制点数量
- 边界范围
- 是否包含起终点
注意:
- `Playfield` 是共享对象,不属于某个 event 私有
- 同一份 KML 可以被多个 event 复用
## 3.3 GameMode
作用:
- 表示一种玩法模式的默认规则对象
例如:
- `classic-sequential`
- `score-o`
最少应包含:
- `code`
- `mode`
- `defaults json`
注意:
- 它不是最终 event 配置
- 只是玩法默认值来源之一
## 3.4 ResourcePack
作用:
- 表示一套可复用资源档
当前最适合放进来的有:
- `audioProfile`
- `contentProfile`
- `themeProfile`
一个资源包内部可以包含:
- 内容模板
- H5 页面
- 图片
- 图标
- 音效
- 主题色与主题变量
## 3.5 Event
作用:
- 表示一个业务活动实例
它应该只负责:
- 引用哪个 `Map`
- 引用哪个 `Playfield`
- 引用哪个 `GameMode`
- 引用哪个 `ResourcePack`
- 叠加少量 `Event Overrides`
不要让 `Event` 负责:
- 保存整份地图资源
- 保存 KML 原件
- 承担所有玩法默认规则
- 拷贝整套资源包
---
## 4. 推荐目录结构
仓库内建议把“源资源”和“活动源配置”拆开。
推荐结构:
```text
resources/
maps/
lxcb-001/
v2026-03-30/
mapmeta.json
tiles/
playfields/
c01/
v2026-03-30/
course.kml
meta.json
resource-packs/
default-race/
v2026-03-30/
content/
content.html
audio/
theme/
game-modes/
classic-sequential/
v1/
mode.json
score-o/
v1/
mode.json
events/
evt-demo-001/
source.json
```
说明:
- `resources/` 放共享对象的源资源
- `game-modes/` 放玩法默认规则对象
- `events/` 只放活动级 source config
当前根目录 [event](D:/dev/cmr-mini/event) 可以继续保留作为过渡区,但后面建议逐步迁到:
- `events/`
- `resources/`
- `game-modes/`
---
## 5. Event Source 应该怎么写
后续 `event source` 不建议继续直接写死地图路径和 KML 路径,而应该引用对象版本。
推荐形态:
```json
{
"schemaVersion": "1",
"version": "2026.04.01",
"app": {
"id": "evt-demo-001",
"title": "积分赛示例"
},
"refs": {
"map": {
"code": "lxcb-001",
"version": "v2026-03-30"
},
"playfield": {
"code": "c01",
"version": "v2026-03-30"
},
"gameMode": {
"code": "score-o",
"version": "v1"
},
"resourcePack": {
"code": "default-race",
"version": "v2026-03-30"
}
},
"overrides": {
"game": {
"session": {
"maxDurationSec": 5400
},
"punch": {
"radiusMeters": 5
}
},
"playfield": {
"metadata": {
"title": "示例路线",
"code": "demo-001"
}
}
}
}
```
这样 `Event` 管的是:
- 引用
- 覆盖
不是:
- 全量资源路径
- 全量运行时配置
---
## 6. Manifest 生成规则
build / publish 时Go 中间层应做装配:
`Map + Playfield + GameMode + ResourcePack + Event Overrides -> manifest.json`
最终生成给客户端的 manifest 可以保持现在的运行结构,例如:
```json
{
"schemaVersion": "1",
"releaseId": "rel_xxx",
"version": "2026.04.01",
"app": {
"id": "evt-demo-001",
"title": "积分赛示例"
},
"map": {
"tiles": "https://.../maps/lxcb-001/v2026-03-30/tiles/",
"mapmeta": "https://.../maps/lxcb-001/v2026-03-30/mapmeta.json"
},
"playfield": {
"kind": "control-set",
"source": {
"type": "kml",
"url": "https://.../playfields/c01/v2026-03-30/course.kml"
}
},
"game": {
"mode": "score-o"
},
"resources": {
"audioProfile": "default",
"contentProfile": "default",
"themeProfile": "default-race"
}
}
```
这层才是客户端真正消费的配置。
重要边界:
- 后端负责对象装配和发布
- 前端继续负责运行时 profile 编译
- 不把玩家设置和运行时状态写回发布配置
再补一条:
- 不把 APP 专属页面状态或小程序专属页面状态写进 manifest
- 如需终端能力差异,后续通过能力声明或运行时适配层处理
---
## 7. OSS / CDN 目录建议
线上目录不要再继续以:
- `gotomars/event/classic-sequential.json`
- `gotomars/event/score-o.json`
这种“玩法文件名”方式长期演进。
建议改成版本化结构:
```text
gotomars/maps/{mapCode}/{version}/...
gotomars/kml/{placeCode}/{version}/route01.kml
gotomars/kml/{placeCode}/{version}/route02.kml
gotomars/kml/{placeCode}/{version}/route03.kml
gotomars/kml/{placeCode}/{version}/route04.kml
gotomars/playfields/{playfieldCode}/{version}/...
gotomars/resource-packs/{packCode}/{version}/...
gotomars/game-modes/{modeCode}/{version}/mode.json
gotomars/event-releases/{eventPublicID}/{releasePublicID}/manifest.json
gotomars/event-releases/{eventPublicID}/{releasePublicID}/asset-index.json
```
好处:
- 共享资源独立版本化
- event release 只固化引用
- 历史 session 可以回溯
- 同一个 map / KML 修复时不会污染所有旧 release
- APP 与小程序可共用相同资源版本,不必重复发两套发布目录
补充约束:
- 正式资源目录只认 `OSS / CDN`,不认仓库本地目录
- `tmp/` 只作为临时收件箱,不作为任何正式发布源
- 当前 manual 多赛道 demo 已切到:
- `gotomars/kml/lxcb-001/2026-04-07/route01.kml`
- `gotomars/kml/lxcb-001/2026-04-07/route02.kml`
- `gotomars/kml/lxcb-001/2026-04-07/route03.kml`
- `gotomars/kml/lxcb-001/2026-04-07/route04.kml`
---
## 8. 数据库建模建议
推荐按“主表 + version 表”建模。
建议对象:
- `maps`
- `map_versions`
- `playfields`
- `playfield_versions`
- `game_modes`
- `game_mode_versions`
- `resource_packs`
- `resource_pack_versions`
- `events`
- `event_versions`
- `event_releases`
其中:
- 主表存稳定元信息
- version 表存 `jsonb` 内容和具体资源引用
例如:
### `maps`
- `id`
- `code`
- `name`
- `status`
- `current_version_id`
### `map_versions`
- `id`
- `map_id`
- `version_code`
- `content_jsonb`
- `published_asset_root`
- `status`
### `playfields`
- `id`
- `code`
- `name`
- `kind`
- `status`
- `current_version_id`
### `playfield_versions`
- `id`
- `playfield_id`
- `version_code`
- `source_type`
- `content_jsonb`
- `asset_root`
- `status`
### `resource_packs`
- `id`
- `code`
- `name`
- `status`
- `current_version_id`
### `resource_pack_versions`
- `id`
- `resource_pack_id`
- `version_code`
- `content_jsonb`
- `asset_root`
- `status`
### `game_modes`
- `id`
- `code`
- `name`
- `status`
- `current_version_id`
### `game_mode_versions`
- `id`
- `game_mode_id`
- `version_code`
- `content_jsonb`
- `status`
### `event_versions`
- `id`
- `event_id`
- `version_code`
- `map_version_id`
- `playfield_version_id`
- `game_mode_version_id`
- `resource_pack_version_id`
- `overrides_jsonb`
- `status`
核心点:
- `Event` 不直接指向文件 URL
- `EventVersion` 指向对象版本
- `Release` 固化当时装配结果
---
## 9. 后端职责边界
后端应强管理:
- 对象关系
- 版本关系
- 引用有效性
- 发布装配
- 发布记录
后端不应强管理:
- 每个玩法的所有细字段解释
- 所有 HUD / 动画 / 实验细项的强结构化列
- 玩家运行时设置
- 玩家实时状态
同样不应做:
- 为 APP 和小程序各维护一套资源目录规范
- 为 APP 和小程序各发布一套不同语义的 event 配置
适合继续走 `jsonb` 的内容:
- `game.sequence.*`
- `game.guidance.*`
- `game.presentation.*`
- `playfield.controlOverrides.*`
- 各类实验性字段
---
## 10. 推荐实施顺序
建议不要一次重构到底,按下面顺序推进:
1. 先把概念定住
- `Map`
- `Playfield`
- `GameMode`
- `ResourcePack`
- `Event`
2. 先做文档和目录规范
3. 后端先补对象模型和 version 表草案
4. 配置构建器改成“按引用装配”
5. 发布器改成“版本化共享资源 + event release manifest”
6. 最后再做正式后台 UI
---
## 11. 当前阶段的务实建议
在完全切换到对象化模型前,当前仓库可以先这样过渡:
- 继续保留 [event](D:/dev/cmr-mini/event) 作为最小样例区
- 继续保留现有 `import-local -> preview -> publish`
- 但新的 source 设计和目录设计先按本文档收口
也就是说:
- 短期不推翻现有链路
- 中期把资源引用模型补进来
- 长期把单文件 `event/*.json` 迁到对象化配置系统
---
## 12. 一句话结论
后端资源管理的正确方向不是“每个活动一堆文件”,而是:
`共享资源对象库 + Event 引用装配 + Release 固化发布`
只有这样地图复用、KML 复用、资源包复用、多活动发布才能长期稳定。
并且这套模型必须从一开始就兼顾未来 APP而不是做成“小程序跑通后再重构”的临时结构。