初始化

This commit is contained in:
2026-04-13 17:32:58 +08:00
commit c6ee0d5243
1342 changed files with 96426 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
'use strict';
const path = require('path');
const {getBaseDir} = require('ee-core/ps');
/**
* 默认配置
*/
module.exports = () => {
return {
openDevTools: false,
singleLock: true,
windowsOption: {
title: 'CN_Tool 灿能运维工具',
menuBarVisible: false, // 隐藏菜单栏
width: 1920,
height: 1000,
minWidth: 1024,
minHeight: 640,
webPreferences: {
//webSecurity: false,
contextIsolation: false, // false -> 可在渲染进程中使用electron的apitrue->需要bridge.js(contextBridge)
nodeIntegration: true,
//preload: path.join(getElectronDir(), 'preload', 'bridge.js'),
},
frame: true,
show: false, // 初始不显示,等待服务启动完成后再显示
icon: path.join(getBaseDir(), 'public', 'images', 'logo-32.png'),
},
logger: {
level: 'INFO',
outputJSON: false,
appLogName: '9100.log',
coreLogName: '9100-core.log',
errorLogName: '9100-error.log'
},
// 远程web地址
remote: {
enable: false,
url: ''
},
socketServer: {
enable: false,
port: 7070,
path: "/socket.io/",
connectTimeout: 45000,
pingTimeout: 30000,
pingInterval: 25000,
maxHttpBufferSize: 1e8,
transports: ["polling", "websocket"],
cors: {
origin: true,
},
channel: 'socket-channel'
},
httpServer: {
enable: false,
https: {
enable: false,
key: '/public/ssl/localhost+1.key',
cert: '/public/ssl/localhost+1.pem'
},
host: '127.0.0.1',
port: 7071,
},
mainServer: {
indexPath: '/public/dist/index.html',
channelSeparator: '/',
},
// MySQL配置
mysql: {
enable: true,
autoStart: true, // 应用启动时自动启动MySQL
path: path.join(getBaseDir(), 'mysql'),
connection: {
host: 'localhost',
port: 3306,
user: 'root',
password: 'njcnpqs',
database: 'npqs9100_db',
charset: 'utf8mb4'
}
}
}
}

View File

@@ -0,0 +1,13 @@
'use strict';
/**
* Development environment configuration, coverage config.default.js
*/
module.exports = () => {
return {
openDevTools: false,
jobs: {
messageLog: false
}
};
};

View File

@@ -0,0 +1,10 @@
'use strict';
/**
* coverage config.default.js
*/
module.exports = () => {
return {
openDevTools: false,
};
};

View File

@@ -0,0 +1,30 @@
'use strict';
const { logger } = require('ee-core/log');
const { exampleService } = require('../service/example');
/**
* example
* @class
*/
class ExampleController {
/**
* 所有方法接收两个参数
* @param args 前端传的参数
* @param event - ipc通信时才有值。详情见控制器文档
*/
/**
* test
*/
async test () {
const result = await exampleService.test('electron');
logger.info('service result:', result);
return 'hello electron-egg';
}
}
ExampleController.toString = () => '[class ExampleController]';
module.exports = ExampleController;

100
electron/controller/java.js Normal file
View File

