|
@@ -7,53 +7,24 @@ const updateManager = require('./updateManager');
|
|
const constants = require('./constants');
|
|
const constants = require('./constants');
|
|
const { getNetworkInterfaces } = require('./networkService');
|
|
const { getNetworkInterfaces } = require('./networkService');
|
|
const { exec } = require('child_process');
|
|
const { exec } = require('child_process');
|
|
-
|
|
|
|
-//app.disableHardwareAcceleration(); //使用windows7 ,或者虚拟机的时候 需要验证 禁用 GPU 加速
|
|
|
|
-//app.commandLine.appendSwitch('ignore-certificate-errors')
|
|
|
|
|
|
+const net = require('net');
|
|
|
|
+const os = require('os');
|
|
|
|
+const utils = require('./utils');
|
|
|
|
+
|
|
let win = null;
|
|
let win = null;
|
|
let tray = null;
|
|
let tray = null;
|
|
app.isQuitting = false; // 添加标志位
|
|
app.isQuitting = false; // 添加标志位
|
|
|
|
+
|
|
|
|
+// 根据操作系统选择命令
|
|
|
|
+const IS_WINDOWS = os.platform() === 'win32';
|
|
|
|
+const FIND_PORT_COMMAND = IS_WINDOWS ? `netstat -ano | findstr :${constants.port}` : `lsof -i :${constants.port} -t`;
|
|
|
|
+const KILL_PROCESS_COMMAND = IS_WINDOWS ? `taskkill /PID {PID} /F` : `kill -9 {PID}`;
|
|
|
|
+
|
|
|
|
+
|
|
// 创建 Electron 窗口的函数
|
|
// 创建 Electron 窗口的函数
|
|
const createWindow = async () => {
|
|
const createWindow = async () => {
|
|
try {
|
|
try {
|
|
- const networks = getNetworkInterfaces();
|
|
|
|
- // console.log('Available networks:', networks);
|
|
|
|
- console.log("运行地址:", networks[0].ip)
|
|
|
|
- try {
|
|
|
|
- let filePath = path.join(constants.serverPath,'certificate.bat');
|
|
|
|
- filePath = `"${filePath}"`;
|
|
|
|
- // 使用 execFile 执行 .bat 文件
|
|
|
|
-
|
|
|
|
- // 执行 .bat 文件
|
|
|
|
-
|
|
|
|
- // 设置超时时间为 60 秒
|
|
|
|
- exec(filePath, (error, stdout, stderr) => {
|
|
|
|
- if (error) {
|
|
|
|
- if (error.code === 'ETIMEDOUT') {
|
|
|
|
- console.error('执行 .bat 文件超时');
|
|
|
|
- } else {
|
|
|
|
- console.error(`执行 .bat 文件时出错: ${error.message}`);
|
|
|
|
- }
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- if (stderr) {
|
|
|
|
- console.error(`.bat 文件执行过程中出现错误: ${stderr}`);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- console.log(`.bat 文件执行结果: ${stdout}`);
|
|
|
|
- });
|
|
|
|
- } catch (error)
|
|
|
|
- {
|
|
|
|
- console.log(`脚本启动执行错误: ${error}`);
|
|
|
|
- }
|
|
|
|
|
|
|
|
-
|
|
|
|
- console.log("开始检查是否启动服务...")
|
|
|
|
- const isServerRunning = await serverManager.checkServerHealth();
|
|
|
|
- if (!isServerRunning) {
|
|
|
|
- console.log('Server is not running, starting it...');
|
|
|
|
- await serverManager.startServer(); // 启动 Web API
|
|
|
|
- }
|
|
|
|
win = new BrowserWindow({
|
|
win = new BrowserWindow({
|
|
width: 800,
|
|
width: 800,
|
|
height: 600,
|
|
height: 600,
|
|
@@ -62,14 +33,19 @@ const createWindow = async () => {
|
|
contextIsolation: false,
|
|
contextIsolation: false,
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
+ win.webContents.openDevTools(); // 打开开发者工具
|
|
//win.webContents.session.setCertificateVerifyProc((request, callback) => {
|
|
//win.webContents.session.setCertificateVerifyProc((request, callback) => {
|
|
// // 始终返回 0 表示验证通过
|
|
// // 始终返回 0 表示验证通过
|
|
// callback(0);
|
|
// callback(0);
|
|
//});///login/admin
|
|
//});///login/admin
|
|
win.maximize();
|
|
win.maximize();
|
|
- win.loadURL(`${constants.baseUrl}/login/admin`, {
|
|
|
|
- agent: constants.agent
|
|
|
|
- });
|
|
|
|
|
|
+ win.loadFile('index.html');
|
|
|
|
+
|
|
|
|
+ // 模拟执行业务过程
|
|
|
|
+ StartProcess();
|
|
|
|
+ //win.loadURL(`${constants.baseUrl}/login/admin`, {
|
|
|
|
+ // agent: constants.agent
|
|
|
|
+ //});
|
|
// 监听窗口关闭事件,隐藏窗口而不是关闭
|
|
// 监听窗口关闭事件,隐藏窗口而不是关闭
|
|
win.on('close', (event) => {
|
|
win.on('close', (event) => {
|
|
if (!app.isQuitting) {
|
|
if (!app.isQuitting) {
|
|
@@ -77,13 +53,128 @@ const createWindow = async () => {
|
|
win.hide();
|
|
win.hide();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
-
|
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
console.error('Error starting server or loading window:', error);
|
|
console.error('Error starting server or loading window:', error);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
+// 当 Electron 应用准备好时创建窗口
|
|
|
|
+app.whenReady().then(() => {
|
|
|
|
+ process.env.NODE_OPTIONS = '--tls-min-v1.2';
|
|
|
|
+ createWindow();
|
|
|
|
+ createTray();
|
|
|
|
+ // 创建菜单并传递回调函数
|
|
|
|
+ menuManager.createMenu(checkForUpdatesHandler);
|
|
|
|
+
|
|
|
|
+ app.on('activate', () => {
|
|
|
|
+ if (BrowserWindow.getAllWindows().length === 0) {
|
|
|
|
+ createWindow();
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 监听 before-quit 事件,关闭 IES.ExamServer.exe 进程
|
|
|
|
+ app.on('before-quit', async (event) => {
|
|
|
|
+ if (app.isQuitting) {
|
|
|
|
+ return; // 如果已经在退出流程中,则直接返回
|
|
|
|
+ }
|
|
|
|
+ app.isQuitting = true; // 标记正在退出
|
|
|
|
+ event.preventDefault(); // 阻止默认的退出行为
|
|
|
|
+ await serverManager.shutdownServer(); // 关闭服务器
|
|
|
|
+ app.quit(); // 触发退出流程
|
|
|
|
+ });
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+// 当所有窗口关闭时退出应用(macOS 除外)
|
|
|
|
+app.on('window-all-closed', function () {
|
|
|
|
+ if (process.platform !== 'darwin') {
|
|
|
|
+ app.quit();
|
|
|
|
+ }
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+// 模拟执行业务过程
|
|
|
|
+async function StartProcess() {
|
|
|
|
+ //步骤1 开始检查
|
|
|
|
+ sendLogMessage('检查评测服务是否启动...');
|
|
|
|
+ //步骤2 检查端口是否占用。
|
|
|
|
+ let serverStarted = false;
|
|
|
|
+ try {
|
|
|
|
+ const inUse = await isPortInUse(constants.port);
|
|
|
|
+ if (inUse) {
|
|
|
|
+ serverStarted = true;
|
|
|
|
+ sendLogMessage(`检查到端口被占用...`);
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+ sendLogMessage(`评测服务未启动...`);
|
|
|
|
+ }
|
|
|
|
+ } catch (err) {
|
|
|
|
+ sendLogMessage(`检查端口时出错...`);
|
|
|
|
+ // console.log(`检查评测服务状态出错...`, err);
|
|
|
|
+ }
|
|
|
|
+ //步骤3,尝试优雅关闭,如果不能关闭再通过 关闭进程号关闭端口
|
|
|
|
+ let needStart = false;
|
|
|
|
+ if (serverStarted) {
|
|
|
|
+ //检测是否是.net6的服务在线
|
|
|
|
+ sendLogMessage('检查是否是评测服务占用端口...');
|
|
|
|
+ const isServerRunning = await serverManager.checkServerHealth();
|
|
|
|
+ if (!isServerRunning) {
|
|
|
|
+ //可能是其他程序占用
|
|
|
|
+ sendLogMessage('检测到其他程序占用端口...');
|
|
|
|
+ sendLogMessage('正在查找占用端口的进程...');
|
|
|
|
+ const pid = await findProcessUsingPort();
|
|
|
|
+ sendLogMessage(`找到占用端口的进程: ${pid}`);
|
|
|
|
+ sendLogMessage('正在关闭进程...');
|
|
|
|
+ const killResult = await killProcess(pid);
|
|
|
|
+ sendLogMessage(`进程关闭结果:${killResult}`);
|
|
|
|
+ utils.delay(500);
|
|
|
|
+ needStart = true;
|
|
|
|
+ } else {
|
|
|
|
+ sendLogMessage('评测服务正在运行中...');
|
|
|
|
+ sendLogMessage('如需强制退出,请点击<设置><退出>按钮...');
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ needStart = true;
|
|
|
|
+ }
|
|
|
|
+ if (needStart) {
|
|
|
|
+ sendLogMessage('正在启动评测服务...');
|
|
|
|
+ await serverManager.startServer();
|
|
|
|
+ const isServerRunning = await serverManager.checkServerHealth();
|
|
|
|
+ if (isServerRunning) {
|
|
|
|
+ sendLogMessage('本地IP域名映射配置成功...');
|
|
|
|
+ sendLogMessage('SSL安全证书安装成功...');
|
|
|
|
+ sendLogMessage('评测服务启动成功...');
|
|
|
|
+ sendLogMessage('正在加载登录页面...');
|
|
|
|
+ utils.delay(2000)
|
|
|
|
+ win.loadURL(`${constants.baseUrl}/login/admin`, {
|
|
|
|
+ agent: constants.agent
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ sendLogMessage('评测服务启动失败...');
|
|
|
|
+ sendLogMessage('请检查hosts文件是否自动映射了exam.habook.local域名...');
|
|
|
|
+ sendLogMessage("如果没有请手动执行<teacher_manual.bat>脚本文件...");
|
|
|
|
+ }
|
|
|
|
+ } else
|
|
|
|
+ {
|
|
|
|
+ sendLogMessage('正在加载登录页面...');
|
|
|
|
+ utils.delay(2000)
|
|
|
|
+ win.loadURL(`${constants.baseUrl}/login/admin`, {
|
|
|
|
+ agent: constants.agent
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+// 发送消息到渲染进程
|
|
|
|
+function sendLogMessage(message) {
|
|
|
|
+ if (win) {
|
|
|
|
+ win.webContents.send('log-message', message);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
const createTray = () => {
|
|
const createTray = () => {
|
|
const iconPath = path.join(__dirname, 'logo.ico'); // 你的托盘图标路径
|
|
const iconPath = path.join(__dirname, 'logo.ico'); // 你的托盘图标路径
|
|
tray = new Tray(iconPath);
|
|
tray = new Tray(iconPath);
|
|
@@ -122,44 +213,83 @@ const createTray = () => {
|
|
});
|
|
});
|
|
};
|
|
};
|
|
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
// 定义回调函数
|
|
// 定义回调函数
|
|
const checkForUpdatesHandler = () => {
|
|
const checkForUpdatesHandler = () => {
|
|
updateManager.checkForUpdates(win, () => {
|
|
updateManager.checkForUpdates(win, () => {
|
|
menuManager.createMenu(checkForUpdatesHandler); // 重新创建菜单并传递回调函数
|
|
menuManager.createMenu(checkForUpdatesHandler); // 重新创建菜单并传递回调函数
|
|
});
|
|
});
|
|
};
|
|
};
|
|
-// 当 Electron 应用准备好时创建窗口
|
|
|
|
-app.whenReady().then(() => {
|
|
|
|
- process.env.NODE_OPTIONS = '--tls-min-v1.2';
|
|
|
|
- createWindow();
|
|
|
|
- createTray();
|
|
|
|
- // 创建菜单并传递回调函数
|
|
|
|
- menuManager.createMenu(checkForUpdatesHandler);
|
|
|
|
|
|
+function isPortInUse(port) {
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
+ const server = net.createServer()
|
|
|
|
+ .once('error', (err) => {
|
|
|
|
+ if (err.code === 'EADDRINUSE') {
|
|
|
|
+ resolve(true); // 端口被占用
|
|
|
|
+ } else {
|
|
|
|
+ reject(err);
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ .once('listening', () => {
|
|
|
|
+ server.close();
|
|
|
|
+ resolve(false); // 端口未被占用
|
|
|
|
+ })
|
|
|
|
+ .listen(port);
|
|
|
|
+ });
|
|
|
|
+}
|
|
|
|
|
|
- app.on('activate', () => {
|
|
|
|
- if (BrowserWindow.getAllWindows().length === 0) {
|
|
|
|
- createWindow();
|
|
|
|
- }
|
|
|
|
|
|
+// 查找占用端口的进程
|
|
|
|
+function findProcessUsingPort() {
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
+ exec(FIND_PORT_COMMAND, (error, stdout, stderr) => {
|
|
|
|
+ if (error) {
|
|
|
|
+ if (error.code === 1) {
|
|
|
|
+ // 未找到占用端口的进程
|
|
|
|
+ resolve(null);
|
|
|
|
+ } else {
|
|
|
|
+ reject(`查找端口占用失败: ${stderr}`);
|
|
|
|
+ }
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ // 提取 PID
|
|
|
|
+ const pid = IS_WINDOWS
|
|
|
|
+ ? stdout.trim().split(/\s+/).pop() // Windows: 取最后一列
|
|
|
|
+ : stdout.trim(); // macOS/Linux: 直接输出 PID
|
|
|
|
+ resolve(pid);
|
|
|
|
+ });
|
|
});
|
|
});
|
|
|
|
+}
|
|
|
|
|
|
- // 监听 before-quit 事件,关闭 IES.ExamServer.exe 进程
|
|
|
|
- app.on('before-quit', async (event) => {
|
|
|
|
- if (app.isQuitting) {
|
|
|
|
- return; // 如果已经在退出流程中,则直接返回
|
|
|
|
|
|
+// 关闭进程
|
|
|
|
+function killProcess(pid) {
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
+ if (!pid) {
|
|
|
|
+ resolve('未找到占用端口的进程');
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
- app.isQuitting = true; // 标记正在退出
|
|
|
|
- event.preventDefault(); // 阻止默认的退出行为
|
|
|
|
- await serverManager.shutdownServer(); // 关闭服务器
|
|
|
|
- app.quit(); // 触发退出流程
|
|
|
|
|
|
+ const command = KILL_PROCESS_COMMAND.replace('{PID}', pid);
|
|
|
|
+ exec(command, (error, stdout, stderr) => {
|
|
|
|
+ if (error) {
|
|
|
|
+ reject(`关闭进程失败: ${stderr}`);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ resolve(`已关闭进程: ${pid}`);
|
|
|
|
+ });
|
|
});
|
|
});
|
|
-});
|
|
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//// 启动 .NET Core 应用程序
|
|
|
|
+//function startDotNetApp() {
|
|
|
|
+// return new Promise((resolve, reject) => {
|
|
|
|
+// const command = `dotnet run --urls=http://localhost:${PORT}`;
|
|
|
|
+// const options = { cwd: DOTNET_PROJECT_PATH }; // 设置工作目录为 .NET 项目路径
|
|
|
|
+// exec(command, options, (error, stdout, stderr) => {
|
|
|
|
+// if (error) {
|
|
|
|
+// reject(`启动 .NET Core 应用程序失败: ${stderr}`);
|
|
|
|
+// return;
|
|
|
|
+// }
|
|
|
|
+// resolve(`.NET Core 应用程序已启动: ${stdout}`);
|
|
|
|
+// });
|
|
|
|
+// });
|
|
|
|
+//}
|
|
|
|
+
|
|
|
|
|
|
-// 当所有窗口关闭时退出应用(macOS 除外)
|
|
|
|
-app.on('window-all-closed', function () {
|
|
|
|
- if (process.platform !== 'darwin') {
|
|
|
|
- app.quit();
|
|
|
|
- }
|
|
|
|
-});
|
|
|