From 7a9ac750ae655339dadfa46049aafe008ad46054 Mon Sep 17 00:00:00 2001 From: imeepos Date: Mon, 14 Jul 2025 15:52:28 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E9=98=9F=E5=88=97=E6=8E=A7=E5=88=B6=E6=8C=89=E9=92=AE=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题修复: - 修复暂停/恢复/停止按钮无法正常工作的问题 - 添加详细的调试日志来诊断按钮操作 - 优化按钮的启用/禁用逻辑和视觉反馈 技术改进: - 在队列操作后添加延迟确保后端状态更新 - 修复refreshQueueStatus方法的返回值类型 - 改进按钮状态判断逻辑,支持更多队列状态 用户体验提升: - 添加状态指示器显示当前队列状态 - 改进按钮的禁用状态样式 - 提供更清晰的按钮提示文本 状态管理优化: - 确保队列控制操作后正确刷新状态 - 添加操作中的加载状态指示 - 改进错误处理和日志输出 任务恢复机制: - 添加recover_stuck_tasks功能恢复卡住的任务 - 在队列启动时自动恢复处理中状态的任务 - 解决应用意外关闭导致任务永久卡住的问题 --- .../services/video_classification_queue.rs | 12 +++ .../services/video_classification_service.rs | 5 ++ .../video_classification_repository.rs | 75 +++++++++++++++++-- apps/desktop/src-tauri/src/lib.rs | 1 + .../commands/video_classification_commands.rs | 22 ++++++ apps/desktop/src/components/Navigation.tsx | 2 +- .../VideoClassificationProgress.tsx | 49 +++++++++--- .../src/store/videoClassificationStore.ts | 42 +++++++++-- 8 files changed, 184 insertions(+), 24 deletions(-) diff --git a/apps/desktop/src-tauri/src/business/services/video_classification_queue.rs b/apps/desktop/src-tauri/src/business/services/video_classification_queue.rs index 9b5785d..aca5034 100644 --- a/apps/desktop/src-tauri/src/business/services/video_classification_queue.rs +++ b/apps/desktop/src-tauri/src/business/services/video_classification_queue.rs @@ -85,6 +85,18 @@ impl VideoClassificationQueue { *status = QueueStatus::Running; drop(status); + // 恢复卡住的任务状态 + match self.service.recover_stuck_tasks().await { + Ok(recovered_count) => { + if recovered_count > 0 { + println!("🔄 队列启动时恢复了 {} 个卡住的任务", recovered_count); + } + } + Err(e) => { + println!("⚠️ 恢复卡住任务时出错: {}", e); + } + } + // 更新统计信息 self.update_stats().await?; diff --git a/apps/desktop/src-tauri/src/business/services/video_classification_service.rs b/apps/desktop/src-tauri/src/business/services/video_classification_service.rs index 37fb339..06f669e 100644 --- a/apps/desktop/src-tauri/src/business/services/video_classification_service.rs +++ b/apps/desktop/src-tauri/src/business/services/video_classification_service.rs @@ -305,6 +305,11 @@ impl VideoClassificationService { self.video_repo.delete_task(task_id).await } + /// 恢复卡住的任务状态 + pub async fn recover_stuck_tasks(&self) -> Result { + self.video_repo.recover_stuck_tasks().await + } + /// 重试失败的任务 pub async fn retry_failed_task(&self, _task_id: &str) -> Result<()> { // 这里需要实现重试逻辑 diff --git a/apps/desktop/src-tauri/src/data/repositories/video_classification_repository.rs b/apps/desktop/src-tauri/src/data/repositories/video_classification_repository.rs index ae1c2b9..407d6ab 100644 --- a/apps/desktop/src-tauri/src/data/repositories/video_classification_repository.rs +++ b/apps/desktop/src-tauri/src/data/repositories/video_classification_repository.rs @@ -472,6 +472,25 @@ impl VideoClassificationRepository { let completed_json = serde_json::to_string(&TaskStatus::Completed)?; let failed_json = serde_json::to_string(&TaskStatus::Failed)?; + // 调试:查看数据库中实际的任务状态分布 + let debug_query = if let Some(project_id) = project_id { + format!("SELECT status, COUNT(*) FROM video_classification_tasks WHERE project_id = '{}' GROUP BY status", project_id) + } else { + "SELECT status, COUNT(*) FROM video_classification_tasks GROUP BY status".to_string() + }; + + let mut debug_stmt = conn.prepare(&debug_query)?; + let debug_rows = debug_stmt.query_map([], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1)?)) + })?; + + println!("🔍 数据库中实际的任务状态分布:"); + for row in debug_rows { + if let Ok((status, count)) = row { + println!(" 状态: {} -> 数量: {}", status, count); + } + } + // 获取任务统计 let mut stmt = conn.prepare(&format!( "SELECT @@ -484,13 +503,12 @@ impl VideoClassificationRepository { ))?; let task_stats = stmt.query_row([&pending_json, &uploading_json, &analyzing_json, &completed_json, &failed_json], |row| { - Ok(( - row.get::<_, i32>(0)?, - row.get::<_, i32>(1)?, - row.get::<_, i32>(2)?, - row.get::<_, i32>(3)?, - row.get::<_, i32>(4)?, - )) + let total: i32 = row.get(0)?; + let pending: i32 = row.get(1)?; + let processing: i32 = row.get(2)?; + let completed: i32 = row.get(3)?; + let failed: i32 = row.get(4)?; + Ok((total, pending, processing, completed, failed)) })?; // 获取分类记录统计 @@ -544,6 +562,49 @@ impl VideoClassificationRepository { Ok(()) } + /// 恢复卡住的任务状态 + /// 将所有处理中的任务(Uploading, Analyzing)重置为Pending状态 + pub async fn recover_stuck_tasks(&self) -> Result { + let conn = self.database.get_connection(); + let conn = conn.lock().unwrap(); + + let uploading_json = serde_json::to_string(&TaskStatus::Uploading)?; + let analyzing_json = serde_json::to_string(&TaskStatus::Analyzing)?; + let pending_json = serde_json::to_string(&TaskStatus::Pending)?; + + println!("🔄 开始恢复卡住的任务状态..."); + + // 查询卡住的任务 + let mut stmt = conn.prepare( + "SELECT id, status FROM video_classification_tasks WHERE status = ? OR status = ?" + )?; + + let stuck_tasks: Vec<(String, String)> = stmt.query_map([&uploading_json, &analyzing_json], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)) + })?.collect::, _>>()?; + + if stuck_tasks.is_empty() { + println!("✅ 没有发现卡住的任务"); + return Ok(0); + } + + println!("🔍 发现 {} 个卡住的任务:", stuck_tasks.len()); + for (id, status) in &stuck_tasks { + println!(" 任务ID: {}, 状态: {}", &id[..8], status); + } + + // 重置任务状态 + let updated = conn.execute( + "UPDATE video_classification_tasks + SET status = ?, started_at = NULL, updated_at = datetime('now') + WHERE status = ? OR status = ?", + [&pending_json, &uploading_json, &analyzing_json] + )?; + + println!("✅ 已恢复 {} 个任务状态为Pending", updated); + Ok(updated) + } + /// 根据ID获取分类任务 pub async fn get_task_by_id(&self, task_id: &str) -> Result> { let conn = self.database.get_connection(); diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 4878d02..7c84db3 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -112,6 +112,7 @@ pub fn run() { commands::video_classification_commands::get_all_classification_task_progress, commands::video_classification_commands::get_project_classification_task_progress, commands::video_classification_commands::stop_classification_queue, + commands::video_classification_commands::recover_stuck_classification_tasks, commands::video_classification_commands::pause_classification_queue, commands::video_classification_commands::resume_classification_queue, commands::video_classification_commands::get_material_classification_records, diff --git a/apps/desktop/src-tauri/src/presentation/commands/video_classification_commands.rs b/apps/desktop/src-tauri/src/presentation/commands/video_classification_commands.rs index bfbcb05..23453af 100644 --- a/apps/desktop/src-tauri/src/presentation/commands/video_classification_commands.rs +++ b/apps/desktop/src-tauri/src/presentation/commands/video_classification_commands.rs @@ -118,6 +118,28 @@ pub async fn stop_classification_queue( queue.stop().await.map_err(|e| e.to_string()) } +/// 恢复卡住的任务状态 +#[command] +pub async fn recover_stuck_classification_tasks( + state: State<'_, AppState>, +) -> Result { + let database = state.get_database(); + let video_repo = Arc::new(VideoClassificationRepository::new(database.clone())); + let ai_classification_repo = Arc::new(AiClassificationRepository::new(database.clone())); + let material_repo = Arc::new(MaterialRepository::new(database.get_connection()).unwrap()); + + let service = VideoClassificationService::new( + video_repo, + ai_classification_repo, + material_repo, + Some(GeminiConfig::default()), + ); + + service.recover_stuck_tasks() + .await + .map_err(|e| e.to_string()) +} + /// 暂停分类队列 #[command] pub async fn pause_classification_queue( diff --git a/apps/desktop/src/components/Navigation.tsx b/apps/desktop/src/components/Navigation.tsx index c1ea7aa..8a360c0 100644 --- a/apps/desktop/src/components/Navigation.tsx +++ b/apps/desktop/src/components/Navigation.tsx @@ -60,7 +60,7 @@ const Navigation: React.FC = () => { return (