Files
mixly3/static-server/api.js

246 lines
7.5 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);
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;