diff --git a/backend/services/download.js b/backend/services/download.js index 63d3627..872a748 100644 --- a/backend/services/download.js +++ b/backend/services/download.js @@ -309,6 +309,8 @@ class DownloadService { // 获取更新后的任务 const updatedTask = this.taskManager.getTask(taskId); + + // 立即通知状态更新 this.progressManager.notifyProgressUpdate(taskId, updatedTask); logger.info('任务暂停完成', { taskId }); @@ -336,13 +338,6 @@ class DownloadService { return { success: false, error: '任务不存在' }; } - // logger.info('尝试恢复任务', { - // taskId, - // currentStatus: task.status, - // type: task.type, - // artwork_id: task.artwork_id - // }); - // 只允许恢复暂停的任务 if (task.status !== 'paused') { logger.warn('恢复任务失败:任务状态不是暂停状态', { @@ -357,14 +352,19 @@ class DownloadService { logger.info('开始恢复任务执行', { taskId }); await this.downloadExecutor.resumeTask(taskId); - // 获取更新后的任务状态 + // 确保状态已经更新后再返回 const updatedTask = this.taskManager.getTask(taskId); + + // 立即通知状态更新 this.progressManager.notifyProgressUpdate(taskId, updatedTask); logger.info('任务恢复成功', { taskId, newStatus: updatedTask.status }); + + // 返回最新的任务状态 + return { success: true, data: updatedTask }; } catch (error) { logger.error('恢复任务执行失败', { taskId, @@ -374,8 +374,6 @@ class DownloadService { // 如果恢复失败,保持暂停状态 return { success: false, error: `恢复任务失败: ${error.message}` }; } - - return { success: true, data: this.taskManager.getTask(taskId) }; } /** diff --git a/backend/services/file-manager.js b/backend/services/file-manager.js index bc33957..9b40d54 100644 --- a/backend/services/file-manager.js +++ b/backend/services/file-manager.js @@ -503,19 +503,20 @@ class FileManager { throw error; } - // 处理其他文件系统错误 - const errorResult = ErrorHandler.handleFileSystemError(error, filePath, 'download'); + // 检查是否是可重试的网络错误 + const isRetryable = ErrorHandler.isRetryableError(error); logger.error(`下载文件失败 (尝试 ${attempt}/${maxRetries}): ${filePath}`, { error: error.message, stack: error.stack, url, - retryable: errorResult.retryable, - attempt + retryable: isRetryable, + attempt, + errorCode: error.code }); // 如果不是可重试的错误,直接抛出 - if (!errorResult.retryable) { + if (!isRetryable) { throw error; } diff --git a/backend/utils/error-handler.js b/backend/utils/error-handler.js index 7c1256a..d030ef5 100644 --- a/backend/utils/error-handler.js +++ b/backend/utils/error-handler.js @@ -305,11 +305,24 @@ class ErrorHandler { } /** - * 检查是否为可重试的错误 + * 判断错误是否可重试 */ static isRetryableError(error) { - const retryableCodes = ['EPERM', 'EACCES', 'EBUSY', 'EAGAIN', 'ENOSPC']; - return retryableCodes.includes(error.code); + const retryableCodes = ['EPERM', 'EACCES', 'EBUSY', 'EAGAIN', 'ENOSPC', 'ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND']; + const retryableMessages = ['aborted', 'socket hang up', 'network timeout']; + + // 检查错误码 + if (retryableCodes.includes(error.code)) { + return true; + } + + // 检查错误消息 + if (error.message) { + const message = error.message.toLowerCase(); + return retryableMessages.some(msg => message.includes(msg)); + } + + return false; } /** @@ -327,7 +340,20 @@ class ErrorHandler { case 'EPERM': case 'EACCES': return Math.min(delay, 5000); // 权限错误,中等延迟 + case 'ECONNRESET': + case 'ETIMEDOUT': + return Math.min(delay, 3000); // 网络错误,较短延迟 + case 'ENOTFOUND': + return Math.min(delay, 5000); // DNS错误,中等延迟 default: + // 检查是否是网络相关的错误消息 + if (error.message && ( + error.message.toLowerCase().includes('aborted') || + error.message.toLowerCase().includes('socket hang up') || + error.message.toLowerCase().includes('network timeout') + )) { + return Math.min(delay, 3000); // 网络错误,较短延迟 + } return delay; } } diff --git a/scripts/create-portable.js b/scripts/create-portable.js index d1624b0..7081ada 100644 --- a/scripts/create-portable.js +++ b/scripts/create-portable.js @@ -49,10 +49,10 @@ set LOG_LEVEL=INFO REM ======================================== REM Auto Open Browser Configuration - Options: true, false -REM true: Automatically open browser when server starts -REM false: Do not automatically open browser (default) +REM true: Automatically open browser when server starts (default) +REM false: Do not automatically open browser REM ======================================== -set AUTO_OPEN_BROWSER=false +set AUTO_OPEN_BROWSER=true echo. echo ======================================== @@ -120,8 +120,8 @@ pause ### 自动打开浏览器设置 修改(AUTO_OPEN_BROWSER=xxxx)的值来启用或禁用自动打开浏览器功能: -- true: 启动服务器后自动打开浏览器 -- false: 不自动打开浏览器(默认) +- true: 启动服务器后自动打开浏览器(默认) +- false: 不自动打开浏览器 ## 注意事项 diff --git a/ui/src/services/download.ts b/ui/src/services/download.ts index 80c0da7..b5ebdef 100644 --- a/ui/src/services/download.ts +++ b/ui/src/services/download.ts @@ -206,7 +206,7 @@ class DownloadService { // 处理不同类型的SSE消息 if (data.type === 'connected') { - console.log('SSE连接已建立:', data.taskId); + // console.log('SSE连接已建立:', data.taskId); } else if (data.type === 'progress') { // 新的数据格式:data.task 包含任务信息 if (data.task) { diff --git a/ui/src/stores/download.ts b/ui/src/stores/download.ts index cc000f5..5f5b4b1 100644 --- a/ui/src/stores/download.ts +++ b/ui/src/stores/download.ts @@ -12,6 +12,26 @@ export const useDownloadStore = defineStore('download', () => { // SSE连接管理 const sseConnections = ref void>>(new Map()); + + // 延迟更新管理 + const delayedUpdates = ref>(new Map()); + + // 清理指定任务的所有延迟更新 + const clearAllDelayedUpdates = (taskId: string) => { + const timeouts = delayedUpdates.value.get(taskId); + if (timeouts) { + timeouts.forEach(timeout => clearTimeout(timeout)); + delayedUpdates.value.delete(taskId); + } + }; + + // 添加延迟更新 + const addDelayedUpdate = (taskId: string, timeout: number) => { + if (!delayedUpdates.value.has(taskId)) { + delayedUpdates.value.set(taskId, []); + } + delayedUpdates.value.get(taskId)!.push(timeout); + }; // 计算属性:显示活跃任务和暂停任务 const activeTasks = computed(() => { @@ -131,7 +151,7 @@ export const useDownloadStore = defineStore('download', () => { sseConnections.value.get(taskId)!(); } - console.log('开始SSE监听任务进度:', taskId); + // console.log('开始SSE监听任务进度:', taskId); // 添加超时处理 - 增加到60秒以匹配后端 const timeoutId = setTimeout(() => { @@ -183,10 +203,24 @@ export const useDownloadStore = defineStore('download', () => { tasks.value.push(task); } - // 如果任务完成或暂停,清理连接 + // 如果任务完成或暂停,清理连接并触发额外的状态同步 if (['completed', 'failed', 'cancelled', 'partial', 'paused'].includes(task.status)) { - console.log('任务状态变更,关闭SSE连接:', taskId); + console.log('任务状态变更,关闭SSE连接:', taskId, task.status); stopTaskStreaming(taskId); + + // 如果任务完成,立即更新本地状态并停止所有延迟操作 + if (['completed', 'failed', 'cancelled', 'partial'].includes(task.status)) { + console.log('任务完成,立即更新状态:', taskId, task.status); + + // 立即更新本地任务状态,防止被其他操作覆盖 + const index = tasks.value.findIndex(t => t.id === taskId); + if (index !== -1) { + tasks.value[index] = { ...task }; + } + + // 取消所有可能的延迟状态更新操作 + clearAllDelayedUpdates(taskId); + } } }, () => { @@ -221,12 +255,23 @@ export const useDownloadStore = defineStore('download', () => { } }); - // 为正在下载的任务建立连接 + // 为正在下载的任务建立连接,增加状态检查 activeTasks.value.forEach(task => { if (task.status === 'downloading' && !sseConnections.value.has(task.id)) { + console.log('为下载任务建立SSE连接:', task.id, task.status); startTaskStreaming(task.id); } }); + + // 清理已暂停或完成任务的连接 + sseConnections.value.forEach((closeConnection, taskId) => { + const task = getTask(taskId); + if (task && ['paused', 'completed', 'failed', 'cancelled', 'partial'].includes(task.status)) { + console.log('清理非活跃任务的SSE连接:', taskId, task.status); + closeConnection(); + sseConnections.value.delete(taskId); + } + }); }; // 清理所有SSE连接 @@ -366,12 +411,22 @@ export const useDownloadStore = defineStore('download', () => { } if (response.success) { - // 立即更新状态为下载中 - updateTask(taskId, { status: 'downloading' }); + // 清理可能存在的延迟更新 + clearAllDelayedUpdates(taskId); + + // 使用后端返回的最新状态,确保状态同步 + if (response.data) { + const index = tasks.value.findIndex(t => t.id === taskId); + if (index !== -1) { + tasks.value[index] = { ...response.data }; + } + } else { + // 如果后端没有返回数据,则手动更新状态 + updateTask(taskId, { status: 'downloading' }); + } + // 立即建立SSE连接 startTaskStreaming(taskId); - // 异步刷新任务列表以确保同步 - setTimeout(() => fetchTasks(), 500); } else { // 如果恢复失败,恢复原状态 await fetchTasks(); @@ -422,12 +477,22 @@ export const useDownloadStore = defineStore('download', () => { } if (response.success) { - // 立即更新状态为已暂停 - updateTask(taskId, { status: 'paused' }); + // 清理可能存在的延迟更新 + clearAllDelayedUpdates(taskId); + + // 使用后端返回的最新状态,确保状态同步 + if (response.data) { + const index = tasks.value.findIndex(t => t.id === taskId); + if (index !== -1) { + tasks.value[index] = { ...response.data }; + } + } else { + // 如果后端没有返回数据,则手动更新状态 + updateTask(taskId, { status: 'paused' }); + } + // 停止SSE连接 stopTaskStreaming(taskId); - // 异步刷新任务列表以确保同步 - setTimeout(() => fetchTasks(), 500); } else { // 如果暂停失败,恢复原状态 await fetchTasks(); diff --git a/ui/src/views/ArtworkView.vue b/ui/src/views/ArtworkView.vue index 545ff61..f96e09d 100644 --- a/ui/src/views/ArtworkView.vue +++ b/ui/src/views/ArtworkView.vue @@ -265,7 +265,7 @@ const handleDownload = async () => { }); if (response.success) { - console.log('下载响应:', response.data); + // console.log('下载响应:', response.data); // 检查是否跳过下载 if (response.data.skipped) {