main.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. const { app, BrowserWindow, Tray, Menu } = require('electron');
  2. const path = require('path');
  3. const serverManager = require('./serverManager');
  4. const menuManager = require('./menuManager');
  5. const updateManager = require('./updateManager');
  6. const constants = require('./constants');
  7. const { getNetworkInterfaces } = require('./networkService');
  8. const { exec } = require('child_process');
  9. const net = require('net');
  10. const os = require('os');
  11. const utils = require('./utils');
  12. let win = null;
  13. let tray = null;
  14. app.isQuitting = false; // 添加标志位
  15. // 根据操作系统选择命令
  16. const IS_WINDOWS = os.platform() === 'win32';
  17. const FIND_PORT_COMMAND = IS_WINDOWS ? `netstat -ano | findstr :${constants.port}` : `lsof -i :${constants.port} -t`;
  18. const KILL_PROCESS_COMMAND = IS_WINDOWS ? `taskkill /PID {PID} /F` : `kill -9 {PID}`;
  19. // 创建 Electron 窗口的函数
  20. const createWindow = async () => {
  21. try {
  22. win = new BrowserWindow({
  23. width: 800,
  24. height: 600,
  25. webPreferences: {
  26. nodeIntegration: true,
  27. contextIsolation: false,
  28. },
  29. });
  30. win.webContents.openDevTools(); // 打开开发者工具
  31. //win.webContents.session.setCertificateVerifyProc((request, callback) => {
  32. // // 始终返回 0 表示验证通过
  33. // callback(0);
  34. //});///login/admin
  35. win.maximize();
  36. win.loadFile('index.html');
  37. // 模拟执行业务过程
  38. StartProcess();
  39. //win.loadURL(`${constants.baseUrl}/login/admin`, {
  40. // agent: constants.agent
  41. //});
  42. // 监听窗口关闭事件,隐藏窗口而不是关闭
  43. win.on('close', (event) => {
  44. if (!app.isQuitting) {
  45. event.preventDefault();
  46. win.hide();
  47. }
  48. });
  49. } catch (error) {
  50. console.error('Error starting server or loading window:', error);
  51. }
  52. };
  53. // 当 Electron 应用准备好时创建窗口
  54. app.whenReady().then(() => {
  55. process.env.NODE_OPTIONS = '--tls-min-v1.2';
  56. createWindow();
  57. createTray();
  58. // 创建菜单并传递回调函数
  59. menuManager.createMenu(checkForUpdatesHandler);
  60. app.on('activate', () => {
  61. if (BrowserWindow.getAllWindows().length === 0) {
  62. createWindow();
  63. }
  64. });
  65. // 监听 before-quit 事件,关闭 IES.ExamServer.exe 进程
  66. app.on('before-quit', async (event) => {
  67. if (app.isQuitting) {
  68. return; // 如果已经在退出流程中,则直接返回
  69. }
  70. app.isQuitting = true; // 标记正在退出
  71. event.preventDefault(); // 阻止默认的退出行为
  72. await serverManager.shutdownServer(); // 关闭服务器
  73. app.quit(); // 触发退出流程
  74. });
  75. });
  76. // 当所有窗口关闭时退出应用(macOS 除外)
  77. app.on('window-all-closed', function () {
  78. if (process.platform !== 'darwin') {
  79. app.quit();
  80. }
  81. });
  82. // 模拟执行业务过程
  83. async function StartProcess() {
  84. //步骤1 开始检查
  85. sendLogMessage('检查评测服务是否启动...');
  86. //步骤2 检查端口是否占用。
  87. let serverStarted = false;
  88. try {
  89. const inUse = await isPortInUse(constants.port);
  90. if (inUse) {
  91. serverStarted = true;
  92. sendLogMessage(`检查到端口被占用...`);
  93. } else {
  94. sendLogMessage(`评测服务未启动...`);
  95. }
  96. } catch (err) {
  97. sendLogMessage(`检查端口时出错...`);
  98. // console.log(`检查评测服务状态出错...`, err);
  99. }
  100. //步骤3,尝试优雅关闭,如果不能关闭再通过 关闭进程号关闭端口
  101. let needStart = false;
  102. if (serverStarted) {
  103. //检测是否是.net6的服务在线
  104. sendLogMessage('检查是否是评测服务占用端口...');
  105. const isServerRunning = await serverManager.checkServerHealth();
  106. if (!isServerRunning) {
  107. //可能是其他程序占用
  108. sendLogMessage('检测到其他程序占用端口...');
  109. sendLogMessage('正在查找占用端口的进程...');
  110. const pid = await findProcessUsingPort();
  111. sendLogMessage(`找到占用端口的进程: ${pid}`);
  112. sendLogMessage('正在关闭进程...');
  113. const killResult = await killProcess(pid);
  114. sendLogMessage(`进程关闭结果:${killResult}`);
  115. utils.delay(500);
  116. needStart = true;
  117. } else {
  118. sendLogMessage('评测服务正在运行中...');
  119. sendLogMessage('如需强制退出,请点击<设置><退出>按钮...');
  120. }
  121. } else {
  122. needStart = true;
  123. }
  124. if (needStart) {
  125. sendLogMessage('正在启动评测服务...');
  126. await serverManager.startServer();
  127. const isServerRunning = await serverManager.checkServerHealth();
  128. if (isServerRunning) {
  129. sendLogMessage('本地IP域名映射配置成功...');
  130. sendLogMessage('SSL安全证书安装成功...');
  131. sendLogMessage('评测服务启动成功...');
  132. sendLogMessage('正在加载登录页面...');
  133. utils.delay(2000)
  134. win.loadURL(`${constants.baseUrl}/login/admin`, {
  135. agent: constants.agent
  136. });
  137. }
  138. else {
  139. sendLogMessage('评测服务启动失败...');
  140. sendLogMessage('请检查hosts文件是否自动映射了exam.habook.local域名...');
  141. sendLogMessage("如果没有请手动执行<teacher_manual.bat>脚本文件...");
  142. }
  143. } else
  144. {
  145. sendLogMessage('正在加载登录页面...');
  146. utils.delay(2000)
  147. win.loadURL(`${constants.baseUrl}/login/admin`, {
  148. agent: constants.agent
  149. });
  150. }
  151. }
  152. // 发送消息到渲染进程
  153. function sendLogMessage(message) {
  154. if (win) {
  155. win.webContents.send('log-message', message);
  156. }
  157. }
  158. const createTray = () => {
  159. const iconPath = path.join(__dirname, 'logo.ico'); // 你的托盘图标路径
  160. tray = new Tray(iconPath);
  161. const contextMenu = Menu.buildFromTemplate([
  162. {
  163. label: '显示',
  164. click: () => {
  165. if (win) {
  166. win.show();
  167. }
  168. }
  169. },
  170. {
  171. label: '退出',
  172. click: () => {
  173. require('electron').app.quit();
  174. //app.isQuitting = true;
  175. //app.quit();
  176. }
  177. }
  178. ]);
  179. tray.setToolTip('评测教师端');
  180. tray.setContextMenu(contextMenu);
  181. // 监听双击托盘图标事件,恢复窗口
  182. tray.on('double-click', () => {
  183. if (win) {
  184. if (win.isVisible()) {
  185. win.focus(); // 如果窗口已经显示,则聚焦窗口
  186. } else {
  187. win.show(); // 如果窗口隐藏,则显示窗口
  188. }
  189. }
  190. });
  191. };
  192. // 定义回调函数
  193. const checkForUpdatesHandler = () => {
  194. updateManager.checkForUpdates(win, () => {
  195. menuManager.createMenu(checkForUpdatesHandler); // 重新创建菜单并传递回调函数
  196. });
  197. };
  198. function isPortInUse(port) {
  199. return new Promise((resolve, reject) => {
  200. const server = net.createServer()
  201. .once('error', (err) => {
  202. if (err.code === 'EADDRINUSE') {
  203. resolve(true); // 端口被占用
  204. } else {
  205. reject(err);
  206. }
  207. })
  208. .once('listening', () => {
  209. server.close();
  210. resolve(false); // 端口未被占用
  211. })
  212. .listen(port);
  213. });
  214. }
  215. // 查找占用端口的进程
  216. function findProcessUsingPort() {
  217. return new Promise((resolve, reject) => {
  218. exec(FIND_PORT_COMMAND, (error, stdout, stderr) => {
  219. if (error) {
  220. if (error.code === 1) {
  221. // 未找到占用端口的进程
  222. resolve(null);
  223. } else {
  224. reject(`查找端口占用失败: ${stderr}`);
  225. }
  226. return;
  227. }
  228. // 提取 PID
  229. const pid = IS_WINDOWS
  230. ? stdout.trim().split(/\s+/).pop() // Windows: 取最后一列
  231. : stdout.trim(); // macOS/Linux: 直接输出 PID
  232. resolve(pid);
  233. });
  234. });
  235. }
  236. // 关闭进程
  237. function killProcess(pid) {
  238. return new Promise((resolve, reject) => {
  239. if (!pid) {
  240. resolve('未找到占用端口的进程');
  241. return;
  242. }
  243. const command = KILL_PROCESS_COMMAND.replace('{PID}', pid);
  244. exec(command, (error, stdout, stderr) => {
  245. if (error) {
  246. reject(`关闭进程失败: ${stderr}`);
  247. return;
  248. }
  249. resolve(`已关闭进程: ${pid}`);
  250. });
  251. });
  252. }
  253. //// 启动 .NET Core 应用程序
  254. //function startDotNetApp() {
  255. // return new Promise((resolve, reject) => {
  256. // const command = `dotnet run --urls=http://localhost:${PORT}`;
  257. // const options = { cwd: DOTNET_PROJECT_PATH }; // 设置工作目录为 .NET 项目路径
  258. // exec(command, options, (error, stdout, stderr) => {
  259. // if (error) {
  260. // reject(`启动 .NET Core 应用程序失败: ${stderr}`);
  261. // return;
  262. // }
  263. // resolve(`.NET Core 应用程序已启动: ${stdout}`);
  264. // });
  265. // });
  266. //}