refactor: 添加进程管理功能,支持强制清理相关进程和获取进程信息

This commit is contained in:
2025-09-24 23:33:45 +08:00
parent 34df37c040
commit af28869d2a
6 changed files with 438 additions and 83 deletions

View File

@@ -11,8 +11,37 @@ import {
} from 'electron'
import * as path from 'path'
import * as fs from 'fs'
import { spawn } from 'child_process'
import { spawn, exec } from 'child_process'
import { getAppRoot, checkEnvironment } from './services/environmentService'
// 强制清理相关进程的函数
async function forceKillRelatedProcesses(): Promise<void> {
try {
const { killAllRelatedProcesses } = await import('./utils/processManager')
await killAllRelatedProcesses()
log.info('所有相关进程已清理')
} catch (error) {
log.error('清理进程时出错:', error)
// 备用清理方法
if (process.platform === 'win32') {
const appRoot = getAppRoot()
const pythonExePath = path.join(appRoot, 'environment', 'python', 'python.exe')
return new Promise((resolve) => {
// 使用更简单的命令强制结束相关进程
exec(`taskkill /f /im python.exe`, (error) => {
if (error) {
log.warn('备用清理方法失败:', error.message)
} else {
log.info('备用清理方法执行成功')
}
resolve()
})
})
}
}
}
import { setMainWindow as setDownloadMainWindow } from './services/downloadService'
import {
setMainWindow as setPythonMainWindow,
@@ -385,7 +414,25 @@ function createWindow() {
updateTrayVisibility(currentConfig)
log.info('窗口已最小化到托盘,任务栏图标已隐藏')
} else {
saveWindowState()
// 立即保存窗口状态,不使用防抖
if (!win.isDestroyed()) {
try {
const config = loadConfig()
const bounds = win.getBounds()
const isMaximized = win.isMaximized()
if (!isMaximized) {
config.UI.size = `${bounds.width},${bounds.height}`
config.UI.location = `${bounds.x},${bounds.y}`
}
config.UI.maximized = isMaximized
saveConfig(config)
log.info('窗口状态已保存')
} catch (error) {
log.error('保存窗口状态失败:', error)
}
}
}
})
@@ -395,6 +442,19 @@ function createWindow() {
screen.removeListener('display-metrics-changed', recomputeMinSize)
// 置空模块级引用
mainWindow = null
// 如果是正在退出,立即执行进程清理
if (isQuitting) {
log.info('窗口关闭,执行最终清理')
setTimeout(async () => {
try {
await forceKillRelatedProcesses()
} catch (e) {
log.error('最终清理失败:', e)
}
process.exit(0)
}, 100)
}
})
win.on('minimize', () => {
@@ -441,7 +501,7 @@ function createWindow() {
// 保存窗口状态(带防抖)
function saveWindowState() {
if (!mainWindow) return
if (!mainWindow || mainWindow.isDestroyed()) return
// 清除之前的定时器
if (saveWindowStateTimeout) {
@@ -451,9 +511,15 @@ function saveWindowState() {
// 设置新的定时器500ms后保存
saveWindowStateTimeout = setTimeout(() => {
try {
// 再次检查窗口是否存在且未销毁
if (!mainWindow || mainWindow.isDestroyed()) {
log.warn('窗口已销毁,跳过保存状态')
return
}
const config = loadConfig()
const bounds = mainWindow!.getBounds()
const isMaximized = mainWindow!.isMaximized()
const bounds = mainWindow.getBounds()
const isMaximized = mainWindow.isMaximized()
// 只有在窗口不是最大化状态时才保存位置和大小
if (!isMaximized) {
@@ -496,10 +562,58 @@ ipcMain.handle('window-maximize', () => {
ipcMain.handle('window-close', () => {
if (mainWindow) {
isQuitting = true
mainWindow.close()
}
})
// 添加强制退出处理器
ipcMain.handle('app-quit', () => {
isQuitting = true
app.quit()
})
// 添加进程管理相关的 IPC 处理器
ipcMain.handle('get-related-processes', async () => {
try {
const { getRelatedProcesses } = await import('./utils/processManager')
return await getRelatedProcesses()
} catch (error) {
log.error('获取进程信息失败:', error)
return []
}
})
ipcMain.handle('kill-all-processes', async () => {
try {
await forceKillRelatedProcesses()
return { success: true }
} catch (error) {
log.error('强制清理进程失败:', error)
return { success: false, error: error instanceof Error ? error.message : String(error) }
}
})
// 添加一个测试用的强制退出命令
ipcMain.handle('force-exit', async () => {
log.info('收到强制退出命令')
isQuitting = true
// 立即清理进程
try {
await forceKillRelatedProcesses()
} catch (e) {
log.error('强制清理失败:', e)
}
// 强制退出
setTimeout(() => {
process.exit(0)
}, 500)
return { success: true }
})
ipcMain.handle('window-is-maximized', () => {
return mainWindow ? mainWindow.isMaximized() : false
})
@@ -627,9 +741,10 @@ ipcMain.handle('start-backend', async () => {
return startBackend(appRoot)
})
// ipcMain.handle('stop-backend', async () => {
// return stopBackend()
// })
ipcMain.handle('stop-backend', async () => {
const { stopBackend } = await import('./services/pythonService')
return stopBackend()
})
// Git相关
ipcMain.handle('download-git', async () => {
@@ -1011,19 +1126,67 @@ app.on('before-quit', async event => {
log.info('应用准备退出')
// 清理定时器
if (saveWindowStateTimeout) {
clearTimeout(saveWindowStateTimeout)
saveWindowStateTimeout = null
}
// 清理托盘
destroyTray()
// try {
// await stopBackend()
// log.info('后端服务已停止')
// } catch (e) {
// log.error('停止后端时出错:', e)
// console.error('停止后端时出错:', e)
// } finally {
// log.info('应用退出')
// app.exit(0)
// }
// 立即开始强制清理,不等待优雅关闭
log.info('开始强制清理所有相关进程')
try {
// 并行执行多种清理方法
const cleanupPromises = [
// 方法1: 使用我们的进程管理器
forceKillRelatedProcesses(),
// 方法2: 直接使用 taskkill 命令
new Promise<void>((resolve) => {
if (process.platform === 'win32') {
const appRoot = getAppRoot()
const commands = [
`taskkill /f /im python.exe`,
`wmic process where "CommandLine like '%main.py%'" delete`,
`wmic process where "CommandLine like '%${appRoot.replace(/\\/g, '\\\\')}%'" delete`
]
let completed = 0
commands.forEach(cmd => {
exec(cmd, () => {
completed++
if (completed === commands.length) {
resolve()
}
})
})
// 2秒超时
setTimeout(resolve, 2000)
} else {
resolve()
}
})
]
// 最多等待3秒
const timeoutPromise = new Promise(resolve => setTimeout(resolve, 3000))
await Promise.race([Promise.all(cleanupPromises), timeoutPromise])
log.info('进程清理完成')
} catch (e) {
log.error('进程清理时出错:', e)
}
log.info('应用强制退出')
// 使用 process.exit 而不是 app.exit更加强制
setTimeout(() => {
process.exit(0)
}, 500)
}
})
@@ -1054,7 +1217,10 @@ app.whenReady().then(() => {
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
if (process.platform !== 'darwin') {
isQuitting = true
app.quit()
}
})
app.on('activate', () => {