From 483d63caaad438e3a0947721b1c5f222b0b34a22 Mon Sep 17 00:00:00 2001 From: imeepos Date: Fri, 18 Jul 2025 13:16:45 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=B8=80=E9=94=AE?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E5=A4=B1=E8=B4=A5=E5=85=A5=E5=BA=93=E5=92=8C?= =?UTF-8?q?=E6=88=90=E5=8A=9F=E7=8E=87=E8=B6=85=E8=BF=87100%=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 修复内容 ### 1. 修复匹配失败时仍然入库的问题 - 在match_materials_with_used_segments方法中添加匹配成功判断 - 只有当所有需要匹配的片段都成功匹配时才保存到数据库 - 匹配失败时不记录资源使用,确保资源可以被后续匹配使用 - 修改match_materials_and_save方法,确保一致的失败处理逻辑 ### 2. 修复匹配失败时的资源释放 - 部分匹配失败时,已分配的资源不会被标记为已使用 - 在批量匹配中正确处理部分匹配失败的情况 - 失败的匹配不会影响全局资源使用状态 ### 3. 修复成功率计算超过100%的问题 - 统一所有地方的成功率计算逻辑,确保基于可匹配片段计算 - 在前端显示时添加Math.min限制,确保成功率不超过100% - 修复前端多个组件中成功率显示不一致的问题: * BatchMatchingSummaryCard.tsx * BatchMatchingResultDialog.tsx * TemplateMatchingResultCard.tsx * TemplateMatchingResultDetailModal.tsx * TemplateMatchingResultStatsPanel.tsx * materialMatchingService.ts ### 4. 改进批量匹配逻辑 - 区分完全匹配失败和部分匹配失败 - 部分匹配失败时提供详细的失败原因 - 保持匹配结果用于分析,但不保存到数据库 ## 技术细节 - 后端成功率统一为0-1的小数格式 - 前端显示时统一乘以100并限制最大值为100 - 确保匹配失败时的事务一致性 - 添加详细的日志输出便于调试 --- .../services/material_matching_service.rs | 146 ++++++++++++------ .../components/BatchMatchingResultDialog.tsx | 4 +- .../components/BatchMatchingSummaryCard.tsx | 2 +- .../components/TemplateMatchingResultCard.tsx | 4 +- .../TemplateMatchingResultDetailModal.tsx | 2 +- .../TemplateMatchingResultStatsPanel.tsx | 4 +- .../src/services/materialMatchingService.ts | 6 +- 7 files changed, 110 insertions(+), 58 deletions(-) diff --git a/apps/desktop/src-tauri/src/business/services/material_matching_service.rs b/apps/desktop/src-tauri/src/business/services/material_matching_service.rs index a570817..3141c94 100644 --- a/apps/desktop/src-tauri/src/business/services/material_matching_service.rs +++ b/apps/desktop/src-tauri/src/business/services/material_matching_service.rs @@ -294,22 +294,30 @@ impl MaterialMatchingService { let matching_duration_ms = start_time.elapsed().as_millis() as u64; - // 如果配置了结果保存服务,则自动保存结果 - if let Some(result_service) = &self.matching_result_service { - match result_service.save_matching_result( - &matching_result, - result_name, - description, - matching_duration_ms, - ).await { - Ok(saved_result) => { - return Ok((matching_result, Some(saved_result))); - } - Err(e) => { - // 保存失败时记录错误但不影响匹配结果的返回 - eprintln!("保存匹配结果失败: {}", e); + // 判断匹配是否成功:没有失败的片段 + let is_matching_successful = matching_result.failed_segments.is_empty(); + + // 只有匹配完全成功时才保存到数据库 + if is_matching_successful { + // 如果配置了结果保存服务,则自动保存结果 + if let Some(result_service) = &self.matching_result_service { + match result_service.save_matching_result( + &matching_result, + result_name, + description, + matching_duration_ms, + ).await { + Ok(saved_result) => { + return Ok((matching_result, Some(saved_result))); + } + Err(e) => { + // 保存失败时记录错误但不影响匹配结果的返回 + eprintln!("保存匹配结果失败: {}", e); + } } } + } else { + println!("⚠️ 匹配失败,不保存到数据库。失败片段数: {}", matching_result.failed_segments.len()); } Ok((matching_result, None)) @@ -734,26 +742,55 @@ impl MaterialMatchingService { match self.match_materials_with_used_segments(matching_request, result_name, &global_used_segment_ids).await { Ok((matching_result, saved_result, newly_used_segments)) => { - round_successful_matches += 1; - successful_matches += 1; + // 检查匹配是否完全成功(没有失败的片段) + let is_fully_successful = matching_result.failed_segments.is_empty(); - // 更新全局已使用片段列表 - global_used_segment_ids.extend(newly_used_segments); + if is_fully_successful { + round_successful_matches += 1; + successful_matches += 1; - matching_results.push(BatchMatchingItemResult { - binding_id: binding_detail.binding.id.clone(), - template_id: binding_detail.binding.template_id.clone(), - template_name: binding_detail.template_name.clone(), - binding_name: binding_detail.binding.binding_name.clone(), - status: BatchMatchingItemStatus::Success, - matching_result: Some(matching_result), - saved_result_id: saved_result.map(|r| r.id), - error_message: None, - duration_ms: binding_start_time.elapsed().as_millis() as u64, - round_number: total_rounds, - attempts_count: 1, - failure_reason: None, - }); + // 更新全局已使用片段列表 + global_used_segment_ids.extend(newly_used_segments); + + matching_results.push(BatchMatchingItemResult { + binding_id: binding_detail.binding.id.clone(), + template_id: binding_detail.binding.template_id.clone(), + template_name: binding_detail.template_name.clone(), + binding_name: binding_detail.binding.binding_name.clone(), + status: BatchMatchingItemStatus::Success, + matching_result: Some(matching_result), + saved_result_id: saved_result.map(|r| r.id), + error_message: None, + duration_ms: binding_start_time.elapsed().as_millis() as u64, + round_number: total_rounds, + attempts_count: 1, + failure_reason: None, + }); + } else { + // 部分匹配失败,视为失败 + round_failed_matches += 1; + failed_matches += 1; + + let failure_reason = format!("部分匹配失败:{} 个片段匹配成功,{} 个片段匹配失败", + matching_result.matches.len(), + matching_result.failed_segments.len() + ); + + matching_results.push(BatchMatchingItemResult { + binding_id: binding_detail.binding.id.clone(), + template_id: binding_detail.binding.template_id.clone(), + template_name: binding_detail.template_name.clone(), + binding_name: binding_detail.binding.binding_name.clone(), + status: BatchMatchingItemStatus::Failed, + matching_result: Some(matching_result), // 仍然返回结果用于分析 + saved_result_id: None, // 但不保存到数据库 + error_message: Some(failure_reason.clone()), + duration_ms: binding_start_time.elapsed().as_millis() as u64, + round_number: total_rounds, + attempts_count: 1, + failure_reason: Some(failure_reason), + }); + } } Err(error) => { round_failed_matches += 1; @@ -990,12 +1027,18 @@ impl MaterialMatchingService { let matched_segments = matches.len() as u32; let failed_segments_count = failed_segments.len() as u32; let fixed_segments_count = fixed_segments.len() as u32; - let success_rate = if total_segments > 0 { - (matched_segments + fixed_segments_count) as f64 / total_segments as f64 + + // 修正成功率计算:只考虑需要匹配的片段 + let matchable_segments = total_segments - fixed_segments_count; + let success_rate = if matchable_segments > 0 { + matched_segments as f64 / matchable_segments as f64 } else { - 0.0 + 1.0 // 如果没有需要匹配的片段,成功率为100% }; + // 判断匹配是否成功:所有需要匹配的片段都成功匹配 + let is_matching_successful = failed_segments_count == 0; + // 创建匹配结果 let matching_result = MaterialMatchingResult { binding_id: request.binding_id.clone(), @@ -1003,7 +1046,7 @@ impl MaterialMatchingService { project_id: request.project_id.clone(), matches, statistics: MatchingStatistics { - total_segments, + total_segments: matchable_segments, // 只统计需要匹配的片段 matched_segments, failed_segments: failed_segments_count, success_rate, @@ -1013,21 +1056,30 @@ impl MaterialMatchingService { failed_segments, }; - // 保存匹配结果到数据库 - let saved_result = if let Some(result_service) = &self.matching_result_service { - let saved = result_service.save_matching_result( - &matching_result, - result_name, - None, - 0, // 匹配耗时,这里简化为0 - ).await?; + // 只有匹配完全成功时才保存到数据库和记录资源使用 + let (saved_result, final_used_segments) = if is_matching_successful { + // 保存匹配结果到数据库 + let saved_result = if let Some(result_service) = &self.matching_result_service { + let saved = result_service.save_matching_result( + &matching_result, + result_name, + None, + 0, // 匹配耗时,这里简化为0 + ).await?; - Some(saved) + Some(saved) + } else { + None + }; + + (saved_result, local_used_segment_ids) } else { - None + // 匹配失败时不保存到数据库,也不记录资源使用 + println!("⚠️ 匹配失败,不保存到数据库。失败片段数: {}, 成功片段数: {}", failed_segments_count, matched_segments); + (None, HashSet::new()) }; - Ok((matching_result, saved_result, local_used_segment_ids)) + Ok((matching_result, saved_result, final_used_segments)) } /// 计算批量匹配汇总信息 diff --git a/apps/desktop/src/components/BatchMatchingResultDialog.tsx b/apps/desktop/src/components/BatchMatchingResultDialog.tsx index fb6f893..ee6e888 100644 --- a/apps/desktop/src/components/BatchMatchingResultDialog.tsx +++ b/apps/desktop/src/components/BatchMatchingResultDialog.tsx @@ -125,7 +125,7 @@ export const BatchMatchingResultDialog: React.FC lines.push(` 失败原因: ${item.failure_reason}`); } if (item.matching_result) { - lines.push(` 成功率: ${(item.matching_result.statistics?.success_rate * 100 || 0).toFixed(1)}%`); + lines.push(` 成功率: ${Math.min((item.matching_result.statistics?.success_rate * 100 || 0), 100).toFixed(1)}%`); } lines.push(''); }); @@ -335,7 +335,7 @@ export const BatchMatchingResultDialog: React.FC

