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); const filePath = path.resolve(TEMP_FOLDER_PATH, 'mixly.zip'); const fileStream = fs.createWriteStream(filePath); // 设置响应头 res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Transfer-Encoding', 'chunked'); // 发起下载请求 const response = await axios({ method: 'GET', url: url, responseType: 'stream' }); const totalSize = parseInt(response.headers['content-length'], 10); let downloadedSize = 0; let lastProgress = 0; // 发送进度信息 const sendProgress = (progress) => { if(progress !== lastProgress) { res.write('data:' + JSON.stringify({ type: 'progress', progress }) + '\n\n'); lastProgress = progress; } }; // 发送解压信息 const sendUnzip = () => { res.write('data:' + JSON.stringify({ type: 'unzip' }) + '\n\n'); }; // 发送完成信息 const sendComplete = (version) => { res.write('data:' + JSON.stringify({ type: 'complete', version }) + '\n\n'); res.end(); }; response.data.pipe(fileStream); // 处理数据流 response.data.on('data', (chunk) => { downloadedSize += chunk.length; const progress = Math.round((downloadedSize / totalSize) * 100); sendProgress(progress); }); fileStream.on('finish', async () => { // 获取版本信息并保存 saveVersionInfo(cloudVersion); sendUnzip(); try { const asyncZip = new AsyncAdmZip(filePath); await asyncZip.extractAllTo(path.resolve(__dirname, '../')); sendComplete(cloudVersion); if(fs.existsSync(TEMP_FOLDER_PATH)) { deleteFolderRecursive(TEMP_FOLDER_PATH); } } catch (error) { console.log(error); res.status(500).json({ error: '解压失败' }); } }); } catch (error) { console.log(error.message); res.status(500).json({ error: '下载失败' }); } }); module.exports = router;