# 资源对象与目录方案 本文档用于把“地图复用、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,而不是做成“小程序跑通后再重构”的临时结构。