fix: 修复S3地址转换和穿搭图片生成任务状态同步

- 统一S3到CDN的URL转换逻辑,使用cdn.roasmax.cn域名
- 添加穿搭图片生成任务的完整状态同步:
  * 开始处理时更新为Processing状态
  * 失败时更新为Failed状态并记录错误信息
  * 成功时更新为Completed状态并保存生成的图片URL
- 确保生成的S3 URL正确转换为可访问的CDN地址
- 改进错误处理,所有失败情况都会正确更新数据库状态

这些修改解决了穿搭图片生成记录状态不同步的问题,确保前端
能够正确显示任务进度和结果。
This commit is contained in:
imeepos 2025-07-30 22:26:38 +08:00
parent 8499fa1927
commit cf672e474b
1 changed files with 61 additions and 13 deletions

View File

@ -243,6 +243,10 @@ pub async fn execute_outfit_image_generation(
// 首先创建生成记录 // 首先创建生成记录
let record_id = create_outfit_image_record(state.clone(), request.clone()).await?; let record_id = create_outfit_image_record(state.clone(), request.clone()).await?;
// 获取数据库和仓库
let database = state.get_database();
let outfit_repo = OutfitImageRepository::new(database.clone());
// 初始化错误处理服务 // 初始化错误处理服务
let error_service = ErrorHandlingService::new(); let error_service = ErrorHandlingService::new();
@ -263,6 +267,13 @@ pub async fn execute_outfit_image_generation(
if !config.is_comfyui_enabled() { if !config.is_comfyui_enabled() {
let simple_error = SimpleError("ComfyUI 功能未启用".to_string()); let simple_error = SimpleError("ComfyUI 功能未启用".to_string());
let user_error = error_service.handle_error(&simple_error, None); let user_error = error_service.handle_error(&simple_error, None);
// 更新记录状态为失败
if let Ok(Some(mut record)) = outfit_repo.get_record_by_id(&record_id) {
record.fail(user_error.message.clone());
let _ = outfit_repo.update_record(&record);
}
return Ok(OutfitImageGenerationResponse { return Ok(OutfitImageGenerationResponse {
record_id, record_id,
generated_images: Vec::new(), generated_images: Vec::new(),
@ -282,6 +293,13 @@ pub async fn execute_outfit_image_generation(
if !comfyui_service.check_connection().await.unwrap_or(false) { if !comfyui_service.check_connection().await.unwrap_or(false) {
let simple_error = SimpleError("ComfyUI 服务器连接失败".to_string()); let simple_error = SimpleError("ComfyUI 服务器连接失败".to_string());
let user_error = error_service.handle_error(&simple_error, None); let user_error = error_service.handle_error(&simple_error, None);
// 更新记录状态为失败
if let Ok(Some(mut record)) = outfit_repo.get_record_by_id(&record_id) {
record.fail(user_error.message.clone());
let _ = outfit_repo.update_record(&record);
}
return Ok(OutfitImageGenerationResponse { return Ok(OutfitImageGenerationResponse {
record_id, record_id,
generated_images: Vec::new(), generated_images: Vec::new(),
@ -292,7 +310,6 @@ pub async fn execute_outfit_image_generation(
} }
// 获取模特图片URL使用model_image_id // 获取模特图片URL使用model_image_id
let database = state.get_database();
let model_repo = ModelRepository::new(database.clone()); let model_repo = ModelRepository::new(database.clone());
// 首先获取模特的所有照片,然后找到指定的照片 // 首先获取模特的所有照片,然后找到指定的照片
@ -350,6 +367,13 @@ pub async fn execute_outfit_image_generation(
} }
}; };
// 更新记录状态为处理中
if let Ok(Some(mut record)) = outfit_repo.get_record_by_id(&record_id) {
record.start_processing();
let _ = outfit_repo.update_record(&record);
info!("📝 更新穿搭图片生成记录状态为处理中");
}
// 处理多个商品图片,为每个商品图片创建生成任务 // 处理多个商品图片,为每个商品图片创建生成任务
let mut all_generated_images = Vec::new(); let mut all_generated_images = Vec::new();
let mut has_errors = false; let mut has_errors = false;
@ -455,7 +479,7 @@ pub async fn execute_outfit_image_generation(
let end_time = Utc::now(); let end_time = Utc::now();
let total_duration = (end_time - start_time).num_milliseconds() as u64; let total_duration = (end_time - start_time).num_milliseconds() as u64;
// 构建响应 // 构建响应并更新数据库状态
let response = if all_generated_images.is_empty() { let response = if all_generated_images.is_empty() {
let error_message = if error_messages.is_empty() { let error_message = if error_messages.is_empty() {
"未生成任何图片".to_string() "未生成任何图片".to_string()
@ -466,6 +490,13 @@ pub async fn execute_outfit_image_generation(
let simple_error = SimpleError(error_message.clone()); let simple_error = SimpleError(error_message.clone());
let user_error = error_service.handle_error(&simple_error, None); let user_error = error_service.handle_error(&simple_error, None);
// 更新记录状态为失败
if let Ok(Some(mut record)) = outfit_repo.get_record_by_id(&record_id) {
record.fail(user_error.message.clone());
let _ = outfit_repo.update_record(&record);
info!("📝 更新穿搭图片生成记录状态为失败");
}
OutfitImageGenerationResponse { OutfitImageGenerationResponse {
record_id, record_id,
generated_images: Vec::new(), generated_images: Vec::new(),
@ -482,9 +513,21 @@ pub async fn execute_outfit_image_generation(
info!("{}", success_message); info!("{}", success_message);
// 转换S3 URL为CDN URL
let cdn_urls: Vec<String> = all_generated_images.iter()
.map(|url| convert_s3_to_cdn_url(url))
.collect();
// 更新记录状态为完成
if let Ok(Some(mut record)) = outfit_repo.get_record_by_id(&record_id) {
record.complete(cdn_urls.clone());
let _ = outfit_repo.update_record(&record);
info!("📝 更新穿搭图片生成记录状态为完成,保存 {} 张图片", cdn_urls.len());
}
OutfitImageGenerationResponse { OutfitImageGenerationResponse {
record_id, record_id,
generated_images: all_generated_images, generated_images: cdn_urls,
generation_time_ms: total_duration, generation_time_ms: total_duration,
success: true, success: true,
error_message: if has_errors { Some(error_messages.join("; ")) } else { None }, error_message: if has_errors { Some(error_messages.join("; ")) } else { None },
@ -539,17 +582,22 @@ async fn upload_image_to_cloud(file_path: &str, cloud_service: &CloudUploadServi
/// 将S3 URL转换为CDN URL /// 将S3 URL转换为CDN URL
fn convert_s3_to_cdn_url(s3_url: &str) -> String { fn convert_s3_to_cdn_url(s3_url: &str) -> String {
// 如果已经是CDN URL直接返回 if s3_url.starts_with("s3://ap-northeast-2/modal-media-cache/") {
if s3_url.contains("bowongai-dev.modal.run") { // 将 s3://ap-northeast-2/modal-media-cache/ 替换为 https://cdn.roasmax.cn/
return s3_url.to_string(); s3_url.replace("s3://ap-northeast-2/modal-media-cache/", "https://cdn.roasmax.cn/")
} } else if s3_url.starts_with("s3://") {
// 处理其他 s3:// 格式转换为通用CDN格式
// 提取S3 key部分 s3_url.replace("s3://", "https://cdn.roasmax.cn/")
if let Some(key_start) = s3_url.find(".com/") { } else if s3_url.contains("amazonaws.com") {
let key = &s3_url[key_start + 5..]; // 如果是完整的S3 HTTPS URL提取key部分
format!("https://bowongai-dev.modal.run/cache/s3/{}", key) if let Some(key_start) = s3_url.find(".com/") {
let key = &s3_url[key_start + 5..];
format!("https://cdn.roasmax.cn/{}", key)
} else {
s3_url.to_string()
}
} else { } else {
// 如果无法解析返回原URL // 如果不是预期的S3格式返回原URL
s3_url.to_string() s3_url.to_string()
} }
} }