Files
dify_market_manager_gui/SINGLE_INSTANCE_FINAL_FIX.md
2025-07-22 17:53:26 +08:00

4.5 KiB
Raw Permalink Blame History

单实例问题最终修复说明

问题描述

编译后的 exe 文件可以打开多个实例,无法实现单实例启动。

问题原因分析

  1. 应用标识符不一致package.json 中的 name 和 electron-builder 配置中的 appId 不一致
  2. 单实例检查逻辑过于复杂:使用了自定义的锁文件管理,而不是直接使用 Electron 内置的单实例机制
  3. 多个进程残留:之前的实例异常退出时,进程没有正确清理,导致锁文件失效

修复内容

1. 统一应用标识符

  • package.json: name 改为 dify-market-manager-gui
  • electron-builder.yml: appId 改为 com.huashiai.dify-market-manager-gui
  • 主进程: setAppUserModelId 设置为 com.huashiai.dify-market-manager-gui

2. 简化单实例实现

参考标准 Electron 单实例实现,使用 app.requestSingleInstanceLock() 方法:

/**
 * 单应用启动实现
 * 请求单一实例锁,
 * 如果该方法返回`false`
 * 则表示已经有一个实例在运行,
 * 可以通过`app.quit()`方法退出当前实例。
 */
const gotTheLock = app.requestSingleInstanceLock()

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]
      if (mainWindow.isMinimized()) {
        mainWindow.restore()
      }
      mainWindow.show()
      mainWindow.focus()
    }
  })
}

3. 清理残留进程和锁文件

  • 创建了 kill-processes.js 脚本来终止所有相关进程
  • 创建了 cleanup-lock-files.js 脚本来清理所有锁文件
  • 终止了 11 个残留的应用进程

4. 简化单实例管理器

  • 移除了复杂的锁文件管理逻辑
  • 直接使用 Electron 内置的单实例机制
  • 保留锁文件作为备份,但不依赖它进行单实例检查

修复后的功能特点

标准单实例行为

  1. 首次启动:应用正常启动,显示主窗口
  2. 重复启动:检测到已有实例,自动退出新实例,激活现有实例窗口
  3. 窗口激活:如果现有窗口最小化,自动恢复并聚焦

跨平台兼容

  • Windows: 使用系统级的单实例锁
  • macOS: 使用系统级的单实例锁
  • Linux: 使用系统级的单实例锁

异常处理

  • 应用崩溃时自动清理锁文件
  • 监听各种退出信号确保清理
  • 提供手动清理功能

使用方法

开发环境测试

# 启动第一个实例
npm run dev

# 尝试启动第二个实例(应该被阻止)
npm run dev

生产环境测试

# 构建应用
npm run build:win

# 安装并运行
# 尝试多次点击 exe 文件,应该只能启动一个实例

清理工具

# 清理锁文件
node cleanup-lock-files.js

# 终止所有相关进程
node kill-processes.js

验证修复效果

1. 正常启动

[时间] 这是第一个实例,继续启动
[时间] 应用已准备就绪,开始初始化...

2. 重复启动

[时间] 检测到已有实例运行,退出当前实例

3. 第二个实例启动

[时间] 检测到第二个实例启动,激活现有实例

相关文件

  • src/main/index.js - 主进程启动文件(已更新单实例逻辑)
  • src/main/utils/singleInstance.js - 简化的单实例管理器
  • package.json - 应用配置(已更新名称)
  • electron-builder.yml - 构建配置(已更新 appId
  • cleanup-lock-files.js - 锁文件清理脚本
  • kill-processes.js - 进程终止脚本
  • test-single-instance-simple.js - 简单测试脚本

注意事项

1. 应用标识符

  • 确保所有配置文件中的应用标识符一致
  • 修改标识符后需要重新构建应用

2. 开发环境

  • 开发时如果遇到单实例问题,使用清理脚本
  • 查看日志文件了解详细情况

3. 生产环境

  • 确保应用正常退出时能清理资源
  • 监控应用的单实例行为

总结

通过统一应用标识符、简化单实例实现逻辑、清理残留进程,成功解决了编译后可以打开多个实例的问题。现在应用严格按照单实例模式运行,符合桌面应用的标准行为。