# T2B 协作清单
> 文档版本：v1.15
> 最后更新：2026-04-03 19:26:23

说明：

- 本文件由总控维护，写给后端线程
- 目标是把“后台生产闭环”第一阶段需要落地的东西讲清楚
- 只写当前阶段实施说明，不写长讨论稿
- 正式架构文档以 [后台生产闭环架构草案](D:/dev/cmr-mini/doc/backend/后台生产闭环架构草案.md) 为准

---

## 0. 当前阶段状态与下一步

backend 当前已完成：

- 生产骨架对象落库与 `/dev/workbench` 最小联调台
- `MapRuntimeBinding -> EventRelease -> launch.runtime` 主链接通
- `EventPresentation / ContentBundle / EventRelease` 第一阶段接通
- `Event` 默认 active 三元组固化：
  - `currentPresentationId`
  - `currentContentBundleId`
  - `currentRuntimeBindingId`
- `publish` 默认继承当前 active 三元组
- `Bootstrap Demo` 与 `一键补齐 Runtime 并发布` 已可从空白状态跑完整测试链
- `一键标准回归` 与 `回归结果汇总` 已接入标准联调入口
- `前端调试日志` 与 `当前 Launch 实际配置摘要` 已接入 workbench
- 三类标准 demo 入口已显式挂出：
  - `evt_demo_001`
  - `evt_demo_score_o_001`
  - `evt_demo_variant_manual_001`
- workbench 日志已补齐：
  - 分步日志
  - 真实错误
  - stack
  - 最后一次 curl
  - 预期判定

当前主线不再是继续补对象，而是进入：

**联调标准化阶段**

本阶段 backend 的核心任务只有 3 件事：

1. 固化“一键测试”链路，确保从空白环境可重复跑通
2. 固化详细日志口径，失败时明确定位在哪一步
3. 固化稳定测试数据，并逐步支持更接近生产的真实输入

当前认为“真实输入替换第二刀”已经完成，backend 当前已完成：

**活动卡片列表最小产品化配合第一刀**

优先顺序建议：

1. 维持现有一键回归链稳定
2. 为活动列表页补最小卡片摘要接口或字段
3. 保持活动详情页与列表页使用同一套活动摘要口径

说明：

- 真实 `content manifest`
- 真实 `presentation schema`
- 真实 `活动文案样例`

这三类输入已接入，当前不再作为本轮重点。

原则：

- 仍走同一条一键回归链
- 不重新设计联调流程
- 只是把 demo 输入逐步换成更接近生产的真实输入

当前 backend 不建议切去做：

- 新的玩家侧页面入口
- 更多管理对象
- 更复杂后台 UI

### 0.5 当前分工

backend 当前这一轮已完成：

1. 活动卡片列表最小字段集对应的后端摘要
2. 卡片摘要接口/返回字段收口
3. 默认体验活动与普通活动的状态标记

当前进入：

**活动卡片列表最小产品化第一刀联调回归与小范围修复配合阶段**

要求：

- 继续挂在现有标准 demo 与一键回归链上
- 不新开流程
- 不新开对象层级
- 优先响应前端在列表页第一刀联调中暴露的字段、默认值和语义问题

当前进一步明确 backend 的执行口径如下：

### 0.1 一键测试链路

请继续以这条链作为唯一标准联调入口维护：

```text
Bootstrap Demo
-> 一键补齐 Runtime 并发布
-> launch / play / result / history 回归
```

要求：

- 从空白环境直接可跑
- 不依赖手工预铺 6~8 个对象
- 同一条链可反复执行
- 失败时能明确知道卡在哪一跳

### 0.2 详细日志口径

workbench 和相关 backend 调试输出，当前应至少统一包含：

- 当前步骤名
- 核心输入参数
- 真实错误信息
- stack
- 最后一次 curl
- 预期判定

不要只输出“失败了”，要能回答：

- 是哪一步失败
- 为什么失败
- 用什么请求复现

### 0.3 稳定测试数据

当前 demo 数据不要继续散落手工维护，统一以 backend 准备的一键测试数据为准。

后续逐步支持以下更接近生产的真实输入：

- 地图资源 URL
- KML / 赛道文件
- 内容 manifest
- presentation schema
- 活动文案样例

