280 lines
8.7 KiB
JavaScript
280 lines
8.7 KiB
JavaScript
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;
|