package handlers import ( "encoding/json" "fmt" "net/http" neturl "net/url" "time" "cmr-backend/internal/httpx" "cmr-backend/internal/service" ) type DevHandler struct { devService *service.DevService } func NewDevHandler(devService *service.DevService) *DevHandler { return &DevHandler{devService: devService} } func (h *DevHandler) BootstrapDemo(w http.ResponseWriter, r *http.Request) { result, err := h.devService.BootstrapDemo(r.Context()) if err != nil { httpx.WriteError(w, err) return } httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result}) } func (h *DevHandler) CreateClientLog(w http.ResponseWriter, r *http.Request) { if !h.devService.Enabled() { http.NotFound(w, r) return } var input service.CreateClientDebugLogInput if err := httpx.DecodeJSON(r, &input); err != nil { httpx.WriteError(w, fmt.Errorf("decode client log: %w", err)) return } entry, err := h.devService.AddClientDebugLog(r.Context(), input) if err != nil { httpx.WriteError(w, err) return } httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": entry}) } func (h *DevHandler) ListClientLogs(w http.ResponseWriter, r *http.Request) { if !h.devService.Enabled() { http.NotFound(w, r) return } limit := 50 if raw := r.URL.Query().Get("limit"); raw != "" { var parsed int if _, err := fmt.Sscanf(raw, "%d", &parsed); err == nil { limit = parsed } } items, err := h.devService.ListClientDebugLogs(r.Context(), limit) if err != nil { httpx.WriteError(w, err) return } httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": items}) } func (h *DevHandler) ClearClientLogs(w http.ResponseWriter, r *http.Request) { if !h.devService.Enabled() { http.NotFound(w, r) return } if err := h.devService.ClearClientDebugLogs(r.Context()); err != nil { httpx.WriteError(w, err) return } httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": map[string]any{"cleared": true}}) } func (h *DevHandler) Workbench(w http.ResponseWriter, r *http.Request) { if !h.devService.Enabled() { http.NotFound(w, r) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") _, _ = w.Write([]byte(devWorkbenchHTML)) } func (h *DevHandler) ManifestSummary(w http.ResponseWriter, r *http.Request) { if !h.devService.Enabled() { http.NotFound(w, r) return } rawURL := r.URL.Query().Get("url") if rawURL == "" { httpx.WriteError(w, fmt.Errorf("manifest summary url is required")) return } parsed, err := neturl.Parse(rawURL) if err != nil || parsed.Scheme == "" || parsed.Host == "" { httpx.WriteError(w, fmt.Errorf("invalid manifest url")) return } client := &http.Client{Timeout: 15 * time.Second} resp, err := client.Get(parsed.String()) if err != nil { httpx.WriteError(w, fmt.Errorf("fetch manifest: %w", err)) return } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { httpx.WriteError(w, fmt.Errorf("fetch manifest: http %d", resp.StatusCode)) return } var manifest map[string]any if err := json.NewDecoder(resp.Body).Decode(&manifest); err != nil { httpx.WriteError(w, fmt.Errorf("decode manifest: %w", err)) return } summary := map[string]any{ "url": parsed.String(), "schemaVersion": pickString(manifest["schemaVersion"]), "playfieldKind": pickNestedString(manifest, "playfield", "kind"), "gameMode": pickNestedString(manifest, "game", "mode"), } httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": summary}) } func (h *DevHandler) DemoPresentationSchema(w http.ResponseWriter, r *http.Request) { if !h.devService.Enabled() { http.NotFound(w, r) return } key := r.PathValue("demoKey") payload, ok := demoPresentationAssets[key] if !ok { http.NotFound(w, r) return } httpx.WriteJSON(w, http.StatusOK, payload) } func (h *DevHandler) DemoContentManifest(w http.ResponseWriter, r *http.Request) { if !h.devService.Enabled() { http.NotFound(w, r) return } key := r.PathValue("demoKey") payload, ok := demoContentAssets[key] if !ok { http.NotFound(w, r) return } httpx.WriteJSON(w, http.StatusOK, payload) } func pickString(v any) string { switch t := v.(type) { case string: return t case float64: return fmt.Sprintf("%.0f", t) default: return "" } } func pickNestedString(m map[string]any, parent, child string) string { value, ok := m[parent] if !ok { return "" } nested, ok := value.(map[string]any) if !ok { return "" } return pickString(nested[child]) } var demoPresentationAssets = map[string]map[string]any{ "classic": { "templateKey": "event.detail.city-run", "sourceType": "schema", "version": "v2026-04-03", "title": "雪熊领秀城区顺序赛展示定义", "event": map[string]any{ "title": "雪熊领秀城区顺序赛", "subtitle": "沿河绿道 6 点经典路线", }, "card": map[string]any{ "heroTitle": "今日推荐路线", "heroSubtitle": "城区步道顺序挑战", "badge": "顺序赛", }, "detail": map[string]any{ "sections": []map[string]any{ {"type": "hero", "title": "顺序打卡", "subtitle": "沿河绿道 6 点路线"}, {"type": "summary", "items": []string{"预计时长 35 分钟", "适合首次联调与新手体验", "默认使用标准 6 点线路"}}, {"type": "safety", "items": []string{"注意路口减速", "夜间建议结伴测试"}}, }, }, }, "score-o": { "templateKey": "event.detail.score-o", "sourceType": "schema", "version": "v2026-04-03", "title": "雪熊领秀城区积分赛展示定义", "event": map[string]any{ "title": "雪熊领秀城区积分赛", "subtitle": "20 分钟自由取点积分挑战", }, "card": map[string]any{ "heroTitle": "自由取点", "heroSubtitle": "在限定时间内尽量拿高分", "badge": "积分赛", }, "detail": map[string]any{ "sections": []map[string]any{ {"type": "hero", "title": "20 分钟自由取点", "subtitle": "控制点分值不同,自由规划路线"}, {"type": "summary", "items": []string{"推荐热身后再开局", "适合熟悉地图后做效率测试", "默认接入 score-o 玩法"}}, {"type": "result", "items": []string{"展示积分、完成点数、路线效率"}}, }, }, }, "manual-variant": { "templateKey": "event.detail.variant-selector", "sourceType": "schema", "version": "v2026-04-03", "title": "雪熊领秀城区多赛道挑战展示定义", "event": map[string]any{ "title": "雪熊领秀城区多赛道挑战", "subtitle": "A / B 线手动选择联调活动", }, "card": map[string]any{ "heroTitle": "多赛道选择", "heroSubtitle": "同一地点,不同路线长度与难度", "badge": "多赛道", }, "detail": map[string]any{ "sections": []map[string]any{ {"type": "hero", "title": "先选赛道再开始", "subtitle": "A 线偏短,B 线偏长"}, {"type": "variants", "items": []string{"A 线:短线体验版", "B 线:长线挑战版"}}, {"type": "summary", "items": []string{"适合验证 variant 选择与回流链", "默认推荐 B 线做联调"}}, }, }, }, } var demoContentAssets = map[string]map[string]any{ "classic": { "manifestVersion": "1", "bundleType": "route_content", "version": "v2026-04-03", "title": "雪熊领秀城区顺序赛内容包", "locale": "zh-CN", "event": map[string]any{ "title": "雪熊领秀城区顺序赛", "subtitle": "沿河绿道 6 点经典路线", }, "hero": map[string]any{ "title": "绿道顺序挑战", "subtitle": "按照既定顺序依次完成 6 个控制点", }, "sections": []map[string]any{ {"type": "intro", "title": "活动说明", "body": "适合首次联调与基础顺序赛流程验证。"}, {"type": "tips", "title": "路线提示", "body": "默认路线沿河绿道展开,注意桥下拐点。"}, {"type": "result", "title": "结果页文案", "body": "完成后展示用时、配速与打卡完成率。"}, }, "assets": map[string]any{ "cover": "https://oss-mbh5.colormaprun.com/gotomars/assets/demo-cover.jpg", "entryHtml": "https://oss-mbh5.colormaprun.com/gotomars/event/content-h5-test-template.html", }, }, "score-o": { "manifestVersion": "1", "bundleType": "result_media", "version": "v2026-04-03", "title": "雪熊领秀城区积分赛内容包", "locale": "zh-CN", "event": map[string]any{ "title": "雪熊领秀城区积分赛", "subtitle": "20 分钟自由取点积分挑战", }, "hero": map[string]any{ "title": "自由规划路线", "subtitle": "在限定时间内尽量争取更高积分", }, "sections": []map[string]any{ {"type": "intro", "title": "玩法说明", "body": "每个控制点分值不同,优先测试路径规划与效率。"}, {"type": "tips", "title": "策略建议", "body": "建议先拿近点,再视剩余时间冲刺高分点。"}, {"type": "result", "title": "结果页文案", "body": "结果页重点展示总积分、完成点位与平均速度。"}, }, "assets": map[string]any{ "cover": "https://oss-mbh5.colormaprun.com/gotomars/assets/demo-cover.jpg", "entryHtml": "https://oss-mbh5.colormaprun.com/gotomars/event/content-h5-test-template.html", }, }, "manual-variant": { "manifestVersion": "1", "bundleType": "route_content", "version": "v2026-04-03", "title": "雪熊领秀城区多赛道挑战内容包", "locale": "zh-CN", "event": map[string]any{ "title": "雪熊领秀城区多赛道挑战", "subtitle": "A / B 线手动选择联调活动", }, "hero": map[string]any{ "title": "同图多赛道", "subtitle": "先选路线,再验证 launch / result / history 回流", }, "sections": []map[string]any{ {"type": "intro", "title": "玩法说明", "body": "A 线适合短线体验,B 线适合长线挑战与数据对比。"}, {"type": "variants", "title": "赛道差异", "body": "两条赛道使用不同 KML,用于验证 variant 选择与恢复链。"}, {"type": "result", "title": "结果页文案", "body": "结果页需展示所选赛道名、routeCode 与成绩摘要。"}, }, "assets": map[string]any{ "cover": "https://oss-mbh5.colormaprun.com/gotomars/assets/demo-cover.jpg", "entryHtml": "https://oss-mbh5.colormaprun.com/gotomars/event/content-h5-test-template.html", }, }, } const devWorkbenchHTML = ` CMR Backend Workbench
Developer Workbench

CMR Backend API Flow Panel

把入口、登录、首页、活动详情、launch、session、profile 串成一条完整调试链。这个页面只在非 production 环境开放,适合后续继续扩展成你想要的 API 测试面板。

第一步:选玩法

先在这里准备 demo 数据并选择玩法入口。顺序赛、积分赛、多赛道各有一套独立 demo 数据,后面一键流程都会复用这里选中的 event。

默认入口 tenant_demo / mini-demo / evt_demo_001
积分赛入口 tenant_demo / mini-demo / evt_demo_score_o_001
多赛道入口 tenant_demo / mini-demo / evt_demo_variant_manual_001
说明:Bootstrap Demo(只准备数据) 只负责把三种玩法的 demo 对象和默认样例准备好;Bootstrap + 发布当前玩法 会先准备 demo,再对当前选中的玩法执行一遍“发布活动配置(自动补 Runtime)”。

本地配置导入与发布

从本地 event 目录导入 source config,生成 preview build,并可在发布时直接挂接 runtime binding。

第四刀发布闭环:publish 时可直接带 runtimeBindingId,旧发布路径继续可用。

当前上下文

当前调试上下文,所有按钮共享这一组状态。

Access Token -
Refresh Token -
Source ID -
Build ID -
Release ID -
Session ID -
Session Token -

短信登录 / 绑定

微信小程序登录

开发环境可直接使用 dev-xxx code。

入口与首页

活动与启动

局内状态

结果查询

当前用户

第二步:点测试目标

先选玩法入口,再按“你现在想测什么”点对应按钮。大多数情况下,你只需要点最后一个“一键标准回归”。

推荐顺序:
1. 先点上面的玩法入口:Use Classic Demo / Use Score-O Demo / Use Manual Variant Demo
2. 想直接验收,就点 整条链一键验收
3. 想只测发布链,就点 发布活动配置(自动补 Runtime)
4. 想只测局内流程,就点 快速进一局、结束并看结果
这些流程会复用当前表单里的手机号、设备、event、channel 等输入。发布活动配置(默认绑定)会自动执行:Get Event -> Import Presentation -> Import Bundle -> Save Event Defaults -> Build Source -> Publish Build -> Get Release。发布活动配置(自动补 Runtime)会在缺少默认 runtime 时自动创建 Runtime Binding,再继续发布链。整条链一键验收会先准备并发布当前玩法,再继续执行:play -> launch -> start -> finish -> result -> history。
预期结果
Release ID -
Presentation -
Content Bundle -
Runtime Binding -
判定 待执行
回归结果汇总
发布链 待执行
Play 待执行
Launch 待执行
Result 待执行
History 待执行
Session ID -
总判定 待执行
当前 Launch 实际配置摘要
Config URL -
Release ID -
Manifest URL -
Schema Version -
Playfield Kind -
Game Mode -
判定 待执行
当前玩法关键状态
Event ID -
Release ID -
Can Launch -
Assignment Mode -
Variant Count -
Game Mode -
Playfield Kind -

请求导出

最后一次请求会生成一条可复制的 curl,后面做问题复现会方便很多。

Last Curl

前端调试日志

前端可把 launch、manifest、地图页、结果页等调试信息直接打到 backend。这里显示最近日志,便于和 workbench 当前配置对口排查。

建议前端至少上报:eventId / releaseId / manifestUrl / game.mode / playfield.kind / 页面阶段。

第一阶段生产骨架联调台

这里只做总控确认的最小范围:地点、地图资产、瓦片版本、赛道输入源、赛道集合、赛道方案、运行绑定。

A. 地点与地图
B. 赛道与 KML
C. 运行绑定

资源对象管理

管理地图、赛场和资源包对象,先建对象,再建版本,后面 Event source 直接引用这些对象。

Event Source 组装

创建 Event 并把 map version、playfield version、resource pack version 组装成 source config。

Event Presentation
Content Bundle

Build / Publish / Rollback

围绕当前 Event 查询 source/build/release 流水线,并执行 build、publish、rollback。

第四刀:发布时可直接带 runtime binding;旧的“先发布再绑定”路径继续保留。
不填发布参数时,后端会先继承当前 Event 的默认 active:presentation / content bundle / runtime。

场景模板

保存当前表单状态为可复用场景,也支持导入导出 JSON,适合后续切换不同俱乐部、入口和 event。

Scenario JSON

响应日志

最后一次请求的结果会记录在这里,便于后续做请求回放和用例保存。

ready
当前进度:待执行 0 / 0
长流程会在这里显示当前步骤。

请求历史

最近 12 次请求会保留在浏览器本地,刷新页面不会丢。

API 目录

把当前已实现接口按分组放进 workbench,直接看中文说明、鉴权要求和关键参数,不用来回翻文档。

共 0 个接口,支持按关键词筛选。
GET/healthz
健康检查接口,用来确认服务是否存活。
鉴权:无需鉴权
POST/auth/sms/send
发送短信验证码,支持登录和绑定手机号两种场景。
鉴权:无需鉴权
关键参数:countryCodemobileclientTypedeviceKeyscene
POST/auth/login/sms
APP 主登录入口,使用手机号验证码登录并返回 access/refresh token。
鉴权:无需鉴权
关键参数:mobilecodeclientTypedeviceKey
POST/auth/login/wechat-mini
微信小程序登录入口。开发环境支持 dev- 前缀 code 直接模拟登录。
鉴权:无需鉴权
关键参数:codeclientType=wechatdeviceKey
POST/auth/bind/mobile
已登录用户绑定手机号,必要时把微信轻账号合并到手机号主账号。
鉴权:Bearer token
关键参数:mobilecodeclientTypedeviceKey
POST/auth/refresh
使用 refresh token 刷新 access token。
鉴权:无需 Bearer token
关键参数:refreshTokenclientTypedeviceKey
POST/auth/logout
登出并撤销 refresh token。
鉴权:可带 Bearer token
GET/entry/resolve
解析当前入口属于哪个 tenant / channel,是多俱乐部、多公众号接入的入口层基础接口。
鉴权:无需鉴权
查询参数:channelCodechannelTypeplatformAppIdtenantCode
GET/home
返回入口首页卡片数据。
鉴权:无需鉴权
GET/cards
只返回卡片列表,适合调试卡片数据本身。
鉴权:无需鉴权
GET/me/entry-home
首页聚合接口,返回用户、tenant、channel、cards、进行中 session 和最近一局。
鉴权:Bearer token
GET/events/{eventPublicID}
活动详情接口,会带当前发布的 release 和 resolvedRelease。
鉴权:无需鉴权
GET/events/{eventPublicID}/play
活动详情页 / 开始前准备页聚合接口,判断是否可启动、继续还是查看上次结果;第一阶段也会返回多赛道 assignmentMode 和 courseVariants。
鉴权:Bearer token
POST/events/{eventPublicID}/launch
基于当前 event 的已发布 release 创建一局 session,并返回 config URL、releaseId、sessionToken;多赛道第一阶段支持可选 variantId,并返回最终绑定的 launch.variant。
鉴权:Bearer token
关键参数:releaseIdvariantIdclientTypedeviceKey
GET/events/{eventPublicID}/config-sources
查看某个 event 下已经导入过的 source config 列表。
鉴权:Bearer token
GET/config-sources/{sourceID}
查看单条 source config 明细。
鉴权:Bearer token
GET/config-builds/{buildID}
查看单次 build 的 manifest 和 asset index。
鉴权:Bearer token
GET/sessions/{sessionPublicID}
查询一局详情,带 session 状态、event 和 resolvedRelease。
鉴权:Bearer token
POST/sessions/{sessionPublicID}/start
把 session 从 launched 推进到 running
鉴权:sessionToken
关键参数:sessionToken
POST/sessions/{sessionPublicID}/finish
结束一局并沉淀结果摘要,是结果页数据的来源。
鉴权:sessionToken
关键参数:sessionTokenstatussummary.*
GET/me/sessions
查询用户最近 session 列表。
鉴权:Bearer token
GET/sessions/{sessionPublicID}/result
单局结果页接口,返回 session 和 result。
鉴权:Bearer token
GET/me/results
查询用户最近结果列表。
鉴权:Bearer token
GET/me
返回当前用户基础信息。
鉴权:Bearer token
GET/me/profile
“我的页”聚合接口,返回绑定概览、绑定项列表和最近记录摘要。
鉴权:Bearer token
POST/dev/bootstrap-demo
开发态自举 demo 数据,会准备 tenant、channel、event、release、card、source、build。
鉴权:仅 non-production,无需鉴权
GET/dev/workbench
开发态工作台页面,集中提供一键流、日志、配置摘要、API 目录和后台运营联调入口。
鉴权:仅 non-production,无需鉴权
POST/dev/client-logs
接收 frontend 主动上报的调试日志,供 backend 在 workbench 中统一查看。
鉴权:仅 non-production,无需鉴权
关键参数:sourcelevelcategorymessageeventIdreleaseIdsessionIdmanifestUrlroutedetails
GET/dev/client-logs
获取 frontend 最近上报的调试日志,便于 backend 直接对照排查。
鉴权:仅 non-production,无需鉴权
查询参数:limit
DELETE/dev/client-logs
清空当前内存中的 frontend 调试日志,方便开始新一轮联调。
鉴权:仅 non-production,无需鉴权
GET/dev/manifest-summary
由 backend 代读指定 manifest,并返回 schemaVersionplayfield.kindgame.mode 调试摘要。
鉴权:仅 non-production,无需鉴权
查询参数:url
GET/dev/demo-assets/presentations/{demoKey}
读取联调用的示例展示定义 schema,给 workbench 快速导入。
鉴权:仅 non-production,无需鉴权
路径参数:demoKey
GET/dev/demo-assets/content-manifests/{demoKey}
读取联调用的示例内容 manifest,给 workbench 快速导入。
鉴权:仅 non-production,无需鉴权
路径参数:demoKey
GET/dev/config/local-files
列出本地配置目录中的 JSON 文件,作为 source config 导入入口。
鉴权:仅 non-production,无需鉴权
POST/dev/events/{eventPublicID}/config-sources/import-local
从本地 event 目录导入 source config。
鉴权:仅 non-production,无需鉴权
关键参数:fileNamenotes
POST/dev/config-builds/preview
基于 source config 生成 preview build,并产出 preview manifest。
鉴权:仅 non-production,无需鉴权
关键参数:sourceId
POST/dev/config-builds/publish
把成功的 build 发布成正式 release,并可选直接挂接 runtime binding。
鉴权:仅 non-production,无需鉴权
关键参数:buildIdruntimeBindingId
GET/admin/maps
后台地图对象列表接口。
鉴权:Bearer token
POST/admin/maps
创建地图对象,后续再为它追加版本。
鉴权:Bearer token
关键参数:codenamestatus
GET/admin/maps/{mapPublicID}
查看单个地图对象和它的版本列表。
鉴权:Bearer token
POST/admin/maps/{mapPublicID}/versions
为地图对象创建一个版本,挂接 mapmeta 和 tiles 根路径。
鉴权:Bearer token
关键参数:versionCodemapmetaUrltilesRootUrlsetAsCurrent
GET/admin/places
第一阶段生产骨架的地点对象列表接口。
鉴权:Bearer token
POST/admin/places
创建地点对象,作为地图资产的上层归属。
鉴权:Bearer token
关键参数:codenameregionstatus
GET/admin/places/{placePublicID}
查看地点详情,并带出该地点下的地图资产列表。
鉴权:Bearer token
POST/admin/places/{placePublicID}/map-assets
在指定地点下创建地图资产,可选挂接已有 legacy map。
鉴权:Bearer token
关键参数:codenamemapTypelegacyMapId
GET/admin/map-assets/{mapAssetPublicID}
查看地图资产详情,带出瓦片版本和赛道集合摘要。
鉴权:Bearer token
POST/admin/map-assets/{mapAssetPublicID}/tile-releases
为地图资产创建瓦片版本,可选关联已有 legacy map version。
鉴权:Bearer token
关键参数:versionCodetileBaseUrlmetaUrlsetAsCurrent
GET/admin/course-sources
查看赛道原始输入源列表,承接 KML / GeoJSON 等输入。
鉴权:Bearer token
POST/admin/course-sources
创建赛道输入源,为后续解析成 CourseVariant 做准备。
鉴权:Bearer token
关键参数:sourceTypefileUrllegacyPlayfieldIdlegacyVersionId
GET/admin/course-sources/{sourcePublicID}
查看单个赛道输入源详情。
鉴权:Bearer token
POST/admin/map-assets/{mapAssetPublicID}/course-sets
在指定地图资产下创建赛道集合。
鉴权:Bearer token
关键参数:codemodenamestatus
GET/admin/course-sets/{courseSetPublicID}
查看单个赛道集合详情和 variant 列表。
鉴权:Bearer token
POST/admin/course-sets/{courseSetPublicID}/variants
为赛道集合创建具体可运行赛道方案。
鉴权:Bearer token
关键参数:sourceIdnamerouteCodemodeisDefault
GET/admin/runtime-bindings
查看活动运行绑定列表。
鉴权:Bearer token
POST/admin/runtime-bindings
把活动和地点、地图资产、瓦片、赛道集合、variant 绑定起来。
鉴权:Bearer token
关键参数:eventIdplaceIdmapAssetIdtileReleaseIdcourseSetIdcourseVariantId
GET/admin/runtime-bindings/{runtimeBindingPublicID}
查看单个运行绑定详情。
鉴权:Bearer token
GET/admin/playfields
后台赛场对象列表接口。
鉴权:Bearer token
POST/admin/playfields
创建赛场对象,适合管理 KML / GeoJSON 这类可复用场地资源。
鉴权:Bearer token
关键参数:codenamekindstatus
GET/admin/playfields/{playfieldPublicID}
查看单个赛场对象和它的版本列表。
鉴权:Bearer token
POST/admin/playfields/{playfieldPublicID}/versions
为赛场对象创建一个版本,挂接 KML 等源文件地址和控制点摘要。
鉴权:Bearer token
关键参数:versionCodesourceTypesourceUrlcontrolCountsetAsCurrent
GET/admin/resource-packs
后台资源包对象列表接口。
鉴权:Bearer token
POST/admin/resource-packs
创建资源包对象,用来管理内容页、音频和主题资源。
鉴权:Bearer token
关键参数:codenamestatus
GET/admin/resource-packs/{resourcePackPublicID}
查看单个资源包对象和它的版本列表。
鉴权:Bearer token
POST/admin/resource-packs/{resourcePackPublicID}/versions
为资源包对象创建版本,配置内容入口、音频根路径和主题代码。
鉴权:Bearer token
关键参数:versionCodecontentEntryUrlaudioRootUrlthemeProfileCodesetAsCurrent
GET/admin/events
后台 event 列表接口。
鉴权:Bearer token
POST/admin/events
创建 event 基础信息。
鉴权:Bearer token
关键参数:tenantCodeslugdisplayNamestatus
GET/admin/events/{eventPublicID}
查看 event 明细、最新 source 和当前 source 摘要。
鉴权:Bearer token
PUT/admin/events/{eventPublicID}
更新 event 基础信息。
鉴权:Bearer token
关键参数:tenantCodeslugdisplayNamestatus
POST/admin/events/{eventPublicID}/source
把 map/playfield/resource pack 版本和 gameModeCode 组装成 source config。
鉴权:Bearer token
关键参数:map.mapIdmap.versionIdplayfield.playfieldIdplayfield.versionIdgameModeCodeoverrides
GET/admin/events/{eventPublicID}/presentations
查看某个 event 下的展示定义列表。
鉴权:Bearer token
POST/admin/events/{eventPublicID}/presentations
为 event 创建一条最小 presentation 定义,供 release 绑定使用。
鉴权:Bearer token
关键参数:codenamepresentationTypeschema
POST/admin/events/{eventPublicID}/presentations/import
通过统一导入入口为 event 创建展示定义,先记录 templateKey、sourceType、schemaUrl、version 和 title。
鉴权:Bearer token
关键参数:titletemplateKeysourceTypeschemaUrlversion
GET/admin/presentations/{presentationPublicID}
查看单条 presentation 明细。
鉴权:Bearer token
GET/admin/events/{eventPublicID}/content-bundles
查看某个 event 下的内容包列表。
鉴权:Bearer token
POST/admin/events/{eventPublicID}/content-bundles
为 event 创建一条最小 content bundle,供 release 绑定使用。
鉴权:Bearer token
关键参数:codenameentryUrlassetRootUrlmetadata
POST/admin/events/{eventPublicID}/content-bundles/import
通过统一导入入口为 event 创建内容包,先记录 bundleType、sourceType、manifestUrl、version 和 assetManifest。
鉴权:Bearer token
关键参数:titlebundleTypesourceTypemanifestUrlversion
GET/admin/content-bundles/{contentBundlePublicID}
查看单条内容包明细。
鉴权:Bearer token
POST/admin/events/{eventPublicID}/defaults
固化 event 当前默认 active 绑定,供后续 publish 在未显式传参时继承。
鉴权:Bearer token
关键参数:presentationIdcontentBundleIdruntimeBindingId
GET/admin/events/{eventPublicID}/pipeline
查看 event 下的 source、build、release 流水线概览。
鉴权:Bearer token
POST/admin/sources/{sourceID}/build
基于 source 生成一条 build 记录和 preview manifest。
鉴权:Bearer token
GET/admin/builds/{buildID}
查看后台 build 明细。
鉴权:Bearer token
POST/admin/builds/{buildID}/publish
把后台 build 发布为正式 release,可选直接挂接 runtime binding、presentation 和内容包,并切换为 event 当前发布版本。
鉴权:Bearer token
关键参数:runtimeBindingIdpresentationIdcontentBundleId
GET/admin/releases/{releasePublicID}
查看单个 release 明细,并带出当前已挂接的 runtime 摘要。
鉴权:Bearer token
POST/admin/releases/{releasePublicID}/runtime-binding
把某个 runtime binding 挂接到指定 release,上游 launch 会透出新的 runtime 摘要。
鉴权:Bearer token
关键参数:runtimeBindingId
POST/admin/events/{eventPublicID}/rollback
将 event 当前发布版本回滚到指定 releaseId。
鉴权:Bearer token
关键参数:releaseId
`