Files
Genarrative/server-rs/crates/api-server/src/tripo3d/artifacts.rs
T
k88936 b33a8a141c 3D 产物下载的重试改为覆盖整个响应体
- download_model / download_rendered_image 把「取响应头 + 读完 body」作为一次可重试事务,
  每次尝试都重新取响应头并整体重下,不做断点续传
- body 传输失败与长度不一致归一成可重试的传输错误(不再带 HTTP 200),HTTP 状态只留在文案里
- 新增 TripoArtifactBytes(读完的产物)并公开 download_model / download_rendered_image 的 max_bytes 上限;
  超限按 OutputSchema 失败且不重试,流式句柄 TripoDownloadedArtifact 收回 crate 内部
- api-server 的 read_artifact 改成对完整产物做落库前校验(只判空),体积上限转由调用方传入
- smoke 示例改为一次写入完整字节;新增「body 中途断开后整体重下成功」与「超限不重试」两个用例
- 同步技术方案与实施计划:把「只重试响应头」的描述改成「重试包住整个 body」
2026-09-23 17:01:40 +08:00

42 lines
1.6 KiB
Rust

//! provider 产物的读取。
//!
//! 下载与「body 中途失败整体重下」都在 `platform-tripo` 的下载入口里完成(重试上限
//! 取自 provider 客户端配置),这里只做 api-server 自己的口径:产物不能为空。
//! TODO(stream): provider SDK 与 `platform-oss` 都支持流式 / 分片后,这里应当直接
//! 把流转交 OSS,不再把几十 MB 的模型完整读进 api-server 内存。
use axum::http::StatusCode;
use platform_tripo::TripoArtifactBytes;
use serde_json::json;
use crate::http_error::AppError;
use super::provider::TRIPO_PROVIDER;
/// 单次下载允许的单个产物上限。超过它说明响应异常或产物口径变了,
/// 宁可失败退款也不要把 worker 的内存打满。
pub(crate) const MODEL3D_MAX_ARTIFACT_BYTES: u64 = 512 * 1024 * 1024;
pub(crate) struct DownloadedArtifact {
pub(crate) bytes: Vec<u8>,
pub(crate) content_type: String,
}
/// 完整读出的产物在落库前的最后一道检查:空产物按上游内容不合法失败,
/// 不写出一个 0 字节的对象再让下游去猜。
pub(crate) fn read_artifact(artifact: TripoArtifactBytes) -> Result<DownloadedArtifact, AppError> {
if artifact.bytes.is_empty() {
return Err(
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
"provider": TRIPO_PROVIDER,
"reason": "model3d-artifact-empty",
"message": "provider 返回的 3D 产物为空。",
})),
);
}
Ok(DownloadedArtifact {
bytes: artifact.bytes,
content_type: artifact.content_type.unwrap_or_default(),
})
}