From 8473877bc328711ce2701120e91c631a5d626a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Tue, 22 Sep 2026 15:17:50 +0800 Subject: [PATCH] =?UTF-8?q?5xx=20=E9=94=99=E8=AF=AF=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E5=9B=9E=E5=B8=A6=20provider=20task=20id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TaskFailure / OutputSchema / 上游兜底分支只回固定文案,完整错误写入服务端日志 - 补一条断言 502 details 不含 task id 与 provider 原文的用例 --- .../crates/api-server/src/tripo3d/errors.rs | 66 ++++++++++++++----- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/server-rs/crates/api-server/src/tripo3d/errors.rs b/server-rs/crates/api-server/src/tripo3d/errors.rs index 31159d583..dde87177e 100644 --- a/server-rs/crates/api-server/src/tripo3d/errors.rs +++ b/server-rs/crates/api-server/src/tripo3d/errors.rs @@ -84,21 +84,32 @@ pub(crate) fn map_provider_error(error: TripoError) -> AppError { "tripo-api-rejected", error.to_string(), ), - TripoError::TaskFailure { .. } => ( - StatusCode::BAD_GATEWAY, - "tripo-task-failed", - error.to_string(), - ), - TripoError::OutputSchema { .. } => ( - StatusCode::BAD_GATEWAY, - "tripo-output-schema", - error.to_string(), - ), - TripoError::Request { .. } | TripoError::Sdk { .. } | TripoError::Api { .. } => ( - StatusCode::BAD_GATEWAY, - "tripo-upstream-error", - error.to_string(), - ), + // 以下都是 5xx:完整错误只进日志。`TripoError` 的 Display 会带上 provider task id, + // 直接回给客户端会破坏本文件「不暴露 task id」的口径,因此对外只给固定文案。 + TripoError::TaskFailure { .. } => { + tracing::error!(error = %error, "tripo task failed"); + ( + StatusCode::BAD_GATEWAY, + "tripo-task-failed", + "3D 生成任务在 provider 侧执行失败。".to_string(), + ) + } + TripoError::OutputSchema { .. } => { + tracing::error!(error = %error, "tripo task output schema error"); + ( + StatusCode::BAD_GATEWAY, + "tripo-output-schema", + "3D 生成任务的结果格式不符合预期,未落库。".to_string(), + ) + } + TripoError::Request { .. } | TripoError::Sdk { .. } | TripoError::Api { .. } => { + tracing::error!(error = %error, "tripo upstream error"); + ( + StatusCode::BAD_GATEWAY, + "tripo-upstream-error", + "3D 生成服务暂时不可用,请稍后重试。".to_string(), + ) + } }; AppError::from_status(status).with_details(json!({ "provider": TRIPO_PROVIDER, @@ -166,4 +177,29 @@ mod tests { }); assert_eq!(error.status_code(), StatusCode::BAD_REQUEST); } + + /// 本模块头部承诺「任何分支都不把 provider task id 带进响应」,502 分支必须守住。 + #[test] + fn provider_5xx_does_not_leak_task_id() { + let cases = [ + TripoError::TaskFailure { + task_id: "task-secret-123".to_string(), + status: "failed".to_string(), + message: Some("provider exploded".to_string()), + }, + TripoError::OutputSchema { + task_id: "task-secret-456".to_string(), + message: "output schema error".to_string(), + }, + ]; + for case in cases { + let error = map_provider_error(case); + assert_eq!(error.status_code(), StatusCode::BAD_GATEWAY); + let serialized = serde_json::to_string(error.details().expect("details 必须存在")) + .expect("details 必须可序列化"); + for leaked in ["task-secret", "provider exploded", "output schema error"] { + assert!(!serialized.contains(leaked), "{serialized}"); + } + } + } }