开发说明.md 18 KB

开发说明

文档版本:v1.25 最后更新:2026-04-03 18:56:46

1. 环境变量

参考 .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. 本地启动

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

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

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
    • 整条链一键验收
  • 当前玩法切换除了切 event / release / source / build,还会自动切换:
    • presentation schema
    • content manifest
    • asset manifest
  • 这些 demo 资源现在由 backend 提供,避免继续在 workbench 里保留 example.com 占位地址:
    • GET /dev/demo-assets/presentations/{demoKey}
    • GET /dev/demo-assets/content-manifests/{demoKey}
  • 如果 frontend 需要把页面侧调试日志直接打到 backend,优先使用:
    • POST /dev/client-logs
    • 然后在 workbench 的 前端调试日志 面板里查看
  • 如果需要判断前端到底拿到了哪份配置,优先看 workbench 的:
    • 当前 Launch 实际配置摘要
  • 这块会直接显示:
    • configUrl
    • releaseId
    • manifestUrl
    • schemaVersion
    • playfield.kind
    • game.mode
  • 这组信息用于和前端地图页实际消费结果对口排查,避免只靠口头描述“像顺序赛/像积分赛”。
  • 注意:
    • 这块摘要由 backend 代读 manifest,只用于 workbench 调试
    • 这样做是为了避免浏览器直接读取 OSS 时受跨域影响
    • 它不替代正式客户端加载逻辑
    • 正式客户端仍必须直接消费 launch.config.configUrllaunch.resolvedRelease.manifestUrl
  • 前端调试日志 也是调试专用能力:
    • backend 当前只在内存里保留最近 200 条
    • 适合前端把关键事实直接打进来,避免只靠截图和口头描述
    • 不替代正式生产日志体系
  • Bootstrap Demo 准备出的联调文案也已换成中文样例:
    • 领秀城公园顺序赛
    • 领秀城公园积分赛
    • 领秀城公园多赛道挑战

4. 活动卡片列表最小摘要

当前 backend 已为以下入口统一补齐活动卡片最小摘要字段:

  • /cards
  • /home
  • /me/entry-home

当前字段集:

  • title
  • subtitle
  • summary
  • status
  • statusCode
  • timeWindow
  • ctaText
  • coverUrl
  • isDefaultExperience
  • eventType
  • currentPresentation
  • currentContentBundle

当前派生规则:

  • summary
    • 无值时回退为:当前暂无活动摘要
  • status
    • running -> 进行中
    • upcoming -> 即将开始
    • ended -> 已结束
    • 其余 -> 状态待确认
  • timeWindow
    • cards.starts_at / ends_at 派生
    • 缺失时回退为:时间待公布
  • ctaText
    • 默认体验活动:进入体验
    • 进行中:进入活动
    • 已结束:查看回顾
    • 其余:查看详情
  • currentPresentation / currentContentBundle
    • 当前继续表示已发布 release 实际绑定摘要
    • 不是 event 草稿默认值

默认会设置:

  • 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-

启动后可直接打开:

当前 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. 如果只是想看发布过程,点 Bootstrap + 发布当前玩法
  4. 如果想只测发布链,点 一键补齐 Runtime 并发布
  5. 如果想直接验整条链,点 一键标准回归

当前这几个按钮的职责已经拆开:

  • Bootstrap Demo(只准备数据)
    • 只负责准备 demo event / source / build / release / runtime 等测试数据
    • 不会基于当前玩法再额外重新发布一版
  • Bootstrap + 发布当前玩法
    • 会先执行一遍 Bootstrap Demo
    • 然后对当前选中的玩法执行“发布活动配置(自动补 Runtime)”
  • 一键补齐 Runtime 并发布
    • 不再隐式 bootstrap
    • 只基于当前已选玩法和当前表单上下文执行发布链

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

  • 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

建议请求体最少包含:

{
  "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.0 玩家进入规则

当前要明确一条玩家链路规则:

  • 玩家进入游戏,必须基于“已发布 release”
  • 不能基于:
    • event 草稿默认绑定
    • 未发布 presentation
    • 未发布 content bundle
    • 未发布 runtime

当前接口中的:

  • currentPresentation
  • currentContentBundle

在玩家链路里表示的是:

  • 当前已发布 release 上实际绑定的展示版本摘要
  • 当前已发布 release 上实际绑定的内容包摘要

不是:

  • event 草稿默认值摘要

所以如果当前 release 还没绑定这些对象,玩家页看到空值是正常行为。前端页面应优先:

  • play.canLaunch 判定是否允许进入
  • 把空值解释成“当前未发布或当前发布未绑定”

当前 canLaunch 已按正式进入规则收紧:

  • 只有当当前 event 满足以下条件时,play.canLaunch = true
    • event status = active
    • 已存在当前发布 release
    • 当前发布 release 有 manifest
    • 当前发布 release 已绑定 runtime
    • 当前发布 release 已绑定 presentation
    • 当前发布 release 已绑定 content bundle

当前 POST /events/{eventPublicID}/launch 也已与 canLaunch 保持同一套前置条件。

3.1 开发阶段先不用 Redis

当前第一版全部依赖:

  • PostgreSQL
  • JWT
  • refresh token 持久化

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

3.2 开发环境短信

当前默认可走 console provider。

用途:

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

3.3 微信小程序开发态

当前支持 dev- 前缀 code。

适合:

  • 后端联调
  • workbench 快速验证

3.4 本地配置目录

当前支持从根目录 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_PATHOSSUTIL_CONFIG_FILE 决定 backend 发布 manifest 时使用哪个 OSS 客户端

4. Migration

当前 migration 文件在 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.jsonasset-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 PlacesCreate Place
  2. 在该 PlaceCreate Map Asset
  3. 在该 MapAssetCreate Tile Release
  4. Create Course Source
  5. 在该 MapAssetCreate Course Set
  6. 在该 CourseSetCreate 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。