# 资源对象与目录方案
> 文档版本：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，而不是做成“小程序跑通后再重构”的临时结构。


