feat: sync mixly static resources, tools and sw-mixly

This commit is contained in:
yczpf2019
2026-01-24 16:12:55 +08:00
parent c8c5fcf726
commit 01b756fed8
173 changed files with 39715 additions and 0 deletions

276
mixly/static-server/api.js Normal file
View File

@@ -0,0 +1,276 @@
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() {
try {
if (fs.existsSync(VERSION_FILE)) {
const data = fs.readFileSync(VERSION_FILE, 'utf8');
return data;
}
} catch (error) {
console.error('读取版本文件失败:', error);
}
return '2025.09.06';
}
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;

View File

@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDNTCCAh0CFGQSgGkvHiuKgOSMVVjT1dFX1Df3MA0GCSqGSIb3DQEBCwUAMFcx
CzAJBgNVBAYTAkNOMRAwDgYDVQQIDAdCZWlqaW5nMRAwDgYDVQQHDAdCZWlqaW5n
MQ4wDAYDVQQKDAVNaXhseTEUMBIGA1UEAwwLMTkyLjE2OC4xLjEwHhcNMjIwODE5
MDg1ODA4WhcNMjMwODE5MDg1ODA4WjBXMQswCQYDVQQGEwJDTjEQMA4GA1UECAwH
QmVpamluZzEQMA4GA1UEBwwHQmVpamluZzEOMAwGA1UECgwFTWl4bHkxFDASBgNV
BAMMCzE5Mi4xNjguMS4xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
yxvOxepem9zCBTGZ2aMjba73xkVgljGuEhbPMgRS8l763ril2rapfGoiBVEGOkTv
RY6YFP6yNtX4E8SXuePcvS75g+XUVgUYLmXJ+2DZfNL/RLnTRciiGR/n53BEbE9R
c81I/yYbCmWJH63F0Zn4SOghzP9AyBSTkVVHt2onm4NNCWEhsmAFlHOsiNQeOd4r
Ji5k9KFjhstKAtlDr/HLjTTew15y2GVhusjko52HYM9zYrNOPrOHzuQza4cxln37
FQUZudJx1TttWJ4u8z8ycAHNJ+Nj9yUzIo55xe/R+/qpF+ADxVMbm+Dpx2wIp6iG
9WZSBWXu+6tfTuUjDsDUNwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQC4gqiSyqpb
8Z8p+VqF3ezJXMBtTyOnBsWXAO8UUJ8pEIrTVWBj+MQfbrYwX54omApNra8CGC+g
yGa84QNVRgor71sV+rpwXu/+2vc1eExJTjsias4TSgmzJYGH2NLcNxAoru7KQjzv
Inrn88QMcDdhIkKTjxpu1uz75p62oMD6Iv58n+4iBLxfW8GPQ5U8BKOVps+DWTOI
lDMtuc4ZySLDUNn8tHsKSLL41kupmkqAd9G12bqR+BOs06Nf/IeVuYGtZDw5gZxg
DpKb826ZTsI7ED34qhG+4mol6py8wM3UeDJ8QX9EANwwDA7DJ1boC8QIxAk5fgA1
rzyUgc30MMx0
-----END CERTIFICATE-----

View File