耗时: {formatDuration(item.duration_ms)}

{item.matching_result && (

- 成功率: {(item.matching_result.statistics?.success_rate * 100 || 0).toFixed(1)}% + 成功率: {Math.min((item.matching_result.statistics?.success_rate * 100 || 0), 100).toFixed(1)}%

)} diff --git a/apps/desktop/src/components/BatchMatchingSummaryCard.tsx b/apps/desktop/src/components/BatchMatchingSummaryCard.tsx index a10f0d3..4a68db7 100644 --- a/apps/desktop/src/components/BatchMatchingSummaryCard.tsx +++ b/apps/desktop/src/components/BatchMatchingSummaryCard.tsx @@ -145,7 +145,7 @@ export const BatchMatchingSummaryCard: React.FC =
平均成功率: - {(result.summary.average_success_rate * 100).toFixed(1)}% + {Math.min(result.summary.average_success_rate * 100, 100).toFixed(1)}%
{result.summary.best_matching_template && ( diff --git a/apps/desktop/src/components/TemplateMatchingResultCard.tsx b/apps/desktop/src/components/TemplateMatchingResultCard.tsx index 09c5c0f..d7a6aa7 100644 --- a/apps/desktop/src/components/TemplateMatchingResultCard.tsx +++ b/apps/desktop/src/components/TemplateMatchingResultCard.tsx @@ -138,8 +138,8 @@ export const TemplateMatchingResultCard: React.FC {/* 成功率 */}
-
- {result.success_rate.toFixed(1)}% +
+ {Math.min(result.success_rate * 100, 100).toFixed(1)}%
成功率
diff --git a/apps/desktop/src/components/TemplateMatchingResultDetailModal.tsx b/apps/desktop/src/components/TemplateMatchingResultDetailModal.tsx index 416ee47..fabef38 100644 --- a/apps/desktop/src/components/TemplateMatchingResultDetailModal.tsx +++ b/apps/desktop/src/components/TemplateMatchingResultDetailModal.tsx @@ -291,7 +291,7 @@ export const TemplateMatchingResultDetailModal: React.FC
- {matching_result.success_rate.toFixed(1)}% + {Math.min(matching_result.success_rate * 100, 100).toFixed(1)}%
成功率
diff --git a/apps/desktop/src/components/TemplateMatchingResultStatsPanel.tsx b/apps/desktop/src/components/TemplateMatchingResultStatsPanel.tsx index fa31847..dbb5271 100644 --- a/apps/desktop/src/components/TemplateMatchingResultStatsPanel.tsx +++ b/apps/desktop/src/components/TemplateMatchingResultStatsPanel.tsx @@ -167,8 +167,8 @@ export const TemplateMatchingResultStatsPanel: React.FC
平均成功率 - - {statistics.average_success_rate.toFixed(1)}% + + {Math.min(statistics.average_success_rate * 100, 100).toFixed(1)}%
diff --git a/apps/desktop/src/services/materialMatchingService.ts b/apps/desktop/src/services/materialMatchingService.ts index 5769ee7..9b39342 100644 --- a/apps/desktop/src/services/materialMatchingService.ts +++ b/apps/desktop/src/services/materialMatchingService.ts @@ -153,14 +153,14 @@ export class MaterialMatchingService { } { const { statistics } = result; - const summary = `匹配完成:${statistics.matched_segments}/${statistics.total_segments} 个片段 (${(statistics.success_rate * 100).toFixed(1)}%)`; - + const summary = `匹配完成:${statistics.matched_segments}/${statistics.total_segments} 个片段 (${Math.min(statistics.success_rate * 100, 100).toFixed(1)}%)`; + const details = [ `成功匹配:${statistics.matched_segments} 个片段`, `匹配失败:${statistics.failed_segments} 个片段`, `使用素材:${statistics.used_materials} 个`, `涉及模特:${statistics.used_models} 个`, - `成功率:${(statistics.success_rate * 100).toFixed(1)}%` + `成功率:${Math.min(statistics.success_rate * 100, 100).toFixed(1)}%` ]; return { summary, details };