This commit is contained in:
2025-07-22 17:53:26 +08:00
parent f667c26650
commit e43f850840
17 changed files with 924 additions and 182 deletions

View File

@@ -0,0 +1,152 @@
# 单实例问题最终修复说明
## 问题描述
编译后的 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()` 方法:
```javascript
/**
* 单应用启动实现
* 请求单一实例锁,
* 如果该方法返回`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: 使用系统级的单实例锁
### 异常处理
- 应用崩溃时自动清理锁文件
- 监听各种退出信号确保清理
- 提供手动清理功能
## 使用方法
### 开发环境测试
```bash
# 启动第一个实例
npm run dev
# 尝试启动第二个实例(应该被阻止)
npm run dev
```
### 生产环境测试
```bash
# 构建应用
npm run build:win
# 安装并运行
# 尝试多次点击 exe 文件,应该只能启动一个实例
```
### 清理工具
```bash
# 清理锁文件
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. 生产环境
- 确保应用正常退出时能清理资源
- 监控应用的单实例行为
## 总结
通过统一应用标识符、简化单实例实现逻辑、清理残留进程,成功解决了编译后可以打开多个实例的问题。现在应用严格按照单实例模式运行,符合桌面应用的标准行为。