From 6b1114356cdbf887abd670ac72b0275cf7ce60a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Wed, 23 Sep 2026 18:04:29 +0800 Subject: [PATCH] =?UTF-8?q?59/60/61/65=20=E6=8C=89=E5=86=B3=E5=AE=9A?= =?UTF-8?q?=E4=B8=8D=E4=BF=AE=E5=A4=8D=EF=BC=8C=E6=94=B9=E4=B8=BA=E5=9C=A8?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=87=8C=E5=86=99=E6=B8=85=E5=8F=96=E8=88=8D?= =?UTF-8?q?=20-=20worker.rs=EF=BC=9A=E8=AF=B4=E6=98=8E=20last=5Ferror=5Fme?= =?UTF-8?q?ssage=20=E6=98=AF=E8=90=BD=E5=BA=93=E4=B8=94=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E5=B1=95=E7=A4=BA=E7=9A=84=E6=91=98=E8=A6=81?= =?UTF-8?q?=EF=BC=8C=E5=8F=AA=E5=8F=96=20provider=20message=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E5=A1=9E=E5=86=85=E9=83=A8=20reason/taskStatus?= =?UTF-8?q?=EF=BC=88#59=EF=BC=89=20-=20platform-tripo=20error.rs=EF=BC=9A?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=20ADR=200001=20=E8=A6=81=E6=B1=82=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E5=B1=82=E4=B8=8D=E5=B8=A6=E5=87=BA=20SDK=20=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E6=A8=A1=E5=9E=8B=EF=BC=8CIo/Serde=20=E5=8F=AA?= =?UTF-8?q?=E4=BF=9D=E7=95=99=E6=96=87=E6=9C=AC=EF=BC=88#60=EF=BC=89=20-?= =?UTF-8?q?=20platform-tripo=20mapping.rs=EF=BC=9A=E8=AF=B4=E6=98=8E=20Tri?= =?UTF-8?q?po=20=E6=96=87=E6=A1=A3=E6=8A=8A=20generated=5Fimage=5Furl=20?= =?UTF-8?q?=E5=88=97=E4=B8=BA=20text=5Fto=5Fmodel=20=E5=9B=BA=E5=AE=9A?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=EF=BC=8C=E6=8C=89=20ADR=200001=20=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E5=85=B3=E9=97=AD=E4=BF=9D=E7=95=99=E5=BF=85=E5=A1=AB?= =?UTF-8?q?=EF=BC=88#61=EF=BC=89=20-=20platform-tripo=20image=5Fto=5Fmodel?= =?UTF-8?q?/client.rs=EF=BC=9A=E8=AF=B4=E6=98=8E=20PublicUrl=20=E5=8F=AA?= =?UTF-8?q?=E5=81=9A=E9=9D=9E=E7=A9=BA=E7=99=BD=E6=A0=A1=E9=AA=8C=E7=9A=84?= =?UTF-8?q?=E8=BE=B9=E7=95=8C=E6=9D=A1=E4=BB=B6=E4=B8=8E=E5=B0=86=E6=9D=A5?= =?UTF-8?q?=E6=8E=A5=E5=A4=96=E9=83=A8=E5=9C=B0=E5=9D=80=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E4=BD=8D=E7=BD=AE=EF=BC=88#65=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server-rs/crates/api-server/src/tripo3d/worker.rs | 5 +++++ server-rs/crates/platform-tripo/src/common/error.rs | 9 +++++++++ server-rs/crates/platform-tripo/src/common/mapping.rs | 4 ++++ .../crates/platform-tripo/src/image_to_model/client.rs | 6 ++++++ 4 files changed, 24 insertions(+) diff --git a/server-rs/crates/api-server/src/tripo3d/worker.rs b/server-rs/crates/api-server/src/tripo3d/worker.rs index 7a27d698a..aa445cce7 100644 --- a/server-rs/crates/api-server/src/tripo3d/worker.rs +++ b/server-rs/crates/api-server/src/tripo3d/worker.rs @@ -71,6 +71,11 @@ pub(crate) async fn process_model3d_job( run_model3d_job(state, caller, job, request, provider_deadline), ) .await + // 这里返回的字符串会成为 job 的 `last_error_message`,并原样回给前端(见 + // `editor_generation_queue_state` 的 `error` 字段),所以刻意只取 provider 错误体里 + // 人类可读的 message:用户可见文案不带内部 reason / taskStatus 枚举。 + // 评审 #59 提过把结构化 details 一并写进去,按决定不改 —— 那会改掉已落库与前端展示的 + // 文案口径;排障要定位 provider 侧任务时看 job 的 provider task id(checkpoint)。 .map_err(|error| error.body_text()) } diff --git a/server-rs/crates/platform-tripo/src/common/error.rs b/server-rs/crates/platform-tripo/src/common/error.rs index e3bf269af..74d967152 100644 --- a/server-rs/crates/platform-tripo/src/common/error.rs +++ b/server-rs/crates/platform-tripo/src/common/error.rs @@ -113,6 +113,13 @@ pub enum TripoError { field: &'static str, message: String, }, + /// SDK 自身报出的错误(本地 IO、反序列化等),只保留文本。 + /// + /// 按 ADR 0001「不把 `tripo3d-sdk` 的错误模型带出平台边界」,这里不保留 + /// `std::io::Error` / serde 错误的类型,也不实现 `Error::source()`:调用方拿到的是 + /// 平台层归一化后的错误,而不是第三方错误链。评审 #60 提过拆 `SdkIo` / `SdkSerde` + /// 变体或加 `source` 字段,按决定不改 —— 那是公共错误枚举的 breaking 变更, + /// 收益只到排障细分一层。 Sdk { message: String, }, @@ -425,6 +432,8 @@ impl From for TripoError { // 等待超时是瞬时的:重试一次可能就等到了。 retryable: true, }, + // Io 与 Serde 拍平成同一个文本变体:上游 SDK 对这两种错误只保证 Display 文本, + // 平台层也不再向上区分(理由见 `Sdk` 变体的注释,评审 #60)。 tripo3d_sdk::Error::Io(error) => Self::Sdk { message: error.to_string(), }, diff --git a/server-rs/crates/platform-tripo/src/common/mapping.rs b/server-rs/crates/platform-tripo/src/common/mapping.rs index 9c05599ef..2055cd4c2 100644 --- a/server-rs/crates/platform-tripo/src/common/mapping.rs +++ b/server-rs/crates/platform-tripo/src/common/mapping.rs @@ -98,6 +98,10 @@ fn map_output( TripoTaskType::TextToModel => Ok(TripoTaskOutput::TextToModel(TripoTextToModelResult { model_url, rendered_image_url, + // Tripo API 文档把 generated_image_url 列为 text_to_model 输出的固定字段, + // 真实 provider smoke 也确认它总是存在(见技术方案《Tripo 3D模型Provider集成》)。 + // 因此按 ADR 0001「结果缺必填字段即以 output schema error 失败关闭」保留必填: + // 真缺失说明上游偏离了文档,属于要暴露的异常,不做静默兜底(评审 #61,按决定不改)。 generated_image_url: required_url( task_id, "generated_image_url", diff --git a/server-rs/crates/platform-tripo/src/image_to_model/client.rs b/server-rs/crates/platform-tripo/src/image_to_model/client.rs index d2fcd1cc7..7311e03d2 100644 --- a/server-rs/crates/platform-tripo/src/image_to_model/client.rs +++ b/server-rs/crates/platform-tripo/src/image_to_model/client.rs @@ -12,6 +12,12 @@ use super::validation::validate_image_to_model_params; /// /// 平台层只接受站内引用,所以调用方只会产出这两种形态:先上传拿 `file_token`, /// 或给出公网可读地址。不提供裸字符串变体,避免 SDK 把字符串按前缀推断成 task_id。 +/// +/// `PublicUrl` 只做非空白校验、不做 scheme / host 白名单:Tripo API 文档允许 `url` 指向任意 +/// 公网可读地址,本地再收窄会误伤文档允许的形态。当前生产路径全部走 `upload_image` → +/// `FileToken`,`PublicUrl` 只由冒烟示例构造,没有不可信输入的现役入口;将来若把外部传入的 +/// 地址接到这条路径,应在构造侧按 `TripoUrl::parse` 的口径校验 scheme 并拒绝 userinfo / +/// 内网主机(评审 #65,按决定不在本层加校验)。 #[derive(Clone, Debug, Eq, PartialEq)] pub enum TripoImageInput { FileToken(String),