From 694a3c6f6cdb7e929c1ba850b1f4fa6c25c2aca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Mon, 21 Sep 2026 15:14:00 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=A7=E7=89=A9=E4=B8=8B=E8=BD=BD=E5=89=8D?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E4=BB=BB=E5=8A=A1=E7=A1=AE=E5=AE=9E=E5=A4=84?= =?UTF-8?q?=E4=BA=8E=E5=AE=8C=E6=88=90=E6=80=81=20-=20download=5Fmodel=20?= =?UTF-8?q?=E4=B8=8E=20download=5Frendered=5Fimage=20=E5=8E=9F=E5=85=88?= =?UTF-8?q?=E5=8F=AA=E7=9C=8B=20output=20=E6=98=AF=E5=90=A6=E5=AD=98?= =?UTF-8?q?=E5=9C=A8=EF=BC=8C=E5=BF=AB=E7=85=A7=E5=AD=97=E6=AE=B5=E5=85=A8?= =?UTF-8?q?=E5=85=AC=E5=BC=80=E3=80=81status=20=E4=B8=8E=20output=20?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E8=A2=AB=E4=BA=BA=E4=B8=BA=E6=9E=84=E9=80=A0?= =?UTF-8?q?=E5=BE=97=E4=B8=8D=E4=B8=80=E8=87=B4=20-=20=E4=B8=A4=E4=B8=AA?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=E7=BB=9F=E4=B8=80=E8=B5=B0=20completed=5Fout?= =?UTF-8?q?put=EF=BC=9A=E5=85=88=E6=A0=A1=E9=AA=8C=20task=5Fid=EF=BC=8C?= =?UTF-8?q?=E5=86=8D=E8=A6=81=E6=B1=82=20status=3DCompleted=20=E4=B8=94?= =?UTF-8?q?=E6=9C=89=E8=BE=93=E5=87=BA=EF=BC=8C=E5=90=A6=E5=88=99=E6=8C=89?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E7=BB=93=E6=9E=84=E9=94=99=E8=AF=AF=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../platform-tripo/src/common/client.rs | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/server-rs/crates/platform-tripo/src/common/client.rs b/server-rs/crates/platform-tripo/src/common/client.rs index d74e331b5..b0b8e558d 100644 --- a/server-rs/crates/platform-tripo/src/common/client.rs +++ b/server-rs/crates/platform-tripo/src/common/client.rs @@ -1,10 +1,11 @@ use std::time::Duration; +use shared_contracts::model3d::common::Model3dTaskStatus; use tripo3d_sdk::TripoClient; use super::{ - TripoDownloadedArtifact, TripoError, TripoSettings, TripoTaskHandle, TripoTaskSnapshot, - TripoUrl, map_task, validate_task_id, + TripoDownloadedArtifact, TripoError, TripoSettings, TripoTaskHandle, TripoTaskOutput, + TripoTaskSnapshot, TripoUrl, map_task, validate_task_id, }; pub struct TripoProviderClient { @@ -47,18 +48,10 @@ impl TripoProviderClient { &self, task: &TripoTaskSnapshot, ) -> Result { - let handle = &task.handle; - validate_task_id(&handle.task_id)?; - let output = task - .output - .as_ref() - .ok_or_else(|| TripoError::OutputSchema { - task_id: handle.task_id.clone(), - message: "task has no completed model output".into(), - })?; + let output = completed_output(task)?; // TODO SDK upstream: expose a streaming artifact API; then replace this // provider-side reqwest client with the SDK stream and remove the duplicate downloader. - self.download_artifact(&handle.task_id, output.model_url()) + self.download_artifact(&task.handle.task_id, output.model_url()) .await } @@ -70,16 +63,8 @@ impl TripoProviderClient { &self, task: &TripoTaskSnapshot, ) -> Result { - let handle = &task.handle; - validate_task_id(&handle.task_id)?; - let output = task - .output - .as_ref() - .ok_or_else(|| TripoError::OutputSchema { - task_id: handle.task_id.clone(), - message: "task has no completed model output".into(), - })?; - self.download_artifact(&handle.task_id, output.rendered_image_url()) + let output = completed_output(task)?; + self.download_artifact(&task.handle.task_id, output.rendered_image_url()) .await } @@ -149,6 +134,27 @@ fn download_backoff(attempt: u32) -> Duration { Duration::from_millis(250u64.saturating_mul(2u64.saturating_pow(attempt.min(6)))) } +/// 取完成态产物:下载入口只接受已完成且带输出的 task 快照。 +/// +/// `TripoTaskSnapshot` 字段全部公开,「完成态才有 output」只是 `map_task` 的约定, +/// 不是类型保证;调用方可以构造出 status 与 output 不一致的快照,所以这里重新校验, +/// 避免把失败 / 取消的任务当成成功产物下载。 +fn completed_output(task: &TripoTaskSnapshot) -> Result<&TripoTaskOutput, TripoError> { + validate_task_id(&task.handle.task_id)?; + if task.status != Model3dTaskStatus::Completed { + return Err(TripoError::OutputSchema { + task_id: task.handle.task_id.clone(), + message: format!("task is not completed (status {:?})", task.status), + }); + } + task.output + .as_ref() + .ok_or_else(|| TripoError::OutputSchema { + task_id: task.handle.task_id.clone(), + message: "task has no completed output".into(), + }) +} + fn transport_error_kind(error: &reqwest::Error) -> &'static str { if error.is_timeout() { "timeout"