595 lines
11 KiB
Markdown
595 lines
11 KiB
Markdown
# 资源对象与目录方案
|
||
> 文档版本:v1.0
|
||
> 最后更新:2026-04-02 08:28:05
|
||
|
||
|
||
本文档用于把“地图复用、KML 复用、内容资源复用、配置发布”统一收成一套后端可执行方案。
|
||
|
||
目标:
|
||
|
||
- 不再把所有资源都塞进单个 `event/*.json`
|
||
- 让地图、KML、内容模板、主题资源都能独立复用
|
||
- 让 `event` 只负责“组合与覆盖”,不拥有底层资源本体
|
||
- 让 `release` 能稳定追溯当时到底用了哪一份地图、哪一份 KML、哪一套资源包
|
||
- 让同一套资源对象既能服务小程序,也能服务未来 APP
|
||
|
||
---
|
||
|
||
## 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/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 与小程序可共用相同资源版本,不必重复发两套发布目录
|
||
|
||
---
|
||
|
||
## 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,而不是做成“小程序跑通后再重构”的临时结构。
|
||
|
||
|