# Backend TodoList
> 文档版本：v1.2
> 最后更新：2026-04-02 11:03:02


## 1. 目标

这份 TodoList 只列当前需要 backend 配合联调和近期应推进的事项。

原则：

- 不重复写已经稳定可用的能力
- 优先写会影响前后端联调闭环的点
- 边界不清的事项单独标记“需确认”

## 2. 当前联调现状

当前已经可联调的主链：

- 微信小程序登录
- `GET /events/{eventPublicID}/play`
- `POST /events/{eventPublicID}/launch`
- `POST /sessions/{sessionPublicID}/start`
- `POST /sessions/{sessionPublicID}/finish`
- `GET /sessions/{sessionPublicID}/result`

小程序侧已经具备：

- backend 地址和 token 持久化
- `launch -> GameLaunchEnvelope` 适配
- 进入地图后自动上报 `session start`
- 对局结束后自动上报 `session finish`

所以 backend 现在最重要的不是再扩散接口，而是把当前契约和语义收稳。

当前已确认不再阻塞主链的事项：

- `evt_demo_001` 的 release manifest 现已可正常加载
- 小程序已能进入地图
- `launch` 关键字段在当前阶段不再单边漂移
- `cancelled / failed / finished` 已从 ongoing 口径里收稳
- 模拟定位 / 调试日志问题已回到小程序与模拟器侧，不再属于 backend 当前阻塞

前端当前需要配合的事项：

- 正式联调时始终以 `launch.resolvedRelease.manifestUrl` 为准，不再回退到本地样例配置路径
- 如果再出现配置加载失败，反馈完整上下文：
  - `eventPublicID`
  - `releaseId`
  - `manifestUrl`
  - 页面报错文案
  - 控制台 / 网络日志
- 当前 demo 联调建议统一使用：
  - `eventPublicID = evt_demo_001`
  - `channelCode = mini-demo`
  - `channelType = wechat_mini`

## 3. P0 已完成

## 3.0 固定 session 状态语义

当前 backend 已明确并固定：

- `finished`
- `failed`
- `cancelled`

建议当前口径：

- 正常打终点完成：`finished`
- 超时结束：`failed`
- 主动退出 / 放弃恢复：`cancelled`

说明：

- 小程序现在已经按这个方向接
- 如果 backend 想改这 3 个状态语义，需要先讨论，不要单边改

## 3.1 明确“放弃恢复”的后端处理

当前小程序本地恢复逻辑已经是：

- 进入程序检测到未正常结束对局
- 弹确认框
- 玩家可“继续恢复”或“放弃”

现在本地“放弃”只会清除本地恢复快照。

backend 已确认的目标语义是：

> 玩家点击“放弃恢复”后，这一局是否应同时在业务后端标记为 `cancelled`。

当前结论：

- **是，应标记为 `cancelled`**

原因：

- 否则 `ongoingSession` 会继续存在
- `/events/{id}/play` 和 `/me/entry-home` 可能一直把它当成可继续的局
- 会和小程序本地“已放弃”产生语义分叉

当前 backend 已收口：

1. `POST /sessions/{id}/finish` 使用 `status=cancelled` 是否就是官方放弃语义
2. 如果客户端持有旧 `sessionToken`，恢复放弃时是否允许直接调用 `finish(cancelled)`
3. `cancelled` 后，`event play` 和 `entry-home` 中不再返回为 `ongoingSession`

备注：

- 小程序侧现在可以把“点击放弃恢复”正式接成同步调用 `finish(cancelled)`。

## 3.2 保证 start / finish 幂等与重复调用安全

联调和真实环境里，以下情况很常见：

- 网络重试
- 页面重进
- 故障恢复后二次补报
- 用户重复点击

当前 backend 已确认：

- `start` 重复调用的幂等语义
- `finish` 重复调用的幂等语义

当前实现：

- `start`：如果已 `running`，返回当前 session，视为成功
- `finish`：如果已进入终态，返回当前 session/result，视为成功

目的：

- 不把客户端补偿逻辑变成一堆冲突分支

## 3.3 固定 `launch` 返回契约，不随意漂移

当前客户端已经按下面这些字段接入：

- `launch.resolvedRelease.releaseId`
- `launch.resolvedRelease.manifestUrl`
- `launch.resolvedRelease.manifestChecksumSha256`
- `launch.config.configUrl`
- `launch.config.configLabel`
- `launch.config.releaseId`
- `launch.config.routeCode`
- `launch.business.sessionId`
- `launch.business.sessionToken`
- `launch.business.sessionTokenExpiresAt`

backend 现在需要做的是：

- 先保持这些字段名稳定
- 如果要调整命名或层级，先沟通

前端当前需要做的是：

- 只消费当前已约定字段
- 不额外推断 release URL
- 不把本地样例配置路径混进正式 launch 流程
- 如果字段缺失或命名变化，直接在联调清单里标阻塞

## 4. P1 应尽快做

## 4.1 多赛道 Variant 第一阶段最小契约

当前前端已给出：

