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 @@
@@ -93,6 +93,20 @@
+
+
+
+
+
+ Mixly编程服务器版本号
+
<%=versionmixly%>
+
+
+
+
+
+
+
-
+
+
+
+
+
@@ -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_input1.append($(""))
- condition1_input1.append($(""))
- condition1_input1.append($(""))
- condition1_input1.append($(""))
- condition1_input1.append($(""))
- condition1_input1.append($(""))
+ condition1_input1.append($(""))
+ condition1_input1.append($(""))
+ condition1_input1.append($(""))
+ condition1_input1.append($(""))
+ condition1_input1.append($(""))
+ condition1_input1.append($(""))
var condition1_input2 = $("")
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_input1.append($(""))
- condition2_input1.append($(""))
- condition2_input1.append($(""))
- condition2_input1.append($(""))
- condition2_input1.append($(""))
- condition2_input1.append($(""))
- condition2_input1.append($(""))
+ condition2_input1.append($(""))
+ condition2_input1.append($(""))
+ condition2_input1.append($(""))
+ condition2_input1.append($(""))
+ condition2_input1.append($(""))
+ condition2_input1.append($(""))
var condition2_input2 = $("")
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_input1.append($(""))
- condition1_input1.append($(""))
- condition1_input1.append($(""))
- condition1_input1.append($(""))
- condition1_input1.append($(""))
- condition1_input1.append($(""))
+ condition1_input1.append($(""))
+ condition1_input1.append($(""))
+ condition1_input1.append($(""))
+ condition1_input1.append($(""))
+ condition1_input1.append($(""))
+ condition1_input1.append($(""))
var condition1_input2 = $("")
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_input1.append($(""))
- condition2_input1.append($(""))
- condition2_input1.append($(""))
- condition2_input1.append($(""))
- condition2_input1.append($(""))
- condition2_input1.append($(""))
- condition2_input1.append($(""))
+ condition2_input1.append($(""))
+ condition2_input1.append($(""))
+ condition2_input1.append($(""))
+ condition2_input1.append($(""))
+ condition2_input1.append($(""))
+ condition2_input1.append($(""))
var condition2_input2 = $("")
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",