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

11 KiB
Raw Blame History

资源对象与目录方案

文档版本v1.0 最后更新2026-04-02

本文档用于把“地图复用、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. 推荐目录结构

仓库内建议把“源资源”和“活动源配置”拆开。

推荐结构:

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 可以继续保留作为过渡区,但后面建议逐步迁到:

  • events/
  • resources/
  • game-modes/

5. Event Source 应该怎么写

后续 event source 不建议继续直接写死地图路径和 KML 路径,而应该引用对象版本。

推荐形态:

{
  "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 可以保持现在的运行结构,例如:

{
  "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

这种“玩法文件名”方式长期演进。

建议改成版本化结构:

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 作为最小样例区
  • 继续保留现有 import-local -> preview -> publish
  • 但新的 source 设计和目录设计先按本文档收口

也就是说:

  • 短期不推翻现有链路
  • 中期把资源引用模型补进来
  • 长期把单文件 event/*.json 迁到对象化配置系统

12. 一句话结论

后端资源管理的正确方向不是“每个活动一堆文件”,而是:

共享资源对象库 + Event 引用装配 + Release 固化发布

只有这样地图复用、KML 复用、资源包复用、多活动发布才能长期稳定。

并且这套模型必须从一开始就兼顾未来 APP而不是做成“小程序跑通后再重构”的临时结构。