### 0.4 当前不建议做

联调标准化阶段不要继续发散去做：

- 新对象扩张
- 新管理面板
- 更复杂 workbench UI
- 复杂后台运营功能
- 与当前联调闭环无关的页面能力

当前不建议 backend 继续发散去做：

- 更多新对象
- 更多 workbench 管理按钮
- 更复杂后台 UI
- 过早扩大奖励、社交、审核流

---

## 1. 本次目标

本次不是让 backend 一次性做完整后台，而是先搭出**最小生产骨架**，让下面这条链能真正闭环：

```text
地图输入
-> 瓦片版本
-> KML 导入
-> 赛道 variant
-> 活动绑定
-> release
-> launch
-> 客户端消费
```

当前重点是：

- 先把对象模型定下来
- 先让地图、KML、活动三条输入链有正式落点
- 先让客户端只认发布产物

补充确认：

- 本次接受 backend 采用**增量演进**方式推进
- 不要求一次性推翻当前已稳定联调的：
  - `Event`
  - `EventRelease`
  - `Session`
  主链

当前补充确认：

- 生产骨架第一阶段与活动运营域第二阶段第四刀已经完成
- backend 当前下一步应切到“联调标准化”，而不是继续新增对象层级

---

## 2. 本次范围

### 2.1 本次必须做

- 定义并落库以下核心对象：
  - `Place`
  - `MapAsset`
  - `TileRelease`
  - `CourseSet`
  - `CourseVariant`
  - `CourseSource`
  - `Event`
  - `EventPresentation`
  - `ContentBundle`
  - `MapRuntimeBinding`
  - `EventRelease`

- 先让 KML 不再只是文件，而能转成 `CourseVariant`
- 先让活动不再只是页面概念，而能正式绑定：
  - 展示定义
  - 内容包
  - 运行绑定
  - 发布版本

- 第一阶段优先落以下对象：
  - `Place`
  - `MapAsset`
  - `TileRelease`
  - `CourseSource`
  - `CourseSet`
  - `CourseVariant`
  - `MapRuntimeBinding`

- 第一阶段允许暂缓完整落库：
  - `EventPresentation`
  - `ContentBundle`
  但对象语义必须先在架构上定清楚

### 2.2 本次先不做

- 奖励系统
- 社交系统
- 复杂审核流
- 完整后台 UI
- 大而全的素材编排器
- 高级权限体系

---

## 3. 后端对象最小理解

### 3.1 地图运行域

#### `Place`
- 地点
- 上层业务对象
- 一个地点下可有多张地图

#### `MapAsset`
- 某个地点下的一张地图资源
- 一张地图可有多个瓦片版本

#### `TileRelease`
- 某张地图的具体瓦片发布版本

#### `CourseSet`
- 一组赛道集合
- 例如：校园顺序赛、校园积分赛

#### `CourseVariant`
- 一个具体可运行赛道方案
- 顺序赛 8 点 / 12 点
- 积分赛 A / B / C 方案
- 客户端最终只应认这个对象

#### `CourseSource`
- 原始输入源
- KML 只是来源，不是最终业务对象

### 3.2 活动运营域

#### `Event`
- 活动业务对象
- 默认体验活动和定制活动都属于它

#### `EventPresentation`
- 活动卡片、详情页、H5 schema

#### `ContentBundle`
- 图片、音频、动画、文创、结果页资源等内容包

#### `MapRuntimeBinding`
- 活动运行时绑定哪张地图、哪条赛道、哪套瓦片、哪套配置

#### `EventRelease`
- 客户端真正消费的活动发布版本

---

## 4. 最小关系建议

建议 backend 先按这个关系理解：

- `Place 1 -> N MapAsset`
- `MapAsset 1 -> N TileRelease`
- `MapAsset 1 -> N CourseSet`
- `CourseSet 1 -> N CourseVariant`
- `CourseVariant N -> 1 CourseSource`

- `Event 1 -> N EventPresentation`
- `Event 1 -> N ContentBundle`
- `Event 1 -> N MapRuntimeBinding`
- `Event 1 -> N EventRelease`

其中：

- `MapRuntimeBinding` 负责引用：
  - `placeId`
  - `mapId`
  - `tileReleaseId`
  - `courseSetId`
  - `courseVariantId`

