diff --git a/server-rs/crates/api-server/src/modules/game_distribution.rs b/server-rs/crates/api-server/src/modules/game_distribution.rs index f3d3024a3..99320232d 100644 --- a/server-rs/crates/api-server/src/modules/game_distribution.rs +++ b/server-rs/crates/api-server/src/modules/game_distribution.rs @@ -2016,6 +2016,9 @@ async fn create_game( // 幂等摘要只覆盖请求内容(元数据 + 图片字节),不含服务端生成的 objectKey, // 这样重放同一个 Idempotency-Key 时摘要稳定、命中既有结果。 let request_digest = publish_request_digest(&payload, cover.as_ref(), screenshots.as_slice())?; + // 先在接触对象存储前完成所有会让用户输入失败的转换:normalize_local_project_id 的 `?` + // 如果留到 create_game_distribution_game 实参里求值,上传完的封面/截图会因提前返回而成为孤儿。 + let local_project_id = normalize_local_project_id(payload.local_project_id.as_deref())?; let (cover_object_key, resolved_screenshots, uploaded_media) = resolve_publish_media( &state, game_id.as_str(), @@ -2057,9 +2060,7 @@ async fn create_game( idempotency_key, request_digest, now_micros: now, - local_project_id: normalize_local_project_id( - payload.local_project_id.as_deref(), - )?, + local_project_id, }, ) .await, @@ -2104,6 +2105,9 @@ async fn create_version( cover.as_ref(), screenshots.as_slice(), )?; + // 与 create_game 同理:用户输入相关的失败必须在 resolve_publish_media 上传之前发生, + // 否则 `?` 会在 cleanup_uncommitted_publish_media 之前返回,留下孤儿媒体对象。 + let local_project_id = normalize_local_project_id(payload.local_project_id.as_deref())?; let (cover_object_key, resolved_screenshots, uploaded_media) = resolve_publish_media( &state, game_id.as_str(), @@ -2137,9 +2141,7 @@ async fn create_version( package_bytes: payload.package_bytes, package_file_count: payload.package_file_count, package_entry_path: payload.package_entry_path, - local_project_id: normalize_local_project_id( - payload.local_project_id.as_deref(), - )?, + local_project_id, idempotency_key, request_digest, now_micros: now,