# 开发说明
> 文档版本：v1.14
> 最后更新：2026-04-03 20:10:25


## 1. 环境变量

参考 [`.env.example`](D:/dev/cmr-mini/backend/.env.example)。

当前最关键的变量：

- `APP_ENV`
- `HTTP_ADDR`
- `DATABASE_URL`
- `JWT_ACCESS_SECRET`
- `AUTH_SMS_PROVIDER`
- `AUTH_DEV_SMS_CODE`
- `WECHAT_MINI_APP_ID`
- `WECHAT_MINI_APP_SECRET`
- `WECHAT_MINI_DEV_PREFIX`
- `LOCAL_EVENT_DIR`
- `ASSET_BASE_URL`
- `ASSET_PUBLIC_BASE_URL`
- `ASSET_BUCKET_ROOT`
- `OSSUTIL_PATH`
- `OSSUTIL_CONFIG_FILE`

## 2. 本地启动

```powershell
cd D:\dev\cmr-mini\backend
.\start-backend.ps1
```

如果你想固定跑开发工作台常用端口 `18090`，直接执行：

```powershell
cd D:\dev\cmr-mini\backend
.\scripts\start-dev.ps1
```

默认会设置：

- `APP_ENV=development`
- `HTTP_ADDR=:18090`
- `DATABASE_URL=postgres://postgres:asdf*123@192.168.100.77:5432/cmr20260401?sslmode=disable`
- `AUTH_SMS_PROVIDER=console`
- `WECHAT_MINI_DEV_PREFIX=dev-`

启动后可直接打开：

