创建游戏同步校验封面与截图素材归属

- 服务端:抽出 `resolve_owned_game_media`,创建游戏与创建版本复用同一素材校验;不存在的素材返回 400,借用他人素材返回 403,避免游戏行先落一个无效素材 ID。
- 脚本:新增 `npm run check:game-distribution-media-e2e`(`scripts/check-game-distribution-media-e2e.mjs`),在本地 dev 栈上跑真实素材直传、冻结、审核生效、匿名换签读与发行网关的可重复检查。
- 文档:后端数据契约补充创建游戏的素材复核口径,实施计划记录真实本地栈 27 项媒体验证与真实浏览器封面/截图展示证据。
This commit is contained in:
2026-09-20 22:34:33 +08:00
parent 4a2b270714
commit 4a91e5865a
5 changed files with 529 additions and 3 deletions
@@ -472,6 +472,9 @@ async fn create_game(
ensure_publish_enabled(&state, Some(auth.claims().user_id())).await?;
let idempotency_key = idempotency_key(&headers)?;
validate_game_metadata(&payload)?;
// 创建游戏时就把封面/截图的归属与类型校验掉:否则游戏行会先落一个不属于当前作者
// 或根本不存在的素材 ID,直到创建版本才失败,留下无法解释的半成品资料。
resolve_owned_game_media(&state, auth.claims().user_id(), &payload).await?;
let now = now_micros();
let game_id = format!("game_{}", Uuid::new_v4().simple());
let request_digest = compute_request_digest(
@@ -1462,11 +1465,11 @@ struct FrozenGameScreenshot {
/// 校验封面/截图素材归属并生成版本冻结资料 JSON。
///
/// 对象键由服务端从素材记录派生,客户端只能提供素材 ID;素材必须属于当前作者且是图片。
async fn resolve_version_metadata_json(
async fn resolve_owned_game_media(
state: &AppState,
owner_user_id: &str,
metadata: &GameDistributionCreateGameRequest,
) -> Result<String, AppError> {
) -> Result<(String, String, Vec<FrozenGameScreenshot>), AppError> {
let cover_asset_id = metadata
.cover_asset_id
.as_deref()
@@ -1488,6 +1491,16 @@ async fn resolve_version_metadata_json(
object_key,
});
}
Ok((cover_asset_id, cover_object_key, screenshots))
}
async fn resolve_version_metadata_json(
state: &AppState,
owner_user_id: &str,
metadata: &GameDistributionCreateGameRequest,
) -> Result<String, AppError> {
let (cover_asset_id, cover_object_key, screenshots) =
resolve_owned_game_media(state, owner_user_id, metadata).await?;
let tags = metadata
.tags
.iter()