const fs = require('fs'); const path = require('path'); const express = require('express'); const axios = require('axios'); const AdmZip = require('adm-zip'); class AsyncAdmZip { constructor(zipPath) { this.zipPath = zipPath; this.zip = new AdmZip(zipPath); } // 异步解压到目录 async extractAllTo(targetPath, overwrite = true) { return new Promise((resolve, reject) => { try { // 确保目标目录存在 fs.promises.mkdir(targetPath, { recursive: true }) .then(() => { this.zip.extractAllTo(targetPath, overwrite); resolve({ success: true, targetPath, fileCount: this.zip.getEntries().length }); }) .catch(reject); } catch (error) { reject(error); } }); } // 异步解压单个文件 async extractEntry(entryName, targetPath, overwrite = true) { return new Promise((resolve, reject) => { try { const entry = this.zip.getEntry(entryName); if (!entry) { reject(new Error(`条目不存在: ${entryName}`)); return; } if (entry.isDirectory) { reject(new Error(`条目是目录: ${entryName}`)); return; } // 确保目标目录存在 const targetDir = path.dirname(targetPath); fs.promises.mkdir(targetDir, { recursive: true }) .then(() => { this.zip.extractEntryTo(entry, targetDir, false, overwrite); resolve({ success: true, entryName, targetPath }); }) .catch(reject); } catch (error) { reject(error); } }); } // 异步获取zip文件信息 async getZipInfo() { return new Promise((resolve) => { const entries = this.zip.getEntries(); const info = { fileCount: entries.length, totalSize: entries.reduce((sum, entry) => sum + entry.header.size, 0), entries: entries.map(entry => ({ name: entry.entryName, size: entry.header.size, isDirectory: entry.isDirectory, compressedSize: entry.header.compressedSize })) }; resolve(info); }); } } const router = express.Router(); const TEMP_FOLDER_PATH = path.resolve(__dirname, '../temp');; const VERSION_FILE = path.resolve(__dirname, '../version.json'); function getLocalVersion() { if(fs.existsSync(TEMP_FOLDER_PATH)) { try { if (fs.existsSync(VERSION_FILE)) { const data = fs.readFileSync(VERSION_FILE, 'utf8'); return data; } } catch (error) { console.error('读取版本文件失败:', error); } return '2025.09.06'; } return 'None'; } function saveVersionInfo(version) { fs.writeFileSync(VERSION_FILE, version); } async function getCloudVersion() { try { const response = await axios.get('http://update.mixly.cn/index.php'); return response.data.mixly.all; } catch (error) { console.error('获取云端版本信息失败:', error); return { file: 'mixly.zip', version: '2025.09.06' }; } } async function checkUpdate() { try { const localVersion = getLocalVersion(); const cloudVersions = await getCloudVersion(); const cloudVersion = cloudVersions['version']; const cloudFile = 'http://update.mixly.cn/download.php?file=' + cloudVersions['file'] return { needsUpdate: localVersion !== cloudVersion, localVersion: localVersion, cloudVersion: cloudVersion, cloudFile: cloudFile, error: '' }; } catch (error) { return { needsUpdate: false, localVersion: '', cloudVersion: '', cloudFile: '', error: error.message } } } // 检查更新接口 router.post('/check-update', async (req, res) => { const updateInfo = await checkUpdate(); res.json(updateInfo); }); // 下载进度返回 function deleteFolderRecursive(dirPath) { if (fs.existsSync(dirPath)) { fs.readdirSync(dirPath).forEach(file => { const curPath = path.join(dirPath, file); if (fs.lstatSync(curPath).isDirectory()) { deleteFolderRecursive(curPath); } else { fs.unlinkSync(curPath); } }); fs.rmdirSync(dirPath); } } router.get('/download', async (req, res) => { try { const { url, cloudVersion } = req.query; // 清理临时文件夹 if (fs.existsSync(TEMP_FOLDER_PATH)) { deleteFolderRecursive(TEMP_FOLDER_PATH); } fs.mkdirSync(TEMP_FOLDER_PATH, { recursive: true }); const filePath = path.resolve(TEMP_FOLDER_PATH, 'mixly.zip'); const fileStream = fs.createWriteStream(filePath); // 设置 SSE 响应头 res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.flushHeaders(); // 发起下载请求 - 添加 NW.js 特定配置 const response = await axios({ method: 'GET', url: url, responseType: 'stream', timeout: 60000, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }, adapter: require('axios/lib/adapters/http') }); const totalSize = parseInt(response.headers['content-length'], 10) || 0; let downloadedSize = 0; let lastProgress = 0; // 发送进度信息 const sendProgress = (progress) => { if (progress !== lastProgress) { const data = JSON.stringify({ type: 'progress', progress }); res.write(`data: ${data}\n\n`); lastProgress = progress; } }; // 管道流处理 response.data.pipe(fileStream); // 进度监控 response.data.on('data', (chunk) => { downloadedSize += chunk.length; if (totalSize > 0) { const progress = Math.round((downloadedSize / totalSize) * 100); sendProgress(progress); } }); // 文件流完成 fileStream.on('finish', async () => { console.log('文件下载完成,开始解压'); // 发送解压信息 res.write(`data: ${JSON.stringify({ type: 'unzip' })}\n\n`); try { const asyncZip = new AsyncAdmZip(filePath); await asyncZip.extractAllTo(path.resolve(__dirname, '../')); // 保存版本信息 saveVersionInfo(cloudVersion); // 发送完成信息 res.write(`data: ${JSON.stringify({ type: 'complete', version: cloudVersion })}\n\n`); // 清理临时文件 if (fs.existsSync(TEMP_FOLDER_PATH)) { deleteFolderRecursive(TEMP_FOLDER_PATH); } res.end(); } catch (error) { console.error('解压失败:', error); res.write(`data: ${JSON.stringify({ type: 'error', message: '解压失败' })}\n\n`); res.end(); } }); // 错误处理 response.data.on('error', (error) => { console.error('下载流错误:', error); res.write(`data: ${JSON.stringify({ type: 'error', message: '下载流错误' })}\n\n`); res.end(); }); fileStream.on('error', (error) => { console.error('文件流错误:', error); res.write(`data: ${JSON.stringify({ type: 'error', message: '文件保存错误' })}\n\n`); res.end(); }); } catch (error) { console.error('下载过程错误:', error); res.write(`data: ${JSON.stringify({ type: 'error', message: '下载失败: ' + error.message })}\n\n`); res.end(); } }); module.exports = router;