From afbcc799785e7b621d81f9a122dd8d6a575a03f7 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 01:26:24 +0800 Subject: [PATCH] =?UTF-8?q?=E9=99=B6=E6=B3=A5=E5=84=BF=E4=BA=A7=E7=89=A9?= =?UTF-8?q?=E6=8C=89=E5=B9=B3=E5=8F=B0=E5=8F=91=E8=A1=8C=E5=8C=85=E4=B8=8A?= =?UTF-8?q?=E9=99=90=E9=A2=84=E6=A3=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build.rs 保留 512 MiB 读取安全线,另按 shared-contracts 的 GAME_DISTRIBUTION_MAX_PACKAGE_BYTES(200 MiB)做预检 - 构建收尾与发布读包两处都预检,超限给「精简资源后重新导出再发布」,不再等上传吃服务端 413 - 新增平台上限单测,并锁定读取安全线必须高于平台上限 --- .../src/export/draft/taonier/build.rs | 56 +++++++++++++++---- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/export/draft/taonier/build.rs b/apps/ai-game-creator-shell/src-tauri/src/export/draft/taonier/build.rs index 6bd4e0fc7..0b7d4212c 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/export/draft/taonier/build.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/export/draft/taonier/build.rs @@ -35,8 +35,24 @@ const OUTPUT_TAIL_MAX_CHARS: usize = 4_000; const ARTIFACT_ENTRY_FILE_NAME: &str = "index.html"; /// 读取产物时单次读入的字节数。sha256 与字节缓冲共用这一遍流式读取。 const PACKAGE_READ_CHUNK_BYTES: usize = 64 * 1024; -/// 产物 zip 的合理上限:与试玩包同一口径(512 MiB),超过它直接拒绝读入内存。 -const ARTIFACT_PACKAGE_MAX_BYTES: u64 = 512 * 1024 * 1024; +/// 读取产物时的体积安全线:超过它直接拒绝读入内存(与旧本地发行包读取口径一致)。 +const ARTIFACT_PACKAGE_READ_MAX_BYTES: u64 = 512 * 1024 * 1024; + +/// 平台发行包上限(200 MiB):单一来源是 `shared-contracts`,这里只做发布前的同口径预检。 +/// +/// 与上一条读取安全线不是一个概念:安全线管「能不能读进内存」,这里管「平台上能不能收」。 +/// 构建收尾与发布读包两处都过它,避免作者等完一轮再吃服务端 413。 +fn ensure_within_platform_package_limit(size_bytes: u64) -> Result<(), String> { + let limit = shared_contracts::game_distribution::GAME_DISTRIBUTION_MAX_PACKAGE_BYTES; + if size_bytes > limit { + return Err(format!( + "发行包 {:.1} MiB 超过平台上限 {} MiB;请精简资源后重新导出再发布", + size_bytes as f64 / (1024.0 * 1024.0), + limit / (1024 * 1024) + )); + } + Ok(()) +} /// 固定产物读出来的包:发布链路只需要这几项事实,不需要逐条文件摘要。 #[derive(Clone, Debug, PartialEq, Eq)] @@ -164,12 +180,14 @@ fn conclude( && !metadata.file_type().is_symlink() && metadata.len() > 0 ); - // zip 规范:根目录必须有 `index.html`。脚本"成功"但产物不是合规 zip(或写到了别处), - // 与没有产物同类,都报 artifactMissing。 + // zip 规范:根目录必须有 `index.html`,且包体不得超过平台发行包上限。脚本"成功"但产物 + // 不合规(或写到了别处),与没有产物同类,都报 artifactMissing。 + let bytes = std::fs::read(&path).ok(); let conformant = produced - && std::fs::read(&path) - .map(|bytes| inspect_artifact(&bytes).is_ok()) - .unwrap_or(false); + && bytes.as_deref().is_some_and(|bytes| { + ensure_within_platform_package_limit(bytes.len() as u64).is_ok() + && inspect_artifact(bytes).is_ok() + }); if !conformant { return Err(TaonierExportBuildError::ArtifactMissing( TaonierArtifactMissing { @@ -201,13 +219,14 @@ pub(crate) fn read_artifact_package(root: &Path) -> Result ARTIFACT_PACKAGE_MAX_BYTES { + if metadata.len() > ARTIFACT_PACKAGE_READ_MAX_BYTES { return Err(format!( "陶泥儿产物大小超出上限:{}B > {}B", metadata.len(), - ARTIFACT_PACKAGE_MAX_BYTES + ARTIFACT_PACKAGE_READ_MAX_BYTES )); } + ensure_within_platform_package_limit(metadata.len())?; let (package_bytes, package_sha256) = read_package_bytes(&path)?; if package_bytes.len() as u64 != metadata.len() { @@ -240,10 +259,10 @@ fn read_package_bytes(path: &Path) -> Result<(Vec, String), String> { } hasher.update(&buffer[..read]); bytes.extend_from_slice(&buffer[..read]); - if bytes.len() as u64 > ARTIFACT_PACKAGE_MAX_BYTES { + if bytes.len() as u64 > ARTIFACT_PACKAGE_READ_MAX_BYTES { return Err(format!( "陶泥儿产物大小超出上限:{}B", - ARTIFACT_PACKAGE_MAX_BYTES + ARTIFACT_PACKAGE_READ_MAX_BYTES )); } } @@ -489,6 +508,21 @@ mod tests { assert_eq!(unavailable.cause, "命令沙箱不可用"); } + #[test] + fn platform_package_limit_is_the_shared_contract_and_reports_mib() { + let limit = shared_contracts::game_distribution::GAME_DISTRIBUTION_MAX_PACKAGE_BYTES; + assert!(ensure_within_platform_package_limit(limit).is_ok()); + // 读取安全线(512 MiB)与平台上限(200 MiB)不是一个概念:这里必须卡平台上限。 + assert!(limit < ARTIFACT_PACKAGE_READ_MAX_BYTES); + let error = + ensure_within_platform_package_limit(limit + 1).expect_err("超过平台上限必须失败"); + assert!( + error.contains("超过平台上限"), + "错误文案应说明超限:{error}" + ); + assert!(error.contains("200 MiB"), "错误文案应带上平台上限:{error}"); + } + #[test] fn reads_the_fixed_artifact_with_hash_size_and_file_count() { let root = fixture_root("read-package");