2025-10-15 14:12:24 +08:00
|
|
|
|
'use strict';
|
|
|
|
|
|
|
2025-10-16 20:14:55 +08:00
|
|
|
|
const path = require('path');
|
2025-10-15 14:12:24 +08:00
|
|
|
|
const { logger } = require('ee-core/log');
|
|
|
|
|
|
const { getConfig } = require('ee-core/config');
|
|
|
|
|
|
const { getMainWindow } = require('ee-core/electron');
|
2025-11-26 08:50:22 +08:00
|
|
|
|
const { getBaseDir } = require('ee-core/ps');
|
2025-10-16 20:14:55 +08:00
|
|
|
|
const { app } = require('electron');
|
|
|
|
|
|
|
|
|
|
|
|
// 动态获取 scripts 目录路径
|
|
|
|
|
|
function getScriptsPath(scriptName) {
|
|
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否是打包后的环境
|
2025-11-26 08:50:22 +08:00
|
|
|
|
// 只要 process.resourcesPath 存在,就是打包后的环境(无论在哪个目录)
|
|
|
|
|
|
const isProd = !!process.resourcesPath;
|
2025-10-16 20:14:55 +08:00
|
|
|
|
|
|
|
|
|
|
if (isProd) {
|
|
|
|
|
|
// 生产环境(打包后):从 resources 目录
|
|
|
|
|
|
const prodPath = path.join(process.resourcesPath, 'scripts', scriptName);
|
|
|
|
|
|
console.log(`[getScriptsPath] Production mode, using: ${prodPath}`);
|
|
|
|
|
|
return prodPath;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 开发环境:从项目根目录
|
|
|
|
|
|
// __dirname 是 electron/preload 或 public/electron/preload
|
|
|
|
|
|
// 需要找到项目根目录
|
|
|
|
|
|
let currentDir = __dirname;
|
|
|
|
|
|
let scriptsPath = null;
|
|
|
|
|
|
|
|
|
|
|
|
// 向上查找,直到找到 scripts 目录
|
|
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
|
|
|
|
currentDir = path.join(currentDir, '..');
|
|
|
|
|
|
const testPath = path.join(currentDir, 'scripts', scriptName + '.js');
|
|
|
|
|
|
if (fs.existsSync(testPath)) {
|
|
|
|
|
|
scriptsPath = path.join(currentDir, 'scripts', scriptName);
|
|
|
|
|
|
console.log(`[getScriptsPath] Development mode, found at: ${scriptsPath}`);
|
|
|
|
|
|
return scriptsPath;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果找不到,返回一个默认路径
|
|
|
|
|
|
console.warn(`[getScriptsPath] Cannot find ${scriptName}, returning default path`);
|
|
|
|
|
|
return path.join(__dirname, '../../../scripts', scriptName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟加载 scripts
|
2025-11-26 08:50:22 +08:00
|
|
|
|
let MySQLServiceManager, JavaRunner, ConfigGenerator, PortChecker, StartupManager, LogWindowManager;
|
2025-10-16 20:14:55 +08:00
|
|
|
|
|
|
|
|
|
|
function loadScripts() {
|
2025-11-26 08:50:22 +08:00
|
|
|
|
if (!MySQLServiceManager) {
|
|
|
|
|
|
MySQLServiceManager = require(getScriptsPath('mysql-service-manager'));
|
2025-10-16 20:14:55 +08:00
|
|
|
|
JavaRunner = require(getScriptsPath('java-runner'));
|
|
|
|
|
|
ConfigGenerator = require(getScriptsPath('config-generator'));
|
|
|
|
|
|
PortChecker = require(getScriptsPath('port-checker'));
|
|
|
|
|
|
StartupManager = require(getScriptsPath('startup-manager'));
|
|
|
|
|
|
LogWindowManager = require(getScriptsPath('log-window-manager'));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-15 14:12:24 +08:00
|
|
|
|
|
|
|
|
|
|
class Lifecycle {
|
2025-10-16 20:14:55 +08:00
|
|
|
|
constructor() {
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.mysqlServiceManager = null;
|
2025-10-16 20:14:55 +08:00
|
|
|
|
this.javaRunner = null;
|
|
|
|
|
|
this.startupManager = null;
|
|
|
|
|
|
this.logWindowManager = null;
|
|
|
|
|
|
this.mysqlPort = null;
|
|
|
|
|
|
this.javaPort = null;
|
|
|
|
|
|
this.autoRefreshTimer = null;
|
|
|
|
|
|
}
|
2025-10-15 14:12:24 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* core app have been loaded
|
|
|
|
|
|
*/
|
|
|
|
|
|
async ready() {
|
|
|
|
|
|
logger.info('[lifecycle] ready');
|
2025-10-16 20:14:55 +08:00
|
|
|
|
// 延迟加载 scripts
|
2025-11-26 08:50:22 +08:00
|
|
|
|
loadScripts();
|
2025-10-16 20:14:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 完整的应用启动流程
|
|
|
|
|
|
*/
|
|
|
|
|
|
async startApplication() {
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.logWindowManager.addLog('system', '▶ 步骤1: 开始启动流程...');
|
|
|
|
|
|
logger.info('[lifecycle] Starting application...');
|
|
|
|
|
|
|
|
|
|
|
|
this.logWindowManager.addLog('system', '▶ 步骤2: 加载配置信息...');
|
2025-10-16 20:14:55 +08:00
|
|
|
|
const config = getConfig();
|
2025-11-26 08:50:22 +08:00
|
|
|
|
logger.info('[lifecycle] Config loaded:', JSON.stringify(config));
|
2025-10-16 20:14:55 +08:00
|
|
|
|
|
|
|
|
|
|
// 步骤1: 初始化
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.logWindowManager.addLog('system', '▶ 步骤3: 初始化启动管理器...');
|
2025-10-16 20:14:55 +08:00
|
|
|
|
this.startupManager.updateProgress('init');
|
|
|
|
|
|
await this.sleep(500);
|
|
|
|
|
|
|
2025-11-26 08:50:22 +08:00
|
|
|
|
// 步骤2-4: 确保 MySQL 服务运行
|
|
|
|
|
|
this.logWindowManager.addLog('system', '▶ 步骤4: 检查 MySQL 配置...');
|
|
|
|
|
|
logger.info('[lifecycle] MySQL config check - enable:', config.mysql?.enable, 'autoStart:', config.mysql?.autoStart);
|
2025-10-16 20:14:55 +08:00
|
|
|
|
|
|
|
|
|
|
if (config.mysql && config.mysql.enable && config.mysql.autoStart) {
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.startupManager.updateProgress('check-mysql-port');
|
|
|
|
|
|
this.logWindowManager.addLog('system', '▶ 步骤5: 启动 MySQL 服务管理器...');
|
2025-10-16 20:14:55 +08:00
|
|
|
|
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.mysqlServiceManager = new MySQLServiceManager(this.logWindowManager);
|
|
|
|
|
|
this.logWindowManager.addLog('system', '正在检查 MySQL 服务状态...');
|
2025-10-16 20:14:55 +08:00
|
|
|
|
|
2025-11-26 08:50:22 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 使用服务管理器确保MySQL服务运行
|
|
|
|
|
|
this.logWindowManager.addLog('system', '▶ 步骤6: 确保 MySQL 服务运行中...');
|
|
|
|
|
|
this.mysqlPort = await this.mysqlServiceManager.ensureServiceRunning(
|
|
|
|
|
|
PortChecker.findAvailablePort.bind(PortChecker),
|
|
|
|
|
|
PortChecker.waitForPort.bind(PortChecker)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(`[lifecycle] MySQL service running on port: ${this.mysqlPort}`);
|
|
|
|
|
|
this.logWindowManager.addLog('success', `✓ MySQL 服务已就绪,端口: ${this.mysqlPort}`);
|
|
|
|
|
|
this.startupManager.updateProgress('wait-mysql', { mysqlPort: this.mysqlPort });
|
|
|
|
|
|
await this.sleep(500);
|
|
|
|
|
|
} catch (error) {
|
2025-12-01 13:48:03 +08:00
|
|
|
|
logger.error('[lifecycle] MySQL error:', error);
|
|
|
|
|
|
this.logWindowManager.addLog('error', `MySQL 错误: ${error.message}`);
|
2025-11-26 08:50:22 +08:00
|
|
|
|
throw error;
|
2025-10-16 20:14:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤5: 检测 Java 端口
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.logWindowManager.addLog('system', '▶ 步骤7: 检测可用的 Java 端口(从18092开始)...');
|
2025-10-16 20:14:55 +08:00
|
|
|
|
this.startupManager.updateProgress('check-java-port', { mysqlPort: this.mysqlPort });
|
|
|
|
|
|
|
|
|
|
|
|
this.javaPort = await PortChecker.findAvailablePort(18092, 100);
|
|
|
|
|
|
|
|
|
|
|
|
if (this.javaPort === -1) {
|
|
|
|
|
|
this.logWindowManager.addLog('error', 'Java 端口检测失败:18092-18191 全部被占用');
|
|
|
|
|
|
throw new Error('无法找到可用的后端服务端口(18092-18191 全部被占用)');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 08:50:22 +08:00
|
|
|
|
// 步骤5.5: 检测 WebSocket 端口
|
|
|
|
|
|
this.logWindowManager.addLog('system', '▶ 步骤8: 检测可用的 WebSocket 端口(从7777开始)...');
|
|
|
|
|
|
|
|
|
|
|
|
this.websocketPort = await PortChecker.findAvailablePort(7777, 100);
|
|
|
|
|
|
|
|
|
|
|
|
if (this.websocketPort === -1) {
|
|
|
|
|
|
this.logWindowManager.addLog('error', 'WebSocket 端口检测失败:7777-7876 全部被占用');
|
|
|
|
|
|
throw new Error('无法找到可用的 WebSocket 端口(7777-7876 全部被占用)');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 20:14:55 +08:00
|
|
|
|
if (this.javaPort !== 18092) {
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.logWindowManager.addLog('warn', `⚠ Java 默认端口 18092 已被占用,自动切换到端口: ${this.javaPort}`);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.logWindowManager.addLog('success', `✓ Java 将使用默认端口: ${this.javaPort}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.websocketPort !== 7777) {
|
|
|
|
|
|
this.logWindowManager.addLog('warn', `⚠ WebSocket 默认端口 7777 已被占用,自动切换到端口: ${this.websocketPort}`);
|
2025-10-16 20:14:55 +08:00
|
|
|
|
} else {
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.logWindowManager.addLog('success', `✓ WebSocket 将使用默认端口: ${this.websocketPort}`);
|
2025-10-16 20:14:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(`[lifecycle] Spring Boot will use port: ${this.javaPort}`);
|
2025-11-26 08:50:22 +08:00
|
|
|
|
logger.info(`[lifecycle] WebSocket will use port: ${this.websocketPort}`);
|
2025-10-16 20:14:55 +08:00
|
|
|
|
this.startupManager.updateProgress('check-java-port', {
|
|
|
|
|
|
mysqlPort: this.mysqlPort,
|
|
|
|
|
|
javaPort: this.javaPort
|
|
|
|
|
|
});
|
|
|
|
|
|
await this.sleep(500);
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤6: 生成配置文件
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.logWindowManager.addLog('system', '▶ 步骤9: 生成 Spring Boot 配置文件...');
|
2025-10-16 20:14:55 +08:00
|
|
|
|
this.startupManager.updateProgress('generate-config', {
|
|
|
|
|
|
mysqlPort: this.mysqlPort,
|
2025-11-26 08:50:22 +08:00
|
|
|
|
javaPort: this.javaPort,
|
|
|
|
|
|
websocketPort: this.websocketPort
|
2025-10-16 20:14:55 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const configGenerator = new ConfigGenerator();
|
|
|
|
|
|
const { configPath, dataPath } = await configGenerator.generateConfig({
|
|
|
|
|
|
mysqlPort: this.mysqlPort,
|
|
|
|
|
|
javaPort: this.javaPort,
|
2025-11-26 08:50:22 +08:00
|
|
|
|
websocketPort: this.websocketPort,
|
2025-10-16 20:14:55 +08:00
|
|
|
|
mysqlPassword: 'njcnpqs'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(`[lifecycle] Configuration generated at: ${configPath}`);
|
|
|
|
|
|
logger.info(`[lifecycle] Data directory: ${dataPath}`);
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.logWindowManager.addLog('success', `✓ 配置文件已生成: ${configPath}`);
|
|
|
|
|
|
this.logWindowManager.addLog('system', ` 数据目录: ${dataPath}`);
|
2025-10-16 20:14:55 +08:00
|
|
|
|
this.startupManager.updateProgress('generate-config', {
|
|
|
|
|
|
mysqlPort: this.mysqlPort,
|
|
|
|
|
|
javaPort: this.javaPort,
|
2025-11-26 08:50:22 +08:00
|
|
|
|
websocketPort: this.websocketPort,
|
2025-10-16 20:14:55 +08:00
|
|
|
|
dataPath: dataPath
|
|
|
|
|
|
});
|
|
|
|
|
|
await this.sleep(500);
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤7: 启动 Spring Boot
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.logWindowManager.addLog('system', '▶ 步骤10: 启动 Spring Boot 应用...');
|
2025-10-16 20:14:55 +08:00
|
|
|
|
this.startupManager.updateProgress('start-java', {
|
|
|
|
|
|
mysqlPort: this.mysqlPort,
|
|
|
|
|
|
javaPort: this.javaPort,
|
|
|
|
|
|
dataPath: dataPath
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
await this.startSpringBoot(configPath);
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤8: 等待 Spring Boot 就绪
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.logWindowManager.addLog('system', '▶ 步骤11: 等待 Spring Boot 就绪(最多60秒)...');
|
2025-10-16 20:14:55 +08:00
|
|
|
|
this.startupManager.updateProgress('wait-java', {
|
|
|
|
|
|
mysqlPort: this.mysqlPort,
|
|
|
|
|
|
javaPort: this.javaPort,
|
|
|
|
|
|
dataPath: dataPath
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const javaReady = await PortChecker.waitForPort(this.javaPort, 60000);
|
|
|
|
|
|
|
|
|
|
|
|
if (!javaReady) {
|
|
|
|
|
|
logger.warn(`[lifecycle] Spring Boot 启动超时,但继续启动应用`);
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.logWindowManager.addLog('warn', '⚠ Spring Boot 启动超时(60秒),但应用将继续启动');
|
2025-10-16 20:14:55 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
logger.info('[lifecycle] Spring Boot is ready');
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.logWindowManager.addLog('success', '✓ Spring Boot 启动成功!');
|
2025-10-16 20:14:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await this.sleep(1000);
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤9: 完成
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.logWindowManager.addLog('system', '▶ 步骤12: 启动完成,准备显示主窗口...');
|
2025-10-16 20:14:55 +08:00
|
|
|
|
this.startupManager.updateProgress('done', {
|
|
|
|
|
|
mysqlPort: this.mysqlPort,
|
|
|
|
|
|
javaPort: this.javaPort,
|
|
|
|
|
|
dataPath: dataPath
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
logger.info('[lifecycle] Application startup completed');
|
|
|
|
|
|
this.logWindowManager.addLog('system', '='.repeat(60));
|
|
|
|
|
|
this.logWindowManager.addLog('success', '✓ NPQS9100 启动完成!所有服务正常运行');
|
|
|
|
|
|
this.logWindowManager.addLog('system', `✓ MySQL 端口: ${this.mysqlPort}`);
|
|
|
|
|
|
this.logWindowManager.addLog('system', `✓ Java 端口: ${this.javaPort}`);
|
2025-11-26 08:50:22 +08:00
|
|
|
|
this.logWindowManager.addLog('system', `✓ WebSocket 端口: ${this.websocketPort}`);
|
2025-10-16 20:14:55 +08:00
|
|
|
|
this.logWindowManager.addLog('system', `✓ 数据目录: ${dataPath}`);
|
|
|
|
|
|
this.logWindowManager.addLog('system', '='.repeat(60));
|
|
|
|
|
|
this.logWindowManager.addLog('system', '应用即将启动...');
|
|
|
|
|
|
|
|
|
|
|
|
// 先关闭 Loading 窗口
|
|
|
|
|
|
this.startupManager.closeLoadingWindow();
|
|
|
|
|
|
|
|
|
|
|
|
// 等待3秒,让用户看清日志信息,然后再显示主窗口
|
|
|
|
|
|
await this.sleep(3000);
|
|
|
|
|
|
|
|
|
|
|
|
// 显示主窗口
|
|
|
|
|
|
const win = getMainWindow();
|
|
|
|
|
|
win.show();
|
|
|
|
|
|
win.focus();
|
2025-11-27 17:10:08 +08:00
|
|
|
|
|
|
|
|
|
|
// 窗口关闭时,使用 Electron 原生对话框确认
|
2025-11-26 08:50:22 +08:00
|
|
|
|
win.on('close', async (event) => {
|
2025-11-27 17:10:08 +08:00
|
|
|
|
if (global.isConfirmedExit) {
|
|
|
|
|
|
logger.info('[lifecycle] Exit already confirmed, proceeding');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 08:50:22 +08:00
|
|
|
|
event.preventDefault();
|
2025-11-27 17:10:08 +08:00
|
|
|
|
logger.info('[lifecycle] Window close intercepted, showing exit confirm dialog');
|
|
|
|
|
|
|
2025-11-26 08:50:22 +08:00
|
|
|
|
const { dialog } = require('electron');
|
2025-11-27 17:10:08 +08:00
|
|
|
|
const { response } = await dialog.showMessageBox(win, {
|
2025-11-26 08:50:22 +08:00
|
|
|
|
type: 'question',
|
2025-11-27 17:10:08 +08:00
|
|
|
|
buttons: ['取消', '退出'],
|
|
|
|
|
|
defaultId: 1,
|
|
|
|
|
|
cancelId: 0,
|
2025-11-26 08:50:22 +08:00
|
|
|
|
title: '退出确认',
|
|
|
|
|
|
message: '确定要退出应用吗?',
|
2025-11-27 17:10:08 +08:00
|
|
|
|
noLink: true
|
2025-11-26 08:50:22 +08:00
|
|
|
|
});
|
2025-11-27 17:10:08 +08:00
|
|
|
|
|
|
|
|
|
|
if (response === 1) {
|
2025-11-26 08:50:22 +08:00
|
|
|
|
logger.info('[lifecycle] User confirmed exit');
|
2025-11-27 17:10:08 +08:00
|
|
|
|
global.isConfirmedExit = true;
|
2025-11-26 08:50:22 +08:00
|
|
|
|
await this.cleanup();
|
2025-11-27 17:10:08 +08:00
|
|
|
|
if (win && !win.isDestroyed()) {
|
|
|
|
|
|
win.destroy();
|
|
|
|
|
|
}
|
2025-11-26 08:50:22 +08:00
|
|
|
|
app.quit();
|
2025-11-27 17:10:08 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
logger.info('[lifecycle] User cancelled exit');
|
2025-11-26 08:50:22 +08:00
|
|
|
|
}
|
2025-10-16 20:14:55 +08:00
|
|
|
|
});
|
2025-11-27 17:10:08 +08:00
|
|
|
|
|
2025-10-16 20:14:55 +08:00
|
|
|
|
// 立即刷新一次,确保显示最新内容
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
if (win && !win.isDestroyed()) {
|
|
|
|
|
|
logger.info('[lifecycle] Reloading main window to ensure fresh content');
|
|
|
|
|
|
this.logWindowManager.addLog('system', '正在加载应用界面...');
|
|
|
|
|
|
win.reload();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 500);
|
|
|
|
|
|
|
|
|
|
|
|
// 设置主窗口加载超时自动刷新(30秒)
|
|
|
|
|
|
this.setupAutoRefresh(win);
|
|
|
|
|
|
|
|
|
|
|
|
// 提示用户
|
|
|
|
|
|
this.logWindowManager.addLog('system', '主应用已打开!此日志窗口可随时关闭');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置主窗口自动刷新机制
|
|
|
|
|
|
*/
|
|
|
|
|
|
setupAutoRefresh(win) {
|
|
|
|
|
|
// 30秒后检查页面是否还在loading,如果是则刷新
|
|
|
|
|
|
this.autoRefreshTimer = setTimeout(() => {
|
|
|
|
|
|
if (win && !win.isDestroyed()) {
|
|
|
|
|
|
// 检查页面是否还在loading状态
|
|
|
|
|
|
win.webContents.executeJavaScript('document.readyState').then(state => {
|
|
|
|
|
|
logger.info(`[lifecycle] Page readyState after 30s: ${state}`);
|
|
|
|
|
|
if (state !== 'complete') {
|
|
|
|
|
|
logger.warn('[lifecycle] Page still loading after 30s, reloading...');
|
|
|
|
|
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
|
|
|
|
|
this.logWindowManager.addLog('warn', '页面加载超时,自动刷新...');
|
|
|
|
|
|
}
|
|
|
|
|
|
win.reload();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
logger.info('[lifecycle] Page loaded successfully');
|
|
|
|
|
|
}
|
|
|
|
|
|
}).catch(err => {
|
|
|
|
|
|
logger.error('[lifecycle] Failed to check page state:', err);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 30000); // 30秒超时
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 清理所有资源
|
|
|
|
|
|
*/
|
|
|
|
|
|
async cleanup() {
|
|
|
|
|
|
logger.info('[lifecycle] Starting cleanup...');
|
|
|
|
|
|
|
|
|
|
|
|
// 清除自动刷新定时器
|
|
|
|
|
|
if (this.autoRefreshTimer) {
|
|
|
|
|
|
clearTimeout(this.autoRefreshTimer);
|
|
|
|
|
|
this.autoRefreshTimer = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 在日志窗口显示清理信息(在关闭之前)
|
|
|
|
|
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
|
|
|
|
|
this.logWindowManager.addLog('system', '='.repeat(60));
|
|
|
|
|
|
this.logWindowManager.addLog('system', '应用正在关闭,清理资源中...');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭 Loading 窗口
|
|
|
|
|
|
if (this.startupManager) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.startupManager.closeLoadingWindow();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 忽略
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 停止 Spring Boot
|
|
|
|
|
|
if (this.javaRunner) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
logger.info('[lifecycle] Stopping Spring Boot...');
|
|
|
|
|
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
|
|
|
|
|
this.logWindowManager.addLog('system', '正在停止 Spring Boot...');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 08:50:22 +08:00
|
|
|
|
// 停止 Java 进程(内部已有完整的等待和清理逻辑)
|
|
|
|
|
|
await this.javaRunner.stopSpringBoot();
|
2025-10-16 20:14:55 +08:00
|
|
|
|
|
|
|
|
|
|
logger.info('[lifecycle] Spring Boot stopped');
|
|
|
|
|
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
|
|
|
|
|
this.logWindowManager.addLog('success', 'Spring Boot 已停止');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logger.error('[lifecycle] Failed to stop Spring Boot:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-01 13:48:03 +08:00
|
|
|
|
// 停止 MySQL 进程(进程模式)
|
|
|
|
|
|
if (this.mysqlServiceManager) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
logger.info('[lifecycle] Stopping MySQL process...');
|
|
|
|
|
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
|
|
|
|
|
this.logWindowManager.addLog('system', '正在停止 MySQL...');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await this.mysqlServiceManager.stopMySQLProcess();
|
|
|
|
|
|
|
|
|
|
|
|
logger.info('[lifecycle] MySQL process stopped');
|
|
|
|
|
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
|
|
|
|
|
this.logWindowManager.addLog('success', 'MySQL 已停止');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logger.error('[lifecycle] Failed to stop MySQL:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 08:50:22 +08:00
|
|
|
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
|
|
|
|
|
this.logWindowManager.addLog('system', '清理完成,应用即将退出');
|
|
|
|
|
|
this.logWindowManager.addLog('system', '='.repeat(60));
|
2025-10-16 20:14:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 等待500ms让用户看到清理日志
|
|
|
|
|
|
await this.sleep(500);
|
|
|
|
|
|
|
|
|
|
|
|
// 最后关闭日志窗口
|
|
|
|
|
|
if (this.logWindowManager) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
logger.info('[lifecycle] Closing log window...');
|
|
|
|
|
|
this.logWindowManager.close();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logger.error('[lifecycle] Failed to close log window:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.info('[lifecycle] Cleanup completed');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 启动 Spring Boot 应用
|
|
|
|
|
|
*/
|
|
|
|
|
|
async startSpringBoot(configPath) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
logger.info('[lifecycle] Starting Spring Boot application...');
|
|
|
|
|
|
this.logWindowManager.addLog('java', '正在启动 Spring Boot 应用...');
|
|
|
|
|
|
|
|
|
|
|
|
// 启动Java应用
|
|
|
|
|
|
this.javaRunner = new JavaRunner();
|
|
|
|
|
|
|
|
|
|
|
|
// 开发环境:build/extraResources/java/entrance.jar
|
|
|
|
|
|
// 打包后:resources/extraResources/java/entrance.jar
|
|
|
|
|
|
const isDev = !process.resourcesPath;
|
|
|
|
|
|
const jarPath = isDev
|
|
|
|
|
|
? path.join(__dirname, '..', 'build', 'extraResources', 'java', 'entrance.jar')
|
|
|
|
|
|
: path.join(process.resourcesPath, 'extraResources', 'java', 'entrance.jar');
|
|
|
|
|
|
|
|
|
|
|
|
const javaProcess = this.javaRunner.runSpringBoot(jarPath, configPath, {
|
|
|
|
|
|
javaPort: this.javaPort
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 监听Java进程输出
|
|
|
|
|
|
javaProcess.stdout.on('data', (data) => {
|
|
|
|
|
|
const output = data.toString().trim();
|
|
|
|
|
|
if (output) {
|
|
|
|
|
|
logger.info('[SpringBoot]', output);
|
|
|
|
|
|
|
|
|
|
|
|
// 根据内容判断日志类型
|
|
|
|
|
|
if (output.includes('ERROR') || output.includes('Exception')) {
|
|
|
|
|
|
this.logWindowManager.addLog('error', `[Java] ${output}`);
|
|
|
|
|
|
} else if (output.includes('WARN')) {
|
|
|
|
|
|
this.logWindowManager.addLog('warn', `[Java] ${output}`);
|
|
|
|
|
|
} else if (output.includes('Started') || output.includes('Completed')) {
|
|
|
|
|
|
this.logWindowManager.addLog('success', `[Java] ${output}`);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.logWindowManager.addLog('java', `[Java] ${output}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
javaProcess.stderr.on('data', (data) => {
|
|
|
|
|
|
const output = data.toString().trim();
|
|
|
|
|
|
if (output) {
|
|
|
|
|
|
logger.error('[SpringBoot]', output);
|
|
|
|
|
|
this.logWindowManager.addLog('error', `[Java Error] ${output}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
javaProcess.on('close', (code) => {
|
|
|
|
|
|
logger.info(`[SpringBoot] Process exited with code ${code}`);
|
|
|
|
|
|
this.logWindowManager.addLog('system', `Spring Boot 进程退出,代码: ${code}`);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
logger.info('[lifecycle] Spring Boot application started');
|
|
|
|
|
|
this.logWindowManager.addLog('success', 'Spring Boot 应用已启动!');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logger.error('[lifecycle] Failed to start Spring Boot:', error);
|
|
|
|
|
|
this.logWindowManager.addLog('error', `Spring Boot 启动失败: ${error.message}`);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 睡眠函数
|
|
|
|
|
|
*/
|
|
|
|
|
|
sleep(ms) {
|
|
|
|
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
2025-10-15 14:12:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* electron app ready
|
|
|
|
|
|
*/
|
|
|
|
|
|
async electronAppReady() {
|
|
|
|
|
|
logger.info('[lifecycle] electron-app-ready');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* main window have been loaded
|
|
|
|
|
|
*/
|
|
|
|
|
|
async windowReady() {
|
2025-11-26 08:50:22 +08:00
|
|
|
|
logger.info('[lifecycle] window-ready hook triggered');
|
|
|
|
|
|
|
2025-12-01 13:48:03 +08:00
|
|
|
|
// 进程模式不需要管理员权限检查
|
2025-11-27 17:10:08 +08:00
|
|
|
|
|
2025-11-26 08:50:22 +08:00
|
|
|
|
// 创建日志管理器(但不显示窗口,仅用于写日志文件)
|
|
|
|
|
|
logger.info('[lifecycle] Creating log window manager...');
|
|
|
|
|
|
this.logWindowManager = new LogWindowManager();
|
|
|
|
|
|
// this.logWindowManager.createLogWindow(); // ← 注释掉,不创建窗口
|
|
|
|
|
|
this.logWindowManager.addLog('system', '='.repeat(80));
|
|
|
|
|
|
this.logWindowManager.addLog('system', 'NPQS9100 应用启动');
|
|
|
|
|
|
this.logWindowManager.addLog('system', '='.repeat(80));
|
|
|
|
|
|
|
|
|
|
|
|
// 创建 Loading 窗口
|
|
|
|
|
|
logger.info('[lifecycle] Creating startup manager and loading window...');
|
|
|
|
|
|
this.startupManager = new StartupManager();
|
|
|
|
|
|
this.startupManager.createLoadingWindow();
|
|
|
|
|
|
this.logWindowManager.addLog('system', '='.repeat(60));
|
|
|
|
|
|
this.logWindowManager.addLog('system', 'NPQS9100 启动中...');
|
|
|
|
|
|
this.logWindowManager.addLog('system', '='.repeat(60));
|
2025-10-16 20:14:55 +08:00
|
|
|
|
|
2025-11-26 08:50:22 +08:00
|
|
|
|
// 开始启动流程
|
|
|
|
|
|
logger.info('[lifecycle] Starting application flow...');
|
|
|
|
|
|
try {
|
|
|
|
|
|
await this.startApplication();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logger.error('[lifecycle] Failed to start application:', error);
|
|
|
|
|
|
this.logWindowManager.addLog('error', `启动失败: ${error.message}`);
|
|
|
|
|
|
this.logWindowManager.addLog('system', '请检查日志窗口了解详细错误信息');
|
|
|
|
|
|
this.startupManager.showError(error.message || '启动失败,请查看日志');
|
|
|
|
|
|
|
|
|
|
|
|
// 显示错误5秒后关闭Loading窗口,但不关闭日志窗口
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.startupManager.closeLoadingWindow();
|
|
|
|
|
|
|
|
|
|
|
|
// 即使启动失败,也显示主窗口(但用户可能需要手动修复问题)
|
|
|
|
|
|
const win = getMainWindow();
|
|
|
|
|
|
win.show();
|
|
|
|
|
|
win.focus();
|
|
|
|
|
|
|
|
|
|
|
|
// 启动失败时,允许用户正常关闭窗口(不强制托盘)
|
|
|
|
|
|
// 因为可能托盘未创建,或用户想直接退出
|
|
|
|
|
|
win.on('close', async (event) => {
|
|
|
|
|
|
logger.info('[lifecycle] Window closing (after error), cleaning up...');
|
|
|
|
|
|
// 不阻止关闭,执行清理
|
|
|
|
|
|
await this.cleanup();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.logWindowManager.addLog('warn', '应用已启动,但部分服务可能未正常运行');
|
|
|
|
|
|
this.logWindowManager.addLog('system', '您可以点击 X 关闭应用');
|
|
|
|
|
|
}, 5000);
|
2025-10-15 14:12:24 +08:00
|
|
|
|
}
|
2025-10-16 20:14:55 +08:00
|
|
|
|
|
|
|
|
|
|
// 主窗口初始化但不显示,等待启动流程完成后再显示
|
|
|
|
|
|
// 在 startApplication() 中会调用 win.show()
|
2025-10-15 14:12:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-16 20:14:55 +08:00
|
|
|
|
* before app close (框架生命周期钩子)
|
2025-10-15 14:12:24 +08:00
|
|
|
|
*/
|
|
|
|
|
|
async beforeClose() {
|
2025-10-16 20:14:55 +08:00
|
|
|
|
logger.info('[lifecycle] before-close hook triggered');
|
2025-11-26 08:50:22 +08:00
|
|
|
|
await this.cleanup();
|
2025-10-15 14:12:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
Lifecycle.toString = () => '[class Lifecycle]';
|
|
|
|
|
|
|
2025-10-16 20:14:55 +08:00
|
|
|
|
// 导出实例而不是类
|
|
|
|
|
|
module.exports = new Lifecycle();
|