- [http://127.0.0.1:18090/dev/workbench](http://127.0.0.1:18090/dev/workbench)

当前 workbench 已覆盖两类调试链：

- 用户主链：`bootstrap -> auth -> entry/home -> event play/launch -> session -> result`
- 后台运营链：`maps/playfields/resource-packs -> admin event source -> build -> publish -> rollback`
- 第一阶段生产骨架联调台：`places -> map-assets -> tile-releases -> course-sources -> course-sets -> course-variants -> runtime-bindings`
- 第三刀最小接线验证：`runtimeBinding -> release -> launch.runtime`
- 第四刀发布闭环验证：`runtimeBinding -> publish(runtimeBindingId) -> release -> launch.runtime`
- 活动运营域第二阶段验证：`presentation -> content bundle -> publish(presentationId, contentBundleId, runtimeBindingId) -> release`
- 活动运营域第二阶段第二刀验证：`event detail / play / launch -> presentation + content bundle 摘要`
- 活动运营域第二阶段第三刀验证：`release 摘要闭环 + content bundle import`
- 活动运营域第二阶段第四刀验证：`presentation import -> event 默认 active 绑定 -> publish 空参继承`
- workbench 一键验证增强：`一键默认绑定发布` 与 `一键补齐 Runtime 并发布`
- `/dev/bootstrap-demo` 现在也会回填最小生产骨架：`place / map asset / tile release / course source / course set / course variant / runtime binding`

### 2.1 当前推荐验证方式

如果目标是验证“从测试数据准备到 release 继承是否完整”，优先使用 workbench 的一键流，而不是手工逐个点按钮。

当前推荐顺序：

1. `Bootstrap Demo`
2. `一键补齐 Runtime 并发布`
3. `一键标准回归`

当前这条一键链会自动完成：

- demo event / source / build / release 准备
- presentation 导入
- content bundle 导入
- event 默认 active 绑定保存
- 最小生产骨架准备：
  - `place`
  - `map asset`
  - `tile release`
  - `course source`
  - `course set`
  - `course variant`
  - `runtime binding`
- publish
- release 回读校验
- `play / launch / result / history` 回归汇总
- demo 活动残留 ongoing session 清理：
  - 会把 demo event 下历史遗留的 `launched / running` session 自动改成 `cancelled`

当前日志能力：

- 每一步都会写到“响应日志”
- 失败时会直接输出：
  - 错误消息
  - stack
  - 最后一次 curl
- 成功时“预期结果”面板会直接给出：
  - `Release ID`
  - `Presentation`
  - `Content Bundle`
  - `Runtime Binding`
  - `判定`
- 成功跑完标准回归后，“回归结果汇总”会直接给出：
  - `发布链`
  - `Play`
  - `Launch`
  - `Result`
  - `History`
  - `Session ID`
  - `总判定`

## 3. 当前开发约定

### 3.1 开发阶段先不用 Redis

当前第一版全部依赖：

- PostgreSQL
- JWT
- refresh token 持久化

Redis 后面只在需要性能优化、限流或短期票据缓存时再接。

### 3.2 开发环境短信

当前默认可走 `console` provider。

用途：

- 本地联调无需接真实短信供应商

### 3.3 微信小程序开发态

当前支持 `dev-` 前缀 code。

适合：

- 后端联调
- workbench 快速验证

### 3.4 本地配置目录

当前支持从根目录 [event](D:/dev/cmr-mini/event) 导入本地配置文件。

相关环境变量：

- `LOCAL_EVENT_DIR`
- `ASSET_BASE_URL`

作用：

- `LOCAL_EVENT_DIR` 决定本地 source config 从哪里读
- `ASSET_BASE_URL` 决定 preview build 时如何把相对资源路径归一化成可运行 URL
- `ASSET_PUBLIC_BASE_URL` 决定 publish 时如何把公开 URL 映射到 OSS 对象 key
- `ASSET_BUCKET_ROOT` 决定发布对象上传到哪个 bucket 根路径
- `OSSUTIL_PATH` 和 `OSSUTIL_CONFIG_FILE` 决定 backend 发布 manifest 时使用哪个 OSS 客户端

## 4. Migration

当前 migration 文件在 [migrations](D:/dev/cmr-mini/backend/migrations)。

执行原则：

1. 按编号顺序执行
2. schema 变更只通过新增 migration 完成
3. 不直接改线上已执行 migration

## 5. 开发工作台

### `POST /dev/bootstrap-demo`

它会保证 demo 数据存在：

- `tenant_demo`
- `mini-demo`
- `evt_demo_001`
- `rel_demo_001`
- `card_demo_001`

### `GET /dev/workbench`

这是当前最重要的联调工具。

可以直接测试：

- 登录
- 入口解析
- 首页聚合
- event play
- 第一阶段生产骨架对象
- 配置导入、preview build、publish build
- launch
- session start / finish
- result
- profile

补充说明：

- `publish build` 现在会真实上传 `manifest.json` 和 `asset-index.json` 到 OSS
- 如果上传失败，接口会直接报错，不再出现“数据库里已有 release，但 OSS 上没有对象”的假成功
- `Save Event Defaults` 会把当前 event 的默认 active 绑定写入：
  - `currentPresentationId`
  - `currentContentBundleId`
  - `currentRuntimeBindingId`
- 之后 `Publish Build` 如果不显式填写这三项，会优先继承 event 默认 active 绑定

并且支持：

- quick flow
- scenario 保存/导入/导出
- curl 导出
- request history

当前第一阶段生产骨架联调台只做：

- `list`
- `create`
- `detail`
- `binding`

明确不做：

- 正式后台 UI
- `edit`
- `delete`
- `batch`
- 审核流

活动运营域第二阶段当前也只做最小动作：

- `list`
- `create`
- `detail`
- `publish 绑定`
- `import`

## 6. 当前推荐联调顺序

### 场景一：小程序快速进入

1. `bootstrap-demo`
2. `login/wechat-mini`
3. `me/entry-home`
4. `events/{id}/play`
5. `events/{id}/launch`
6. `sessions/{id}/start`
7. `sessions/{id}/finish`
8. `sessions/{id}/result`

### 场景二：APP 主身份

1. `auth/sms/send`
2. `auth/login/sms`
3. `me/entry-home`
4. `launch`
5. `session`
6. `result`

### 场景三：微信轻账号绑定手机号

1. `login/wechat-mini`
2. `auth/sms/send` with `scene=bind_mobile`
3. `auth/bind/mobile`
4. `me/profile`

### 场景四：配置发布到可启动 release

1. `bootstrap-demo`
2. `dev/events/{eventPublicID}/config-sources/import-local`
3. `dev/config-builds/preview`
4. `dev/config-builds/publish`
5. `events/{id}`
6. `events/{id}/launch`

### 场景五：第一阶段生产骨架最小闭环

在 `/dev/workbench` 的 `后台运营` 模式中，按下面顺序操作：

1. `List Places` 或 `Create Place`
2. 在该 `Place` 下 `Create Map Asset`
3. 在该 `MapAsset` 下 `Create Tile Release`
4. `Create Course Source`
5. 在该 `MapAsset` 下 `Create Course Set`
6. 在该 `CourseSet` 下 `Create Variant`
7. `Create Runtime Binding`

成功后应能拿到这些 ID：

- `placeId`
- `mapAssetId`
- `tileReleaseId`
- `courseSourceId`
- `courseSetId`
- `courseVariantId`
- `runtimeBindingId`

建议第一次联调时用这组最小规则：

- `Place` 先建 1 个
- 每个 `Place` 先只建 1 个 `MapAsset`
- 每个 `MapAsset` 先只建 1 个 `TileRelease`
- 每个 `CourseSet` 先只建 1 个默认 `CourseVariant`
- `RuntimeBinding` 先只绑定当前正在验证的 `Event`

这条链当前只验证对象关系闭环，不验证：

- 发布链切换
- `launch` 返回运行对象字段
- `EventPresentation`
- `ContentBundle`

### 场景六：第三刀最小接线验证

在 `/dev/workbench` 的 `后台运营` 模式中，先完成“场景五”，再按下面顺序操作：

1. `Get Pipeline`
2. 确认当前 `Release ID`
3. 填或复用 `Runtime Binding ID`
4. `Bind Runtime`
5. `Get Release`
6. 切回 `前台联调`
7. 对同一个 `event` 执行 `Launch`

### 场景七：活动运营域第二阶段最小闭环

在 `/dev/workbench` 的 `后台运营` 模式中，按下面顺序操作：

1. `Get Event`
2. `Create Presentation`
3. `Create Bundle`
4. `Assemble Source`
5. `Build Source`
6. 在发布区填：
   - `Runtime Binding ID`
   - `Presentation ID`
   - `Content Bundle ID`
7. `Publish Build`
8. `Get Release`

成功后应能在 release 返回中看到：

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

并且这 3 类绑定当前都已固化到 `event_release`。

成功后应能看到：

- `GET /admin/releases/{releasePublicID}` 返回 `runtime`
- `POST /events/{eventPublicID}/launch` 返回 `launch.runtime`

当前阶段的约束是：

- 只新增 `runtime` 字段块
- 不改旧的：
  - `resolvedRelease`
  - `business`
  - `variant`
- release 如果没挂 `runtimeBindingId`，则 `launch.runtime` 为空

### 场景八：活动运营域第二阶段第三刀验证

在 `/dev/workbench` 的 `后台运营` 模式中，先完成“场景七”，再按下面顺序操作：

1. `Create Presentation` 或直接复用现有 `Presentation ID`
2. `Import Bundle`
3. `Get Bundle`
4. `Get Pipeline`
5. `Publish Build`
6. `Get Release`
7. 切回 `前台联调`
8. `Event Detail`
9. `Event Play`
10. `Launch`

成功后应能同时看到这三组摘要：

- `release.presentation.templateKey / version`
- `release.contentBundle.bundleType / version`
- `release.runtime.placeId / mapId / tileReleaseId / courseVariantId`

同时客户端消费侧应保持一致：

- `GET /events/{eventPublicID}`
- `GET /events/{eventPublicID}/play`
- `POST /events/{eventPublicID}/launch`

当前 Content Bundle Import 只做统一导入入口，不做复杂资源平台：

- 输入：
  - `title`
  - `bundleType`
  - `sourceType`
  - `manifestUrl`
  - `version`
  - `assetManifest`
- 输出：
  - `bundleId`
  - `bundleType`
  - `version`
  - `assetManifest`
  - `status`

### 场景七：第四刀发布闭环验证

在 `/dev/workbench` 的 `后台运营` 模式中，先完成“场景五”，再按下面顺序操作：

1. `Create Runtime Binding`
2. `Get Pipeline`
3. 确认 `Build ID`
4. 在发布区填 `Runtime Binding ID`
5. `Publish Build`
6. `Get Release`
7. 切回 `前台联调`
8. 对同一个 `event` 执行 `Launch`

成功后应能看到：

- `POST /admin/builds/{buildID}/publish` 返回带 `runtime`
- `GET /admin/releases/{releasePublicID}` 返回同一条 `runtime`
- `POST /events/{eventPublicID}/launch` 返回同一条 `launch.runtime`

当前第四刀的兼容要求是：

- 旧的“先 `publish`，再 `bind runtime`”路径继续可用
- 新的“`publish` 时直接传 `runtimeBindingId`”优先推荐
- 不修改旧的：
  - `resolvedRelease`
  - `business`
  - `variant`

## 7. 当前后续开发建议

文档整理完之后，后面建议按这个顺序继续：

1. 抽出更通用的 `play context -> launch` 模型
2. 补赛事与报名层
3. 补页面配置和白标首页
4. 再考虑实时网关票据

不要跳回去把玩法规则塞进 backend。


