优化
This commit is contained in:
@@ -109,10 +109,7 @@ logger.info(app.getPath('userData'))
|
||||
logger.info('应用启动,日志文件路径:', logger.getLogPath())
|
||||
|
||||
// 添加更多启动信息
|
||||
logger.info('开始检查单实例锁...')
|
||||
|
||||
// 创建单实例管理器
|
||||
const singleInstanceManager = new SingleInstanceManager()
|
||||
logger.info('开始初始化应用...')
|
||||
|
||||
// 清理缓存目录
|
||||
function cleanupCacheDirectories() {
|
||||
@@ -166,43 +163,25 @@ function cleanupCacheDirectories() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单应用启动实现
|
||||
* 请求单一实例锁,
|
||||
* 如果该方法返回`false`,
|
||||
* 则表示已经有一个实例在运行,
|
||||
* 可以通过`app.quit()`方法退出当前实例。
|
||||
*/
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
|
||||
|
||||
// 检查是否为第一个实例
|
||||
const isFirstInstance = singleInstanceManager.checkSingleInstance()
|
||||
|
||||
if (!isFirstInstance) {
|
||||
logger.info('检测到已有实例运行,尝试激活现有实例')
|
||||
|
||||
// 尝试激活第一个实例的窗口
|
||||
try {
|
||||
const windows = BrowserWindow.getAllWindows()
|
||||
if (windows.length > 0) {
|
||||
const mainWindow = windows[0]
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore()
|
||||
}
|
||||
mainWindow.show()
|
||||
mainWindow.focus()
|
||||
logger.info('成功激活现有实例窗口')
|
||||
app.quit() // 退出当前实例
|
||||
} else {
|
||||
logger.warn('未找到现有实例窗口,可能是锁未正确释放')
|
||||
logger.info('强制退出当前实例,避免重复托盘')
|
||||
app.quit() // 强制退出,避免创建重复托盘
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('激活现有实例失败:', error)
|
||||
logger.info('强制退出当前实例,避免重复托盘')
|
||||
app.quit() // 强制退出,避免创建重复托盘
|
||||
}
|
||||
if (!gotTheLock) {
|
||||
logger.info('检测到已有实例运行,退出当前实例')
|
||||
app.quit()
|
||||
} else {
|
||||
logger.info('这是第一个实例,继续启动')
|
||||
// 这是第一个实例
|
||||
|
||||
// 监听第二个实例的启动
|
||||
// 监听第二个实例被运行时
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
// 当运行第二个实例时,将显示第一个实例的窗口
|
||||
logger.info('检测到第二个实例启动,激活现有实例')
|
||||
// 当有第二个实例被运行时,激活之前的实例并将焦点置于其窗口
|
||||
const windows = BrowserWindow.getAllWindows()
|
||||
if (windows.length > 0) {
|
||||
const mainWindow = windows[0]
|
||||
@@ -228,7 +207,7 @@ if (!isFirstInstance) {
|
||||
callback(true);
|
||||
});
|
||||
|
||||
electronApp.setAppUserModelId('com.electron')
|
||||
electronApp.setAppUserModelId('com.huashiai.dify-market-manager-gui')
|
||||
|
||||
app.on('browser-window-created', (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
@@ -281,7 +260,7 @@ if (!isFirstInstance) {
|
||||
}
|
||||
|
||||
if (!app.isQuiting) {
|
||||
|
||||
logger.info('用户关闭窗口,隐藏应用')
|
||||
// 如果不是主动退出,则隐藏所有窗口
|
||||
BrowserWindow.getAllWindows().forEach(window => {
|
||||
window.hide()
|
||||
@@ -299,9 +278,11 @@ if (!isFirstInstance) {
|
||||
app.quit();
|
||||
|
||||
} else {
|
||||
logger.info('主动退出,完全关闭应用')
|
||||
// 如果是主动退出,则销毁托盘并退出应用
|
||||
destroyTray()
|
||||
app.quit()
|
||||
// 使用 exit 确保完全退出
|
||||
app.exit(0)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -313,6 +294,7 @@ if (!isFirstInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('应用即将退出,开始清理资源')
|
||||
// 在应用程序即将退出时执行操作,例如保存数据
|
||||
event.preventDefault();
|
||||
|
||||
@@ -325,7 +307,8 @@ if (!isFirstInstance) {
|
||||
|
||||
// 销毁托盘并退出应用
|
||||
destroyTray()
|
||||
app.quit();
|
||||
// 使用 exit 确保完全退出
|
||||
app.exit(0);
|
||||
});
|
||||
|
||||
// 在应用退出时注销所有快捷键
|
||||
@@ -336,6 +319,7 @@ if (!isFirstInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('应用即将退出,注销快捷键')
|
||||
event.preventDefault();
|
||||
|
||||
// 清除所有会话数据
|
||||
@@ -348,12 +332,10 @@ if (!isFirstInstance) {
|
||||
unregisterAllShortcuts()
|
||||
destroyTray()
|
||||
|
||||
// 清理完成后退出应用
|
||||
app.quit();
|
||||
// 清理完成后退出应用,使用 exit 确保完全退出
|
||||
app.exit(0);
|
||||
})
|
||||
|
||||
|
||||
|
||||
// 监听进程退出信号,确保在系统强制关闭时也能清理缓存
|
||||
process.on('SIGTERM', async () => {
|
||||
logger.info('收到 SIGTERM 信号,开始清理缓存');
|
||||
@@ -389,11 +371,8 @@ if (!isFirstInstance) {
|
||||
// 不要立即退出,给应用一个恢复的机会
|
||||
logger.error('应用遇到未处理的 Promise 拒绝,但将继续运行');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function checkIsKeepAlive(){
|
||||
|
||||
const checkIsKeepAliveTimer=setInterval(async () => {
|
||||
|
||||
@@ -174,12 +174,41 @@ export function createTray() {
|
||||
{
|
||||
label: '退出应用',
|
||||
click: () => {
|
||||
logger.info('用户点击退出应用')
|
||||
app.isQuiting = true
|
||||
|
||||
// 确保所有窗口都被关闭
|
||||
BrowserWindow.getAllWindows().forEach(window => {
|
||||
window.destroy()
|
||||
const windows = BrowserWindow.getAllWindows()
|
||||
logger.info(`准备关闭 ${windows.length} 个窗口`)
|
||||
|
||||
windows.forEach(window => {
|
||||
if (!window.isDestroyed()) {
|
||||
try {
|
||||
window.destroy()
|
||||
logger.info('窗口销毁成功')
|
||||
} catch (error) {
|
||||
logger.warn('销毁窗口时出错:', error)
|
||||
}
|
||||
}
|
||||
})
|
||||
app.quit()
|
||||
|
||||
// 延迟执行退出,确保窗口销毁完成
|
||||
setTimeout(() => {
|
||||
try {
|
||||
logger.info('执行应用退出')
|
||||
// 使用 exit 而不是 quit,确保完全退出
|
||||
app.exit(0)
|
||||
} catch (error) {
|
||||
logger.error('应用退出失败,尝试强制退出:', error)
|
||||
try {
|
||||
process.exit(0)
|
||||
} catch (processError) {
|
||||
logger.error('强制退出也失败:', processError)
|
||||
// 最后的强制退出
|
||||
process.kill(process.pid, 'SIGKILL')
|
||||
}
|
||||
}
|
||||
}, 500) // 延迟500ms确保窗口销毁完成
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
@@ -4,7 +4,8 @@ import path from 'path'
|
||||
import logger from './logger.js'
|
||||
|
||||
/**
|
||||
* 单实例锁管理工具
|
||||
* 简化的单实例管理器
|
||||
* 使用 Electron 内置的 requestSingleInstanceLock 方法
|
||||
*/
|
||||
export class SingleInstanceManager {
|
||||
constructor() {
|
||||
@@ -13,98 +14,27 @@ export class SingleInstanceManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理可能存在的无效单实例锁
|
||||
* 检查是否为第一个实例
|
||||
* 使用 Electron 内置的单实例锁机制
|
||||
*/
|
||||
cleanupInvalidLock() {
|
||||
try {
|
||||
const userDataPath = app.getPath('userData')
|
||||
const possibleLockFiles = [
|
||||
path.join(userDataPath, 'single-instance-lock'),
|
||||
path.join(userDataPath, 'lockfile'),
|
||||
path.join(userDataPath, '.lock'),
|
||||
path.join(process.cwd(), 'single-instance-lock'),
|
||||
path.join(process.cwd(), 'lockfile'),
|
||||
path.join(process.cwd(), '.lock')
|
||||
]
|
||||
|
||||
for (const lockFile of possibleLockFiles) {
|
||||
if (fs.existsSync(lockFile)) {
|
||||
logger.info(`发现锁文件: ${lockFile}`)
|
||||
|
||||
// 检查锁文件是否有效(检查进程是否还在运行)
|
||||
try {
|
||||
const lockContent = fs.readFileSync(lockFile, 'utf8')
|
||||
const pid = parseInt(lockContent.trim())
|
||||
|
||||
if (pid && this.isProcessRunning(pid)) {
|
||||
logger.info(`进程 ${pid} 仍在运行,锁文件有效`)
|
||||
} else {
|
||||
logger.info(`进程 ${pid} 不存在,尝试删除无效锁文件`)
|
||||
this.safeDeleteFile(lockFile)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.info(`锁文件无效,尝试删除: ${lockFile}`)
|
||||
this.safeDeleteFile(lockFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('清理无效锁文件失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全删除文件,处理文件被占用的情况
|
||||
*/
|
||||
safeDeleteFile(filePath) {
|
||||
try {
|
||||
// 首先尝试直接删除
|
||||
fs.unlinkSync(filePath)
|
||||
logger.info(`成功删除文件: ${filePath}`)
|
||||
} catch (error) {
|
||||
if (error.code === 'EBUSY' || error.code === 'EACCES') {
|
||||
logger.warn(`文件被占用,无法删除: ${filePath}`)
|
||||
logger.warn(`错误信息: ${error.message}`)
|
||||
|
||||
// 在 Windows 上尝试使用 del 命令
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
const { execSync } = require('child_process')
|
||||
execSync(`del /F /Q "${filePath}"`, { stdio: 'ignore' })
|
||||
logger.info(`使用 del 命令删除文件: ${filePath}`)
|
||||
} catch (delError) {
|
||||
logger.error(`del 命令删除失败: ${filePath}`, delError)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.error(`删除文件失败: ${filePath}`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查进程是否还在运行
|
||||
*/
|
||||
isProcessRunning(pid) {
|
||||
try {
|
||||
// 在 Windows 上使用 tasklist 命令检查进程
|
||||
if (process.platform === 'win32') {
|
||||
const { execSync } = require('child_process')
|
||||
const result = execSync(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, { encoding: 'utf8' })
|
||||
return result.includes(pid.toString())
|
||||
} else {
|
||||
// 在 Unix 系统上使用 kill -0 检查进程
|
||||
const { execSync } = require('child_process')
|
||||
execSync(`kill -0 ${pid}`, { stdio: 'ignore' })
|
||||
return true
|
||||
}
|
||||
} catch (error) {
|
||||
checkSingleInstance() {
|
||||
// 直接使用 Electron 的单实例锁
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
logger.info(`单实例锁检查结果: ${gotTheLock}`)
|
||||
|
||||
if (gotTheLock) {
|
||||
// 成功获取锁,创建锁文件作为备份
|
||||
this.createLock()
|
||||
return true
|
||||
} else {
|
||||
// 无法获取锁,说明已有实例运行
|
||||
logger.info('检测到已有实例运行,阻止新实例启动')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建锁文件
|
||||
* 创建锁文件作为备份
|
||||
*/
|
||||
createLock() {
|
||||
try {
|
||||
@@ -162,44 +92,6 @@ export class SingleInstanceManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为第一个实例
|
||||
*/
|
||||
checkSingleInstance() {
|
||||
// 首先清理可能存在的无效锁
|
||||
this.cleanupInvalidLock()
|
||||
|
||||
// 尝试获取单实例锁
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
logger.info(`单实例锁检查结果: ${gotTheLock}`)
|
||||
|
||||
if (gotTheLock) {
|
||||
// 成功获取锁,创建锁文件
|
||||
this.createLock()
|
||||
return true
|
||||
} else {
|
||||
// 无法获取锁,检查是否真的有必要阻止启动
|
||||
logger.info('检测到已有实例运行')
|
||||
|
||||
// 检查是否有实际的窗口存在
|
||||
const windows = require('electron').BrowserWindow.getAllWindows()
|
||||
if (windows.length === 0) {
|
||||
logger.warn('未找到现有实例窗口,可能是锁文件问题,允许强制启动')
|
||||
// 尝试强制获取锁
|
||||
try {
|
||||
this.createLock()
|
||||
return true
|
||||
} catch (error) {
|
||||
logger.error('强制创建锁失败:', error)
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
logger.info(`找到 ${windows.length} 个现有窗口,阻止新实例启动`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SingleInstanceManager
|
||||
Reference in New Issue
Block a user