From a6925ff0b747eb9a66d87e28f09967ea49851c4d Mon Sep 17 00:00:00 2001 From: Eason010212 <1371033826@qq.com> Date: Sat, 6 Sep 2025 23:55:54 +0800 Subject: [PATCH] add-online-update --- ejs/manage.ejs | 116 ++++++++++++++++++++-- js/projects.js | 24 ++--- js/widgets.js | 43 +++++---- mixio.js | 240 +++++++++++++++++++++++++++++++++++++++++++++- package-lock.json | 103 ++++++++++++++++++++ package.json | 1 + 6 files changed, 487 insertions(+), 40 deletions(-) diff --git a/ejs/manage.ejs b/ejs/manage.ejs index 8d058cb..f2ffe8f 100644 --- a/ejs/manage.ejs +++ b/ejs/manage.ejs @@ -70,11 +70,11 @@
- 当前服务器状态
-
<%=status%>
+ 平台类型
+
<%=platform%>
- +
@@ -84,7 +84,7 @@
- 版本号
+ MixIO版本号
<%=version%>
@@ -93,6 +93,20 @@
+
+
+
+
+
+ Mixly编程服务器版本号
+
<%=versionmixly%>
+
+
+ +
+
+
+
@@ -108,12 +122,17 @@
- + +
+
+ +
@@ -463,8 +482,91 @@ } }) } - var fresh = function(){ - window.location.href = window.location.href + + var fresh = function(product, platform){ + var textarea = $("") + var add_text = function(text){ + textarea.text(textarea.text() + text + "\n") + textarea.scrollTop(textarea[0].scrollHeight); + } + var d = dialog({ + content: textarea + }); + d.showModal(); + add_text("发送更新请求中...") + fetch('/api/check-update', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + product: product, + platform: platform + }) + }) + .then((response) => response.json()) + .then((result) => { + add_text("本地版本:" + result["localVersion"]) + add_text("云端版本:" + result["cloudVersion"]) + add_text("需要更新:" + result["needsUpdate"]) + var url = result["cloudFile"] + var error = result["error"] + if(error) + { + add_text(error) + } + if(result["needsUpdate"]) + { + const eventSource = new EventSource(`/api/download?url=${encodeURIComponent(url)}&cloudVersion=${result["cloudVersion"]}`); + add_text("下载开始...") + eventSource.onmessage = function(event) { + console.log(event) + const data = JSON.parse(event.data); + console.log(data) + if (data.type === 'progress') { + add_text(`下载中: ${data.progress}%`) + } + else if (data.type === 'unzip') { + add_text(`解压中...`) + } + else if (data.type === 'complete') { + add_text(`${data.version}更新完成!`); + eventSource.close(); + if(data.version == "mixio"){ + setTimeout( + function(){ + window.location.reload() + } + , 10000) + alert("更新已完成,服务器将在10秒左右自动重启!") + } + add_text('5秒后自动退出...'); + setTimeout(function(){ + d.close() + }, 5000) + window.location.reload() + } + }; + eventSource.onerror = function(error) { + console.log(error) + add_text('下载失败!5秒后自动退出...'); + setTimeout(function(){ + d.close() + }, 5000) + eventSource.close(); + }; + } + else + { + add_text("已经是最新版本!5秒后自动退出...") + setTimeout(function(){ + d.close() + }, 5000) + } + }) + .catch((error) => { + add_text(error); + }); } var view = function(index){ a = [1,2,3] diff --git a/js/projects.js b/js/projects.js index 3ed6c19..f9a6fc7 100644 --- a/js/projects.js +++ b/js/projects.js @@ -3123,12 +3123,12 @@ function add_widget() { editForm.append($('
' + JSLang[lang].condition + '1
')) var condition1_input_div = $('
') var condition1_input1 = $("") condition1_input_div.append(condition1_input1) condition1_input_div.append(condition1_input2) @@ -3137,12 +3137,12 @@ function add_widget() { var condition2_input_div = $('
') var condition2_input1 = $("") condition2_input_div.append(condition2_input1) condition2_input_div.append(condition2_input2) diff --git a/js/widgets.js b/js/widgets.js index 10dcc72..6a4b5b4 100644 --- a/js/widgets.js +++ b/js/widgets.js @@ -2664,18 +2664,24 @@ async function add_trigger(user_title, user_topic, user_content, user_style, tit var dstMessage = user_content.split("$$$")[6] var itemdiv = add_block(1, 1, contents, attrs) var relationLogic = function(message, condition_1, condition_2) { + try{ + message = parseFloat(message) + } + catch{ + + } var condition_2 = parseFloat(condition_2) - if (condition_1 == ">") + if (condition_1 == "gt") return message > condition_2 - else if (condition_1 == "≥") + else if (condition_1 == "gte") return message >= condition_2 - else if (condition_1 == "<") + else if (condition_1 == "lt") return message < condition_2 - else if (condition_1 == "≤") + else if (condition_1 == "lte") return message <= condition_2 - else if (condition_1 == "=") + else if (condition_1 == "eq") return message == condition_2 - else if (condition_1 == "≠") + else if (condition_1 == "neq") return message != condition_2 else if (condition_1 == "--") return true @@ -2772,12 +2778,12 @@ async function add_trigger(user_title, user_topic, user_content, user_style, tit editForm.append($('
' + JSLang[lang].condition + '1
')) var condition1_input_div = $('
') var condition1_input1 = $("") condition1_input_div.append(condition1_input1) condition1_input_div.append(condition1_input2) @@ -2786,12 +2792,12 @@ async function add_trigger(user_title, user_topic, user_content, user_style, tit var condition2_input_div = $('
') var condition2_input1 = $("") condition2_input_div.append(condition2_input1) condition2_input_div.append(condition2_input2) @@ -7312,7 +7318,6 @@ copyButton.click(function() { var user_style = itemdiv.attr("style") // add the item, use the function of user-type toolkits[user_type](itemTitle + "_copy", user_topic, user_content, user_style) - }) var styleButton = $('') diff --git a/mixio.js b/mixio.js index b180801..a401172 100644 --- a/mixio.js +++ b/mixio.js @@ -1,5 +1,6 @@ -var VERSION = "1.10.0" +var VERSION = "1.10.5.0906" require('events').EventEmitter.defaultMaxListeners = 50; +const extract = require('extract-zip') defaultCrt = `-----BEGIN CERTIFICATE----- MIID0TCCArmgAwIBAgICYxswDQYJKoZIhvcNAQELBQAwczELMAkGA1UEBhMCQ04x @@ -106,6 +107,11 @@ var request = require('request'); const cors = require('cors'); const axios = require('axios'); var globalQPSControl = {} +const os = require('os'); +const arch = os.arch(); // 或者 process.arch +// 获取操作系统平台(如win32, linux, darwin) +const platform = os.platform(); // 或者 process.platform +const platformString = `${arch}-${platform}`; function init(cb) { if (!fs.existsSync("logs")) { @@ -419,8 +425,10 @@ async function daemon_start() { if (req.session.admin) { ejs.renderFile(__dirname + '/ejs/manage.ejs', { 'configs': configs, + 'platform': platformString, 'status': serverStatus ? "运行中" : "已暂停", - 'version': VERSION + 'version': VERSION, + 'versionmixly': getLocalVersion('mixly', '') }, function(err, data) { res.send(data) }) @@ -560,6 +568,234 @@ async function daemon_start() { res.send('-1') }) + const VERSION_FILE = 'versions.json'; + function getLocalVersion(product, platform) { + if(product == "mixly") + { + if(fs.existsSync("../mixly")) + { + try { + if (fs.existsSync(VERSION_FILE)) { + return fs.readFileSync(VERSION_FILE, 'utf8'); + } + } catch (error) { + console.error('读取版本文件失败:', error); + } + return '2025.09.06'; + } + return 'None' + } + else if(product == "mixio"){ + return VERSION + } + } + function saveVersionInfo(version) { + fs.writeFileSync(VERSION_FILE, version); + } + async function getCloudVersion(product,platform) { + try { + const response = await axios.get('http://update.mixly.cn/index.php'); + return response.data[product][platform]; + } catch (error) { + console.error('获取云端版本信息失败:', error); + return "2025.09.06" + } + } + async function checkUpdate(product, platform) { + try { + const localVersion = getLocalVersion(product,platform); + const cloudVersions = await getCloudVersion(product, platform); + 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 + } + } + } + // 检查更新接口 + app.post('/api/check-update', async (req, res) => { + var product = req.body.product + var platform = req.body.platform + const updateInfo = await checkUpdate(product, platform); + 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); + } + } + + app.get('/api/download', async (req, res) => { + try { + const {url, cloudVersion} = req.query; + var filePath, fileStream + if(url.indexOf(".zip")==-1) + { + filePath = "." + if(process.platform == 'win32') + { + fileStream = fs.createWriteStream(filePath + "/mixio.exe.tmp") + } + else + { + fileStream = fs.createWriteStream(filePath + "/mixio.tmp") + } + } + else + { + filePath = "../mixly" + if(fs.existsSync(filePath)){ + deleteFolderRecursive(filePath) + } + fs.mkdirSync("../mixly") + fileStream = fs.createWriteStream(filePath + "/mixly.zip") + } + // 设置响应头 + 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; + var lastProgress = 0; + // 发送进度信息 + const sendProgress = (progress) => { + if(progress != lastProgress) + { + res.write('data:' + JSON.stringify({ type: 'progress', progress }) + '\n\n'); + lastProgress = progress + } + }; + + // 发送完成信息 + 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 () => { + // 获取版本信息并保存 + if(url.indexOf(".zip")!=-1) + { + saveVersionInfo(cloudVersion); + res.write('data:' + JSON.stringify({ type: 'unzip'}) + '\n\n'); + extract("../mixly/mixly.zip", { dir: path.join(path.dirname(process.execPath), '../mixly') }).then(()=>{ + sendComplete("mixly"); + }) + } + else{ + sendComplete("mixio"); + if (process.platform === "win32") { + // Windows平台 + const batScriptContent = ` +ping 127.0.0.1 -n 5 >nul +del "mixio.exe" +rename "mixio.exe.tmp" "mixio.exe" +start "" "mixio.exe" start +del "%~f0"`; + try { + // 写入bat脚本 + fs.writeFileSync('update_mixio.bat', batScriptContent, 'utf8'); + // 执行bat脚本(异步执行,不等待完成) + const child = spawn('update_mixio.bat', [], { + stdio: 'ignore', // 分离后不再需要继承stdio + shell: true, + windowsHide: true, + detached: true, + windowsVerbatimArguments: false + }); + // 解除关联 + child.unref(); + setTimeout(() => { + process.exit(0); + }, 1000); + + } catch (error) { + console.error('创建或执行bat脚本时出错:', error); + } + } else { + // Linux/macOS平台 + const shScriptContent = `#!/bin/bash + sleep 5 + rm -f "mixio" + mv "mixio.tmp" "mixio" + chmod +x "mixio" + "mixio" start + rm -- "$0"`; + try { + // 写入sh脚本 + fs.writeFileSync('update_mixio.sh', shScriptContent, 'utf8'); + + // 给脚本添加执行权限 + await fs.chmod('update_mixio.sh', '755'); + + // 执行sh脚本(异步执行,不等待完成) + const child = spawn('nohup', ['./update_mixio.sh'], { + stdio: 'ignore', + detached: true, + shell: false + }); + + // 解除关联 + child.unref(); + + // 关闭服务器 + setTimeout(() => { + process.exit(0); + }, 1000); + + } catch (error) { + console.error('创建或执行sh脚本时出错:', error); + } + } + } + }); + response.data.on('error', (error) => { + res.status(500).json({ error: '下载失败' }); + }); + + + } catch (error) { + console.log(error.message) + res.status(500).json({ error: '下载失败' }); + } + }); + app.use('/js', express.static(path.join(__dirname, 'js'))); diff --git a/package-lock.json b/package-lock.json index 1009730..8c0ace2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "ejs": "^3.1.10", "express": "^4.21.2", "express-session": "^1.18.1", + "extract-zip": "^2.0.1", "forever-monitor": "^3.0.3", "fs-extra": "^11.3.0", "iconv-lite": "^0.6.3", @@ -423,6 +424,16 @@ "@types/node": "*" } }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1082,6 +1093,15 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2087,6 +2107,49 @@ "node": ">=0.10.0" } }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/extract-zip/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -2229,6 +2292,15 @@ "integrity": "sha512-XBU9RXeoYc2/VnvMhplAxEmZLfIk7cvTBu+xwoBuTI8pL19E03cmca17QQycKIdxgwCeFA/a4u27gv1h3ya5LQ==", "license": "ISC" }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -2608,6 +2680,21 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -4709,6 +4796,12 @@ "through": "~2.3" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -7174,6 +7267,16 @@ "engines": { "node": ">=10" } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } } } } diff --git a/package.json b/package.json index 8e714dd..dd7d5e4 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "ejs": "^3.1.10", "express": "^4.21.2", "express-session": "^1.18.1", + "extract-zip": "^2.0.1", "forever-monitor": "^3.0.3", "fs-extra": "^11.3.0", "iconv-lite": "^0.6.3",