- [多赛道 Variant 五层设计草案](D:/dev/cmr-mini/doc/gameplay/多赛道Variant五层设计草案.md)
- [多赛道 Variant 前后端最小契约](D:/dev/cmr-mini/doc/gameplay/多赛道Variant前后端最小契约.md)

backend 当前建议第一阶段只做最小闭环：

- `play.assignmentMode`
- `play.courseVariants[]`
- `launch.variant.*`
- `session / result / ongoing / recent` 补 `variantId / variantName / routeCode`

当前目标：

1. 一个 session 最终只绑定一个 `variantId`
2. `launch` 返回最终绑定结果
3. 恢复链不重新分配 variant
4. 结果页、ongoing、历史结果都能追溯 variant

备注：

- 当前只先定最小契约，不先做完整后台 variant 编排模型
- 当前第一阶段最小后端链路已补入：
  - `play.assignmentMode`
  - `play.courseVariants[]`
  - `launch.variant.*`
  - `session / result / ongoing / recent` 的 `variantId / variantName / routeCode`
- 下一步应由前端按该契约联调，不再继续扩后台 variant 模型

## 4.2 增加用户身体资料读取接口

小程序侧已经有：

- telemetry profile 合并入口
- 心率/卡路里计算逻辑

backend 下一步建议提供：

- 当前用户 body profile 查询接口

建议返回至少包含：

- `birthDate` 或 `heartRateAge`
- `weightKg`
- `restingHeartRateBpm`
- `maxHeartRateBpm`（可选）

这样后面心率页和消耗估算就能真实接业务数据。

## 4.3 给 `session result` 补一点稳定摘要字段校验

客户端现在会上报：

- `finalDurationSec`
- `finalScore`
- `completedControls`
- `totalControls`
- `distanceMeters`
- `averageSpeedKmh`

backend 建议补两件事：

- 合理性校验
- 空值容忍

不要因为某个可选字段缺失就整局 finish 失败。

## 4.4 dev workbench 增加一组“恢复 / 取消恢复”场景按钮

当前 workbench 已经很好用了。

建议后续再补：

- 标记 session 为 `cancelled`
- 查询 ongoing session
- 快速查看某个用户最新 session 状态

这会很适合配合小程序故障恢复联调。

## 4.5 前端预埋“放弃恢复”调用位

这项先预埋，不要先自行定语义。

前端建议准备好：

- 在“放弃恢复”按钮点击后，预留调用 `finish(cancelled)` 的位置
- 但是否正式启用，要等 backend 把 `cancelled` 语义确认完

这样一旦 backend 确认语义，小程序就能快速切过去，不需要再改一轮页面流程。

## 5. P2 下一阶段

## 5.1 配置后台 source / build / release 真正开始做

当前已经有：

- 表结构
- 架构文档

还缺：

- source CRUD
- build 触发
- manifest 产物生成
- release 发布
- asset index 查询

这个建议在当前主链联稳之后再推进。

## 5.2 page / cards / competition 等业务对象继续长出来

这部分不是当前联调阻塞项，但后面会成为业务壳的重要组成。

## 5.3 兼顾未来 APP 的统一后端约束

backend 后续建设需要继续坚持：

- 不做“小程序专用后端”
- 用户模型保持平台级
- `event / release / session / result` 不按终端拆两套
- 终端差异只通过上下文字段和运行时适配处理

建议优先保持：

- 业务接口统一
- 配置发布结构统一
- 结果沉淀结构统一

这样后面 APP 接入时不会推翻现有 backend 结构。

## 6. 需要先讨论再动的边界

这些事项 backend 不建议自己先拍板：

### 6.1 `failed` 是否专指超时

当前建议是：

- 超时 -> `failed`
- 主动退出 / 放弃恢复 -> `cancelled`

如果 backend 有别的语义方案，需要先统一。

### 6.2 放弃恢复是否一定写后端

我个人建议写后端，并落成 `cancelled`。

但如果 backend 团队认为：

- 放弃恢复只影响本地
- 业务上仍允许以后继续从服务端 ongoing session 恢复

那就必须明确告知客户端，不然两边会冲突。

### 6.3 result 页是以后继续本地展示，还是跳业务结果页

当前客户端是本地结果页。

backend 后面如果要接业务结果页，最好提前定：

- finish 成功后是否仍停留地图内结果页
- 还是跳业务壳结果页

## 7. 我建议的最近动作

backend 现在最值得先做的，不是继续铺更多页面接口，而是先推进下面 3 条：

1. 与前端确认多赛道第一阶段最小契约
2. 已按最小契约扩完 `play -> launch -> session/result`
3. 再补用户身体资料接口和 workbench 恢复场景按钮

这样不会打断当前主链，同时能把下一阶段多赛道联调接上。

## 8. 一句话结论

当前 backend 最重要的任务不是“再加更多接口”，而是：

> 在不破坏当前稳定主链的前提下，先把多赛道 Variant 第一阶段最小契约定稳，再继续向配置与后台模型延伸。