@@ -0,0 +1,100 @@
const path = require('path');
// 动态获取 scripts 目录路径
function getScriptsPath(scriptName) {
// 开发环境
const devPath = path.join(__dirname, '../../scripts', scriptName);
// 生产环境(打包后)
const prodPath = path.join(process.resourcesPath, 'scripts', scriptName);
try {
// 先尝试开发环境路径
require.resolve(devPath);
return devPath;
} catch (e) {
// 如果开发环境路径不存在,使用生产环境路径
return prodPath;
}
}
// 延迟加载 JavaRunner
let JavaRunner = null;
function getJavaRunner() {
if (!JavaRunner) {
JavaRunner = require(getScriptsPath('java-runner'));
}
return JavaRunner;
}
/**
* Java 控制器 - 提供 JRE 管理和 Java 程序调用接口
*/
class JavaController {
/**
* 检查 JRE 是否可用
*/
async checkAvailability() {
try {
const JavaRunnerClass = getJavaRunner();
const javaRunner = new JavaRunnerClass();
const available = javaRunner.isJREAvailable();
return { success: true, data: { available } };
} catch (error) {
return { success: false, message: error.message };
}
}
/**
* 获取 Java 版本
*/
async getVersion() {
try {
const JavaRunnerClass = getJavaRunner();
const javaRunner = new JavaRunnerClass();
const version = await javaRunner.getVersion();
return { success: true, data: { version } };
} catch (error) {
return { success: false, message: error.message };
}
}
/**
* 获取 JRE 路径信息
*/
async getPathInfo() {
try {
const JavaRunnerClass = getJavaRunner();
const javaRunner = new JavaRunnerClass();
const pathInfo = javaRunner.getPathInfo();
return { success: true, data: pathInfo };
} catch (error) {
return { success: false, message: error.message };
}
}
/**
* 运行 JAR 文件(异步,等待完成)
* @param {Object} params - { jarPath, args }
*/
async runJar(params) {
const { jarPath, args = [] } = params;
if (!jarPath) {
return { success: false, message: 'JAR path is required' };
}
try {
const JavaRunnerClass = getJavaRunner();
const javaRunner = new JavaRunnerClass();
const exitCode = await javaRunner.runJarAsync(jarPath, args);
return { success: true, data: { exitCode } };
} catch (error) {
return { success: false, message: error.message };
}
}
}
JavaController.toString = () => '[class JavaController]';
module.exports = JavaController;

278
electron/main.js Normal file
View File

