From 08771d39e877246764224c9a18147a468e4d8363 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 12:09:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AA=92=E4=BD=93=E5=85=AC=E5=BC=80=E8=AF=BB?= =?UTF-8?q?=E6=8E=88=E6=9D=83=E6=8C=89=20objectKey=20=E5=8F=8D=E8=A7=A3=20?= =?UTF-8?q?gameId=20=E6=94=B9=20O(1)=20=E6=9F=A5=E6=89=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 从 GAME_DISTRIBUTION_MEDIA_PREFIX 后的首段反解 game_id,按主键读那一行核对媒体归属 - 不再逐行扫描全部游戏及其 screenshots_json - 补充 key 布局(非本前缀、缺 gameId、缺文件名、多级路径)的单测 --- .../spacetime-module/src/game_distribution.rs | 78 ++++++++++++++++--- 1 file changed, 66 insertions(+), 12 deletions(-) diff --git a/server-rs/crates/spacetime-module/src/game_distribution.rs b/server-rs/crates/spacetime-module/src/game_distribution.rs index c8b8b6f46..c86356477 100644 --- a/server-rs/crates/spacetime-module/src/game_distribution.rs +++ b/server-rs/crates/spacetime-module/src/game_distribution.rs @@ -4208,23 +4208,48 @@ fn game_distribution_game_is_publicly_visible(game: &GameDistributionGame) -> bo && game.deleted_at.is_none() } +/// 从发布媒体 objectKey 里反解 game_id;布局不符(含非本前缀的 key)返回 `None`。 +/// +/// 媒体 key 由 api-server 按 `{prefix}{game_id}/{kind}-{uuid}.{ext}` 生成,这里只做字符串切分, +/// 不信任调用方传入的任意 key。 +fn game_distribution_media_object_key_game_id(object_key: &str) -> Option<&str> { + let rest = object_key + .strip_prefix(shared_contracts::game_distribution::GAME_DISTRIBUTION_MEDIA_PREFIX)?; + let (game_id, file_name) = rest.split_once('/')?; + if game_id.is_empty() || file_name.is_empty() { + return None; + } + Some(game_id) +} + /// 已公开游戏的封面/截图 OSS objectKey 允许匿名读者换取签名读地址。 +/// +/// 媒体 key 自带 game_id,直接按主键读那一行再核对 key 是否属于它的封面/截图,避免逐行扫描全部游戏。 pub(crate) fn game_distribution_media_has_public_read_grant( ctx: &ReducerContext, object_key: &str, ) -> bool { - ctx.db.game_distribution_game().iter().any(|game| { - if !game_distribution_game_is_publicly_visible(&game) { - return false; - } - if game.cover_object_key.as_deref() == Some(object_key) { - return true; - } - game.screenshots_json - .as_deref() - .and_then(|json| serde_json::from_str::>(json).ok()) - .is_some_and(|screenshots| screenshots.iter().any(|key| key == object_key)) - }) + let Some(game_id) = game_distribution_media_object_key_game_id(object_key) else { + return false; + }; + let Some(game) = ctx + .db + .game_distribution_game() + .game_id() + .find(&game_id.to_string()) + else { + return false; + }; + if !game_distribution_game_is_publicly_visible(&game) { + return false; + } + if game.cover_object_key.as_deref() == Some(object_key) { + return true; + } + game.screenshots_json + .as_deref() + .and_then(|json| serde_json::from_str::>(json).ok()) + .is_some_and(|screenshots| screenshots.iter().any(|key| key == object_key)) } fn game_distribution_version_snapshot( @@ -4488,4 +4513,33 @@ mod tests { apply_game_distribution_frozen_metadata(&mut game, &priced); assert_eq!(game.price_mud_points, 100); } + + #[test] + fn media_object_key_game_id_is_derived_without_scanning_all_games() { + const PREFIX: &str = shared_contracts::game_distribution::GAME_DISTRIBUTION_MEDIA_PREFIX; + assert_eq!( + game_distribution_media_object_key_game_id(&format!("{PREFIX}game_abc/cover-1.png")), + Some("game_abc") + ); + assert_eq!( + game_distribution_media_object_key_game_id(&format!( + "{PREFIX}game_abc/screenshot-2.webp" + )), + Some("game_abc") + ); + // 非本前缀、缺 gameId、缺文件名、以及多级路径都不认。 + assert_eq!( + game_distribution_media_object_key_game_id("generated/assets/cover.png"), + None + ); + assert_eq!(game_distribution_media_object_key_game_id(PREFIX), None); + assert_eq!( + game_distribution_media_object_key_game_id(&format!("{PREFIX}game_abc/")), + None + ); + assert_eq!( + game_distribution_media_object_key_game_id(&format!("{PREFIX}/cover-1.png")), + None + ); + } }