From 5142c2601134341c2ad55c80fa3035d29edec988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Wed, 7 Oct 2026 10:27:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=90=8C=E6=AD=A5=E6=B8=B8?= =?UTF-8?q?=E6=88=8F=E5=88=86=E5=8F=91=20owner=20=E5=AA=92=E4=BD=93?= =?UTF-8?q?=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端契约/玩法链路/AGC 实施计划补 owner 作用域读路由与作者预览口径(软删仍 404) - decision-log 2026-10-06 条目记录 owner 读决策,并改「临时计划已删除」为保留计划与里程碑文档 - pitfalls 新增作者预览必须走 owner 读、不能回退素材库 ACL 的排障口径 --- docs/project-memory/shared-memory/decision-log.md | 3 ++- docs/project-memory/shared-memory/pitfalls.md | 1 + .../【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md | 2 +- docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md | 4 ++-- docs/【玩法创作】平台入口与玩法链路-2026-05-15.md | 4 ++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 6018d9008..1bc98a88c 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -9908,4 +9908,5 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - schema 删字段属 breaking:按既有先例用 `SPACETIME_SCHEMA_GUARD_ALLOW_BREAKING=1 npm run check:spacetime-schema` 验证,并同步 `migration.rs`、表目录与生成绑定。 - AI 资料建议 / 封面生成退役:删前端调用、Rust 命令与注册、api-server 路由与专属 DTO;保留发布进度对话框。 - skill pack 例外:`agc-skill-pack.v1` 只登记 `vite-export-taonier`;未跟踪的 `vite-export-taptaph5` 是另一条 WIP,不登记,`npm run agc:skill-pack:check` 对其未声明文件报红为已知接受项。 -- 权威行为已回写 AGC 实施计划「2026-10-06」章节、玩法链路「游戏分发发布媒体直传合同」与后端数据契约;临时实施计划与里程碑规范已删除,历史由 Git 保存。 +- 权威行为已回写 AGC 实施计划「2026-10-06」章节、玩法链路「游戏分发发布媒体直传合同」与后端数据契约;实施计划《【实施计划】陶泥儿导出文件草稿化与发布媒体直传-2026-10-06.md》与对应里程碑规范**保留在 `docs/project-memory/plans/`**,不再按该计划 §5.4 在收尾时删除。 +- owner 作用域媒体读(同日补充):公开读只覆盖已发布作品,作者预览未发布 / 待审 / 被驳回作品的封面与截图会退化成占位图。新增 `GET /api/game-distribution/my-games/{gameId}/media/read-url`(及 `.../media/read-bytes`,需 bearer),判定 = `gameId` 属于当前登录主体且 objectKey 命中该作品当前行的 `cover_object_key` / `screenshots_json`;复用同一签名器与 `get_game_distribution_game(owner_user_id)`,不新增 procedure、不给素材库 ACL 开特例,公开读判定不变。AGC `resolve_preview_url`、平台 web `useGameDistributionMediaReadUrl`(带 `gameId`)、`MyGamesPage` 封面与发布/编辑回填全部切到 owner 路由。 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 0048d5630..c4929e8dd 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -8,6 +8,7 @@ - **幂等键与发布账本口径**:AGC 根幂等键由账本按「账号 + origin + 本地项目 + 包摘要 + 目标游戏/版本 + 资料摘要」解析,同一次用户发布的重试、响应丢失后的重发与进程重启后的分片续传都复用同一 root key,包内容 / 目标版本 / 资料任一变化才换键;分包上传再用 `:upload`。服务端 `game_distribution_idempotency_receipt` 以 `owner_user_id + action + idempotency_key` 唯一,同 key 同摘要回 `replayed=true` 并复用结果 ID,同 key 不同摘要返回 409。不要把重试实现成新 key,也不要让摘要漏掉图片字节或沿用 objectKey,否则媒体直传下的重放语义失效。 - **`npm run agc:skill-pack:check` 因未登记 taptap 报红是已知接受项**:`vite-export-taptaph5` 是另一条 WIP,未进 `AGC_SKILL_PACK_EXPECTED_NAMES` / `EXPECTED_SKILL_NAMES`;check 会列出它的未声明文件并退出非零。当前接受该红,不为它补登记或改清单;要恢复全绿必须先收口或正式登记 taptap skill。taonier 自身的清单与指纹已锁步。 - **taonier zip 必须存档根 `index.html`**:`.export/taonier.zip` 的存档根必须直接有 `index.html`,不能套外层文件夹(`taonier/index.html`、`game/index.html` 都算失败)。`vite-export-taonier/scripts/pack.mjs` 在落盘前按根入口校验并拒绝;即使绕过,服务端发行合同只认根 `index.html`,会以 `ReleasePackageError::MissingEntry` 映射 422 `PACKAGE_VALIDATION_FAILED` 拒收。适配非标准工程布局时,让 `build:taonier` 把 `--vite-built-dir` 指到真正含 `index.html` 的目录,或让 `pack.mjs` 不套外层;客户端 `run_taonier_export_build` 也在产物收尾复核根入口。 +- **作者预览未发布作品必须走 owner 媒体读,不能再回退素材库 ACL**:发布媒体是项目快照桶对象、不建 `asset_object`,`/api/assets/read-url` 不会给它授权;公开读路由 `GET /api/game-distribution/media/read-url` 只认「已发布且 active」,两条都不覆盖"作者看自己未发布/被驳回作品"。症状是作者中心与 AGC 线上值封面/截图静默退化成占位图(换签失败被吞成空地址),不报错、不阻断。现行口径:作者侧一律走 `GET /api/game-distribution/my-games/{gameId}/media/read-url`(`read-bytes` 同理,需 bearer),判定 = 作品归属 + objectKey 命中该作品当前行的 `cover_object_key` / `screenshots_json`;AGC `resolve_preview_url` 与平台 web 的 `useGameDistributionMediaReadUrl({ gameId })`、`resolveGamePublishImagePreview(objectKey, gameId)` 都已切换。新增读路径时先确认它属于公开面还是 owner 面,别再让作者预览落到公开判定上。 ## 2026-10-05 PR #607 复核修复:档位点对齐/对比度、状态文案也走浮层、键盘去重、卸载 flush diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index 7801fd433..881c5241e 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -9,7 +9,7 @@ - 适配与错误:未适配弹「让陶泥儿帮我调通」;adapt/fill/repair 三类指令与小红书一致,失败现场原样转发 code agent;「帮我填」只写元数据,不改打包适配。 - 线上值:草稿是唯一真相;读命令额外返回只读线上值(live game 行),不自动 seed/回填;文案字段与图片均可「沿用线上值」,图片路径为空表示沿用(发布时带线上 objectKey)。 - 退役收口:`exports/` 目录、历史试玩包链、`prepare/upload/list` 命令、生成任务产物声明、prompts、run-trace 白名单与排除列表都不再存在;AI 资料建议与封面生成、`useGameDistributionPublishForm`/`GameDistributionPublishFormView`/`GamePublishBlockedDialog` 一并删除,保留发布进度对话框。 -- 发布媒体:`POST /api/game-distribution/games`、`POST .../versions`、`PATCH .../my-games/{id}` 收 `multipart/form-data`(元数据文本 + 封面/截图原始二进制 + 沿用 objectKey);整包仍走 raw/chunked。图片落项目快照桶 `agc/project-snapshots/v1/game-distribution/media/...`,不建 `asset_object`;公开读走 `GET /api/game-distribution/media/read-url`(及 `read-bytes`),判定 = 已发布且 active 的 game 且 objectKey 命中封面/截图,不经素材库 ACL。硬切,无旧 JSON/assetId 兼容。 +- 发布媒体:`POST /api/game-distribution/games`、`POST .../versions`、`PATCH .../my-games/{id}` 收 `multipart/form-data`(元数据文本 + 封面/截图原始二进制 + 沿用 objectKey);整包仍走 raw/chunked。图片落项目快照桶 `agc/project-snapshots/v1/game-distribution/media/...`,不建 `asset_object`;公开读走 `GET /api/game-distribution/media/read-url`(及 `read-bytes`),判定 = 已发布且 active 的 game 且 objectKey 命中封面/截图,不经素材库 ACL。作者预览自己作品的线上封面/截图走 owner 作用域 `GET /api/game-distribution/my-games/{gameId}/media/read-url`(需 bearer,判定 = 作品归属 + objectKey 命中该作品当前媒体),未发布 / 待审 / 被驳回也能换签;AGC `resolve_preview_url` 已切到该路由,不再走素材库 `/api/assets/read-url`。硬切,无旧 JSON/assetId 兼容。 - 验收:草稿/打包/失败转发/沿用线上值与发布直传的定向测试;真实 vite 工程首轮适配与一次真实发布 smoke。 ## 2026-10-05 导出产物面板与小红书小工具导出 diff --git a/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md b/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md index a9179bef2..9c75a98d3 100644 --- a/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md +++ b/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md @@ -497,7 +497,7 @@ Responses 的终态载荷既是工具调用的恢复源,也是正文的恢复 - Rust 结构体:`GameDistributionGame` - 源码:`server-rs/crates/spacetime-module/src/game_distribution.rs` - 用途:游戏分发稳定身份与公开版本指针。保存 owner、标题/简介/分类资料、设备与输入声明、`publication_revision`、当前 `active_version_id`、可见性和游玩计数;标签与输入模式按版本化 JSON 保存,展示资料由 `api-server` 通过 `spacetime-client` 归一后返回。 -- 公开素材:游戏行末尾追加可空 `cover_object_key` 与 `screenshots_json`(objectKey 字符串数组)。发布写路径(创建游戏 / 创建版本 / 编辑资料)收 `multipart/form-data`:新图由服务端直写项目快照桶 `agc/project-snapshots/v1/game-distribution/media//{cover|screenshot}-.` 并只回 objectKey,不建 `asset_object`;沿用槽位带该作品当前媒体的 objectKey,不命中即 400,附图校验 `image/*`、张数与体积上限。 发布写入受灰度配置键 `game-distribution:publish` 约束:**灰度默认关闭**,未配置或 `enabled=false` 时写入口(创建游戏/版本、确认包、送审、审核通过激活)返回 503 `GAME_DISTRIBUTION_PUBLISH_DISABLED`,`enabled=true` 且白名单/比例/标签命中才放行,读取与安全下架保持可用;同一判据在 `GET /api/runtime/frontend-config` 以 `gameDistributionPublishEnabled` 下发给前端入口,匿名恒为 `false`。只有可见性为 `published`、存在有效 `active_version_id` 且未软删除的游戏,其 `cover_object_key` / `screenshots_json` 中的 objectKey 才在 `GET /api/game-distribution/media/read-url`(需要同源字节时用 `.../media/read-bytes`)上按游戏分发域内判定获得匿名读授权,不经素材库 ACL。 +- 公开素材:游戏行末尾追加可空 `cover_object_key` 与 `screenshots_json`(objectKey 字符串数组)。发布写路径(创建游戏 / 创建版本 / 编辑资料)收 `multipart/form-data`:新图由服务端直写项目快照桶 `agc/project-snapshots/v1/game-distribution/media//{cover|screenshot}-.` 并只回 objectKey,不建 `asset_object`;沿用槽位带该作品当前媒体的 objectKey,不命中即 400,附图校验 `image/*`、张数与体积上限。 发布写入受灰度配置键 `game-distribution:publish` 约束:**灰度默认关闭**,未配置或 `enabled=false` 时写入口(创建游戏/版本、确认包、送审、审核通过激活)返回 503 `GAME_DISTRIBUTION_PUBLISH_DISABLED`,`enabled=true` 且白名单/比例/标签命中才放行,读取与安全下架保持可用;同一判据在 `GET /api/runtime/frontend-config` 以 `gameDistributionPublishEnabled` 下发给前端入口,匿名恒为 `false`。只有可见性为 `published`、存在有效 `active_version_id` 且未软删除的游戏,其 `cover_object_key` / `screenshots_json` 中的 objectKey 才在 `GET /api/game-distribution/media/read-url`(需要同源字节时用 `.../media/read-bytes`)上按游戏分发域内判定获得匿名读授权,不经素材库 ACL;作者预览自己作品(未发布 / 待审 / 被驳回)走 owner 作用域 `GET /api/game-distribution/my-games/{gameId}/media/read-url`(及 `.../media/read-bytes`,需 bearer),判定 = 该 `gameId` 属于当前登录主体且 objectKey 命中其 `cover_object_key` / `screenshots_json`,与公开读共用同一把签名器;软删作品在该读路径上一律 404。 - 复用规则:末尾可空列 `local_project_id` 保存发布方本地项目标识(AGC 的 `manifest.projectId`)。同一 `owner_user_id` 再次以相同 `local_project_id` 创建游戏时复用既有 `game_id` 并只新增版本,避免“更新”被实现成新建游戏;该字段只是复用提示,不构成所有权或路径凭证,也不能用于跨账号匹配。已软删除的游戏不参与复用:删除后重新发布同一本地项目应得到新的游戏身份。 - 软删除:游戏行末尾追加可空 `deleted_at`(2026-10-01)。非空表示作者已删除该作品:`delete_game_distribution_game_and_return` 只写该时间戳并把公开投影下线(可见性回到 `unpublished`、撤销当前公开版本、递增 `publication_revision`),版本行、发行包与其冻结资料一律不改写。软删行不进入作者列表(`list_owner_game_distribution_games_and_return`)、公开目录(`list_public_game_distribution_games_and_return`)、公开详情(`get_public_game_distribution_game_and_return`)、公开媒体读取判定(`get_game_distribution_media_read_access_and_return`)与审核队列;后台默认视图同样排除,只有显式 `status=deleted` 才会读到。作者侧版本回读对软删作品返回空(404),因此上传、确认与送审入口一并关闭。 - 资料编辑:`update_game_distribution_game_metadata_and_return` 覆盖游戏行上的展示字段(标题/简介/详介/分类/标签/封面/截图/设备/输入模式/方向)并立即生效,要求 `expected_publication_revision` CAS;版本行与冻结资料不变,下一次审核通过仍会用新版本的冻结资料覆盖游戏行。**资料编辑不得直接改公开价格**:调价必须走新版本审核。 @@ -506,7 +506,7 @@ Responses 的终态载荷既是工具调用的恢复源,也是正文的恢复 - 购买与播放鉴权 HTTP(2026-10-05):`POST /api/game-distribution/games/{gameId}/purchase`(`require_bearer_auth` + 必填 `Idempotency-Key`,请求体 `{ expectedPriceMudPoints }`)经 facade 调 `purchase_game_distribution_game_and_return`,返回 `{ purchase, walletBalance, replayed }`;余额不足 400 `INSUFFICIENT_MUD_POINTS`、价格已变化 409、免费游戏 400、作者本人自购 400 `GAME_PURCHASE_OWNER_EXEMPT`(作者免购买,绝不扣费)、管理员令牌 403 `GAME_PURCHASE_ADMIN_NOT_ALLOWED`(购买只接受普通用户 bearer)、游戏不可见 404、缺幂等键 400、未登录 401。`POST /api/game-distribution/games/{gameId}/play-session` 对免费作品直接回既有公开入口 `/games/{gameId}/`;付费作品同时接受管理员令牌(按现有 admin 鉴权)与用户令牌,已购买 / 作者本人 / 管理员才签发绑定 `gameId + userId`、2 小时有效期的进程内会话,令牌为内存态,进程重启即失效。网关 `GET /api/game-distribution/play-sessions/{token}[/{assetPath}]` 不挂登录中间件、凭令牌读取当前公开版本包,能解析出平台刷新会话 Cookie 时 403,令牌过期 / 不存在、游戏下架 / 封禁或没有有效公开版本一律 404,全部 `no-store`。公开详情 `GET /api/game-distribution/games/{gameId}` 可选鉴权读取查看者:`purchased` 只反映真实购买记录,付费作品对未购买且非作者 / 非管理员把 `currentVersion.entryUrl` 置 `null`(资料与价格仍可见);`GET /api/game-distribution/releases/{gameId}[/{assetPath}]` 在当前公开版本 `price_mud_points > 0` 时同样 404,付费作品只能经播放会话路径播放。 - 游玩计数写入:`play_count` 只由批量 procedure `increment_game_distribution_game_play_counts_and_return`(输入 `GameDistributionPlayCountIncrementInput { increments: Vec<{ gameId, delta }> }`)累加。`api-server` 在内存里按 `identity + gameId` 做 30 分钟去重、按 `IP + gameId` 做固定窗口限流后,按 `GENARRATIVE_GAME_PLAY_COUNTER_FLUSH_INTERVAL_MS`(默认 5 秒)批量落库;事务内只对 `published` 且存在有效 `active_version_id` 的游戏 `saturating_add`,非公开静默跳过,且**不更新** `updated_at`。公开 HTTP 入口为 `POST /api/game-distribution/games/{gameId}/plays`,完整行为见玩法链路的「游玩计数(已实现)」。 -- 发布媒体直传(2026-10-06):游戏行只存 `cover_object_key` 与 objectKey 数组 `screenshots_json`;版本冻结资料 `GameDistributionFrozenMetadata` 用 `coverObjectKey`(`String`)与 `screenshots`(`Vec`,objectKey),`GameDistributionFrozenScreenshot` 不存在。请求 DTO(create game / create version / update metadata)用 `coverObjectKey` 与 `screenshots: (string|null)[]`:`string` 槽位沿用该作品当前媒体的 objectKey,`null` 槽位按序消费可重复的 `screenshot` 二进制 part,可选 `cover` part 出现时覆盖 `metadata.coverObjectKey`。公开读走 `GET /api/game-distribution/media/read-url`(及 `read-bytes`),判定为「已发布且 active 的 game + objectKey 命中 `cover_object_key` / `screenshots_json`」,不经素材库 ACL;公开读授权 procedure 只按 objectKey 查询(`get_game_distribution_media_read_access_and_return`)。`request_digest` 覆盖规范化元数据 + 图片字节 hash + 沿用 objectKey,覆盖 create / version / update 三条写路径。硬切、无历史数据;schema 变更仍须同步 `migration.rs`、表目录与生成绑定并运行 `npm run check:spacetime-schema`。 +- 发布媒体直传(2026-10-06):游戏行只存 `cover_object_key` 与 objectKey 数组 `screenshots_json`;版本冻结资料 `GameDistributionFrozenMetadata` 用 `coverObjectKey`(`String`)与 `screenshots`(`Vec`,objectKey),`GameDistributionFrozenScreenshot` 不存在。请求 DTO(create game / create version / update metadata)用 `coverObjectKey` 与 `screenshots: (string|null)[]`:`string` 槽位沿用该作品当前媒体的 objectKey,`null` 槽位按序消费可重复的 `screenshot` 二进制 part,可选 `cover` part 出现时覆盖 `metadata.coverObjectKey`。公开读走 `GET /api/game-distribution/media/read-url`(及 `read-bytes`),判定为「已发布且 active 的 game + objectKey 命中 `cover_object_key` / `screenshots_json`」,不经素材库 ACL;owner 作用域读路由 `GET /api/game-distribution/my-games/{gameId}/media/read-url`(`read-bytes` 同理,需 bearer)按「作品归属 + objectKey 命中该作品当前媒体」放行,供作者预览未发布 / 被驳回作品的封面与截图(软删作品该路径 404);公开读授权 procedure 只按 objectKey 查询(`get_game_distribution_media_read_access_and_return`),owner 读直接复用 `get_game_distribution_game`(带 `owner_user_id`),不新增 procedure。`request_digest` 覆盖规范化元数据 + 图片字节 hash + 沿用 objectKey,覆盖 create / version / update 三条写路径。硬切、无历史数据;schema 变更仍须同步 `migration.rs`、表目录与生成绑定并运行 `npm run check:spacetime-schema`。 ### `game_distribution_review` diff --git a/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md b/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md index 5d90058f7..827f40d25 100644 --- a/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md +++ b/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md @@ -62,7 +62,7 @@ - 写:`POST /api/game-distribution/games`、`POST /api/game-distribution/games/{gameId}/versions`、`PATCH /api/game-distribution/my-games/{gameId}` 接受 `multipart/form-data`:`metadata` 文本 part(JSON,含 `coverObjectKey` 与 `screenshots: (string|null)[]`);`cover` 二进制 part(可选,出现时覆盖 `coverObjectKey`);`screenshot` 二进制 parts(≤6 张,按 `null` 槽位顺序消费)。`string` 槽位表示沿用线上 objectKey。硬切,不接受素材 ID 形式的媒体引用。 - 存储:新图由服务端写入项目快照桶 `agc/project-snapshots/v1/game-distribution/media//{cover|screenshot}-.`,**不建 `asset_object`**;冻结资料与游戏行只存 `coverObjectKey` / 截图 objectKey。 -- 读:`GET /api/game-distribution/media/read-url`(匿名)按 objectKey 返回签名读地址;判定 = 已发布且 active 的 game 且 objectKey 命中其冻结媒体;需要同源字节时用 `.../media/read-bytes`。 +- 读:`GET /api/game-distribution/media/read-url`(匿名)按 objectKey 返回签名读地址;判定 = 已发布且 active 的 game 且 objectKey 命中其冻结媒体;需要同源字节时用 `.../media/read-bytes`。作者预览自己作品走 owner 作用域 `GET /api/game-distribution/my-games/{gameId}/media/read-url`(需 bearer,`read-bytes` 同理):判定 = 该 `gameId` 属于当前登录主体且 objectKey 命中该作品当前媒体,未发布 / 待审 / 被驳回的作品也能换签(软删作品返回 404),与公开读共用签名器。 - 幂等:`request_digest` 覆盖规范化元数据 + 新图字节 hash + 沿用 objectKey,保持重放语义。 - 归属:仅 owner 可写;沿用 objectKey 必须属于该游戏当前媒体;附图校验 `image/*`、张数与体积上限。 - 与素材库解耦:公开读判定只按游戏分发域内规则(已发布且 active 的 game + objectKey 命中冻结媒体),不经素材库 ACL;素材桶 `/api/assets/read-url` 的通用签名与字节中转抽成公共 helper 供新路由复用,授权仍域内分离。 @@ -529,7 +529,7 @@ - 详情必须展示:发布者 ID/名称/头像、游戏标题、简介、详细介绍、分类、标签、操作方式、支持设备、输入方式、方向、封面、截图、版本号、包大小、文件数、SHA-256、提交时间、审核时间、审核理由和 `publicationRevision`。 - 展示的游戏资料优先使用该版本冻结的 `metadata_json`;不能用审核期间作者后来修改的 game 行资料替代待审快照。 - 发布者和游戏资料仅通过管理员受保护接口读取;公开目录不得因此增加作者私有字段或待审版本字段。 -- 封面与截图通过 `GET /api/game-distribution/media/read-url` 按 objectKey 换取签名读地址,不把私有 Object Key 当作浏览器直链。 +- 封面与截图通过 `GET /api/game-distribution/media/read-url` 按 objectKey 换取签名读地址,不把私有 Object Key 当作浏览器直链;作者中心预览自己未发布 / 被驳回的作品时改走 `GET /api/game-distribution/my-games/{gameId}/media/read-url`(带 bearer),避免公开读判定把作者自己的封面挡成占位图。 ### 待审版本试玩