diff --git a/backend/services/download.js b/backend/services/download.js index 544a35a..2b35114 100644 --- a/backend/services/download.js +++ b/backend/services/download.js @@ -317,42 +317,25 @@ class DownloadService { if (artworkMatch && artworkMatch[1] === artworkId.toString()) { const artworkPath = path.join(artistPath, artworkEntry); - // 检查作品信息文件 + // 检查作品信息文件 - 这是最可靠的判断标准 const infoPath = path.join(artworkPath, 'artwork_info.json'); if (!(await this.fileManager.fileExists(infoPath))) { - console.log(`作品 ${artworkId} 缺少信息文件`); + console.log(`作品 ${artworkId} 缺少信息文件,认为未下载`); return false; } - // 检查图片文件 + // 检查是否有图片文件 const files = await this.fileManager.listDirectory(artworkPath); const imageFiles = files.filter(file => /\.(jpg|jpeg|png|gif|webp)$/i.test(file) && file !== 'artwork_info.json'); if (imageFiles.length === 0) { - console.log(`作品 ${artworkId} 没有图片文件`); + console.log(`作品 ${artworkId} 有信息文件但没有图片文件,认为未下载`); return false; } - // 检查每个图片文件的完整性 - let validFiles = 0; - for (const imageFile of imageFiles) { - const imagePath = path.join(artworkPath, imageFile); - const integrity = await this.fileManager.checkFileIntegrity(imagePath); - if (integrity.valid) { - validFiles++; - } else { - console.log(`作品 ${artworkId} 的图片文件 ${imageFile} 不完整: ${integrity.reason}`); - } - } - - // 只有当所有图片文件都完整时,才认为作品已下载 - if (validFiles === imageFiles.length) { - console.log(`作品 ${artworkId} 已完整下载,共 ${validFiles} 个文件`); - return true; - } else { - console.log(`作品 ${artworkId} 下载不完整,${validFiles}/${imageFiles.length} 个文件有效`); - return false; - } + // 有信息文件且有图片文件,认为已下载 + console.log(`作品 ${artworkId} 已下载,有信息文件和 ${imageFiles.length} 个图片文件`); + return true; } } } @@ -390,7 +373,7 @@ class DownloadService { if (artworkMatch && artworkMatch[1] === artworkId.toString()) { const artworkPath = path.join(artistPath, artworkEntry); - // 检查作品信息文件 + // 检查作品信息文件 - 这是最可靠的判断标准 const infoPath = path.join(artworkPath, 'artwork_info.json'); if (!(await this.fileManager.fileExists(infoPath))) { console.log(`作品 ${artworkId} 缺少信息文件,清理目录`); @@ -402,12 +385,12 @@ class DownloadService { }; } - // 检查图片文件 + // 检查是否有图片文件 const files = await this.fileManager.listDirectory(artworkPath); const imageFiles = files.filter(file => /\.(jpg|jpeg|png|gif|webp)$/i.test(file) && file !== 'artwork_info.json'); if (imageFiles.length === 0) { - console.log(`作品 ${artworkId} 没有图片文件,清理目录`); + console.log(`作品 ${artworkId} 有信息文件但没有图片文件,清理目录`); await this.fileManager.removeDirectory(artworkPath); return { is_downloaded: false, @@ -416,45 +399,12 @@ class DownloadService { }; } - // 检查每个图片文件的完整性,清理不完整的文件 - let validFiles = 0; - for (const imageFile of imageFiles) { - const imagePath = path.join(artworkPath, imageFile); - const integrity = await this.fileManager.checkFileIntegrity(imagePath); - if (integrity.valid) { - validFiles++; - } else { - console.log(`作品 ${artworkId} 的图片文件 ${imageFile} 不完整,删除: ${integrity.reason}`); - await this.fileManager.safeDeleteFile(imagePath); - cleanedFiles++; - } - } - - // 如果所有文件都被清理了,删除整个目录 - if (validFiles === 0) { - console.log(`作品 ${artworkId} 所有图片文件都不完整,清理目录`); - await this.fileManager.removeDirectory(artworkPath); - return { - is_downloaded: false, - cleaned_files: cleanedFiles + 1, - message: '所有图片文件都不完整,已清理整个目录' - }; - } - - // 只有当所有图片文件都完整时,才认为作品已下载 - if (validFiles === imageFiles.length) { - return { - is_downloaded: true, - cleaned_files: cleanedFiles, - message: `作品已完整下载,共 ${validFiles} 个文件` - }; - } else { - return { - is_downloaded: false, - cleaned_files: cleanedFiles, - message: `作品下载不完整,${validFiles}/${imageFiles.length} 个文件有效,已清理 ${cleanedFiles} 个不完整文件` - }; - } + // 有信息文件且有图片文件,认为已下载 + return { + is_downloaded: true, + cleaned_files: cleanedFiles, + message: `作品已下载,有信息文件和 ${imageFiles.length} 个图片文件` + }; } } } @@ -544,6 +494,9 @@ class DownloadService { await this.taskManager.saveTasks(); + // 立即发送初始状态更新,让前端能立即看到进度条 + this.progressManager.notifyProgressUpdate(task.id, task); + // 立即返回任务ID,异步执行下载 this.downloadExecutor.executeArtworkDownload(task, images, size, artworkDir, artwork); @@ -554,6 +507,7 @@ class DownloadService { artwork_id: artworkId, artist_name: artistName, artwork_title: artworkTitle, + total_files: images.length, status: 'downloading', message: '下载任务已创建,正在后台执行', }, @@ -599,6 +553,9 @@ class DownloadService { await this.taskManager.saveTasks(); + // 立即发送初始状态更新,让前端能立即看到进度条 + this.progressManager.notifyProgressUpdate(task.id, task); + // 如果没有需要下载的作品,直接返回 if (filteredIds.length === 0) { await this.taskManager.updateTask(task.id, { diff --git a/backend/services/file-manager.js b/backend/services/file-manager.js index 0e18db4..23e816d 100644 --- a/backend/services/file-manager.js +++ b/backend/services/file-manager.js @@ -84,58 +84,11 @@ class FileManager { } // 检查文件是否过小(可能下载不完整) - if (stats.size < 1024) { // 小于1KB的文件可能是损坏的 + if (stats.size < 512) { // 小于512字节的文件可能是损坏的 return { valid: false, reason: '文件过小,可能下载不完整', size: stats.size }; } - // 检查文件头,验证是否为有效的图片文件 - try { - const fileHandle = await fs.open(filePath, 'r'); - const buffer = Buffer.alloc(12); - await fileHandle.read(buffer, 0, 12, 0); - await fileHandle.close(); - - // 检查常见图片格式的文件头 - const isJPEG = buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF; - const isPNG = buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47; - const isGIF = (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46) || - (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38); - const isWebP = buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46; - - if (!isJPEG && !isPNG && !isGIF && !isWebP) { - return { valid: false, reason: '文件格式无效或损坏', size: stats.size }; - } - - // 对于JPEG文件,检查文件尾 - if (isJPEG) { - const endBuffer = Buffer.alloc(2); - const endHandle = await fs.open(filePath, 'r'); - await endHandle.read(endBuffer, 0, 2, stats.size - 2); - await endHandle.close(); - - if (endBuffer[0] !== 0xFF || endBuffer[1] !== 0xD9) { - return { valid: false, reason: 'JPEG文件不完整(缺少结束标记)', size: stats.size }; - } - } - - // 对于PNG文件,检查文件尾 - if (isPNG) { - const endBuffer = Buffer.alloc(8); - const endHandle = await fs.open(filePath, 'r'); - await endHandle.read(endBuffer, 0, 8, stats.size - 8); - await endHandle.close(); - - const pngEnd = Buffer.from([0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82]); - if (!endBuffer.equals(pngEnd)) { - return { valid: false, reason: 'PNG文件不完整(缺少结束标记)', size: stats.size }; - } - } - - } catch (headerError) { - console.warn('文件头检查失败,但继续验证:', headerError.message); - // 如果文件头检查失败,但文件大小正常,仍然认为是有效的 - } - + // 文件存在且大小正常,认为有效 return { valid: true, size: stats.size }; } catch (error) { return { valid: false, reason: '检查文件失败', error: error.message }; diff --git a/ui/src/views/ArtworkView.vue b/ui/src/views/ArtworkView.vue index 7b6ba34..efd19c4 100644 --- a/ui/src/views/ArtworkView.vue +++ b/ui/src/views/ArtworkView.vue @@ -55,13 +55,6 @@ 重新下载 下载 - - - @@ -206,7 +199,6 @@ const imageLoaded = ref(false); const imageError = ref(false); const downloading = ref(false); const isDownloaded = ref(false); -const checkingDownloadStatus = ref(false); // 下载任务状态 const currentTask = ref(null); @@ -296,7 +288,6 @@ const fetchArtworkDetail = async () => { // 检查下载状态 const checkDownloadStatus = async (artworkId: number, retryCount = 0) => { try { - checkingDownloadStatus.value = true; const response = await repositoryStore.checkArtworkDownloaded(artworkId); console.log('下载状态检查响应:', response); @@ -324,8 +315,6 @@ const checkDownloadStatus = async (artworkId: number, retryCount = 0) => { checkDownloadStatus(artworkId, retryCount + 1); }, 2000 * (retryCount + 1)); } - } finally { - checkingDownloadStatus.value = false; } }; @@ -355,21 +344,24 @@ const handleDownload = async () => { return; } - // 如果是新任务,开始监听进度 + // 如果是新任务,立即创建任务状态并开始监听进度 if (response.data.task_id) { + // 立即创建任务状态,让进度条立即显示 currentTask.value = { id: response.data.task_id, type: 'artwork', status: 'downloading', progress: 0, - total_files: 0, + total_files: response.data.total_files || 0, completed_files: 0, failed_files: 0, artwork_id: artwork.value.id, + artist_name: response.data.artist_name, + artwork_title: response.data.artwork_title, start_time: new Date().toISOString() }; - // 开始SSE监听任务进度 + // 立即开始SSE监听任务进度 startTaskStreaming(response.data.task_id); } } else { @@ -383,50 +375,6 @@ const handleDownload = async () => { } }; -// 强制检查下载状态 -const handleForceCheck = async () => { - if (!artwork.value) return; - - try { - checkingDownloadStatus.value = true; - console.log('开始强制检查下载状态...'); - - // 调用强制检查API - const response = await fetch(`${getApiBaseUrl()}/api/download/force-check/${artwork.value.id}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - } - }); - - if (response.ok) { - const result = await response.json(); - if (result.success) { - console.log('强制检查结果:', result.data); - - // 更新下载状态 - isDownloaded.value = result.data.is_downloaded; - - // 显示检查结果 - if (result.data.cleaned_files > 0) { - alert(`检查完成!${result.data.message}`); - } else { - alert(`检查完成!${result.data.message}`); - } - } else { - throw new Error(result.error || '强制检查失败'); - } - } else { - throw new Error('强制检查请求失败'); - } - } catch (err) { - console.error('强制检查失败:', err); - alert(`强制检查失败: ${err instanceof Error ? err.message : '未知错误'}`); - } finally { - checkingDownloadStatus.value = false; - } -}; - // 开始SSE监听任务进度 const startTaskStreaming = (taskId: string) => { // 清除之前的连接 @@ -448,6 +396,7 @@ const startTaskStreaming = (taskId: string) => { total: task.total_files }); + // 立即更新任务状态,让进度条立即显示 currentTask.value = task; // 如果任务完成,清理连接并检查下载状态 @@ -456,8 +405,8 @@ const startTaskStreaming = (taskId: string) => { stopTaskStreaming(); // 延迟检查下载状态,确保文件写入完成 - // 对于大文件,可能需要更长时间 - const delay = task.total_files > 1 ? 3000 : 2000; // 多文件延迟3秒,单文件延迟2秒 + // 减少延迟时间,提高响应速度 + const delay = task.total_files > 1 ? 1500 : 1000; // 多文件延迟1.5秒,单文件延迟1秒 setTimeout(async () => { // 检查当前页面是否还是同一个作品,避免页面切换后的状态更新 @@ -472,7 +421,7 @@ const startTaskStreaming = (taskId: string) => { if (artwork.value && artwork.value.id === task.artwork_id) { await checkDownloadStatus(artwork.value.id); } - }, 2000); + }, 1000); } // 清理任务状态,显示下载完成状态