---

## 5. 后端第一阶段建议实施顺序

### 第一步：按增量方式落库最小对象

建议先把表和基础模型定下来。

第一阶段优先级建议：

1. `places`
2. `map_assets`
3. `tile_releases`
4. `course_sources`
5. `course_sets`
6. `course_variants`
7. `map_runtime_bindings`

第二阶段再补：

8. `event_presentations`
9. `content_bundles`

说明：

- 当前稳定的 `events / event_releases / sessions` 主链保留
- 本次是在现有骨架上增量补生产对象，不做一次性替换式重构

### 第二步：先打通 KML 导入链

目标：

- 上传 KML
- 保存 `CourseSource`
- 解析控制点与起终点
- 生成一个 `CourseVariant`
- 归入某个 `CourseSet`

### 第三步：先打通活动绑定链

目标：

- 一个 `Event` 可绑定：
  - `EventPresentation`
  - `ContentBundle`
  - `MapRuntimeBinding`

### 第四步：先打通发布链

目标：

- 生成 `EventRelease`
- `launch` 先继续返回当前稳定字段：
  - `resolvedRelease`
  - `business`
  - `variant`
- 第二阶段再补完整运行对象字段：
  - `placeId`
  - `mapId`
  - `tileReleaseId`
  - `courseVariantId`
  - `eventReleaseId`

### 第五步：把第一阶段生产骨架接口接入 `/dev/workbench`

目标：

- 不让第一阶段对象只停留在 API 目录里
- 在 workbench 里形成最小可操作联调面板
- 用于验证对象关系和生产闭环，不用于替代正式后台

本步建议只做：

#### A. 地点与地图
- `Place` 列表
- 新建 `Place`
- 在 `Place` 下新建 `MapAsset`
- 在 `MapAsset` 下新建 `TileRelease`
- 查看详情

#### B. 赛道与 KML
- `CourseSource` 列表
- 新建 `CourseSource`
- 新建 `CourseSet`
- 在 `CourseSet` 下新建 `CourseVariant`
- 查看详情

#### C. 运行绑定
- `MapRuntimeBinding` 列表
- 新建 `MapRuntimeBinding`
- 选择：
  - `place`
  - `map`
  - `tile release`
  - `course variant`
- 查看详情

本步明确不做：

- 完整后台 UI
- Event 全量编辑
- `EventPresentation` 可视化搭建
- `ContentBundle` 大资源管理台
- Build / Release 全流程可视化
- 删除、批量操作、审核流

一句话：

**workbench 当前只做“第一阶段生产骨架联调台”，不做“正式后台管理系统”。**

### 第六步：进入“最小接线”阶段

目标：

- 把 `MapRuntimeBinding` 和当前 `EventRelease` 接起来
- 让运行对象开始逐步进入 `launch`
- 保持当前前端稳定链不被打断

本步建议优先做：

#### A. `EventRelease` 接 `MapRuntimeBinding`
- 在 `EventRelease` 上补 `runtimeBindingId`
- 查询 `EventRelease` 时可带出最小 `runtime binding` 摘要

#### B. `launch` 新增 `runtime` 摘要块
- 保留当前稳定字段：
  - `resolvedRelease`
  - `business`
  - `variant`
- 新增一个兼容性的 `runtime` 块，建议最少返回：
  - `runtimeBindingId`
  - `placeId`
  - `mapId`
  - `tileReleaseId`
  - `courseSetId`
  - `courseVariantId`
- 如字段成本不高，可附带：
  - `placeName`
  - `mapName`
  - `routeCode`

#### C. `workbench` 最小接线验证
- 在 `/dev/workbench` 上增加：
  - `EventRelease` 选择或查看
  - 绑定 `runtimeBinding`
  - 查看 release 当前已接入的运行对象摘要

本步明确要求：

- 不修改旧字段语义
- 不移除旧字段
- 不让前端现有 `launch` 链断掉
- 先做到“后端可挂接、可透出、可验证”

---

## 6. 当前接口落地方向

当前阶段建议 backend 后续接口逐步收敛到：

### 6.1 生产侧接口

- 创建地点
- 创建地图
- 创建瓦片版本记录
- 上传 KML
- 生成赛道 variant
- 创建活动
- 保存活动展示定义
- 保存内容包引用
- 保存运行绑定
- 创建活动 release

