zizi 063ec4edf2 docs(rename): ③ 文档/契约漂移清理——contracts+docs+根文档 yudao 标识同步 com.wanxiang.huijing(follow-up,脚本)
- 范围:全仓除已完成的 game-cloud/game-admin(及排除区);91 文件
- cn.iocoder.yudao→com.wanxiang.huijing / cn.wanxiang.game→com.wanxiang.huijing.game / cn.iocoder.cloud→com.wanxiang / Yudao*→Huijing* / bare yudao→huijing
- 含 contracts/api-schemas(apiInterface FQCN)、docs/architecture+agent-specs(含中文名,git ls-files -z 修复)、AGENTS.md/CLAUDE.md 等根文档
- 保留不动:上游归属 URL(gitee/github yudaocode·YunaiV,改则断链+违反署名)、game-cloud Flyway 迁移注释(保 checksum)、lockfile(装包重生)
- 纯文档/字符串,零构建影响(game-cloud/game-admin 字节码未动)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 14:32:44 +00:00

324 lines
21 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

openapi: 3.0.3
# 契约 #1 API | 模块:runtime(编译 / 沙箱预览 / 试玩会话)| owner:WS3(runtime 工位主笔,全员 review)
# 端:/app-api(产品端 game-studio,用户 Token+DataPermission) /admin-api(管理端 game-admin,RBAC);前缀由 huijing 框架按 controller.app/admin 包名自动添加
# 错误码段:runtime = 1-102-***-***(本模块独占,禁止与他模块重叠)
# 响应统一 Huijing CommonResult 信封:{ code, data, msg };code=0 成功
#
# 脊柱对接(闭环贯通关键):
# - 上游输入:GamePackage(契约 #4 game-package.schema.json)—— 由 aigc/studio 生成、project 持久化版本元数据;runtime 编译产出版本化运行包。
# - 对外提供:版本运行包清单(manifest URL + 入口 + checksum + 预加载策略)给「试玩宿主」(game-studio iframe 宿主)。
# - 宿主↔游戏边界:postMessage 协议(契约 #3 sdk-interface.d.ts)由宿主与游戏侧落地;runtime 不实现 postMessage,只为宿主提供加载清单与会话开/收端点。
# - 试玩会话:game_runtime_session 记录 play_start/play_end/时长,供回灌 telemetry(契约 #5 events.schema.json,由 telemetry 模块消费)。
#
# MVP 范围纪律:只做核心闭环所需端点 —— 编译产出运行包 → 预览/试玩取包清单 → 试玩会话开/收 → 会话回灌。
# 渠道转换(微信/抖音/快手/TapTap 试玩包,T-RT-21~24/31/32)、云游戏容器(T-RT-30)属"扩"远期,不进 MVP,仅在 §对接点 留契约挂点。
info:
title: 绘境AI runtime 模块 API
version: 1.0.0
description: GameConfig→版本化可运行包编译、iframe 沙箱预览/试玩、运行包清单分发、试玩会话开收(回灌 telemetry)。前端据此 vite-plugin-mock 自动生成 mock。
servers:
- url: http://localhost:48080
description: 本地(Swagger/Knife4j http://localhost:48080/doc.html)
paths:
# ==========================================================================
# 产品端 /app-api —— 预览/试玩宿主取包清单 + 试玩会话开收(用户 Token)
# ==========================================================================
/app-api/runtime/package/{versionId}:
get:
tags: [app-runtime]
summary: 取版本运行包清单(预览/试玩宿主据此渲染 iframe、桥接 SDK ←#3)
description: >
宿主据返回的 manifest(入口/包URL/checksum/预加载策略)加载游戏并做完整性校验(门禁 Manifest 完整性 T-RT-15)。
取包门禁判定字段 = game_runtime_package.status(运行包状态,非 project.game_version.status):
scene=preview 放行 status∈{0 预览就绪, 1 已发布},并校验调用者为版本 owner(创作者本人预览未发布版本);
scene=play 仅放行 status=1(已发布)。无对应就绪运行包返回 1-102-001-001。
parameters:
- { name: versionId, in: path, required: true, schema: { type: integer, format: int64 }, description: 版本 ID(project.game_version.id) }
- { name: scene, in: query, required: false, schema: { type: string, enum: [preview, play], default: play }, description: 'preview=创作者预览未发布版本 / play=玩家试玩已发布版本' }
responses:
'200':
description: 成功
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultRuntimePackage' }
/app-api/runtime/package/{versionId}/manifest:
get:
tags: [app-runtime]
summary: 取版本运行包 manifest 原始 JSON(宿主 fetch 此端点取 manifest 并做 sha256 完整性校验)
description: >
【§3.4 C4 关键约束】返回 manifest 的原始 JSON 文本,**不包 CommonResult、不 parse/re-serialize、不加换行**——
宿主对该响应文本原始字节算 sha256,须与 RuntimePackageRespVO.checksum 严格一致(PackageFactory 写入时按相同字节计算并存 game_runtime_package.checksum),
任何包裹/重序列化都会破坏字节一致性导致校验失败。
MVP 无 OSS:manifest 整包存 game_runtime_package.package_json,由 PackageStore 的 DB impl 读出原样返回(M3 接 OSS 后换 OSS impl,调用方/契约不变,退场契约)。
取包门禁与 GET /app-api/runtime/package/{versionId} 同源:scene=preview 放行 status∈{0,1} 且校验版本 owner;scene=play 仅放行 status=1;无就绪运行包返回 1-102-001-001。
parameters:
- { name: versionId, in: path, required: true, schema: { type: integer, format: int64 }, description: 版本 ID(project.game_version.id) }
- { name: scene, in: query, required: false, schema: { type: string, enum: [preview, play], default: play }, description: 'preview=创作者预览未发布版本 / play=玩家试玩已发布版本' }
responses:
'200':
description: manifest 原始 JSON 文本(application/json,不包 CommonResult;sha256(响应文本)==checksum)
content:
application/json:
schema:
type: string
description: 'GamePackage.manifest 的原始 JSON 字符串(原样字节,宿主据此算 sha256 校验后注入运行容器,对齐 #4 manifest 段)'
/app-api/runtime/session/start:
post:
tags: [app-runtime]
summary: 开始试玩会话(即点即玩 P-PLZ-03 / 预览试玩 P-CRT-08)
description: >
宿主在游戏 game_start(生命周期 ←#3)时调用,落 game_runtime_session 一行并返回 sessionId。
sessionId 作为本局唯一键,贯穿后续 end 与 telemetry 回灌的关联键。
clientPlayToken 幂等:同一 token 重复提交返回同一 sessionId(防抖/重连不重复计数)。
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SessionStartReqVO' }
responses:
'200':
description: 返回会话 ID
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultSessionStart' }
/app-api/runtime/session/end:
post:
tags: [app-runtime]
summary: 结束试玩会话(仅记录时长/质量观测,不计 play_count)
description: >
宿主在游戏 game_end 或容器卸载(sendBeacon 兜底)时调用,写 play_end 与 duration_ms,置会话状态=1已结束。
服务端以 sessionId 为准计算时长(end-start),客户端上报 durationMs 仅作离线兜底。
幂等:会话已结束再次提交直接返回成功,不二次累加。
注:runtime session 仅作时长/质量观测,不计 play_count;play_count 权威源 = telemetry 的 game_play_start 事件聚合
(见 telemetry V5.0.0 / telemetry.yaml)。runtime sessionId(int64) 透传进 telemetry envelope.session_id(转 string)作对账键。
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SessionEndReqVO' }
responses:
'200':
description: 成功
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
# ==========================================================================
# 管理端 /admin-api —— 编译编排 + 编译状态 + 会话/质量观测(管理 RBAC)
# 注:编译由 project 统一发布编排(T-PRJ-08)或运营触发;此处暴露管理侧入口与查询。
# ==========================================================================
/admin-api/runtime/compile:
post:
tags: [admin-runtime]
summary: 触发编译(GameConfig→版本化运行包,消费 aigc 产物 ←#4)
description: >
以 versionId 为入参,读取该版本的 GamePackage(#4),执行 GameConfig 静态校验(门禁 T-RT-16)、
体积门禁(≤10MB/首屏≤2MB,T-RT-08)、Manifest 生成与打包(T-RT-02)、资源版本化上传(T-RT-03)。
编译为异步幂等任务:返回 buildId;同一 versionId 重复触发命中进行中任务返回同一 buildId。
编译成功后回写 game_runtime_package(manifest/包URL/checksum),并置 game_runtime_package.status=0(预览就绪);
发布态(status=1 已发布 + 回写 project.game_version.status=3)不在此处写,统一由 publish 对接点
POST /admin-api/runtime/package/{versionId}/publish 在发布编排成功后回写。
同时按「决策5 写权属」回写 project.game_version 的 package_url/checksum/bundle_size(runtime 为该三字段权威写者,与 game_runtime_package 同源)。
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CompileReqVO' }
responses:
'200':
description: 返回编译任务 ID
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultCompile' }
/admin-api/runtime/build/{buildId}:
get:
tags: [admin-runtime]
summary: 查询编译任务状态(发布编排/运营轮询)
parameters:
- { name: buildId, in: path, required: true, schema: { type: integer, format: int64 }, description: 编译任务 ID }
responses:
'200':
description: 成功
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultBuild' }
/admin-api/runtime/package/{versionId}/publish:
post:
tags: [admin-runtime]
summary: 发布态回写对接点(编译就绪运行包 → 置为已发布,补全发布态写入链路)
description: >
由 project.publish 统一发布编排(T-PRJ-08)成功后调用(admin-api,RBAC / 内部编排调用,非用户直接触发)。
作用:置 game_runtime_package.status=1(已发布,玩家可试玩),并回写 project.game_version.status=3(已发布),
补全「编译就绪 → 统一发布编排 → 发布态写入」链路的最后一跳,消除原先发布态无写入对接点的断点。
幂等:运行包已是 status=1 时重复调用直接返回成功,不二次写入。
前置:该 versionId 必须已编译成功且存在就绪运行包(status=0 预览就绪);否则返回 1-102-001-001。
parameters:
- { name: versionId, in: path, required: true, schema: { type: integer, format: int64 }, description: '版本 ID(project.game_version.id);其余入参由路径参数即可承载,无需请求体' }
responses:
'200':
description: 成功(运行包已置已发布、版本状态已回写)
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
/admin-api/runtime/session/page:
get:
tags: [admin-runtime]
summary: 试玩会话列表(运行时质量观测 / 回灌核对)
parameters:
- { name: gameId, in: query, required: false, schema: { type: integer, format: int64 }, description: 按游戏筛选 }
- { name: versionId, in: query, required: false, schema: { type: integer, format: int64 }, description: 按版本筛选 }
- { name: status, in: query, required: false, schema: { type: integer }, description: '会话状态:0进行中 1已结束 2异常' }
- { name: pageNo, in: query, required: false, schema: { type: integer, default: 1 } }
- { name: pageSize, in: query, required: false, schema: { type: integer, default: 10 } }
responses:
'200':
description: 成功
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultSessionPage' }
components:
schemas:
# ---- Huijing CommonResult 信封 ----
CommonResultBoolean:
type: object
properties:
code: { type: integer, description: '0=成功,非0=错误码 1-102-***-***' }
data: { type: boolean }
msg: { type: string }
CommonResultRuntimePackage:
type: object
properties:
code: { type: integer }
data: { $ref: '#/components/schemas/RuntimePackageRespVO' }
msg: { type: string }
CommonResultSessionStart:
type: object
properties:
code: { type: integer }
data: { $ref: '#/components/schemas/SessionStartRespVO' }
msg: { type: string }
CommonResultCompile:
type: object
properties:
code: { type: integer }
data: { $ref: '#/components/schemas/CompileRespVO' }
msg: { type: string }
CommonResultBuild:
type: object
properties:
code: { type: integer }
data: { $ref: '#/components/schemas/BuildRespVO' }
msg: { type: string }
CommonResultSessionPage:
type: object
properties:
code: { type: integer }
data:
type: object
properties:
list: { type: array, items: { $ref: '#/components/schemas/SessionRespVO' } }
total: { type: integer, format: int64 }
msg: { type: string }
# ---- 运行包清单(脊柱对外产物:宿主据此加载 + 校验,对齐 #4 manifest 段)----
RuntimePackageRespVO:
type: object
description: >-
版本运行包清单。字段语义对齐 GamePackage(#4).manifest,宿主据此渲染 iframe 并桥接 SDK(#3)。
【GAP-2 取包约束】① 宿主先 fetch manifestUrl 取 manifest,按 checksum 做 sha256 完整性校验通过后再注入运行容器(T-RT-15),校验失败拒绝加载并落 error;
② OSS/CDN 须放行宿主 origin 的 CORS(AllowedOrigin=宿主 origin、AllowedMethod=GET/HEAD、ExposeHeader=ETag),否则宿主跨域 fetch 被拦;
③ scene=preview|play 鉴权与取包门禁须一致:preview 仅版本 owner 可取未发布包、play 仅放行已发布包(与 getPackageManifest 门禁同源,不在前端兜底)。
properties:
gameId: { type: integer, format: int64, description: 游戏 ID }
versionId: { type: integer, format: int64, description: 版本 ID }
templateId: { type: string, description: 玩法模板 ID(宿主据此选 Runtime 容器) }
packageUrl: { type: string, description: 'GamePackage(manifest)OSS/CDN URL,按 /games/{gameId}/versions/{versionId}/ 版本化(#4)' }
manifestUrl: { type: string, description: '§3.4 C4:指向 GET /app-api/runtime/package/{versionId}/manifest 端点(MVP 包存 DB,端点原样服务 manifest JSON)。宿主先 fetch 此端点取 manifest,对响应原始文本算 sha256 与 checksum 严格比对通过后再注入运行容器(相对 URL 须 resolve 到 API base,见 §2.5);M3 接 OSS 后改指向 OSS/CDN 版本化 URL(退场契约)' }
entry: { type: string, description: 入口文件相对路径(#4 manifest.entry) }
runtimeVersion: { type: string, description: '目标 WanxiangGameSDK / Canvas Runtime 版本(semver,#4 manifest.runtimeVersion)' }
preloadPolicy: { type: string, enum: [eager, lazy], description: '预加载策略(#4 manifest.preloadPolicy)' }
bundleSize: { type: integer, format: int64, description: 包总字节(#4 manifest.bundleSize) }
checksum: { type: string, description: '整包 sha256(hex),宿主做 Manifest 完整性校验 T-RT-15(#4 manifest.checksum)' }
sandbox: { $ref: '#/components/schemas/SandboxPolicyVO' }
traceId: { type: string, description: '贯穿 生成→编译→加载→运行 的 trace_id(#3 init / #4 provenance)' }
SandboxPolicyVO:
type: object
description: >-
iframe 沙箱隔离策略(T-RT-04),宿主据此设置 iframe sandbox 属性与 CSP、postMessage origin 白名单(T-RT-14)。
【GAP-2 CORS 约束】allowOrigins 同时是 OSS/CDN 取包(manifestUrl/packageUrl)须放行的跨域来源:
OSS 桶 CORS 规则 AllowedOrigin 应与本白名单一致(含宿主 origin),AllowedMethod=GET/HEAD、ExposeHeader=ETag,否则宿主跨域 fetch manifest 被拦。
properties:
sandboxAttr: { type: string, description: "iframe sandbox 属性值(如 'allow-scripts allow-same-origin')" }
allowOrigins: { type: array, items: { type: string }, description: 'postMessage 允许的 origin 白名单(宿主侧 #3 双校验);亦为 OSS/CDN 取包 CORS 须放行的宿主 origin' }
# ---- 试玩会话开/收(脊柱:play_start/end/时长 → 回灌 telemetry #5)----
SessionStartReqVO:
type: object
required: [gameId, versionId, clientPlayToken]
properties:
gameId: { type: integer, format: int64, description: 游戏 ID }
versionId: { type: integer, format: int64, description: 版本 ID }
clientPlayToken: { type: string, maxLength: 64, description: 客户端本局唯一 token(幂等键,防抖/重连不重复计数) }
scene: { type: string, enum: [preview, play], default: play, description: 'preview=创作者预览 / play=玩家试玩' }
traceId: { type: string, description: '贯穿链路 trace_id(透传至 telemetry 回灌)' }
SessionStartRespVO:
type: object
properties:
sessionId: { type: integer, format: int64, description: '试玩会话 ID(本局唯一键,贯穿 end 与 telemetry 回灌关联)。透传进 telemetry envelope.session_id(int64→string)作对账键;session 仅观测时长,play_count 权威源=telemetry game_play_start(见 telemetry V5.0.0 / telemetry.yaml)' }
startTime: { type: string, format: date-time, description: 服务端记录的开局时间 }
SessionEndReqVO:
type: object
required: [sessionId]
properties:
sessionId: { type: integer, format: int64, description: 试玩会话 ID(start 返回) }
durationMs: { type: integer, format: int64, description: 客户端上报时长毫秒(离线兜底;服务端以 end-start 为准) }
endReason: { type: string, enum: [game_end, quit, error, timeout], description: '结束原因(game_end 正常结束 / quit 主动退出 / error 运行错误 / timeout 加载或心跳超时)' }
# ---- 编译编排(消费 #4 产物,产出运行包;外部 OSS/打包为对接点不实现)----
CompileReqVO:
type: object
required: [versionId]
properties:
versionId: { type: integer, format: int64, description: '待编译版本 ID(读取该版本 GamePackage #4)' }
force: { type: boolean, default: false, description: 是否强制重编(忽略已就绪缓存) }
CompileRespVO:
type: object
properties:
buildId: { type: integer, format: int64, description: '编译任务 ID(异步幂等,轮询 /admin-api/runtime/build/{buildId})' }
reused: { type: boolean, description: 是否命中进行中/已就绪任务复用(幂等结果) }
BuildRespVO:
type: object
properties:
buildId: { type: integer, format: int64 }
versionId: { type: integer, format: int64 }
status: { type: integer, description: '编译状态机:0排队 1编译中 2成功 3失败' }
packageUrl: { type: string, description: 成功后产出包 URL(失败为空) }
checksum: { type: string, description: 成功后整包 sha256 }
bundleSize: { type: integer, format: int64, description: 包总字节(体积门禁 T-RT-08 校验值) }
failCode: { type: string, description: '失败错误码(如 1-102-002-002 体积超限 / 1-102-002-001 GameConfig 校验失败),成功为空' }
failReason: { type: string, description: 失败原因(回填运营,成功为空) }
finishTime: { type: string, format: date-time, description: 完成时间 }
# ---- 会话观测 VO ----
SessionRespVO:
type: object
properties:
id: { type: integer, format: int64, description: 会话 ID }
gameId: { type: integer, format: int64 }
versionId: { type: integer, format: int64 }
playerUserId: { type: integer, format: int64, description: 试玩玩家用户 ID }
scene: { type: string, enum: [preview, play] }
status: { type: integer, description: '会话状态:0进行中 1已结束 2异常' }
durationMs: { type: integer, format: int64, description: 时长毫秒(服务端 end-start) }
startTime: { type: string, format: date-time }
endTime: { type: string, format: date-time }