feat: sync mixly static resources, tools and sw-mixly
This commit is contained in:
276
mixly/static-server/api.js
Normal file
276
mixly/static-server/api.js
Normal 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;
|
||||
20
mixly/static-server/certs/server.crt
Normal file
20
mixly/static-server/certs/server.crt
Normal 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-----
|
||||
16
mixly/static-server/certs/server.csr
Normal file
16
mixly/static-server/certs/server.csr
Normal 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-----
|
||||
27
mixly/static-server/certs/server.key
Normal file
27
mixly/static-server/certs/server.key
Normal 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-----
|
||||
31
mixly/static-server/server.js
Normal file
31
mixly/static-server/server.js
Normal 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;
|
||||
}
|
||||
22
mixly/static-server/static-server.js
Normal file
22
mixly/static-server/static-server.js
Normal 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;
|
||||
27
mixly/static-server/static-sslserver.js
Normal file
27
mixly/static-server/static-sslserver.js
Normal 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;
|
||||
Reference in New Issue
Block a user