# 后台生产闭环架构草案 > 文档版本:v1.10 > 最后更新:2026-04-03 20:05:00 本文档用于明确当前系统后续必须建立的后台生产闭环,重点回答以下问题: - 地图如何输入与管理 - KML 如何输入与管理 - 活动卡片如何输入与管理 - 客户端最终到底消费什么 - 一次活动从生产到发布的最小闭环是什么 本文档当前已经从初版的“地图 / 赛道 / 活动 / 发布”四对象方案,升级为更完整的**双域骨架**: - 地图运行域 - 活动运营域 目标是让后端线程后续可以按统一对象模型落地,而不是继续围绕散装文件、散装页面地址和临时配置推进。 --- ## 1. 总体结论 当前系统后续不应继续按“前端页面 + 若干散装配置”的方式推进,而应正式收口成一套后台生产与发布闭环。 当前补充确认: - 第一阶段采用**增量演进**,不一次性推翻现有稳定主链 - 当前已稳定的: - `Event` - `EventRelease` - `Session` 继续保留 - 新对象先以生产骨架方式增量引入 建议长期按以下链路理解: ```text 地图资源输入 -> 地点 / 地图资产管理 -> 瓦片生成 -> 瓦片版本发布 KML 输入 -> 赛道解析 -> 赛道集合 / 赛道方案管理 -> 赛道版本发布 活动卡片输入 -> 活动展示配置 -> 绑定地图 / 赛道 / 规则 / 内容包 -> 活动发布版本 最终: Event Release -> launch -> manifest / config / tile release / variant -> 客户端消费 ``` 核心判断: - 地图是资源资产 - KML 是赛道原始输入源,不是最终业务对象 - 活动卡片是活动展示层,不是素材仓库 - 发布版本才是客户端真正消费的正式产物 --- ## 2. 双域骨架 当前建议把后台对象正式拆成两大域。 ### 2.1 地图运行域 负责: - 地点 - 地图资源 - 瓦片版本 - 赛道集合 - 赛道方案 - KML 原始输入源 ### 2.2 活动运营域 负责: - 活动业务对象 - 活动展示定义 - 活动内容资源 - 活动运行绑定 - 活动发布版本 这两个域必须分开管理,但要通过运行绑定和发布版本连接起来。 --- ## 3. 地图运行域对象 ### 3.1 地点对象 `Place` 作用: - 作为最上层业务地点对象 - 组织一个地点下的多张地图 - 作为地图与活动的上层归属 建议最小字段: - `placeId` - `name` - `region` - `coverUrl` - `description` - `centerPoint` - `status` 关键原则: - 后台上层以地点为中心管理,不以地图为最高层 - 用户认的是地点,不是某个底层 mapId ### 3.2 地图资源对象 `MapAsset` 作用: - 管理某个地点下的一张具体地图资源 - 可表示竞赛图、训练图、导览图等不同图种 建议最小字段: - `mapId` - `placeId` - `name` - `mapType` - `coverUrl` - `description` - `sourceFiles` - `status` 关键原则: - 一个地点可有多张不同地图 - 地图原始资产与瓦片产物必须分开 ### 3.3 瓦片版本对象 `TileRelease` 作用: - 管理某张地图的具体瓦片发布版本 建议最小字段: - `tileReleaseId` - `mapId` - `version` - `tileBaseUrl` - `metaUrl` - `status` - `publishedAt` 关键原则: - 一张地图可以对应多个瓦片版本 - 客户端运行时必须落到具体 `TileRelease` ### 3.4 赛道集合对象 `CourseSet` 作用: - 管理一组同主题赛道集合 - 例如:校园顺序赛、校园积分赛、亲子体验赛 建议最小字段: - `courseSetId` - `placeId` - `mapId` - `mode` - `name` - `description` - `status` 关键原则: - 顺序赛和积分赛都统一落在 `CourseSet` - `CourseSet` 是赛道方案集合,不是具体方案本身 ### 3.5 赛道方案对象 `CourseVariant` 作用: - 表示一个具体可运行的赛道方案 - 是客户端真正应该认的赛道对象 建议最小字段: - `variantId` - `courseSetId` - `name` - `routeCode` - `mode` - `controlCount` - `difficulty` - `status` - `sourceId` - `configPatch` - `isDefault` 关键原则: - 顺序赛下的 8 点、12 点、16 点都应是 `CourseVariant` - 积分赛下不同布点方案也应是 `CourseVariant` - 客户端只认 `CourseVariant`,不直接认 KML ### 3.6 原始输入对象 `CourseSource` 作用: - 管理原始输入源 - KML 只是输入来源,不是最终业务对象 建议最小字段: - `sourceId` - `type` - `fileUrl` - `checksum` - `importedAt` - `parserVersion` 关键原则: - KML 不直接下发客户端 - KML 先进入后台解析,再转成 `CourseVariant` --- ## 4. 活动运营域对象 ### 4.1 活动对象 `Event` 作用: - 管理活动卡片、活动详情、报名、签到、排行榜等展示与运营能力 - 作为对外活动业务对象 建议最小字段: - `eventId` - `title` - `subtitle` - `eventType` - `status` - `timeWindow` - `defaultExperience` - `playMode` - `currentPresentationId` - `currentBundleId` 关键原则: - 地图默认活动和定制活动都统一属于 `Event` - 只是 `eventType` 不同,例如: - `default_experience` - `standard_event` - `custom_campaign` ### 4.2 展示对象 `EventPresentation` 作用: - 管理活动卡片、详情页、H5 结构和区块 schema 建议最小字段: - `presentationId` - `eventId` - `cardSchema` - `detailSchema` - `templateKey` - `status` - `version` 关键原则: - 活动页继续允许 H5 + API 快搭 - 但展示页必须有正式对象和版本,不应只是散装页面地址 ### 4.3 内容资源对象 `ContentBundle` 作用: - 管理活动使用的静态资源和内容资源包 建议最小字段: - `bundleId` - `name` - `bundleType` - `assetManifest` - `version` - `status` 资源类型可包括: - 图片 - 音频 - 动画 - 文创内容 - H5 详情资源 - 结果页资源 - 品牌素材 关键原则: - 活动对象不直接吞大量资源 - 活动只引用 `ContentBundle` ### 4.4 运行绑定对象 `MapRuntimeBinding` 作用: - 把活动运营域和地图运行域连接起来 - 明确一个活动实际运行时使用哪张地图、哪条赛道、哪套瓦片和配置 建议最小字段: - `runtimeBindingId` - `eventId` - `placeId` - `mapId` - `tileReleaseId` - `courseSetId` - `courseVariantId` - `configReleaseId` 关键原则: - 活动不直接管理 KML、地图原始文件 - 活动只引用已经整理好的运行资源 ### 4.5 活动发布对象 `EventRelease` 作用: - 管理活动对外发布的正式产物 - 形成客户端唯一消费入口 建议最小字段: - `eventReleaseId` - `eventId` - `presentationId` - `bundleId` - `runtimeBindingId` - `manifestUrl` - `configUrl` - `status` - `publishedAt` - `rollbackFrom` 关键原则: - 客户端只认 `EventRelease` - `EventRelease` 必须版本化、可回滚、可追踪 --- ## 5. 三条输入链如何定 ### 5.1 地图输入链 建议支持两类入口: 1. 上传新的地图原始资源 2. 基于已有地图重新生成新瓦片版本 最小流程: 1. 创建 `Place` 2. 在地点下创建 `MapAsset` 3. 上传原始资源 4. 后台校验与处理 5. 生成瓦片 6. 生成 `TileRelease` 建议后台状态: - 草稿 - 处理中 - 可发布 - 已发布 - 已归档 ### 5.2 KML 输入链 建议支持: 1. 上传 KML 文件 2. 后台自动解析 3. 预览起点、终点、控制点 4. 校验字段与几何合法性 5. 保存为 `CourseSource` 6. 生成 `CourseVariant` 7. 归入某个 `CourseSet` 关键要求: - KML 解析过程必须可预览 - 起终点、控制点、点位顺序必须可人工复核 - 发布后形成稳定 `CourseVariant`,不再直接依赖原始 KML ### 5.3 活动卡片输入链 建议继续保持当前 H5 + API 快搭方向,但后台对象要正式化。 最小流程: 1. 创建 `Event` 2. 配置活动基础信息 3. 配置 `EventPresentation` 4. 导入或绑定 `ContentBundle` 5. 绑定 `MapRuntimeBinding` 6. 生成 `EventRelease` 关键要求: - 保持 H5 灵活性 - 但活动页必须有正式对象、正式版本、正式发布入口 --- ## 6. 后台模块建议 第一阶段建议只做 5 个后台模块,不要一开始铺太大。 ### 6.1 地点与地图管理 负责: - 地点列表 - 地图列表 - 地图详情 - 原始资源上传 - 瓦片版本管理 ### 6.2 赛道管理 负责: - KML 上传 - 赛道解析预览 - 控制点校验 - `CourseSet` - `CourseVariant` ### 6.3 活动管理 负责: - 活动列表 - 活动基础信息 - 默认体验活动标记 - 活动时间与状态 ### 6.4 展示与内容管理 负责: - 活动卡片与详情配置 - H5 页面 schema - 内容资源包管理 ### 6.5 发布管理 负责: - 运行绑定 - 当前发布版本 - 历史版本 - 回滚 - 发布记录 补充说明: - 第一阶段为了提高联调效率,可以把新增生产骨架对象接入 `/dev/workbench` - 但 `/dev/workbench` 当前定位应当是: - 第一阶段生产骨架联调台 - 对象关系验证台 - 输入链路验证台 - 不应当在这一阶段把它扩张成完整后台系统 --- ## 7. 客户端最终消费什么 后续客户端不应直接消费后台散装对象,而应以发布产物为准。 客户端正式消费边界建议为: - 活动摘要列表 - 活动详情数据 - `launch` - `manifestUrl` - `configUrl` - `eventReleaseId` - `place/map/tile/course/variant` 摘要 也就是说: - 客户端不直接碰地图原始文件 - 客户端不直接碰原始 KML - 客户端不直接碰活动草稿 - 客户端只消费发布后的稳定产物 --- ## 8. 第一阶段 workbench 接入建议 建议 backend 在第一阶段把新增生产骨架接口接入 `/dev/workbench`,但范围严格控制。 ### 8.1 建议接入的对象 - `Place` - `MapAsset` - `TileRelease` - `CourseSource` - `CourseSet` - `CourseVariant` - `MapRuntimeBinding` ### 8.2 建议提供的最小能力 - list - create - detail - binding ### 8.3 当前不建议接入的能力 - 完整 Event 管理台 - `EventPresentation` 可视化搭建 - `ContentBundle` 资源管理台 - Build / Release 完整后台 - 编辑、删除、批量操作、审核流 设计原则: **当前 `/dev/workbench` 只承担“第一阶段生产骨架联调台”的角色,不承担正式后台系统角色。** --- ## 9. 最小接线阶段建议 在第一阶段生产骨架对象已经落库、接口已接入 workbench 之后,下一步建议进入真正的“最小接线”阶段。 ### 9.1 接线目标 - 把 `MapRuntimeBinding` 和现有 `EventRelease` 接起来 - 让运行对象开始逐步进入 `launch` - 继续保持当前稳定前端链兼容 ### 9.2 接线顺序 #### 第一步:`EventRelease` 挂接 `runtimeBindingId` 目标: - 当前发布版本不仅指向展示和配置,还能指向实际运行对象 #### 第二步:查询 release 时带出最小 runtime 摘要 建议最少包括: - `runtimeBindingId` - `placeId` - `mapId` - `tileReleaseId` - `courseSetId` - `courseVariantId` #### 第三步:`launch` 增加兼容性的 `runtime` 块 要求: - 继续保留当前稳定字段: - `resolvedRelease` - `business` - `variant` - 新增 `runtime` 摘要块 - 先透出,不要求客户端第一阶段立即强依赖 ### 9.3 当前阶段原则 - 只加字段,不改旧字段语义 - 只补连接,不推翻主链 - 先做到“可挂接、可透出、可验证” - 前端现有稳定联调链不能被打断 ### 9.4 第四刀:发布闭环阶段 在第三刀已经完成: - `MapRuntimeBinding -> EventRelease` - `launch.runtime` 兼容透出 之后,下一步建议进入**发布闭环阶段**。 目标: - 让 `EventRelease` 在 publish 时就直接带上 `runtimeBindingId` - 避免继续依赖“先 publish,再手工 bind runtime”的两段式操作 - 保持当前旧链路完全兼容,避免打断现有前端稳定联调 建议顺序: #### 第一步:publish/build 接口支持传入 `runtimeBindingId` 要求: - 如果 publish 时传入 `runtimeBindingId` - 则发布完成后直接生成一个已接 runtime 的完整 `EventRelease` - 如果不传入,则继续允许后续通过现有 bind runtime 接口补挂 #### 第二步:workbench publish 区接入 runtime 选择 要求: - 在 `/dev/workbench` 的发布操作里增加 `Runtime Binding` 选择项 - 支持最小联调链: - 选 release source/build - 选 `runtimeBindingId` - publish - launch 验证 #### 第三步:保持 release / launch 查询摘要一致 要求: - `Get Release` - `launch` 继续稳定返回最小 `runtime` 摘要,便于前端和总控验证发布结果是否完整。 关键原则: - 只加能力,不改旧语义 - 新流程优先,旧流程兼容 - 发布结果尽量原子,减少漏挂 runtime 的人工步骤 ### 9.5 第五刀:前端接线阶段 在第四刀已经完成: - publish 直接支持 `runtimeBindingId` - workbench publish 区可选择或填写 runtime - 发布成功返回 `runtime` 之后,下一步建议进入**前端正式接线阶段**。 目标: - 前端开始正式消费 `launch.runtime` - 运行对象摘要逐步进入准备页、地图页、结果页、历史页 - 保持旧字段兼容,不打断当前稳定主链 建议顺序: #### 第一步:准备页接 `launch.runtime` 最先展示: - `place` - `map` - `course variant` - `routeCode` #### 第二步:地图页接 runtime 摘要 要求: - 地图页至少能在调试区或摘要区确认当前实际运行的是哪套地图和赛道 #### 第三步:结果与历史逐步接入 要求: - 单局结果页优先透出当前运行对象摘要 - 历史详情页随后补齐 - 列表页可最后再做 关键原则: - 前端从新增 `runtime` 摘要开始接,不要求一次性重构所有页面 - `resolvedRelease / business / variant` 旧字段继续保留 - 这一步的重点是“让运行对象真正被前端看见”,不是再扩更多后端对象 ### 9.6 第六刀:活动运营域第二阶段 在第五刀前端摘要接线已经完成第一轮之后,当前主线建议切换为: - `EventPresentation` - `ContentBundle` - `EventRelease` 这一组对象的正式化落地。 当前建议优先做: 1. `EventPresentation` 最小落库 2. `ContentBundle` 最小落库 3. `EventRelease` 明确绑定: - `presentationId` - `bundleId` - `runtimeBindingId` 当前不建议继续优先扩: - Place / Map / Tile / Course 一侧对象 - runtime 摘要继续扩更多前端页面 - 复杂活动后台 UI ### 9.7 第七刀:活动运营域第二阶段第二刀 在第六刀已经完成: - `EventPresentation` 最小落库 - `ContentBundle` 最小落库 - `EventRelease` 已绑定: - `presentationId` - `bundleId` - `runtimeBindingId` - publish 已支持显式挂接: - `presentationId` - `contentBundleId` - `runtimeBindingId` 之后,下一步建议进入活动运营域第二阶段第二刀。 目标: - 让 `EventPresentation / ContentBundle` 从“后台对象”进入“可查询、可验证、可消费的发布摘要” - 继续保持现有前端稳定链兼容 建议顺序: #### 第一步:`event detail` 透出当前展示与内容包摘要 建议最少透出: - `currentPresentation` - `presentationId` - `templateKey` - `version` - `currentContentBundle` - `bundleId` - `bundleType` - `version` #### 第二步:`Get Release` 透出完整最小摘要 建议 `release detail` 同时包含: - `presentation` - `contentBundle` - `runtime` 其中前两者以摘要形式返回即可,避免一开始就下发复杂 schema。 #### 第三步:`launch` 增加兼容性的活动运营摘要块 在保持旧字段与当前 `runtime` 不变的前提下,新增: - `presentation` - `contentBundle` 建议最少包括: - `presentationId` - `templateKey` - `bundleId` - `bundleType` #### 第四步:publish 增加默认补齐逻辑 如果 publish 未显式传入: - `presentationId` - `contentBundleId` 允许按 event 当前默认配置自动补齐。 关键原则: - 只加摘要,不先做全量 schema 下发 - 只加能力,不改旧语义 - 先让前后端能验证活动运营域对象已经进入发布链 ### 9.8 第八刀:活动运营域发布摘要闭环与内容包统一导入入口 在第七刀已经完成之后,当前下一步建议切到: - 让 `EventRelease` 成为统一可验证的活动运营发布对象 - 让 `ContentBundle` 不再只靠手工创建,而有统一导入入口 #### 第一部分:发布摘要闭环 目标: - 保证 `event detail / event play / launch / release detail` 对活动运营摘要的返回语义一致 建议 `release detail` 最少固定透出: - `presentation` - `presentationId` - `templateKey` - `version` - `contentBundle` - `bundleId` - `bundleType` - `version` - `runtime` - `runtimeBindingId` - `placeId` - `mapId` - `tileReleaseId` - `courseVariantId` 这一步的意义是: - 让 `EventRelease` 真正成为客户端、总控、workbench 都能验证的统一发布对象 #### 第二部分:`ContentBundle` 统一导入入口 目标: - 后续静态资源、动画、音频、文创等内容,不直接散落在活动对象里 - 统一先进入 `ContentBundle` 当前阶段建议只做最小导入能力: - 导入入口 - 资源清单/manifest 记录 - bundle 元信息 而不在这一刀里做: - 复杂资源平台 - 大而全素材管理 - 审核流 - 全量页面 schema 组装 关键原则: - 活动对象继续只引用 `ContentBundle` - 客户端继续只消费摘要,不消费资源明细 - 统一导入入口优先于复杂管理后台 ### 9.9 第九刀:展示定义统一导入与默认绑定 在第八刀已经完成之后,当前下一步建议切到: - `EventPresentation` 统一导入入口 - `Event` 当前默认 active 绑定稳定化 #### 第一部分:`EventPresentation` 统一导入入口 目标: - 外部活动卡片/H5 搭建系统后续可以正式接入 backend - 展示定义不再只靠手工创建 当前阶段建议只做最小导入能力: - 导入入口 - schema 入口记录 - presentation 元信息 建议最小字段: - `templateKey` - `sourceType` - `schemaUrl` - `version` - `title` 不在这一刀里做: - 可视化编辑器 - 全量 schema 验证平台 - 复杂运营后台 #### 第二部分:`Event` 默认 active 绑定 目标: - 固定 `Event` 当前默认使用的: - `presentation` - `contentBundle` - `runtimeBinding` 这样 publish 在未显式传入时,才有稳定默认继承基础。 建议至少明确: - `currentPresentationId` - `currentContentBundleId` - `currentRuntimeBindingId` #### 第三部分:workbench 最小验证 建议 workbench 增加: - `Import Presentation` - 查看 event 当前 active 三元组 - publish 默认继承验证 关键原则: - `Event` 继续是业务编排壳 - `EventPresentation` 和 `ContentBundle` 都走统一导入入口 - 前端继续只消费发布摘要,不直接消费后台草稿对象 --- ## 10. 一次标准活动上线的最小闭环 建议先把一次活动上线收成最小闭环: 1. 创建地点 2. 创建地图并生成瓦片版本 3. 上传并解析 KML,生成赛道 variant 4. 搭建活动卡片与活动详情 5. 绑定地图、赛道、玩法与内容包 6. 生成 `EventRelease` 7. 客户端通过 `launch` 消费该 `EventRelease` 这就是后续联调和上线的最小标准流程。 --- ## 11. 第一阶段实施建议 结合 backend 当前已有骨架,建议第一阶段按以下方式推进: ### 9.1 先增量引入地图运行域对象 第一阶段建议优先落库: - `Place` - `MapAsset` - `TileRelease` - `CourseSource` - `CourseSet` - `CourseVariant` - `MapRuntimeBinding` 原因: - 这批对象最直接承接地图、瓦片、KML、赛道输入链 - 与当前多赛道 variant 联调方向一致 - 对现有 `Event / EventRelease / Session` 主链侵入最小 ### 9.2 活动运营域先保持主链稳定 第一阶段建议: - 保留当前 `Event` - 保留当前 `EventRelease` - `EventPresentation` 与 `ContentBundle` 先作为架构对象明确下来 - 后续第二阶段再补完整落库 ### 9.3 launch 采用两阶段兼容 第一阶段: - 保留当前稳定字段: - `resolvedRelease` - `business` - `variant` 第二阶段: - 再补完整运行对象字段: - `placeId` - `mapId` - `tileReleaseId` - `courseVariantId` - `eventReleaseId` 这样可以避免打断当前已稳定的前后端联调主链。 --- ## 12. 当前阶段最该先定的内容 当前建议优先定以下内容,不要先陷入后台 UI 细节: 1. `Place` 的最小字段与状态机 2. `MapAsset / TileRelease` 的最小字段与版本关系 3. `CourseSet / CourseVariant / CourseSource` 的最小字段与 KML 解析结果结构 4. `Event / EventPresentation / ContentBundle` 的最小字段与引用关系 5. `MapRuntimeBinding / EventRelease` 的发布与回滚规则 这些一旦定下来: - 地图怎么进来 - KML 怎么进来 - 活动卡片怎么进来 - 客户端怎么消费 就都能真正闭环。 --- ## 13. 一句话结论 当前最重要的不是继续加页面,而是先把后台生产闭环正式化。 后续整个系统建议围绕以下双域骨架继续收口: ### 地图运行域 - `Place` - `MapAsset` - `TileRelease` - `CourseSet` - `CourseVariant` - `CourseSource` ### 活动运营域 - `Event` - `EventPresentation` - `ContentBundle` - `MapRuntimeBinding` - `EventRelease` 只有这套对象模型和三条输入链定下来,地图、KML、活动卡片三类资产才能真正形成可维护、可发布、可回滚的完整生产体系。