@@ -0,0 +1,278 @@
const { ElectronEgg } = require('ee-core');
const { app, Menu, ipcMain, Tray, dialog, BrowserWindow } = require('electron');
const path = require('path');
const lifecycle = require('./preload/lifecycle');
const { preload } = require('./preload');
const APP_DISPLAY_NAME = 'CN_Tool 灿能运维工具';
// new app
const electronApp = new ElectronEgg();
// 全局变量
let tray = null;
let isQuitting = false;
/**
* 统一定位当前应用自己的主窗口,避免继续依赖旧品牌关键字。
* 优先按当前主窗口标题精确匹配;如果主窗口尚未完全就绪,再回退到顶层可用窗口。
*/
function findAppMainWindow() {
const windows = BrowserWindow.getAllWindows().filter(win => !win.isDestroyed());
return windows.find(win => win.getTitle() === APP_DISPLAY_NAME)
|| windows.find(win => !win.getParentWindow())
|| null;
}
// 创建系统托盘
function createTray() {
try {
// 开发环境和生产环境的图标路径
const isDev = !process.resourcesPath;
const iconPath = isDev
? path.join(__dirname, '..', 'public', 'images', 'tray.png')
: path.join(process.resourcesPath, 'app.asar.unpacked', 'public', 'images', 'tray.png');
console.log('[Tray] Icon path:', iconPath);
// 检查图标文件是否存在
const fs = require('fs');
if (!fs.existsSync(iconPath)) {
console.error('[Tray] Icon file not found:', iconPath);
// 如果找不到,尝试使用备用路径(主图标)
const fallbackIcon = isDev
? path.join(__dirname, '..', 'public', 'images', 'icon.png')
: path.join(process.resourcesPath, 'app.asar.unpacked', 'public', 'images', 'icon.png');
if (fs.existsSync(fallbackIcon)) {
console.log('[Tray] Using fallback icon:', fallbackIcon);
tray = new Tray(fallbackIcon);
} else {
console.error('[Tray] No icon available, tray not created');
return;
}
} else {
tray = new Tray(iconPath);
}
tray.setToolTip(APP_DISPLAY_NAME);
console.log('[Tray] Tray created successfully');
// 创建托盘菜单
const contextMenu = Menu.buildFromTemplate([
{
label: '显示主窗口',
click: () => {
const mainWindow = findAppMainWindow();
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.show();
mainWindow.focus();
}
}
},
{ type: 'separator' },
{
label: '退出',
click: async () => {
// 弹出确认对话框
const { response } = await dialog.showMessageBox({
type: 'question',
title: '退出确认',
message: '确定退出应用吗?',
buttons: ['取消', '确定退出'],
defaultId: 0,
cancelId: 0
});
if (response === 1) {
// 用户点击了"确定退出"
isQuitting = true;
// 获取主窗口
const mainWindow = findAppMainWindow();
// 移除所有 close 监听器,避免阻止关闭
if (mainWindow) {
mainWindow.removeAllListeners('close');
}
// 执行清理
await lifecycle.cleanup();
// 退出应用
app.quit();
}
}
}
]);
tray.setContextMenu(contextMenu);
// 双击托盘图标显示窗口
tray.on('double-click', () => {
const mainWindow = findAppMainWindow();
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.show();
mainWindow.focus();
}
});
} catch (error) {
console.error('[Tray] Failed to create tray:', error);
tray = null;
}
}
// 创建应用菜单
function createApplicationMenu() {
// 生产环境隐藏菜单栏
Menu.setApplicationMenu(null);
// 调试时可以使用下面的菜单(取消注释)
/*
const template = [
{
label: '查看',
submenu: [
{ role: 'reload', label: '刷新' },
{ role: 'forceReload', label: '强制刷新' },
{ type: 'separator' },
{ role: 'toggleDevTools', label: '开发者工具' }
]
},
{
label: '帮助',
submenu: [
{
label: '使用说明',
click: () => {
// 可以打开帮助文档
}
},
{ type: 'separator' },
{
label: '关于',
click: () => {
// 可以显示关于信息
}
}
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
*/
}
// 注册 IPC 处理器
ipcMain.handle('show-log-window', () => {
if (lifecycle.logWindowManager) {
lifecycle.logWindowManager.show();
}
});
ipcMain.handle('hide-log-window', () => {
if (lifecycle.logWindowManager) {
lifecycle.logWindowManager.hide();
}
});
ipcMain.handle('toggle-log-window', () => {
if (lifecycle.logWindowManager) {
lifecycle.logWindowManager.toggle();
}
});
// 检查是否正在退出
ipcMain.handle('is-quitting', () => {
return isQuitting;
});
// 处理单实例:当尝试启动第二个实例时,聚焦已有窗口
app.on('second-instance', (event, commandLine, workingDirectory) => {
console.log('[Main] Second instance detected, focusing main window...');
// 单实例激活时只聚焦当前应用自己的主窗口,避免误用历史命名判断。
const mainWindow = findAppMainWindow();
if (mainWindow) {
// 如果窗口最小化,恢复它
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
// 如果窗口隐藏,显示它
if (!mainWindow.isVisible()) {
mainWindow.show();
}
// 聚焦窗口
mainWindow.focus();
console.log('[Main] Main window focused');
} else {
console.warn('[Main] Main window not found');
}
});
// 监听应用退出前事件(确保清理托盘)
app.on('before-quit', () => {
console.log('[Main] App before-quit, destroying tray...');
// 销毁托盘图标
if (tray && !tray.isDestroyed()) {
tray.destroy();
tray = null;
}
});
// 监听 will-quit 事件(强制退出时)
app.on('will-quit', () => {
console.log('[Main] App will-quit');
// 确保托盘被销毁
if (tray && !tray.isDestroyed()) {
tray.destroy();
tray = null;
}
});
// register lifecycle (绑定 this 上下文)
electronApp.register("ready", lifecycle.ready.bind(lifecycle));
electronApp.register("electron-app-ready", () => {
lifecycle.electronAppReady.bind(lifecycle)();
createApplicationMenu();
createTray(); // 创建系统托盘
});
electronApp.register("window-ready", lifecycle.windowReady.bind(lifecycle));
electronApp.register("before-close", async () => {
// 如果不是真正退出,不执行清理
if (!isQuitting) {
console.log('[Main] Not quitting, skip cleanup');
return;
}
console.log('[Main] Quitting, execute cleanup');
// 销毁托盘图标
if (tray && !tray.isDestroyed()) {
tray.destroy();
tray = null;
}
await lifecycle.beforeClose.bind(lifecycle)();
});
// register preload
electronApp.register("preload", preload);
// run
electronApp.run();

