From 4cd784313460274df0e3377758dc9191f1589c47 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:03 +0800 Subject: [PATCH] =?UTF-8?q?AGC=20=E4=BD=9C=E8=80=85=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E5=88=87=E5=88=B0=20owner=20=E5=AA=92=E4=BD=93=E8=AF=BB?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - resolve_preview_url 改走 /api/game-distribution/my-games/{gameId}/media/read-url,不再走素材库 /api/assets/read-url - 新增 owner_media_read_url,复用 endpoint 的路径段转义并编码 objectKey - 新增路由与编码单测,钉住不再回退素材库 ACL --- .../src/game_distribution_publish.rs | 72 ++++++++++++++++--- 1 file changed, 64 insertions(+), 8 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs b/apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs index 95a1a2ce5..92a37e851 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs @@ -585,11 +585,27 @@ fn clear_publication_binding(root: &Path) { }); } -fn asset_read_url(api_base_url: &str, object_key: &str) -> Result { - let mut url = Url::parse(&format!( - "{}/api/assets/read-url", - api_base_url.trim_end_matches('/') - )) +/// owner 作用域媒体读地址:`/api/game-distribution/my-games/{gameId}/media/read-url`。 +/// +/// 不能再走 `/api/assets/read-url`:发布媒体是项目快照桶对象、不建 `asset_object`,素材库 ACL +/// 不会给它授权;owner 路由按「objectKey 命中调用者自己作品当前行的媒体」放行,未发布 / 被驳回 +/// 的封面与截图也能预览。 +fn owner_media_read_url( + snapshot: &PlatformSessionSnapshot, + game_id: &str, + object_key: &str, +) -> Result { + let mut url = Url::parse(&endpoint( + snapshot, + &[ + "api", + "game-distribution", + "my-games", + game_id, + "media", + "read-url", + ], + )?) .map_err(|_| "陶泥儿服务地址无效".to_string())?; url.query_pairs_mut().append_pair("objectKey", object_key); Ok(url.to_string()) @@ -599,13 +615,14 @@ fn asset_read_url(api_base_url: &str, object_key: &str) -> Result, ) -> Option { let object_key = object_key?.trim(); if object_key.is_empty() { return None; } - let read_url = asset_read_url(&snapshot.api_base_url, object_key).ok()?; + let read_url = owner_media_read_url(snapshot, game_id, object_key).ok()?; match crate::assets::resolve_external_asset_signed_url( client, snapshot.access_token.as_str(), @@ -797,7 +814,13 @@ async fn build_publication_draft( let cover = if cover_asset_id.is_some() || cover_object_key.is_some() { Some(GameDistributionPublicationImageDraft { asset_id: normalize_asset_id(cover_asset_id), - preview_url: resolve_preview_url(client, snapshot, cover_object_key.as_deref()).await, + preview_url: resolve_preview_url( + client, + snapshot, + entry.id.as_str(), + cover_object_key.as_deref(), + ) + .await, }) } else { None @@ -806,7 +829,13 @@ async fn build_publication_draft( for (asset_id, object_key) in screenshots { screenshot_drafts.push(GameDistributionPublicationImageDraft { asset_id: normalize_asset_id(asset_id), - preview_url: resolve_preview_url(client, snapshot, object_key.as_deref()).await, + preview_url: resolve_preview_url( + client, + snapshot, + entry.id.as_str(), + object_key.as_deref(), + ) + .await, }); } GameDistributionPublicationDraft { @@ -1499,6 +1528,33 @@ mod tests { assert!(endpoint(&snapshot, &["api", "bad?query"]).is_err()); } + #[test] + fn owner_media_read_url_targets_owner_route_and_encodes_object_key() { + let snapshot = PlatformSessionSnapshot { + user_id: "user-1".to_string(), + access_token: "token".to_string(), + api_base_url: "https://dev.genarrative.world".to_string(), + identity_generation: 1, + revision: 1, + }; + let url = owner_media_read_url( + &snapshot, + "game-1", + "agc/project-snapshots/v1/game-distribution/media/game-1/cover-1.png", + ) + .expect("owner media read url"); + assert!(url.starts_with( + "https://dev.genarrative.world/api/game-distribution/my-games/game-1/media/read-url?" + )); + assert!( + url.contains("objectKey=agc%2Fproject-snapshots%2Fv1%2F"), + "objectKey 必须按查询参数编码:{url}" + ); + // 作者预览不再回退到素材库 ACL 路由:发布媒体不是 asset_object。 + assert!(!url.contains("/api/assets/read-url")); + assert!(owner_media_read_url(&snapshot, "bad/id", "cover.png").is_err()); + } + #[test] fn response_data_unwraps_v1_envelope_and_legacy_payload() { assert_eq!(