### 6.2 客户端消费接口

- 活动列表
- 活动详情
- `launch`
- session start
- session finish
- result
- history

关键要求：

- 客户端只消费 release 产物
- 不再消费原始 KML
- 不再消费地图原始资产
- `launch` 采用两阶段兼容，不要求第一阶段打断当前前端稳定链

### 6.3 workbench 联调台

backend 下一步建议把以下接口先接到 `/dev/workbench`：

- `Place`
- `MapAsset`
- `TileRelease`
- `CourseSource`
- `CourseSet`
- `CourseVariant`
- `MapRuntimeBinding`

接入目标：

- list
- create
- detail
- binding

不建议当前阶段接入：

- edit
- delete
- batch
- 审核流

### 6.4 最小接线阶段接口方向

backend 下一步建议新增或补齐以下能力：

- `EventRelease` 挂接 `runtimeBindingId`
- 查询 `EventRelease` 时返回最小运行绑定摘要
- `launch` 返回新增 `runtime` 摘要块

当前目标不是让前端强依赖新字段，而是先让：

- release 和 runtime binding 接上
- `launch` 能把运行对象透出来
- 前后端可以开始验证运行对象链是活的

### 6.5 第四刀：发布闭环阶段

在第三刀已经完成：

- `MapRuntimeBinding -> EventRelease`
- `launch.runtime` 兼容透出

之后，下一步建议进入真正的**发布闭环阶段**。

目标：

- 不再要求“先 publish，再手工 bind runtime”
- 改为 publish 时就能直接产出带 `runtimeBindingId` 的完整 `EventRelease`
- 保持当前旧接口和旧字段完全兼容

本步建议优先做：

#### A. publish/build 接口支持 `runtimeBindingId`

- 在当前发布链中允许显式传入 `runtimeBindingId`
- 如果传入，则发布完成后直接把 release 绑好 runtime
- 如果不传入，则继续保持当前兼容行为

#### B. workbench publish 面板接入 runtime 选择

- 在 `/dev/workbench` 的发布操作区增加 `Runtime Binding` 选择
- 支持一条完整联调链：
  - 选 release source / build
  - 选 `runtimeBindingId`
  - publish
  - 直接 launch 验证

#### C. release 查询继续返回 runtime 摘要

- `Get Release`
- `launch`

都继续透出当前最小 `runtime` 摘要，供前端和总控验证。

本步关键要求：

- 只加能力，不改旧语义
- 新流程优先，但旧流程继续可用
- 发布结果尽量原子，避免漏掉 runtime 挂接

---

## 7. 当前需要 backend 重点注意的边界

1. KML 只是输入源，不是最终业务对象  
2. 活动不是素材仓库，活动只引用 `ContentBundle`  
3. 地图上层必须有 `Place`，不要让 `MapAsset` 直接当最上层  
4. 客户端最终必须只认 `EventRelease`  
5. launch 返回必须落到具体：
   - `placeId`
   - `mapId`
   - `tileReleaseId`
   - `courseVariantId`
   - `eventReleaseId`

---

## 8. 当前待 backend 回写确认

请 backend 线程后续重点回写以下确认：

1. 第一阶段表结构是否接受这套对象拆分  
2. `Place / MapAsset / TileRelease / CourseSource / CourseSet / CourseVariant / MapRuntimeBinding` 是否按当前顺序推进  
3. KML 导入链是否准备按 `CourseSource -> CourseVariant` 落  
4. 活动是否接受拆成：
   - `Event`
   - `EventPresentation`
   - `ContentBundle`
   - `MapRuntimeBinding`
   - `EventRelease`
5. `launch` 两阶段兼容方案是否按当前确认推进  
6. workbench 是否按“第一阶段生产骨架联调台”接入，且只做 list / create / detail / binding  

本轮新增执行项：

7. 是否按“第三刀最小接线”推进：
   - `MapRuntimeBinding -> EventRelease`
   - `launch.runtime` 摘要透出
   - 继续保持旧字段兼容

8. 是否按“第四刀发布闭环”推进：
   - publish 直接支持 `runtimeBindingId`
   - workbench publish 面板增加 runtime 选择
   - 继续保留“先 publish，再 bind runtime”的兼容路径

