|
@@ -1,43 +1,216 @@
|
|
|
-const { app, BrowserWindow } = require('electron');
|
|
|
+const { app, BrowserWindow, Menu, dialog } = require('electron');
|
|
|
const { exec } = require('child_process');
|
|
|
const path = require('path');
|
|
|
const axios = require('axios');
|
|
|
const https = require('https');
|
|
|
const fs = require('fs');
|
|
|
-const AdmZip = require('adm-zip'); // 用于解压 zip 文件
|
|
|
-// 忽略证书的检测
|
|
|
-//app.commandLine.appendSwitch('ignore-certificate-errors'); 原本用于解决SignalR 自签名证书不能使用的问题,将 "@microsoft/signalr": "^8.0.7", 改为 "@microsoft/signalr": "^7.0.14",
|
|
|
+const AdmZip = require('adm-zip'); // 鐢ㄤ簬瑙e帇 zip 鏂囦欢
|
|
|
+// 忽略证书的检测
|
|
|
+//app.commandLine.appendSwitch('ignore-certificate-errors'); 鍘熸湰鐢ㄤ簬瑙e喅SignalR 鑷��鍚嶈瘉涔︿笉鑳戒娇鐢ㄧ殑闂��锛屽皢 "@microsoft/signalr": "^8.0.7", 鏀逛负 "@microsoft/signalr": "^7.0.14",
|
|
|
const cert = fs.readFileSync('server\\Configs\\cer\\cert.pem');
|
|
|
const agent = new https.Agent({
|
|
|
ca: cert,
|
|
|
- rejectUnauthorized: true, // 启用证书验证
|
|
|
+ rejectUnauthorized: true, // 鍚�敤璇佷功楠岃瘉
|
|
|
});
|
|
|
-// 定义 Web API 的启动路径和健康检测 URL
|
|
|
+let win = null;
|
|
|
+// 定义 Web API 的启动路径和健康检测 URL
|
|
|
let serverPath;
|
|
|
if (app.isPackaged) {
|
|
|
- // 打包后的路径
|
|
|
+ // 鎵撳寘鍚庣殑璺�緞
|
|
|
serverPath = path.dirname(app.getPath('exe')) ;
|
|
|
} else {
|
|
|
- // 开发环境的路径
|
|
|
+ // 寮€鍙戠幆澧冪殑璺�緞
|
|
|
serverPath = __dirname;
|
|
|
}
|
|
|
-console.log('Server path:', serverPath);// 打印路径进行检查
|
|
|
+console.log('Server path:', serverPath);// 打印路径进行检查
|
|
|
const baseUrl = 'https://exam.habook.local:8888';
|
|
|
+const remoteVersionsUrl = 'https://teammodelos.blob.core.chinacloudapi.cn/0-public/exam-server/versions.json'; // 浜戠�鐗堟湰淇℃伅 URL
|
|
|
+const remoteZipBaseUrl = 'https://teammodelos.blob.core.chinacloudapi.cn/0-public/exam-server'; // 浜戠� zip 鏂囦欢鐨勫熀纭€ URL
|
|
|
let serverProcess;
|
|
|
-// 读取本地 appsettings.json 文件
|
|
|
+// 璇诲彇鏈�湴 appsettings.json 鏂囦欢
|
|
|
const getLocalVersion = () => {
|
|
|
const appSettingsPath = path.join(serverPath, 'server', 'appsettings.json');
|
|
|
+ console.log(appSettingsPath)
|
|
|
try {
|
|
|
- const appSettings = JSON.parse(fs.readFileSync(appSettingsPath, 'utf-8'));
|
|
|
- return appSettings.Version; // 假设 version 字段存储版本号
|
|
|
+ if (fs.existsSync(appSettingsPath)) {
|
|
|
+ const appSettings = JSON.parse(fs.readFileSync(appSettingsPath, 'utf-8'));
|
|
|
+ console.log(appSettings)
|
|
|
+ return appSettings.Version || '1.250101.01'; // 默认版本号
|
|
|
+ }
|
|
|
} catch (error) {
|
|
|
console.error('Error reading appsettings.json:', error);
|
|
|
+ return '1.250101.01';
|
|
|
+ }
|
|
|
+};
|
|
|
+// 从云端获取历史版本信息
|
|
|
+const getRemoteVersions = async () => {
|
|
|
+ try {
|
|
|
+ const response = await axios.get(remoteVersionsUrl, {
|
|
|
+ validateStatus: (status) => status === 200 || status === 404, // 鍏佽� 404 鐘舵€佺爜
|
|
|
+ timeout: 5000 // 设置超时时间为 5 秒
|
|
|
+ });
|
|
|
+ if (response.status === 404) {
|
|
|
+ console.error('Remote versions file not found.');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return response.data.versions; // 返回版本号数组
|
|
|
+ } catch (error) {
|
|
|
+ if (error.code === 'ECONNABORTED') {
|
|
|
+ console.error('Request to fetch remote versions timed out.');
|
|
|
+ } else {
|
|
|
+ console.error('Error fetching remote versions:', error);
|
|
|
+ }
|
|
|
return null;
|
|
|
}
|
|
|
};
|
|
|
-// 启动 Web API 的函数
|
|
|
|
|
|
-// 检查服务器健康状态的函数
|
|
|
+// 版本号格式化(补全当天版本号为两位数)
|
|
|
+const formatVersion = (version) => {
|
|
|
+ const parts = version.split('.');
|
|
|
+ if (parts.length === 3) {
|
|
|
+ const dayVersion = parts[2].padStart(2, '0'); // 补全当天版本号为两位数
|
|
|
+ return `${parts[0]}.${parts[1]}.${dayVersion}`;
|
|
|
+ }
|
|
|
+ return version;
|
|
|
+};
|
|
|
+// 版本号比较(去掉 . 并转换为数字)
|
|
|
+const compareVersions = (localVersion, remoteVersion) => {
|
|
|
+ const localNumber = parseInt(localVersion.replace(/\./g, ''), 10);
|
|
|
+ const remoteNumber = parseInt(remoteVersion.replace(/\./g, ''), 10);
|
|
|
+ return remoteNumber > localNumber;
|
|
|
+};
|
|
|
+// 涓嬭浇鏂囦欢
|
|
|
+const downloadFile = async (url, outputPath) => {
|
|
|
+ try {
|
|
|
+ const writer = fs.createWriteStream(outputPath);
|
|
|
+
|
|
|
+ console.log(`Downloading file from ${url}...`);
|
|
|
+ const response = await axios({
|
|
|
+ url,
|
|
|
+ method: 'GET',
|
|
|
+ responseType: 'stream',
|
|
|
+ timeout: 10000 // 设置超时时间为 10 秒
|
|
|
+ });
|
|
|
+
|
|
|
+ response.data.pipe(writer);
|
|
|
+
|
|
|
+ await new Promise((resolve, reject) => {
|
|
|
+ writer.on('finish', resolve);
|
|
|
+ writer.on('error', reject);
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log('Download completed.');
|
|
|
+ return true;
|
|
|
+ } catch (error) {
|
|
|
+ if (error.code === 'ECONNABORTED') {
|
|
|
+ console.error('Download timed out.');
|
|
|
+ } else {
|
|
|
+ console.error('Error downloading file:', error);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 检查是否需要更新
|
|
|
+const checkForUpdates = async () => {
|
|
|
+ const localVersion = formatVersion(getLocalVersion());
|
|
|
+ console.log('Local version:', localVersion);
|
|
|
+
|
|
|
+ const remoteVersions = await getRemoteVersions();
|
|
|
+ if (!remoteVersions || remoteVersions.length === 0) {
|
|
|
+ console.log('No remote versions found.');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 鑾峰彇鏈€鏂扮増鏈�彿
|
|
|
+ const latestRemoteVersion = formatVersion(remoteVersions[remoteVersions.length - 1]);
|
|
|
+ console.log('Latest remote version:', latestRemoteVersion);
|
|
|
+
|
|
|
+ if (compareVersions(localVersion, latestRemoteVersion)) {
|
|
|
+ const { response } = await dialog.showMessageBox({
|
|
|
+ type: 'info',
|
|
|
+ title: '鐗堟湰鏇存柊',
|
|
|
+ message: `是否更新到(${latestRemoteVersion}) 版本?`,
|
|
|
+ buttons: ['是', '否']
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response === 0) { // 鐢ㄦ埛閫夋嫨 Yes
|
|
|
+ await updateServer(latestRemoteVersion);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.log('未检测到新版本。');
|
|
|
+ const { response } = await dialog.showMessageBox({
|
|
|
+ type: 'info',
|
|
|
+ title: '鐗堟湰鏇存柊',
|
|
|
+ message: `鏈��娴嬪埌鏂扮増鏈�€俙,
|
|
|
+ buttons: ['鍏抽棴']
|
|
|
+ });
|
|
|
+ createMenu();
|
|
|
+ }
|
|
|
+};
|
|
|
+// 下载并更新 IES.ExamServer.exe
|
|
|
+const updateServer = async (latestVersion) => {
|
|
|
+ try {
|
|
|
+ const zipUrl = `${remoteZipBaseUrl}/server-${latestVersion}.zip`; // 构造下载 URL
|
|
|
+ const zipPath = path.join(serverPath, 'IES.ExamServer.zip');
|
|
|
+
|
|
|
+ // 1. 涓嬭浇鏈€鏂扮殑 IES.ExamServer.zip
|
|
|
+ const downloadSuccess = await downloadFile(zipUrl, zipPath);
|
|
|
+ if (!downloadSuccess) {
|
|
|
+ throw new Error('Failed to download IES.ExamServer.zip.');
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 2. 鍏抽棴 IES.ExamServer.exe
|
|
|
+ console.log('Shutting down IES.ExamServer...');
|
|
|
+ const response = await axios.get(`${baseUrl}/index/shutdown`, {
|
|
|
+ httpsAgent: agent,
|
|
|
+ validateStatus: (status) => status === 200 || status === 404, // 鍏佽� 404 鐘舵€佺爜
|
|
|
+ timeout: 5000 // 设置超时时间为 5 秒
|
|
|
+ });
|
|
|
+ if (response.status === 404) {
|
|
|
+ console.error('API: index/shutdown not found.');
|
|
|
+
|
|
|
+ }
|
|
|
+ if (response.status === 200) {
|
|
|
+ console.log('IES.ExamServer shutdown completed.');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error Shutting down IES.ExamServer...', error);
|
|
|
+ }
|
|
|
+ await delay(1000);
|
|
|
+ // 3. 瑙e帇 IES.ExamServer.zip
|
|
|
+ console.log('Extracting IES.ExamServer.zip...');
|
|
|
+ const zip = new AdmZip(zipPath);
|
|
|
+ zip.extractAllTo(path.join(serverPath, 'server'), true); // 瑕嗙洊瑙e帇
|
|
|
+ console.log('Extraction completed.');
|
|
|
+ // 4. 鍚�姩鏂扮殑 IES.ExamServer.exe
|
|
|
+ console.log('Starting IES.ExamServer...');
|
|
|
+ // 5. 更新菜单栏的当前版本号
|
|
|
+ createMenu(); // 閲嶆柊鍒涘缓鑿滃崟
|
|
|
+ await startServer();
|
|
|
+ console.log('IES.ExamServer started successfully.');
|
|
|
+ const { response } = await dialog.showMessageBox({
|
|
|
+ type: 'info',
|
|
|
+ title: '鐗堟湰鏇存柊',
|
|
|
+ message: `鏇存柊鎴愬姛銆俙,
|
|
|
+ buttons: ['鍏抽棴']
|
|
|
+ });
|
|
|
+ win.loadURL(baseUrl, {
|
|
|
+ agent: agent
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error updating server:', error);
|
|
|
+ }
|
|
|
+};
|
|
|
+// 启动 Web API 的函数
|
|
|
+function delay(ms) {
|
|
|
+ return new Promise(resolve => setTimeout(resolve, ms));
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+// 妫€鏌ユ湇鍔″櫒鍋ュ悍鐘舵€佺殑鍑芥暟
|
|
|
const checkServerHealth = async () => {
|
|
|
try {
|
|
|
const response = await axios.get(`${baseUrl}/index/health`, {
|
|
@@ -55,25 +228,25 @@ const checkServerHealth = async () => {
|
|
|
const startServer = () => {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
serverProcess = exec(path.join(serverPath, 'server', 'IES.ExamServer.exe'), {
|
|
|
- cwd: `${serverPath}/server` // 设置工作目录为 server 目录
|
|
|
+ cwd: `${serverPath}/server` // 设置工作目录为 server 目录
|
|
|
, stdio: 'inherit'
|
|
|
});
|
|
|
- // 监听标准输出
|
|
|
+ // 鐩戝惉鏍囧噯杈撳嚭
|
|
|
serverProcess.stdout.on('data', (data) => {
|
|
|
console.log(`Server stdout: ${data}`);
|
|
|
});
|
|
|
|
|
|
- // 监听标准错误输出
|
|
|
+ // 鐩戝惉鏍囧噯閿欒�杈撳嚭
|
|
|
serverProcess.stderr.on('data', (data) => {
|
|
|
console.log(`Server stderr: ${data}`);
|
|
|
});
|
|
|
|
|
|
- // 监听进程退出事件
|
|
|
+ // 监听进程退出事件
|
|
|
serverProcess.on('close', (data) => {
|
|
|
console.log(`Server process exited with code ${data}`);
|
|
|
reject(new Error(`Server process exited with code ${data}`));
|
|
|
});
|
|
|
- // 等待 Web API 启动成功
|
|
|
+ // 绛夊緟 Web API 鍚�姩鎴愬姛
|
|
|
const checkHealth = async () => {
|
|
|
try {
|
|
|
const response = await axios.get(`${baseUrl}/index/health`, {
|
|
@@ -85,21 +258,62 @@ const startServer = () => {
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.log('Waiting for server to start...');
|
|
|
- setTimeout(checkHealth, 1000); // 每隔 1 秒检查一次
|
|
|
+ setTimeout(checkHealth, 1000); // 每隔 1 秒检查一次
|
|
|
}
|
|
|
};
|
|
|
checkHealth();
|
|
|
});
|
|
|
};
|
|
|
|
|
|
-// 创建 Electron 窗口的函数
|
|
|
+// 鍒涘缓鑿滃崟
|
|
|
+const createMenu = () => {
|
|
|
+ const localVersion = formatVersion(getLocalVersion());
|
|
|
+ console.log('Local version:', localVersion);
|
|
|
+ // 获取 Electron、Node.js 和 Chrome 的版本
|
|
|
+ const electronVersion = process.versions.electron;
|
|
|
+ const nodeVersion = process.versions.node;
|
|
|
+ const chromeVersion = process.versions.chrome;
|
|
|
+ const appVersion = app.getVersion(); // 鑾峰彇搴旂敤绋嬪簭鐗堟湰
|
|
|
+ const template = [
|
|
|
+ {
|
|
|
+ label: '甯�姪',
|
|
|
+ submenu: [
|
|
|
+ {
|
|
|
+ label: '妫€鏌ユ洿鏂版湇鍔$�',
|
|
|
+ click: () => {
|
|
|
+ checkForUpdates();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: `服务端版本: ${localVersion}`
|
|
|
+
|
|
|
+ }
|
|
|
+ ,{
|
|
|
+ label: `搴旂敤绋嬪簭鐗堟湰: ${appVersion}` // 鏄剧ず搴旂敤绋嬪簭鐗堟湰
|
|
|
+ },
|
|
|
+ //{
|
|
|
+ // label: `Electron 鐗堟湰: ${electronVersion}` // 鏄剧ず Electron 鐗堟湰
|
|
|
+ //},
|
|
|
+ //{
|
|
|
+ // label: `Node.js 鐗堟湰: ${nodeVersion}` // 鏄剧ず Node.js 鐗堟湰
|
|
|
+ //},
|
|
|
+ {
|
|
|
+ label: `浏览器内核版本: ${chromeVersion}` // 显示 Chrome 版本
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ];
|
|
|
+ const menu = Menu.buildFromTemplate(template);
|
|
|
+ Menu.setApplicationMenu(menu);
|
|
|
+};
|
|
|
+// 创建 Electron 窗口的函数
|
|
|
const createWindow = async () => {
|
|
|
try {
|
|
|
const isServerRunning = await checkServerHealth();
|
|
|
if (!isServerRunning) {
|
|
|
- await startServer(); // 启动 Web API
|
|
|
+ await startServer(); // 鍚�姩 Web API
|
|
|
}
|
|
|
- const win = new BrowserWindow({
|
|
|
+ win = new BrowserWindow({
|
|
|
width: 800,
|
|
|
height: 600,
|
|
|
webPreferences: {
|
|
@@ -117,25 +331,25 @@ const createWindow = async () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-// 当 Electron 应用准备好时创建窗口
|
|
|
+// 当 Electron 应用准备好时创建窗口
|
|
|
app.whenReady().then(() => {
|
|
|
createWindow();
|
|
|
-
|
|
|
+ createMenu();
|
|
|
app.on('activate', () => {
|
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
|
createWindow();
|
|
|
}
|
|
|
});
|
|
|
- // 监听 before-quit 事件,关闭 IES.ExamServer.exe 进程
|
|
|
+ // 监听 before-quit 事件,关闭 IES.ExamServer.exe 进程
|
|
|
app.on('before-quit', async (event) => {
|
|
|
- event.preventDefault(); // 阻止默认的退出行为
|
|
|
+ event.preventDefault(); // 阻止默认的退出行为
|
|
|
if (serverProcess) {
|
|
|
console.log('Killing server process...');
|
|
|
serverProcess.kill();
|
|
|
}
|
|
|
try {
|
|
|
console.log('index/shutdown api ...');
|
|
|
- // 发起 HTTP 请求来关闭.NET Core Web API
|
|
|
+ // 发起 HTTP 请求来关闭.NET Core Web API
|
|
|
const response = await axios.get(`${baseUrl}/index/shutdown`, {
|
|
|
httpsAgent: agent
|
|
|
});
|
|
@@ -143,15 +357,15 @@ app.whenReady().then(() => {
|
|
|
console.log('Server is shutdown!');
|
|
|
//resolve();
|
|
|
}
|
|
|
- // app.quit(); // 关闭 Electron 应用程序
|
|
|
+ // app.quit(); // 鍏抽棴 Electron 搴旂敤绋嬪簭
|
|
|
} catch (error) {
|
|
|
- console.error('关闭.NET Core Web API 时出错:', error);
|
|
|
+ console.error('关闭.NET Core Web API 时出错:', error);
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
-// 当所有窗口关闭时退出应用(macOS 除外)
|
|
|
+// 当所有窗口关闭时退出应用(macOS 除外)
|
|
|
app.on('window-all-closed', function () {
|
|
|
- // 无论什么平台,都退出程序
|
|
|
+ // 无论什么平台,都退出程序
|
|
|
app.quit();
|
|
|
});
|