feat(electron):优化应用启动和健康检查逻辑

- 修改 Spring Boot 配置启用懒加载初始化
- 优化主进程窗口打开逻辑,增加销毁状态检查
- 简化数据迁移函数中的条件判断
- 添加 JVM 参数 UseSerialGC优化内存使用- 移除 Spring 进程的标准输出和错误流监听- 改进健康检查机制,使用版本接口确认服务就绪
- 调整启动超时时间并优化重试间隔
- 延迟更新检查时机以提升启动速度
This commit is contained in:
2025-10-28 11:05:53 +08:00
parent 1aceceb38f
commit 6443cdc8d0
3 changed files with 57 additions and 70 deletions

View File

@@ -20,9 +20,12 @@ let downloadedJarPath: string | null = null;
let isQuitting = false; let isQuitting = false;
let currentDownloadAbortController: AbortController | null = null; let currentDownloadAbortController: AbortController | null = null;
function openAppIfNotOpened() { function openAppIfNotOpened() {
if (appOpened) return; if (appOpened || !mainWindow || mainWindow.isDestroyed()) {
!appOpened && setTimeout(openAppIfNotOpened, 50);
return;
}
appOpened = true; appOpened = true;
if (mainWindow && !mainWindow.isDestroyed()) {
isDev isDev
? mainWindow.loadURL(`http://localhost:${process.argv[2] || 8083}`) ? mainWindow.loadURL(`http://localhost:${process.argv[2] || 8083}`)
: mainWindow.loadFile(join(__dirname, '../renderer/index.html')); : mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
@@ -30,8 +33,7 @@ function openAppIfNotOpened() {
mainWindow.webContents.once('did-finish-load', () => { mainWindow.webContents.once('did-finish-load', () => {
setTimeout(() => { setTimeout(() => {
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
const config = loadConfig(); const shouldMinimize = loadConfig().launchMinimized || false;
const shouldMinimize = config.launchMinimized || false;
if (!shouldMinimize) { if (!shouldMinimize) {
mainWindow.show(); mainWindow.show();
mainWindow.focus(); mainWindow.focus();
@@ -42,9 +44,8 @@ function openAppIfNotOpened() {
splashWindow.close(); splashWindow.close();
splashWindow = null; splashWindow = null;
} }
}, 500); }, 100);
}); });
}
} }
// 通用资源路径获取函数 // 通用资源路径获取函数
@@ -127,21 +128,15 @@ function saveConfig(config: AppConfig) {
function migrateDataFromPublic(): void { function migrateDataFromPublic(): void {
if (!isDev) return; if (!isDev) return;
const oldDataPath = join(__dirname, '../../public/data'); const oldDataPath = join(__dirname, '../../public/data');
if (!existsSync(oldDataPath)) return; if (!existsSync(oldDataPath)) return;
const newDataPath = getDataDirectoryPath(); const newDataPath = getDataDirectoryPath();
try { try {
readdirSync(oldDataPath).forEach(file => { readdirSync(oldDataPath).forEach(file => {
const destFile = join(newDataPath, file); const destFile = join(newDataPath, file);
if (!existsSync(destFile)) { !existsSync(destFile) && copyFileSync(join(oldDataPath, file), destFile);
copyFileSync(join(oldDataPath, file), destFile);
}
}); });
} catch (error) { } catch {}
console.log('数据迁移失败,使用默认配置');
}
} }
@@ -160,6 +155,7 @@ function startSpringBoot() {
try { try {
const springArgs = [ const springArgs = [
'-XX:+UseSerialGC',
'-jar', jarPath, '-jar', jarPath,
`--spring.datasource.url=jdbc:sqlite:${dataDir}/erp-cache.db`, `--spring.datasource.url=jdbc:sqlite:${dataDir}/erp-cache.db`,
`--server.port=8081`, `--server.port=8081`,
@@ -169,54 +165,46 @@ function startSpringBoot() {
springProcess = spawn(javaPath, springArgs, { springProcess = spawn(javaPath, springArgs, {
cwd: dataDir, cwd: dataDir,
detached: false, detached: false,
stdio: ['ignore', 'pipe', 'pipe'] stdio: 'ignore'
}); });
let startupCompleted = false; let startupCompleted = false;
springProcess.stdout?.on('data', (data) => { springProcess.on('close', () => mainWindow ? mainWindow.close() : app.quit());
console.log('[Spring Boot]', data.toString().trim());
});
springProcess.stderr?.on('data', (data) => {
console.log('[Spring Boot]', data.toString().trim());
});
springProcess.on('close', (code) => {
mainWindow ? mainWindow.close() : app.quit();
});
springProcess.on('error', (error) => { springProcess.on('error', (error) => {
let errorMessage = '启动 Java 应用失败'; dialog.showErrorBox('启动失败', error.message.includes('ENOENT')
if (error.message.includes('ENOENT')) { ? '找不到 Java 运行环境'
errorMessage = '找不到 Java 运行环境\n请确保应用包含 JRE 或系统已安装 Java'; : '启动 Java 应用失败');
}
dialog.showErrorBox('启动失败', errorMessage);
app.quit(); app.quit();
}); });
const checkHealth = () => { const checkHealth = () => {
if (startupCompleted) return; if (startupCompleted) return;
http.get('http://127.0.0.1:8081/api/system/version', (res) => {
http.get('http://127.0.0.1:8081', (res) => { if (res.statusCode !== 200) {
if (!startupCompleted) { setTimeout(checkHealth, 100);
startupCompleted = true; return;
console.log('[Spring Boot] 服务已就绪'); }
openAppIfNotOpened(); let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const json = JSON.parse(data);
if (json.success && json.currentVersion) {
startupCompleted = true;
openAppIfNotOpened();
} else {
setTimeout(checkHealth, 100);
}
} catch {
setTimeout(checkHealth, 100);
} }
}).on('error', () => {
setTimeout(checkHealth, 200);
}); });
}).on('error', () => setTimeout(checkHealth, 100));
}; };
setTimeout(checkHealth, 1000); setTimeout(checkHealth, 100);
setTimeout(() => !startupCompleted && openAppIfNotOpened(), 60000);
setTimeout(() => {
if (!startupCompleted) {
console.log('[Spring Boot] 启动超时,强制打开窗口');
openAppIfNotOpened();
}
}, 15000);
} catch (error) { } catch (error) {
dialog.showErrorBox('启动异常', `无法启动应用: ${error}`); dialog.showErrorBox('启动异常', `无法启动应用: ${error}`);
app.quit(); app.quit();
@@ -296,7 +284,7 @@ function createWindow() {
}); });
mainWindow.webContents.once('did-finish-load', () => { mainWindow.webContents.once('did-finish-load', () => {
setTimeout(() => checkPendingUpdate(), 500); setTimeout(checkPendingUpdate, 100);
}); });
} }

View File

@@ -64,7 +64,6 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- 拆分 hutool-all 为按需依赖,减少 JAR 包体积 -->
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId> <artifactId>hutool-crypto</artifactId>

View File

@@ -9,7 +9,7 @@ javafx:
spring: spring:
main: main:
lazy-initialization: false lazy-initialization: true
datasource: datasource:
url: jdbc:sqlite:./data/erp-cache.db?journal_mode=WAL&synchronous=NORMAL&cache_size=10000&temp_store=memory&busy_timeout=30000 url: jdbc:sqlite:./data/erp-cache.db?journal_mode=WAL&synchronous=NORMAL&cache_size=10000&temp_store=memory&busy_timeout=30000
driver-class-name: org.sqlite.JDBC driver-class-name: org.sqlite.JDBC