9. 是否进入“活动运营域第二阶段”：
   - `EventPresentation` 最小落库
   - `ContentBundle` 最小落库
   - `EventRelease` 明确绑定 `presentation / bundle / runtime`

10. 是否进入“活动运营域第二阶段第二刀”：
   - `event detail` 透出最小 `presentation / bundle` 摘要
   - `release detail` 透出最小 `presentation / bundle / runtime` 摘要
   - `launch` 增加兼容性的 `presentation / contentBundle` 摘要块
   - publish 在未显式传入时允许按 event 当前默认配置自动补齐 `presentation / bundle`

---

## 9. 一句话结论

本次给 backend 的实施要求很简单：

**先别继续围绕散装页面和散装配置推进，先把地图运行域和活动运营域的最小骨架搭起来。**

当前下一步重点已经进一步明确为：

**活动运营域第二阶段第三刀第一版已完成，backend 下一步切到“展示定义统一导入与默认绑定”阶段。**

### 6.6 第五刀：前端正式接线阶段

当前 backend 已完成第四刀第一版：

- publish 直接支持 `runtimeBindingId`
- workbench publish 区支持直接填写 `Runtime Binding ID`
- 发布成功返回 `runtime`
- 旧的“先 publish，再 bind runtime”路径继续兼容

因此下一步建议正式进入**前端接线阶段**。

目标：

- 前端开始正式消费 `launch.runtime`
- 活动准备页、地图页、结果页、历史页开始逐步展示运行对象摘要
- 继续保持旧字段兼容，不要求一轮切掉老逻辑

前端第一阶段建议优先做：

#### A. `launch.runtime` 消费

- 读取并缓存：
  - `runtimeBindingId`
  - `placeId`
  - `mapId`
  - `tileReleaseId`
  - `courseSetId`
  - `courseVariantId`
- 如后端已返回名称摘要，也同步接入：
  - `placeName`
  - `mapName`
  - `routeCode`

#### B. 准备页与地图页最小展示

- 在准备页展示当前地点 / 地图 / 赛道摘要
- 在地图页调试或摘要区透出当前 runtime 对象

#### C. 结果与历史摘要逐步接入

- 先不强改所有列表
- 优先让单局结果页和历史详情页能看见：
  - `place`
  - `map`
  - `variant`
  - `routeCode`

当前阶段原则：

- 前端正式上场，但只接新增摘要，不推翻现有稳定页面主链
- `resolvedRelease / business / variant` 旧字段仍继续保留和可用
- 如果后端某些名称摘要尚未补齐，前端先按 ID + 已有字段兜底

### 6.7 第六刀之后的下一步：活动运营域第二阶段第二刀

当前 backend 已完成：

- `0009_event_ops_phase2.sql`
- `EventPresentation` 最小落库
- `ContentBundle` 最小落库
- `EventRelease` 已可绑定：
  - `presentationId`
  - `bundleId`
  - `runtimeBindingId`
- publish 已支持显式挂接：
  - `presentationId`
  - `contentBundleId`
  - `runtimeBindingId`

因此下一步建议进入“活动运营域第二阶段第二刀”。目标是：

- 让 `EventPresentation / ContentBundle` 不只存在于后台和 publish 输入里
- 而是正式进入可查询、可验证、可消费的发布摘要

建议顺序：

#### A. `event detail` 透出当前展示与内容包摘要

建议最少返回：

- `currentPresentation`
  - `presentationId`
  - `templateKey`
  - `version`
- `currentContentBundle`
  - `bundleId`
  - `bundleType`
  - `version`

#### B. `Get Release` 透出完整最小摘要

建议 `release detail` 同时包含：

- `presentation`
- `contentBundle`
- `runtime`

前两者先以摘要形式返回，不先做复杂 schema 下发。

#### C. `launch` 增加兼容性的活动运营摘要块

在保持旧字段与当前 `runtime` 不变的前提下，新增：

- `presentation`
- `contentBundle`

建议最少包括：

- `presentationId`
- `templateKey`
- `bundleId`
- `bundleType`

#### D. publish 增加默认补齐逻辑

如果 publish 未显式传入：

- `presentationId`
- `contentBundleId`