@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICnDCCAYQCAQAwVzELMAkGA1UEBhMCQ04xEDAOBgNVBAgMB0JlaWppbmcxEDAO
BgNVBAcMB0JlaWppbmcxDjAMBgNVBAoMBU1peGx5MRQwEgYDVQQDDAsxOTIuMTY4
LjEuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMsbzsXqXpvcwgUx
mdmjI22u98ZFYJYxrhIWzzIEUvJe+t64pdq2qXxqIgVRBjpE70WOmBT+sjbV+BPE
l7nj3L0u+YPl1FYFGC5lyftg2XzS/0S500XIohkf5+dwRGxPUXPNSP8mGwpliR+t
xdGZ+EjoIcz/QMgUk5FVR7dqJ5uDTQlhIbJgBZRzrIjUHjneKyYuZPShY4bLSgLZ
Q6/xy4003sNecthlYbrI5KOdh2DPc2KzTj6zh87kM2uHMZZ9+xUFGbnScdU7bVie
LvM/MnABzSfjY/clMyKOecXv0fv6qRfgA8VTG5vg6cdsCKeohvVmUgVl7vurX07l
Iw7A1DcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBiNeSZNC0CFlxd1lxhu0cb
+w5T2ikskgKUyxe/5ZDVFfzXaDw/JrE/9sdsnecl/t0Wbsir5lRLH0LOe0XyusuD
DcVeOEVUjVWTSgtR5Te9pjKHBphyu0fdHQG6LKNa6UGThc6aaKyjkxRv8dFIKkjl
FHceesd51pBKRdIhmFXg9kx+9iJVKgLL92U8uKgsjZBB4ZzYaUD9RLaPxaK52gOf
y6TswblJbCUxEl8vCIyB1v5rGngJ7kCjuEk7lNDSRI58GBkEzFQYLHAi7C/BOLDY
DDpJaPr9crfmD3UboAqgLdi5S8W2BN59VcLUtp4/2bgfcvYQFUahKvyTymf6OmU9
-----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAyxvOxepem9zCBTGZ2aMjba73xkVgljGuEhbPMgRS8l763ril
2rapfGoiBVEGOkTvRY6YFP6yNtX4E8SXuePcvS75g+XUVgUYLmXJ+2DZfNL/RLnT
RciiGR/n53BEbE9Rc81I/yYbCmWJH63F0Zn4SOghzP9AyBSTkVVHt2onm4NNCWEh
smAFlHOsiNQeOd4rJi5k9KFjhstKAtlDr/HLjTTew15y2GVhusjko52HYM9zYrNO
PrOHzuQza4cxln37FQUZudJx1TttWJ4u8z8ycAHNJ+Nj9yUzIo55xe/R+/qpF+AD
xVMbm+Dpx2wIp6iG9WZSBWXu+6tfTuUjDsDUNwIDAQABAoIBABOlPevQzpPe13lv
IcV2TR/304l+/meoqIChaisZVfiRjUxrqccs8dnR3jaLbsHGFyqwLy+grxY0vgkT
c+WMD7bQy1uhqFclqQAb4lyJMqArPHumSbQvQtaRSnoNVuDvDx7XVV8wjV8FES1a
Po8WiHhs05AjhF2V9+wPxp8MCoa1EWMh+A9gkswvbaDrh+YTC3qori2H9gvt4DPp
feb1ofiMLSM2HzUtJgw+kp5SvWtYvg001f8pO7A2KIV0MoE38tXuaebxta0YHql+
BuICWCe2M7RZau2JHZM51tPQ2/3l6ya2YSwdPPRCUQqpJ8uFS1M1QnjZKFlAEXY/
KU4lmskCgYEA7J9EyIMqfI6pbGLeLnbf8aN/6C5ldOvEYZThlcjDpOMveajmylYs
P0SFsK05MaFf5PeM0U2WHfzx8bLyLUvIpICvJ5w9q9vR/ZOxHCBd87dMqjVDFgXm
iUz+m87D7wVNFKzZF+lnqdqOpouuBfMlJqbCygmcw4ISEwntOnDRsy0CgYEA273v
eRMTXM+VmJ4ZcTYXpRFGOFqP8yITyQ8dm7Yr+RpJAxLS/fN9DZEsl8xWNevFmbp4
isDtKNKjmQIsvpxt2s30dpqg53YWnfHjFVN/JLYbxzYavYcciJ/Apl6RyRYukTPC
T99gUMfu+/7LHgS2ytzZ0kmVBmKaXAwUzU3lE3MCgYAYm/frYrjoe23jd+TjsDla
SEblPu4OWvbxrypHCbpPS9GENazLHms7qUS+O0XXg5EVnylmG0uhks0W9iV50Ift
k/SjifxgA1yzosioxDUBQ+8VRLTVdYekf/169uYp1cNOgyuQ8RV29OQhLiXLOJ6E
hpN7r8Q+ESkQEdg6W8FzgQKBgHKbZHvsVAvzBJ39z105jil8kfgwW6W+Xz1dEd81
q0eXyv68Yakrxkw+LFjbrRcgagYcuGP97XN+MO9LsBSWN8GH63m0ejleYLtt/jcQ
Pl7iUCidcmLpRhuH3o2nAzgyxoTazvyjj3NyY5WwtTVp1gCGIWFJGV2kLcfWUT8m
4lQ7AoGBAM5R5jI9n8ndNhGpoIisu8YCeowS5QGDJznBN932NwenUgiZw4suv71r
v5FAdjIMKwSQcjaZoPPqqTuXnTIo1mXQpiMitAAXeb1HmJnPvZgSk5RI3OnrOZDE
CClJzjTTEprhaw9LR7uqWUp12f1Z+lAq9kQFAeYBarpqIplH6/Gi
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,31 @@
const fs = require('fs')
const StaticServer = require('./static-server.js');
const SSLStaticServer = require('./static-sslserver.js');
function deleteFile(filePath) {
try {
if (!fs.existsSync(filePath)) {
return;
}
const stats = fs.statSync(filePath);
if (stats.isFile()) {
fs.unlinkSync(filePath);
console.log('File deleted successfully.');
}
} catch (err) {
console.error('Error deleting file:', err);
}
}
const init = () => {
StaticServer.run('7000');
SSLStaticServer.run('8000');
}
if (!module.parent) {
deleteFile('./nw_cache/Default/Preferences');
init();
} else {
module.exports = init;
}

View File

@@ -0,0 +1,22 @@
const http = require('http');
const express = require('express');
const path = require('path');
const apiRoutes = require('./api.js');
const StaticServer = {};
StaticServer.run = (port) => {
const app = express();
app.use(express.static(path.resolve(__dirname, '../')));
app.use('/api/', apiRoutes);
const httpServer = http.createServer(app);
httpServer.listen(port);
console.log('Static服务器正在运行 [端口 - ' + port + ', http]...');
console.log('访问地址http://127.0.0.1:' + port);
StaticServer.server = httpServer;
StaticServer.app = app;
StaticServer.port = port;
StaticServer.protocol = 'http';
}
module.exports = StaticServer;

View File

@@ -0,0 +1,27 @@
const https = require('https');
const express = require('express');
const fs = require('fs');
const path = require('path');
const SSLStaticServer = {};
SSLStaticServer.run = (port) => {
const KEY_PATH = path.resolve(__dirname, './certs/server.key');
const CRT_PATH = path.resolve(__dirname, './certs/server.crt');
const options = {
key: fs.readFileSync(KEY_PATH),
cert: fs.readFileSync(CRT_PATH)
};
const app = express();
app.use(express.static(path.resolve(__dirname, '../')));
const httpsServer = https.createServer(options, app);
httpsServer.listen(port);
console.log('Static服务器正在运行 [端口 - ' + port + ', https]...');
console.log('访问地址https://127.0.0.1:' + port);
SSLStaticServer.server = httpsServer;
SSLStaticServer.app = app;
SSLStaticServer.port = port;
SSLStaticServer.protocol = 'https';
}
module.exports = SSLStaticServer;