# 开发说明 > 文档版本:v1.20 > 最后更新:2026-04-03 16:16:38 ## 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 ``` ## 3. Workbench 当前重点 - 推荐联调入口: - `Bootstrap Demo` - `Use Classic Demo / Use Score-O Demo / Use Manual Variant Demo` - `整条链一键验收` - 如果 frontend 需要把页面侧调试日志直接打到 backend,优先使用: - `POST /dev/client-logs` - 然后在 workbench 的 `前端调试日志` 面板里查看 - 如果需要判断前端到底拿到了哪份配置,优先看 workbench 的: - `当前 Launch 实际配置摘要` - 这块会直接显示: - `configUrl` - `releaseId` - `manifestUrl` - `schemaVersion` - `playfield.kind` - `game.mode` - 这组信息用于和前端地图页实际消费结果对口排查,避免只靠口头描述“像顺序赛/像积分赛”。 - 注意: - 这块摘要由 backend 代读 manifest,只用于 workbench 调试 - 这样做是为了避免浏览器直接读取 OSS 时受跨域影响 - 它不替代正式客户端加载逻辑 - 正式客户端仍必须直接消费 `launch.config.configUrl` 或 `launch.resolvedRelease.manifestUrl` - `前端调试日志` 也是调试专用能力: - backend 当前只在内存里保留最近 200 条 - 适合前端把关键事实直接打进来,避免只靠截图和口头描述 - 不替代正式生产日志体系 默认会设置: - `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. 选择一种玩法入口: - `Use Classic Demo` - `Use Score-O Demo` - `Use Manual Variant Demo` 3. `一键补齐 Runtime 并发布` 4. `一键标准回归` 当前这条一键链会自动完成: - 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` - 真实输入替换第一刀: - `CourseSource.fileUrl` 当前已切到真实 KML: - `https://oss-mbh5.colormaprun.com/gotomars/kml/lxcb-001/10/c01.kml` - `https://oss-mbh5.colormaprun.com/gotomars/kml/lxcb-001/10/c02.kml` - `TileRelease.tileBaseUrl / metaUrl` 当前已切到真实地图资源: - `https://oss-mbh5.colormaprun.com/gotomars/map/lxcb-001/tiles/` - `https://oss-mbh5.colormaprun.com/gotomars/map/lxcb-001/tiles/meta.json` - manual 多赛道 demo 当前已使用两条真实赛道输入: - `variant_a -> c01.kml` - `variant_b -> c02.kml` - 显式玩法测试入口: - 顺序赛:`evt_demo_001 -> rel_demo_001 -> classic-sequential.json` - 积分赛:`evt_demo_score_o_001 -> rel_demo_score_o_001 -> score-o.json` - 多赛道:`evt_demo_variant_manual_001 -> rel_demo_variant_manual_001` 当前日志能力: - 每一步都会写到“响应日志” - 失败时会直接输出: - 错误消息 - stack - 最后一次 curl - 成功时“预期结果”面板会直接给出: - `Release ID` - `Presentation` - `Content Bundle` - `Runtime Binding` - `判定` - 成功跑完标准回归后,“回归结果汇总”会直接给出: - `发布链` - `Play` - `Launch` - `Result` - `History` - `Session ID` - `总判定` - workbench 现在还支持查看 frontend 主动上报的调试日志: - `拉取前端日志` - `清空前端日志` - 前端建议最少带: - `eventId` - `releaseId` - `sessionId` - `manifestUrl` - `route` - `game.mode` - `playfield.kind` - 当前页面阶段或动作名 ### 2.2 前端调试日志最小约定 dev 环境下,frontend 可直接把关键调试事实发到 backend: - `POST /dev/client-logs` 建议请求体最少包含: ```json { "source": "miniprogram", "level": "info", "category": "runtime", "message": "map page loaded manifest", "eventId": "evt_demo_score_o_001", "releaseId": "rel_xxx", "sessionId": "sess_xxx", "manifestUrl": "https://oss-mbh5.colormaprun.com/...", "route": "pages/map/map", "occurredAt": "2026-04-03T16:16:38+08:00", "details": { "schemaVersion": "1", "playfield.kind": "control-set", "game.mode": "score-o", "phase": "map-init" } } ``` 当前说明: - `source`:建议填终端来源,例如 `miniprogram` - `level`:建议填 `info / warn / error` - `category`:建议填 `launch / runtime / cache / network` - `message`:一句话说明当前发生了什么 - `details`:放结构化调试细节,backend 原样收下 辅助接口: - `GET /dev/client-logs?limit=50` - `DELETE /dev/client-logs` ## 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。