允许按 event 当前默认配置自动补齐。

本步明确不建议做：

- 前端立即全面消费 presentation / bundle
- 复杂活动页面 schema 下发
- 内容包全量资源编排
- 正式后台大 UI

### 6.8 活动运营域第二阶段第三刀：发布摘要闭环与内容包导入入口

当前已确认：

- frontend 已完成“活动运营域摘要第一刀”
- 当前前端进入联调回归与小范围修复阶段
- backend 下一步不应继续围绕前端页面做字段补丁，而应继续把活动运营域本身做完整

本刀建议拆成两步，但按一个阶段推进。

#### A. 先把 release 摘要闭环

目标：

- 让 `EventRelease` 真正成为活动运营域统一发布产物
- 保证以下几处返回的摘要语义一致：
  - `event detail`
  - `event play`
  - `launch`
  - `release detail`

建议 `release detail` 最少透出：

- `presentation`
  - `presentationId`
  - `templateKey`
  - `version`
- `contentBundle`
  - `bundleId`
  - `bundleType`
  - `version`
- `runtime`
  - `runtimeBindingId`
  - `placeId`
  - `mapId`
  - `tileReleaseId`
  - `courseVariantId`

同时建议 `/dev/workbench` 的 release 查看区，能直接验证这三类摘要。

#### B. 再打开 `ContentBundle` 统一导入入口

目标：

- 不再只手工创建 `ContentBundle`
- 让后续静态资源、音频、动画、文创等内容，先通过统一导入入口进入 bundle

当前阶段建议只做“入口”和“元信息”，不做完整资源平台。

建议最小能力：

- 新增 `ContentBundle Import` 最小接口
- 接收：
  - `bundleType`
  - `sourceType`
  - `manifestUrl` 或等价资源清单入口
  - `version`
  - `title`
- 创建后生成：
  - `bundleId`
  - `bundleType`
  - `version`
  - `assetManifest`
  - `status`

当前明确不做：

- 复杂资源上传工作流
- 大文件管理台
- 资源审核流
- 全量 H5 schema 组装

关键原则：

- `ContentBundle` 先做统一导入入口，不先做复杂资源管理系统
- frontend 当前不消费资源明细，只继续认摘要
- 先把“发布对象完整”和“内容资源有正式入口”两件事做起来

### 6.9 活动运营域第二阶段第四刀：展示定义统一导入与默认绑定

当前已确认：

- backend 已完成：
  - `event detail / play / launch / release detail` 活动运营摘要闭环
  - `ContentBundle` 统一导入入口第一版
- frontend 当前不再扩新页面链，继续联调回归

所以下一步 backend 不建议继续围绕玩家侧摘要补字段，而应继续把活动运营域生产链做完整。

#### A. 打开 `EventPresentation` 统一导入入口

目标：

- 后续外部活动卡片/H5 搭建系统，不再只靠手工创建 presentation
- 而是通过统一入口把展示定义正式导入 backend

建议最小能力：

- 新增 `EventPresentation Import` 最小接口
- 接收：
  - `templateKey`
  - `sourceType`
  - `schemaUrl` 或等价 schema 入口
  - `version`
  - `title`
- 创建后生成：
  - `presentationId`
  - `templateKey`
  - `version`
  - `schema`
  - `status`

#### B. 固化 `Event` 当前默认 active 绑定

目标：

- 把 `Event` 当前默认使用的：
  - `presentation`
  - `contentBundle`
  - `runtimeBinding`
  三者关系稳定下来

建议至少明确：

- `currentPresentationId`
- `currentContentBundleId`
- `currentRuntimeBindingId`

以及 publish 在未显式传入时，默认如何继承这三者。

#### C. `/dev/workbench` 增加最小验证

建议补：

- `Import Presentation`
- 查看 event 当前 active：
  - presentation
  - bundle
  - runtime
- 在 publish 区验证默认继承是否正确

当前明确不做：

- 复杂展示编辑器
- 全量 H5 schema 编排平台
- 大型资源后台

关键原则：

- `EventPresentation` 和 `ContentBundle` 都要有统一导入入口
- `Event` 继续做业务壳和默认绑定，不吞大资源
- 玩家前端继续只认发布摘要，不认后台草稿对象