View File

@@ -0,0 +1,14 @@
/*
* 如果启用了上下文隔离渲染进程无法使用electron的api
* 可通过contextBridge 导出api给渲染进程使用
*/
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electron', {
ipcRenderer: ipcRenderer,
// 日志窗口控制
showLogWindow: () => ipcRenderer.invoke('show-log-window'),
hideLogWindow: () => ipcRenderer.invoke('hide-log-window'),
toggleLogWindow: () => ipcRenderer.invoke('toggle-log-window'),
})

16
electron/preload/index.js Normal file
View File

@@ -0,0 +1,16 @@
/*************************************************
** preload为预加载模块该文件将会在程序启动时加载 **
*************************************************/
const { logger } = require('ee-core/log');
function preload() {
logger.info('[preload] load 1');
}
/**
* 预加载模块入口
*/
module.exports = {
preload
}

View File

@@ -0,0 +1,570 @@
'use strict';
const path = require('path');
const { logger } = require('ee-core/log');
const { getConfig } = require('ee-core/config');
const { getMainWindow } = require('ee-core/electron');
const { getBaseDir } = require('ee-core/ps');
const { app } = require('electron');
// 动态获取 scripts 目录路径
function getScriptsPath(scriptName) {
const fs = require('fs');
// 判断是否是打包后的环境
// 只要 process.resourcesPath 存在,就是打包后的环境(无论在哪个目录)
const isProd = !!process.resourcesPath;
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
let MySQLProcessManager, JavaRunner, ConfigGenerator, PortChecker, StartupManager, LogWindowManager;
function loadScripts() {
if (!MySQLProcessManager) {
MySQLProcessManager = require(getScriptsPath('mysql-process-manager'));
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'));
}
}
class Lifecycle {
constructor() {
this.mysqlProcessManager = null;
this.javaRunner = null;
this.startupManager = null;
this.logWindowManager = null;
this.mysqlPort = null;
this.javaPort = null;
this.autoRefreshTimer = null;
}
/**
* core app have been loaded
*/
async ready() {
logger.info('[lifecycle] ready');
// 延迟加载 scripts
loadScripts();
}
/**
* 完整的应用启动流程
*/
async startApplication() {
this.logWindowManager.addLog('system', '▶ 步骤1: 开始启动流程...');
logger.info('[lifecycle] Starting application...');
this.logWindowManager.addLog('system', '▶ 步骤2: 加载配置信息...');
const config = getConfig();
logger.info('[lifecycle] Config loaded:', JSON.stringify(config));
// 步骤1: 初始化
this.logWindowManager.addLog('system', '▶ 步骤3: 初始化启动管理器...');
this.startupManager.updateProgress('init');
await this.sleep(500);
// 步骤2-4: 确保 MySQL 进程运行
this.logWindowManager.addLog('system', '▶ 步骤4: 检查 MySQL 配置...');
logger.info('[lifecycle] MySQL config check - enable:', config.mysql?.enable, 'autoStart:', config.mysql?.autoStart);
if (config.mysql && config.mysql.enable && config.mysql.autoStart) {
this.startupManager.updateProgress('check-mysql-port');
this.logWindowManager.addLog('system', '▶ 步骤5: 启动 MySQL 进程管理器...');
this.mysqlProcessManager = new MySQLProcessManager(this.logWindowManager);
this.logWindowManager.addLog('system', '正在检查 MySQL 进程状态...');
try {
// 使用进程管理器确保 MySQL 进程运行
this.logWindowManager.addLog('system', '▶ 步骤6: 确保 MySQL 进程运行中...');
this.mysqlPort = await this.mysqlProcessManager.ensureServiceRunning(
PortChecker.findAvailablePort.bind(PortChecker),
PortChecker.waitForPort.bind(PortChecker)
);
logger.info(`[lifecycle] MySQL process 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) {
logger.error('[lifecycle] MySQL error:', error);
this.logWindowManager.addLog('error', `MySQL 错误: ${error.message}`);
throw error;
}
}
// 步骤5: 检测 Java 端口
this.logWindowManager.addLog('system', '▶ 步骤7: 检测可用的 Java 端口从18093开始...');
this.startupManager.updateProgress('check-java-port', { mysqlPort: this.mysqlPort });
this.javaPort = await PortChecker.findAvailablePort(18093, 100);
if (this.javaPort === -1) {
this.logWindowManager.addLog('error', 'Java 端口检测失败18093-18192 全部被占用');
throw new Error('无法找到可用的后端服务端口18093-18192 全部被占用)');
}
// 步骤5.5: 检测 WebSocket 端口
this.logWindowManager.addLog('system', '▶ 步骤8: 检测可用的 WebSocket 端口从7778开始...');
this.websocketPort = await PortChecker.findAvailablePort(7778, 100);
if (this.websocketPort === -1) {
this.logWindowManager.addLog('error', 'WebSocket 端口检测失败7778-7877 全部被占用');
throw new Error('无法找到可用的 WebSocket 端口7778-7877 全部被占用)');
}
if (this.javaPort !== 18093) {
this.logWindowManager.addLog('warn', `⚠ Java 默认端口 18093 已被占用,自动切换到端口: ${this.javaPort}`);
} else {
this.logWindowManager.addLog('success', `✓ Java 将使用默认端口: ${this.javaPort}`);
}
if (this.websocketPort !== 7778) {
this.logWindowManager.addLog('warn', `⚠ WebSocket 默认端口 7778 已被占用,自动切换到端口: ${this.websocketPort}`);
} else {
this.logWindowManager.addLog('success', `✓ WebSocket 将使用默认端口: ${this.websocketPort}`);
}
logger.info(`[lifecycle] Spring Boot will use port: ${this.javaPort}`);
logger.info(`[lifecycle] WebSocket will use port: ${this.websocketPort}`);
this.startupManager.updateProgress('check-java-port', {
mysqlPort: this.mysqlPort,
javaPort: this.javaPort
});
await this.sleep(500);
// 步骤6: 生成配置文件
this.logWindowManager.addLog('system', '▶ 步骤9: 生成 Spring Boot 配置文件...');
this.startupManager.updateProgress('generate-config', {
mysqlPort: this.mysqlPort,
javaPort: this.javaPort,
websocketPort: this.websocketPort
});
const configGenerator = new ConfigGenerator();
const { configPath, dataPath } = await configGenerator.generateConfig({
mysqlPort: this.mysqlPort,
javaPort: this.javaPort,
websocketPort: this.websocketPort,
mysqlPassword: 'njcnpqs'
});
logger.info(`[lifecycle] Configuration generated at: ${configPath}`);
logger.info(`[lifecycle] Data directory: ${dataPath}`);
this.logWindowManager.addLog('success', `✓ 配置文件已生成: ${configPath}`);
this.logWindowManager.addLog('system', ` 数据目录: ${dataPath}`);
this.startupManager.updateProgress('generate-config', {
mysqlPort: this.mysqlPort,
javaPort: this.javaPort,
websocketPort: this.websocketPort,
dataPath: dataPath
});
await this.sleep(500);
// 步骤7: 启动 Spring Boot
this.logWindowManager.addLog('system', '▶ 步骤10: 启动 Spring Boot 应用...');
this.startupManager.updateProgress('start-java', {
mysqlPort: this.mysqlPort,
javaPort: this.javaPort,
dataPath: dataPath
});
await this.startSpringBoot(configPath, dataPath);
// 步骤8: 等待 Spring Boot 就绪
this.logWindowManager.addLog('system', '▶ 步骤11: 等待 Spring Boot 就绪最多60秒...');
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 启动超时,但继续启动应用`);
this.logWindowManager.addLog('warn', '⚠ Spring Boot 启动超时60秒但应用将继续启动');
} else {
logger.info('[lifecycle] Spring Boot is ready');
this.logWindowManager.addLog('success', '✓ Spring Boot 启动成功!');
}
await this.sleep(1000);
// 步骤9: 完成
this.logWindowManager.addLog('system', '▶ 步骤12: 启动完成,准备显示主窗口...');
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', '✓ CN_Tool 灿能运维工具 启动完成!所有服务正常运行');
this.logWindowManager.addLog('system', `✓ MySQL 端口: ${this.mysqlPort}`);
this.logWindowManager.addLog('system', `✓ Java 端口: ${this.javaPort}`);
this.logWindowManager.addLog('system', `✓ WebSocket 端口: ${this.websocketPort}`);
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();
// 窗口关闭时,使用 Electron 原生对话框确认
win.on('close', async (event) => {
if (global.isConfirmedExit) {
logger.info('[lifecycle] Exit already confirmed, proceeding');
return;
}
event.preventDefault();
logger.info('[lifecycle] Window close intercepted, showing exit confirm dialog');
const { dialog } = require('electron');
const { response } = await dialog.showMessageBox(win, {
type: 'question',
buttons: ['取消', '退出'],
defaultId: 1,
cancelId: 0,
title: '退出确认',
message: '确定要退出应用吗?',
noLink: true
});
if (response === 1) {
logger.info('[lifecycle] User confirmed exit');
global.isConfirmedExit = true;
await this.cleanup();
if (win && !win.isDestroyed()) {
win.destroy();
}
app.quit();
} else {
logger.info('[lifecycle] User cancelled exit');
}
});
// 立即刷新一次,确保显示最新内容
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...');
}
// 停止 Java 进程(内部已有完整的等待和清理逻辑)
await this.javaRunner.stopSpringBoot();
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);
}
}
// 停止 MySQL 进程(进程模式)
if (this.mysqlProcessManager) {
try {
logger.info('[lifecycle] Stopping MySQL process...');
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
this.logWindowManager.addLog('system', '正在停止 MySQL...');
}
await this.mysqlProcessManager.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);
}
}
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
this.logWindowManager.addLog('system', '清理完成,应用即将退出');
this.logWindowManager.addLog('system', '='.repeat(60));
}
// 等待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, dataPath) {
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 logPath = path.join(dataPath, 'logs');
const javaProcess = this.javaRunner.runSpringBoot(jarPath, configPath, {
javaPort: this.javaPort,
logPath: logPath // 传递日志路径
});
// 监听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));
}
/**
* electron app ready
*/
async electronAppReady() {
logger.info('[lifecycle] electron-app-ready');
}
/**
* main window have been loaded
*/
async windowReady() {
logger.info('[lifecycle] window-ready hook triggered');
// 进程模式不需要管理员权限检查
// 创建日志管理器(但不显示窗口,仅用于写日志文件)
logger.info('[lifecycle] Creating log window manager...');
this.logWindowManager = new LogWindowManager();
// this.logWindowManager.createLogWindow(); // ← 注释掉,不创建窗口
this.logWindowManager.addLog('system', '='.repeat(80));
this.logWindowManager.addLog('system', 'CN_Tool 灿能运维工具 应用启动');
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', 'CN_Tool 灿能运维工具 启动中...');
this.logWindowManager.addLog('system', '='.repeat(60));
// 开始启动流程
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);
}
// 主窗口初始化但不显示,等待启动流程完成后再显示
// 在 startApplication() 中会调用 win.show()
}
/**
* before app close (框架生命周期钩子)
*/
async beforeClose() {
logger.info('[lifecycle] before-close hook triggered');
await this.cleanup();
}
}
Lifecycle.toString = () => '[class Lifecycle]';
// 导出实例而不是类
module.exports = new Lifecycle();

View File

@@ -0,0 +1,30 @@
'use strict';
const { logger } = require('ee-core/log');
/**
* 示例服务
* @class
*/
class ExampleService {
/**
* test
*/
async test(args) {
let obj = {
status:'ok',
params: args
}
logger.info('ExampleService obj:', obj);
return obj;
}
}
ExampleService.toString = () => '[class ExampleService]';
module.exports = {
ExampleService,
exampleService: new ExampleService()
};