重启之后清除登录信息
This commit is contained in:
97
CACHE_CLEAR_FIX.md
Normal file
97
CACHE_CLEAR_FIX.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# 缓存清理功能修复说明
|
||||
|
||||
## 问题描述
|
||||
重启电脑时,清除缓存没有生效,并且日志文件中也没有看到对应的日志。
|
||||
|
||||
## 问题原因分析
|
||||
1. **异步操作处理不当**:`clearStorageData` 是异步操作,但在事件处理中没有正确等待完成
|
||||
2. **日志配置问题**:日志可能没有及时写入文件,导致系统关机时日志丢失
|
||||
3. **事件处理顺序问题**:`event.resume()` 在缓存清理完成前就被调用
|
||||
4. **缺少重启事件监听**:只监听了 `shutdown` 事件,没有监听 `restart` 事件
|
||||
|
||||
## 修复内容
|
||||
|
||||
### 1. 创建统一的缓存清理工具 (`src/main/utils/cacheUtils.js`)
|
||||
- 提供 `clearAllSessionData()` 函数,统一处理会话数据清理
|
||||
- 提供 `clearAllWindowsCache()` 函数,清理所有窗口的缓存
|
||||
- 包含详细的错误处理和日志记录
|
||||
- 确保日志及时写入文件
|
||||
|
||||
### 2. 改进日志配置 (`src/main/utils/logger.js`)
|
||||
- 设置 `log.transports.file.sync = true` 确保日志立即写入
|
||||
- 添加日志文件路径获取功能
|
||||
- 在应用启动时显示日志文件路径
|
||||
|
||||
### 3. 修复主进程事件处理 (`src/main/index.js`)
|
||||
- 修复 `powerMonitor.on('shutdown')` 事件处理,使用 async/await 正确等待缓存清理
|
||||
- 添加 `powerMonitor.on('restart')` 事件监听,处理系统重启
|
||||
- 修复所有 `clearStorageData` 调用,使用统一的工具函数
|
||||
- 添加进程信号监听 (`SIGTERM`, `SIGINT`) 确保强制关闭时也能清理缓存
|
||||
- 添加未捕获异常处理
|
||||
|
||||
### 4. 改进托盘功能 (`src/main/tray.js`)
|
||||
- 使用统一的缓存清理函数
|
||||
- 添加"查看日志"菜单项,方便用户查看日志文件
|
||||
|
||||
### 5. 添加测试脚本 (`test-cache-clear.js`)
|
||||
- 提供缓存清理功能的测试代码
|
||||
|
||||
## 修复后的功能特点
|
||||
|
||||
### 缓存清理覆盖的场景
|
||||
1. **系统关机** (`powerMonitor.on('shutdown')`)
|
||||
2. **系统重启** (`powerMonitor.on('restart')`)
|
||||
3. **应用正常退出** (`app.on('before-quit')`)
|
||||
4. **窗口关闭** (`app.on('window-all-closed')`)
|
||||
5. **应用退出** (`app.on('will-quit')`)
|
||||
6. **进程信号** (`SIGTERM`, `SIGINT`)
|
||||
7. **托盘手动清理**
|
||||
|
||||
### 日志记录改进
|
||||
- 每个清理操作都有详细的日志记录
|
||||
- 包含操作上下文信息(如 'shutdown', 'restart' 等)
|
||||
- 日志立即写入文件,避免丢失
|
||||
- 提供日志文件路径查看功能
|
||||
|
||||
### 错误处理
|
||||
- 完善的错误捕获和日志记录
|
||||
- 即使清理失败也不会阻止应用正常退出
|
||||
- 详细的错误信息记录
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 查看日志
|
||||
1. 右键点击托盘图标
|
||||
2. 选择"查看日志"
|
||||
3. 系统会自动打开日志文件所在目录
|
||||
|
||||
### 手动清理缓存
|
||||
1. 右键点击托盘图标
|
||||
2. 选择"清除缓存"
|
||||
3. 系统会清理所有缓存并重新加载窗口
|
||||
|
||||
### 测试缓存清理
|
||||
```bash
|
||||
node test-cache-clear.js
|
||||
```
|
||||
|
||||
## 日志文件位置
|
||||
- **Windows**: `%APPDATA%\[应用名称]\logs\main.log`
|
||||
- **macOS**: `~/Library/Logs/[应用名称]/main.log`
|
||||
- **Linux**: `~/.config/[应用名称]/logs/main.log`
|
||||
|
||||
## 验证修复效果
|
||||
1. 重启电脑
|
||||
2. 查看日志文件,应该能看到类似以下内容:
|
||||
```
|
||||
[时间] 系统将要重启.
|
||||
[时间] [restart] 开始清除所有会话数据
|
||||
[时间] [restart] 会话数据清除成功
|
||||
[时间] [restart] 缓存清理完成
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
- 修复后的代码确保了缓存清理的可靠性
|
||||
- 日志记录更加详细,便于问题排查
|
||||
- 即使系统强制关闭应用,也会尝试清理缓存
|
||||
- 所有清理操作都是异步的,不会阻塞系统关机/重启流程
|
||||
153
LOCK_FILE_FIX.md
Normal file
153
LOCK_FILE_FIX.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# 锁文件和缓存问题修复说明
|
||||
|
||||
## 问题描述
|
||||
运行 `npm run dev` 时遇到以下问题:
|
||||
1. **锁文件被占用**:`EBUSY: resource busy or locked, unlink 'lockfile'`
|
||||
2. **单实例检查失败**:即使锁文件无效,也无法启动新实例
|
||||
3. **缓存错误**:`Unable to move the cache: 拒绝访问` 和 `Failed to delete the database`
|
||||
|
||||
## 问题原因分析
|
||||
1. **文件占用**:锁文件被其他进程占用,无法删除
|
||||
2. **进程检查不准确**:锁文件中的 PID 为 NaN,导致进程检查失败
|
||||
3. **缓存冲突**:多个实例同时访问缓存目录导致冲突
|
||||
4. **权限问题**:某些文件需要管理员权限才能删除
|
||||
|
||||
## 修复内容
|
||||
|
||||
### 1. 改进锁文件删除机制 (`src/main/utils/singleInstance.js`)
|
||||
- 添加 `safeDeleteFile()` 方法,处理文件被占用的情况
|
||||
- 在 Windows 上使用 `del /F /Q` 命令强制删除
|
||||
- 改进错误处理和日志记录
|
||||
|
||||
### 2. 智能单实例检查
|
||||
- 检查是否有实际的窗口存在,而不仅仅依赖锁文件
|
||||
- 如果找不到现有窗口,允许强制启动新实例
|
||||
- 添加详细的日志记录
|
||||
|
||||
### 3. 缓存清理和配置 (`src/main/index.js`)
|
||||
- 添加缓存相关命令行参数
|
||||
- 在应用启动时清理缓存目录
|
||||
- 避免缓存冲突
|
||||
|
||||
### 4. 托盘菜单增强 (`src/main/tray.js`)
|
||||
- 改进"清理实例锁"功能,处理文件占用情况
|
||||
- 添加"强制重启应用"选项
|
||||
- 提供更安全的文件删除机制
|
||||
|
||||
### 5. 测试脚本 (`test-lock-cleanup.js`)
|
||||
- 提供锁文件清理功能的测试
|
||||
- 验证文件删除机制
|
||||
- 帮助调试锁文件问题
|
||||
|
||||
## 修复后的功能特点
|
||||
|
||||
### 安全的文件删除
|
||||
1. **多重尝试**:首先尝试直接删除,失败后使用系统命令
|
||||
2. **错误处理**:区分不同类型的错误,提供相应的解决方案
|
||||
3. **日志记录**:详细记录删除过程和结果
|
||||
|
||||
### 智能启动逻辑
|
||||
1. **窗口检查**:检查是否有实际的窗口存在
|
||||
2. **强制启动**:如果锁文件无效且无窗口,允许强制启动
|
||||
3. **详细日志**:记录每个决策步骤
|
||||
|
||||
### 缓存管理
|
||||
1. **启动时清理**:应用启动时自动清理缓存目录
|
||||
2. **命令行配置**:添加缓存相关配置避免冲突
|
||||
3. **错误处理**:处理缓存清理过程中的错误
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 自动修复
|
||||
1. 重新运行 `npm run dev`
|
||||
2. 应用会自动处理锁文件和缓存问题
|
||||
3. 如果锁文件被占用,会尝试强制删除
|
||||
|
||||
### 手动清理
|
||||
1. 右键点击托盘图标
|
||||
2. 选择"清理实例锁"或"强制重启应用"
|
||||
3. 查看日志确认操作结果
|
||||
|
||||
### 测试功能
|
||||
```bash
|
||||
# 测试锁文件清理功能
|
||||
node test-lock-cleanup.js
|
||||
```
|
||||
|
||||
## 修复后的启动流程
|
||||
|
||||
### 正常启动
|
||||
```
|
||||
[时间] 开始检查单实例锁...
|
||||
[时间] 发现锁文件: [路径]
|
||||
[时间] 进程 [PID] 不存在,尝试删除无效锁文件
|
||||
[时间] 成功删除文件: [路径]
|
||||
[时间] 单实例锁检查结果: true
|
||||
[时间] 这是第一个实例,继续启动
|
||||
[时间] 应用已准备就绪,开始初始化...
|
||||
[时间] 清理缓存目录: [路径]
|
||||
[时间] 缓存目录清理完成: [路径]
|
||||
```
|
||||
|
||||
### 锁文件被占用
|
||||
```
|
||||
[时间] 发现锁文件: [路径]
|
||||
[时间] 文件被占用,无法删除: [路径]
|
||||
[时间] 使用 del 命令删除文件: [路径]
|
||||
[时间] 单实例锁检查结果: true
|
||||
[时间] 这是第一个实例,继续启动
|
||||
```
|
||||
|
||||
### 强制启动
|
||||
```
|
||||
[时间] 单实例锁检查结果: false
|
||||
[时间] 检测到已有实例运行
|
||||
[时间] 未找到现有实例窗口,可能是锁文件问题,允许强制启动
|
||||
[时间] 这是第一个实例,继续启动
|
||||
```
|
||||
|
||||
## 常见问题解决
|
||||
|
||||
### 1. 锁文件仍然无法删除
|
||||
1. 使用托盘菜单的"强制重启应用"功能
|
||||
2. 手动删除锁文件:
|
||||
```cmd
|
||||
del /F /Q "C:\Users\[用户名]\AppData\Roaming\market-manager-gui\lockfile"
|
||||
```
|
||||
3. 重启开发环境
|
||||
|
||||
### 2. 缓存错误仍然存在
|
||||
1. 手动清理缓存目录:
|
||||
```cmd
|
||||
rmdir /S /Q "C:\Users\[用户名]\AppData\Roaming\market-manager-gui\Cache"
|
||||
rmdir /S /Q "C:\Users\[用户名]\AppData\Roaming\market-manager-gui\Code Cache"
|
||||
```
|
||||
2. 以管理员身份运行应用
|
||||
|
||||
### 3. 权限问题
|
||||
1. 以管理员身份运行命令提示符
|
||||
2. 手动删除相关文件
|
||||
3. 检查文件权限设置
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 开发环境
|
||||
- 在开发过程中,锁文件和缓存可能会频繁创建和删除
|
||||
- 如果遇到问题,使用托盘菜单的清理功能
|
||||
- 查看日志文件获取详细信息
|
||||
|
||||
### 2. 生产环境
|
||||
- 确保应用正常退出时能清理锁文件
|
||||
- 监控缓存目录的大小
|
||||
- 定期检查是否有无效锁文件
|
||||
|
||||
### 3. 调试建议
|
||||
- 使用 `test-lock-cleanup.js` 脚本测试文件删除功能
|
||||
- 查看日志文件了解锁文件和缓存的状态
|
||||
- 使用托盘菜单的手动清理功能
|
||||
|
||||
## 相关文件
|
||||
- `src/main/utils/singleInstance.js` - 单实例锁管理工具(已更新)
|
||||
- `src/main/index.js` - 主进程启动文件(已更新)
|
||||
- `src/main/tray.js` - 托盘功能(已更新)
|
||||
- `test-lock-cleanup.js` - 锁文件清理测试脚本
|
||||
147
LOG_OPTIMIZATION.md
Normal file
147
LOG_OPTIMIZATION.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# 日志优化说明
|
||||
|
||||
## 问题描述
|
||||
应用运行时产生了大量重复的日志,特别是:
|
||||
1. **tokenExpireTimer** 每10秒打印3条日志
|
||||
2. **WebSocket 心跳消息** 每30秒打印1条日志
|
||||
3. **WebSocket 连接成功** 打印重复的 emoji 日志
|
||||
|
||||
## 优化内容
|
||||
|
||||
### 1. tokenExpireTimer 日志优化 (`src/main/window.js`)
|
||||
- **优化前**:每10秒打印3条日志
|
||||
```
|
||||
tokenExpireTimer 触发
|
||||
tokenExpireTimer 触发 对比时间戳
|
||||
tokenExpireTimer 触发 对比时间戳差距为:23
|
||||
```
|
||||
- **优化后**:每60秒打印1条日志
|
||||
```
|
||||
tokenExpireTimer 检查 - 时间差距: 23分钟
|
||||
```
|
||||
|
||||
### 2. WebSocket 心跳消息优化 (`src/main/utils/WebSocketClient.js`)
|
||||
- **优化前**:每30秒打印心跳消息
|
||||
```
|
||||
收到消息: {"cmd":"heartcheck"}
|
||||
```
|
||||
- **优化后**:心跳消息不打印日志,或仅在调试模式下打印
|
||||
|
||||
### 3. WebSocket 连接日志优化
|
||||
- **优化前**:连接成功时打印3行重复的 emoji
|
||||
```
|
||||
😀😀😀😀 WebSocket connect create ok 😀😀😀😀
|
||||
😀😀😀😀 WebSocket connect create ok 😀😀😀😀
|
||||
😀😀😀😀 WebSocket connect create ok 😀😀😀😀
|
||||
```
|
||||
- **优化后**:简洁的连接成功日志
|
||||
```
|
||||
WebSocket 连接成功
|
||||
WebSocket protocol: [协议]
|
||||
```
|
||||
|
||||
### 4. 日志级别控制系统 (`src/main/utils/logger.js`)
|
||||
- 添加日志级别控制功能
|
||||
- 支持 ERROR、WARN、INFO、DEBUG 四个级别
|
||||
- 可以动态调整日志级别
|
||||
|
||||
### 5. 托盘菜单日志设置 (`src/main/tray.js`)
|
||||
- 添加"日志设置"菜单项
|
||||
- 提供三个日志级别选项:
|
||||
- 详细日志 (DEBUG)
|
||||
- 标准日志 (INFO)
|
||||
- 仅错误日志 (ERROR)
|
||||
|
||||
## 优化效果
|
||||
|
||||
### 日志数量对比
|
||||
- **优化前**:每分钟约 18-21 条日志
|
||||
- tokenExpireTimer: 6条/分钟
|
||||
- WebSocket 心跳: 2条/分钟
|
||||
- 其他日志: 10-13条/分钟
|
||||
- **优化后**:每分钟约 2-5 条日志
|
||||
- tokenExpireTimer: 1条/分钟
|
||||
- WebSocket 心跳: 0条/分钟
|
||||
- 其他日志: 1-4条/分钟
|
||||
|
||||
### 日志文件大小
|
||||
- **优化前**:日志文件增长速度快,容易达到几MB
|
||||
- **优化后**:日志文件增长缓慢,便于管理和查看
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 自动优化
|
||||
- 应用启动后自动应用优化设置
|
||||
- 无需用户干预
|
||||
|
||||
### 2. 手动调整日志级别
|
||||
1. 右键点击托盘图标
|
||||
2. 选择"日志设置"
|
||||
3. 选择所需的日志级别:
|
||||
- **详细日志**:显示所有日志,包括调试信息
|
||||
- **标准日志**:显示重要信息,隐藏调试信息
|
||||
- **仅错误日志**:只显示错误和警告信息
|
||||
|
||||
### 3. 查看日志
|
||||
1. 右键点击托盘图标
|
||||
2. 选择"查看日志"
|
||||
3. 系统会自动打开日志文件所在目录
|
||||
|
||||
## 日志级别说明
|
||||
|
||||
### DEBUG (详细日志)
|
||||
- 显示所有日志信息
|
||||
- 包括调试信息、心跳消息等
|
||||
- 适用于开发和调试
|
||||
|
||||
### INFO (标准日志)
|
||||
- 显示重要信息
|
||||
- 隐藏调试信息和心跳消息
|
||||
- 适用于日常使用
|
||||
|
||||
### ERROR (仅错误日志)
|
||||
- 只显示错误和警告信息
|
||||
- 隐藏所有其他日志
|
||||
- 适用于生产环境
|
||||
|
||||
## 配置选项
|
||||
|
||||
### 环境变量
|
||||
```bash
|
||||
# 设置日志级别
|
||||
NODE_ENV=development # 开发环境,显示详细日志
|
||||
NODE_ENV=production # 生产环境,显示标准日志
|
||||
```
|
||||
|
||||
### 代码中设置
|
||||
```javascript
|
||||
import logger from './utils/logger';
|
||||
|
||||
// 设置日志级别
|
||||
logger.setLogLevel('DEBUG'); // 详细日志
|
||||
logger.setLogLevel('INFO'); // 标准日志
|
||||
logger.setLogLevel('ERROR'); // 仅错误日志
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 开发环境
|
||||
- 建议使用"详细日志"级别
|
||||
- 便于调试和问题排查
|
||||
- 可以查看所有系统状态
|
||||
|
||||
### 2. 生产环境
|
||||
- 建议使用"标准日志"或"仅错误日志"级别
|
||||
- 减少日志文件大小
|
||||
- 提高应用性能
|
||||
|
||||
### 3. 问题排查
|
||||
- 如果遇到问题,可以临时切换到"详细日志"级别
|
||||
- 查看完整的系统运行状态
|
||||
- 问题解决后可以切换回标准级别
|
||||
|
||||
## 相关文件
|
||||
- `src/main/window.js` - tokenExpireTimer 优化
|
||||
- `src/main/utils/WebSocketClient.js` - WebSocket 日志优化
|
||||
- `src/main/utils/logger.js` - 日志级别控制系统
|
||||
- `src/main/tray.js` - 托盘菜单日志设置
|
||||
163
SINGLE_INSTANCE_FIX.md
Normal file
163
SINGLE_INSTANCE_FIX.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# 单实例锁问题修复说明
|
||||
|
||||
## 问题描述
|
||||
运行 `npm run dev` 时,程序检测到已有实例运行并退出,但任务管理器中并没有看到已启动的实例:
|
||||
```
|
||||
13:05:39.374 > 单实例锁检查结果: false
|
||||
13:05:39.375 > 检测到已有实例运行,退出当前实例
|
||||
Process finished with exit code 0
|
||||
```
|
||||
|
||||
## 问题原因分析
|
||||
1. **锁文件未正确清理**:之前的实例异常退出时,单实例锁文件没有被正确删除
|
||||
2. **进程检查不完善**:只检查了锁文件存在,没有验证对应的进程是否还在运行
|
||||
3. **锁文件位置不明确**:Electron 的单实例锁可能存储在不同位置
|
||||
4. **异常退出处理不当**:应用崩溃或强制关闭时,锁文件没有被清理
|
||||
|
||||
## 修复内容
|
||||
|
||||
### 1. 创建单实例锁管理工具 (`src/main/utils/singleInstance.js`)
|
||||
- 提供完整的锁文件管理功能
|
||||
- 自动清理无效的锁文件
|
||||
- 检查进程是否还在运行
|
||||
- 在应用退出时自动清理锁文件
|
||||
|
||||
### 2. 改进单实例检查逻辑 (`src/main/index.js`)
|
||||
- 使用新的单实例管理器
|
||||
- 在启动时自动清理无效锁文件
|
||||
- 如果找不到现有实例窗口,允许强制启动
|
||||
- 添加详细的日志记录
|
||||
|
||||
### 3. 添加手动清理功能 (`src/main/tray.js`)
|
||||
- 在托盘菜单中添加"清理实例锁"选项
|
||||
- 允许用户手动清理锁文件
|
||||
- 提供清理结果反馈
|
||||
|
||||
### 4. 创建测试脚本 (`test-single-instance.js`)
|
||||
- 提供锁文件检查和清理的测试功能
|
||||
- 验证进程检查逻辑
|
||||
- 帮助调试锁文件问题
|
||||
|
||||
## 修复后的功能特点
|
||||
|
||||
### 自动锁文件管理
|
||||
1. **启动时检查**:应用启动时自动检查并清理无效锁文件
|
||||
2. **进程验证**:验证锁文件中的进程是否还在运行
|
||||
3. **自动清理**:应用正常退出时自动清理锁文件
|
||||
4. **异常处理**:监听各种退出信号,确保锁文件被清理
|
||||
|
||||
### 手动清理功能
|
||||
1. **托盘菜单**:右键点击托盘图标,选择"清理实例锁"
|
||||
2. **批量清理**:清理多个可能的锁文件位置
|
||||
3. **结果反馈**:显示清理的文件数量和结果
|
||||
|
||||
### 智能启动逻辑
|
||||
1. **锁文件检查**:启动前检查锁文件有效性
|
||||
2. **窗口激活**:如果找到现有实例,尝试激活其窗口
|
||||
3. **强制启动**:如果找不到现有实例,允许强制启动
|
||||
4. **详细日志**:记录每个步骤的执行情况
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 自动修复
|
||||
1. 重新运行 `npm run dev`
|
||||
2. 应用会自动检查并清理无效锁文件
|
||||
3. 如果清理成功,应用会正常启动
|
||||
|
||||
### 手动清理
|
||||
1. 如果应用已经启动,右键点击托盘图标
|
||||
2. 选择"清理实例锁"
|
||||
3. 查看日志确认清理结果
|
||||
4. 重新启动应用
|
||||
|
||||
### 测试锁文件
|
||||
```bash
|
||||
# 运行测试脚本
|
||||
node test-single-instance.js
|
||||
```
|
||||
|
||||
## 锁文件位置
|
||||
|
||||
### Windows 系统
|
||||
- `%APPDATA%\[应用名称]\single-instance-lock`
|
||||
- `%APPDATA%\[应用名称]\lockfile`
|
||||
- `%APPDATA%\[应用名称]\.lock`
|
||||
|
||||
### macOS 系统
|
||||
- `~/Library/Application Support/[应用名称]/single-instance-lock`
|
||||
- `~/Library/Application Support/[应用名称]/lockfile`
|
||||
- `~/Library/Application Support/[应用名称]/.lock`
|
||||
|
||||
### Linux 系统
|
||||
- `~/.config/[应用名称]/single-instance-lock`
|
||||
- `~/.config/[应用名称]/lockfile`
|
||||
- `~/.config/[应用名称]/.lock`
|
||||
|
||||
## 验证修复效果
|
||||
|
||||
### 1. 正常启动
|
||||
运行 `npm run dev` 后,应该看到:
|
||||
```
|
||||
[时间] 单实例锁检查结果: true
|
||||
[时间] 这是第一个实例,继续启动
|
||||
[时间] 应用已准备就绪,开始初始化...
|
||||
```
|
||||
|
||||
### 2. 重复启动
|
||||
如果尝试启动第二个实例,应该看到:
|
||||
```
|
||||
[时间] 单实例锁检查结果: false
|
||||
[时间] 检测到已有实例运行,尝试激活现有实例
|
||||
[时间] 成功激活现有实例窗口
|
||||
```
|
||||
|
||||
### 3. 异常恢复
|
||||
如果之前的实例异常退出,应该看到:
|
||||
```
|
||||
[时间] 发现锁文件: [路径]
|
||||
[时间] 进程 [PID] 不存在,删除无效锁文件
|
||||
[时间] 单实例锁检查结果: true
|
||||
[时间] 这是第一个实例,继续启动
|
||||
```
|
||||
|
||||
## 常见问题解决
|
||||
|
||||
### 1. 仍然无法启动
|
||||
1. 手动清理锁文件:
|
||||
- 右键点击托盘图标 → 清理实例锁
|
||||
- 或者手动删除锁文件
|
||||
2. 检查任务管理器,确保没有残留进程
|
||||
3. 重启开发环境
|
||||
|
||||
### 2. 锁文件清理失败
|
||||
1. 检查文件权限
|
||||
2. 确保没有其他程序占用锁文件
|
||||
3. 以管理员身份运行应用
|
||||
|
||||
### 3. 进程检查不准确
|
||||
1. 在 Windows 上使用 `tasklist` 命令手动检查
|
||||
2. 确认进程 ID 是否正确
|
||||
3. 检查系统权限
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 开发环境
|
||||
- 在开发过程中,锁文件可能会频繁创建和删除
|
||||
- 如果遇到问题,使用托盘菜单的清理功能
|
||||
- 查看日志文件获取详细信息
|
||||
|
||||
### 2. 生产环境
|
||||
- 确保应用正常退出时能清理锁文件
|
||||
- 监控锁文件的状态
|
||||
- 定期检查是否有无效锁文件
|
||||
|
||||
### 3. 调试建议
|
||||
- 使用 `test-single-instance.js` 脚本测试锁文件功能
|
||||
- 查看日志文件了解锁文件的状态
|
||||
- 使用托盘菜单的手动清理功能
|
||||
|
||||
## 相关文件
|
||||
- `src/main/utils/singleInstance.js` - 单实例锁管理工具
|
||||
- `src/main/index.js` - 主进程启动文件(已更新)
|
||||
- `src/main/tray.js` - 托盘功能(已更新)
|
||||
- `test-single-instance.js` - 锁文件测试脚本
|
||||
138
STARTUP_FIX.md
Normal file
138
STARTUP_FIX.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# 应用启动问题修复说明
|
||||
|
||||
## 问题描述
|
||||
运行 `npm run dev` 时,程序在启动过程中立即退出,日志显示:
|
||||
```
|
||||
13:03:04.627 > 应用启动,日志文件路径: C:\Users\coolp\AppData\Roaming\market-manager-gui\logs\main.log
|
||||
Process finished with exit code 0
|
||||
```
|
||||
|
||||
## 问题原因分析
|
||||
1. **WebSocketClient 过早初始化**:在应用启动时立即创建 WebSocket 连接,但此时可能缺少必要的配置信息
|
||||
2. **异步操作处理不当**:`app.whenReady()` 是异步的,但程序在等待完成前就退出了
|
||||
3. **错误处理不完善**:某些同步操作可能抛出异常导致程序退出
|
||||
4. **缺少启动状态日志**:无法确定程序在哪个阶段退出
|
||||
|
||||
## 修复内容
|
||||
|
||||
### 1. 延迟 WebSocket 连接创建
|
||||
- 将 WebSocketClient 的创建延迟到应用完全启动后
|
||||
- 添加错误处理和重试机制
|
||||
- 避免在应用启动时立即连接
|
||||
|
||||
### 2. 改进错误处理
|
||||
- 为所有可能失败的同步操作添加 try-catch
|
||||
- 改进未捕获异常的处理,不立即退出程序
|
||||
- 添加详细的错误日志
|
||||
|
||||
### 3. 增加启动日志
|
||||
- 添加详细的启动过程日志
|
||||
- 记录每个关键步骤的执行状态
|
||||
- 便于定位问题所在
|
||||
|
||||
### 4. 修复异步操作
|
||||
- 确保 `app.whenReady()` 正确等待
|
||||
- 添加启动完成确认日志
|
||||
- 改进事件监听器的设置
|
||||
|
||||
## 修复后的启动流程
|
||||
|
||||
### 启动日志示例
|
||||
```
|
||||
[时间] 当前运行平台: win32
|
||||
[时间] Windows 系统,设置控制台编码为 UTF-8
|
||||
[时间] Store 初始化成功
|
||||
[时间] 硬件加速已禁用
|
||||
[时间] 应用启动,日志文件路径: [路径]
|
||||
[时间] 开始检查单实例锁...
|
||||
[时间] 单实例锁检查结果: true
|
||||
[时间] 这是第一个实例,继续启动
|
||||
[时间] 应用已准备就绪,开始初始化...
|
||||
[时间] 应用初始化完成
|
||||
[时间] WebSocket连接已打开 (延迟2秒后)
|
||||
```
|
||||
|
||||
### 关键修复点
|
||||
|
||||
#### 1. WebSocketClient 延迟创建
|
||||
```javascript
|
||||
// 延迟创建WebSocket连接
|
||||
setTimeout(() => {
|
||||
createWebSocketClient()
|
||||
}, 2000); // 延迟2秒创建WebSocket连接
|
||||
```
|
||||
|
||||
#### 2. 错误处理改进
|
||||
```javascript
|
||||
// 改进未捕获异常处理
|
||||
process.on('uncaughtException', (error) => {
|
||||
logger.error('未捕获的异常:', error);
|
||||
// 不要立即退出,给应用一个恢复的机会
|
||||
logger.error('应用遇到未捕获的异常,但将继续运行');
|
||||
});
|
||||
```
|
||||
|
||||
#### 3. 同步操作保护
|
||||
```javascript
|
||||
try {
|
||||
app.disableHardwareAcceleration()
|
||||
logger.info('硬件加速已禁用')
|
||||
} catch (error) {
|
||||
logger.error('禁用硬件加速失败:', error)
|
||||
}
|
||||
```
|
||||
|
||||
## 验证修复效果
|
||||
|
||||
### 1. 检查启动日志
|
||||
运行 `npm run dev` 后,查看控制台输出,应该能看到完整的启动日志,包括:
|
||||
- 应用准备就绪
|
||||
- 初始化完成
|
||||
- WebSocket 连接成功
|
||||
|
||||
### 2. 检查应用状态
|
||||
- 应用应该正常启动并显示主窗口
|
||||
- 托盘图标应该正常显示
|
||||
- 不应该出现 "Process finished with exit code 0" 的提前退出
|
||||
|
||||
### 3. 检查日志文件
|
||||
查看日志文件 `C:\Users\coolp\AppData\Roaming\market-manager-gui\logs\main.log`,应该包含完整的启动过程记录。
|
||||
|
||||
## 测试脚本
|
||||
|
||||
### 启动测试
|
||||
```bash
|
||||
# 运行应用
|
||||
npm run dev
|
||||
|
||||
# 检查日志
|
||||
tail -f "C:\Users\coolp\AppData\Roaming\market-manager-gui\logs\main.log"
|
||||
```
|
||||
|
||||
### 功能测试
|
||||
1. 应用启动后,右键点击托盘图标
|
||||
2. 选择"查看日志"验证日志功能
|
||||
3. 选择"清除缓存"验证缓存清理功能
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 开发环境
|
||||
- 在开发环境中,应用可能需要更长时间启动
|
||||
- WebSocket 连接可能会因为缺少配置而失败,这是正常的
|
||||
- 查看日志文件获取详细的错误信息
|
||||
|
||||
### 2. 生产环境
|
||||
- 确保所有必要的配置都已设置
|
||||
- WebSocket 连接应该能够正常建立
|
||||
- 应用应该能够稳定运行
|
||||
|
||||
### 3. 调试建议
|
||||
- 如果仍然有问题,查看日志文件中的详细错误信息
|
||||
- 检查配置文件是否正确设置
|
||||
- 确认网络连接是否正常
|
||||
|
||||
## 相关文件
|
||||
- `src/main/index.js` - 主进程启动文件
|
||||
- `src/main/utils/WebSocketClient.js` - WebSocket 客户端
|
||||
- `src/main/utils/logger.js` - 日志工具
|
||||
- `test-startup.js` - 启动测试脚本
|
||||
125
TRAY_MENU_SIMPLIFICATION.md
Normal file
125
TRAY_MENU_SIMPLIFICATION.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# 托盘菜单简化说明
|
||||
|
||||
## 简化内容
|
||||
|
||||
根据用户需求,移除了托盘菜单中的以下选项:
|
||||
1. **日志设置** - 包含详细日志、标准日志、仅错误日志的子菜单
|
||||
2. **查看日志** - 打开日志文件所在目录的功能
|
||||
3. **清理实例锁** - 手动清理锁文件的功能
|
||||
4. **强制重启应用** - 强制重启应用的功能
|
||||
|
||||
## 简化后的托盘菜单
|
||||
|
||||
### 当前菜单结构
|
||||
```
|
||||
显示主窗口
|
||||
隐藏主窗口
|
||||
---
|
||||
配置
|
||||
└─ 配置客户端地址
|
||||
---
|
||||
显示隐藏桌面悬浮
|
||||
---
|
||||
检查更新
|
||||
---
|
||||
清除缓存
|
||||
---
|
||||
退出登录
|
||||
---
|
||||
退出应用
|
||||
```
|
||||
|
||||
### 保留的核心功能
|
||||
1. **窗口管理**
|
||||
- 显示主窗口
|
||||
- 隐藏主窗口
|
||||
|
||||
2. **配置管理**
|
||||
- 配置客户端地址
|
||||
|
||||
3. **悬浮窗口**
|
||||
- 显示隐藏桌面悬浮
|
||||
|
||||
4. **系统功能**
|
||||
- 检查更新
|
||||
- 清除缓存
|
||||
- 退出登录
|
||||
- 退出应用
|
||||
|
||||
## 移除的功能说明
|
||||
|
||||
### 1. 日志设置
|
||||
- **移除原因**:用户不需要在托盘中调整日志级别
|
||||
- **替代方案**:日志级别可以通过代码或环境变量设置
|
||||
- **影响**:日志优化功能仍然有效,只是不能通过托盘菜单调整
|
||||
|
||||
### 2. 查看日志
|
||||
- **移除原因**:用户不需要频繁查看日志文件
|
||||
- **替代方案**:日志文件位置:`%APPDATA%\[应用名称]\logs\main.log`
|
||||
- **影响**:日志仍然正常记录,只是不能通过托盘菜单快速访问
|
||||
|
||||
### 3. 清理实例锁
|
||||
- **移除原因**:应用已经自动处理锁文件清理
|
||||
- **替代方案**:应用启动时自动清理无效锁文件
|
||||
- **影响**:单实例锁管理功能仍然有效,只是不能手动触发
|
||||
|
||||
### 4. 强制重启应用
|
||||
- **移除原因**:用户不需要强制重启功能
|
||||
- **替代方案**:正常退出后重新启动应用
|
||||
- **影响**:应用启动问题已经修复,通常不需要强制重启
|
||||
|
||||
## 简化效果
|
||||
|
||||
### 菜单简洁性
|
||||
- **简化前**:12个菜单项
|
||||
- **简化后**:8个菜单项
|
||||
- **减少**:33% 的菜单项
|
||||
|
||||
### 用户体验
|
||||
- **更简洁**:菜单更短,更容易浏览
|
||||
- **更专注**:只保留核心功能
|
||||
- **更直观**:功能分类更清晰
|
||||
|
||||
### 维护性
|
||||
- **代码更简洁**:减少了不必要的菜单项代码
|
||||
- **功能更集中**:专注于核心功能
|
||||
- **更易维护**:减少了菜单相关的维护工作
|
||||
|
||||
## 功能保留说明
|
||||
|
||||
### 自动功能
|
||||
以下功能仍然自动运行,无需用户干预:
|
||||
1. **日志优化**:自动减少日志打印频率
|
||||
2. **锁文件管理**:自动清理无效锁文件
|
||||
3. **缓存管理**:自动处理缓存冲突
|
||||
4. **错误处理**:自动处理各种异常情况
|
||||
|
||||
### 手动功能
|
||||
以下功能仍然可以通过托盘菜单使用:
|
||||
1. **窗口控制**:显示/隐藏主窗口
|
||||
2. **配置管理**:配置客户端地址
|
||||
3. **悬浮窗口**:控制桌面悬浮窗口
|
||||
4. **系统操作**:更新、缓存清理、退出等
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 日志管理
|
||||
- 日志仍然正常记录和优化
|
||||
- 如果需要调整日志级别,可以通过代码修改
|
||||
- 日志文件仍然可以手动查看
|
||||
|
||||
### 2. 问题排查
|
||||
- 如果遇到启动问题,应用会自动处理
|
||||
- 如果遇到锁文件问题,应用会自动清理
|
||||
- 如果遇到缓存问题,可以使用"清除缓存"功能
|
||||
|
||||
### 3. 开发调试
|
||||
- 开发时可以通过代码设置日志级别
|
||||
- 调试时可以直接查看日志文件
|
||||
- 问题排查功能仍然完整
|
||||
|
||||
## 相关文件
|
||||
- `src/main/tray.js` - 托盘菜单(已简化)
|
||||
- `src/main/utils/logger.js` - 日志系统(功能保留)
|
||||
- `src/main/utils/singleInstance.js` - 单实例管理(功能保留)
|
||||
- `src/main/utils/cacheUtils.js` - 缓存管理(功能保留)
|
||||
@@ -3,28 +3,46 @@ import { electronApp, optimizer } from '@electron-toolkit/utils'
|
||||
import Store from 'electron-store'
|
||||
import { createWindow, createDrageWindow, unregisterAllShortcuts } from './window.js'
|
||||
import { setupIPC } from './ipc.js'
|
||||
import { createTray, destroyTray } from './tray.js'
|
||||
import { getStoreValue } from './store.js'
|
||||
import { createTray, destroyTray,clearBrowserCache } from './tray.js'
|
||||
import XEUtils from 'xe-utils'
|
||||
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import dayjs from 'dayjs'
|
||||
import logger from './utils/logger'
|
||||
|
||||
import { clearAllSessionData } from './utils/cacheUtils.js'
|
||||
import SingleInstanceManager from './utils/singleInstance.js'
|
||||
import { setStoreValue, getStoreValue ,deleteStore} from './store.js'
|
||||
import AutoLaunch from 'auto-launch'
|
||||
|
||||
import WebSocketClient from './utils/WebSocketClient';
|
||||
|
||||
let wsClient=null
|
||||
|
||||
wsClient=new WebSocketClient({
|
||||
autoReconnect: true,
|
||||
autoReconnectAttempts: 9999,
|
||||
autoReconnectInterval: 5000,
|
||||
timeout: 30000,
|
||||
})
|
||||
// 延迟创建WebSocket连接,等待应用完全启动后再连接
|
||||
function createWebSocketClient() {
|
||||
try {
|
||||
wsClient = new WebSocketClient({
|
||||
autoReconnect: true,
|
||||
autoReconnectAttempts: 9999,
|
||||
autoReconnectInterval: 5000,
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
wsClient.on('open', () => {
|
||||
logger.info('WebSocket连接已打开')
|
||||
});
|
||||
wsClient.on('open', () => {
|
||||
logger.info('WebSocket连接已打开')
|
||||
});
|
||||
|
||||
wsClient.on('error', (error) => {
|
||||
logger.error('WebSocket连接错误:', error)
|
||||
});
|
||||
|
||||
wsClient.on('close', (data) => {
|
||||
logger.info('WebSocket连接已关闭:', data)
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('创建WebSocket客户端失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var minecraftAutoLauncher = new AutoLaunch({
|
||||
@@ -53,40 +71,132 @@ if(!XEUtils.isEmpty(difySite)){
|
||||
logger.info(`当前运行平台: ${process.platform}`)
|
||||
if (process.platform === 'win32') {
|
||||
logger.info('Windows 系统,设置控制台编码为 UTF-8')
|
||||
require('child_process').execSync('chcp 65001', { stdio: 'ignore' })
|
||||
try {
|
||||
require('child_process').execSync('chcp 65001', { stdio: 'ignore' })
|
||||
} catch (error) {
|
||||
logger.error('设置控制台编码失败:', error)
|
||||
}
|
||||
} else {
|
||||
logger.info('非 Windows 系统,使用默认编码')
|
||||
}
|
||||
|
||||
logger.info('%cRed text. %cGreen text', 'color: red', 'color: green')
|
||||
|
||||
const store = new Store()
|
||||
try {
|
||||
const store = new Store()
|
||||
logger.info('Store 初始化成功')
|
||||
} catch (error) {
|
||||
logger.error('Store 初始化失败:', error)
|
||||
}
|
||||
|
||||
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
|
||||
app.commandLine.appendSwitch('ignore-certificate-errors')
|
||||
|
||||
app.disableHardwareAcceleration()
|
||||
// 添加缓存相关配置,避免缓存错误
|
||||
app.commandLine.appendSwitch('disable-http-cache')
|
||||
app.commandLine.appendSwitch('disable-background-timer-throttling')
|
||||
app.commandLine.appendSwitch('disable-renderer-backgrounding')
|
||||
|
||||
try {
|
||||
app.disableHardwareAcceleration()
|
||||
logger.info('硬件加速已禁用')
|
||||
} catch (error) {
|
||||
logger.error('禁用硬件加速失败:', error)
|
||||
}
|
||||
|
||||
logger.info(app.getPath('userData'))
|
||||
logger.info('应用启动,日志文件路径:', logger.getLogPath())
|
||||
|
||||
// 添加更多启动信息
|
||||
logger.info('开始检查单实例锁...')
|
||||
|
||||
// 创建单实例管理器
|
||||
const singleInstanceManager = new SingleInstanceManager()
|
||||
|
||||
// 清理缓存目录
|
||||
function cleanupCacheDirectories() {
|
||||
try {
|
||||
const userDataPath = app.getPath('userData')
|
||||
const cachePaths = [
|
||||
path.join(userDataPath, 'Cache'),
|
||||
path.join(userDataPath, 'Code Cache'),
|
||||
path.join(userDataPath, 'GPUCache'),
|
||||
path.join(userDataPath, 'Service Worker'),
|
||||
path.join(userDataPath, 'Session Storage')
|
||||
]
|
||||
|
||||
for (const cachePath of cachePaths) {
|
||||
if (fs.existsSync(cachePath)) {
|
||||
logger.info(`清理缓存目录: ${cachePath}`)
|
||||
try {
|
||||
// 递归删除目录
|
||||
const deleteRecursive = (dirPath) => {
|
||||
if (fs.existsSync(dirPath)) {
|
||||
const files = fs.readdirSync(dirPath)
|
||||
for (const file of files) {
|
||||
const curPath = path.join(dirPath, file)
|
||||
if (fs.lstatSync(curPath).isDirectory()) {
|
||||
deleteRecursive(curPath)
|
||||
} else {
|
||||
try {
|
||||
fs.unlinkSync(curPath)
|
||||
} catch (error) {
|
||||
logger.warn(`无法删除缓存文件: ${curPath}`, error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
fs.rmdirSync(dirPath)
|
||||
} catch (error) {
|
||||
logger.warn(`无法删除缓存目录: ${dirPath}`, error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteRecursive(cachePath)
|
||||
logger.info(`缓存目录清理完成: ${cachePath}`)
|
||||
} catch (error) {
|
||||
logger.error(`清理缓存目录失败: ${cachePath}`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('清理缓存目录失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 检查是否为第一个实例
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
const isFirstInstance = singleInstanceManager.checkSingleInstance()
|
||||
|
||||
if (!gotTheLock) {
|
||||
// 如果不是第一个实例,尝试激活第一个实例的窗口
|
||||
const windows = BrowserWindow.getAllWindows()
|
||||
if (windows.length > 0) {
|
||||
const mainWindow = windows[0]
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore()
|
||||
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('尝试强制启动新实例...')
|
||||
// 继续启动
|
||||
}
|
||||
mainWindow.show()
|
||||
mainWindow.focus()
|
||||
} catch (error) {
|
||||
logger.error('激活现有实例失败:', error)
|
||||
logger.info('尝试强制启动新实例...')
|
||||
// 继续启动
|
||||
}
|
||||
app.quit() // 退出当前实例
|
||||
} else {
|
||||
logger.info('这是第一个实例,继续启动')
|
||||
// 这是第一个实例
|
||||
|
||||
// 监听第二个实例的启动
|
||||
@@ -104,6 +214,10 @@ if (!gotTheLock) {
|
||||
})
|
||||
|
||||
app.whenReady().then(() => {
|
||||
logger.info('应用已准备就绪,开始初始化...');
|
||||
|
||||
// 清理缓存目录
|
||||
cleanupCacheDirectories()
|
||||
|
||||
// 获取默认会话并全局设置允许第三方 Cookie
|
||||
const defaultSession = session.defaultSession;
|
||||
@@ -134,6 +248,13 @@ if (!gotTheLock) {
|
||||
createTray()
|
||||
setupIPC()
|
||||
|
||||
// exe退出三秒之后即认为关闭过exe程序,退出登录
|
||||
checkIsKeepAlive()
|
||||
|
||||
// 延迟创建WebSocket连接
|
||||
setTimeout(() => {
|
||||
createWebSocketClient()
|
||||
}, 2000); // 延迟2秒创建WebSocket连接
|
||||
|
||||
minecraftAutoLauncher.isEnabled()
|
||||
.then(function(isEnabled){
|
||||
@@ -151,26 +272,134 @@ if (!gotTheLock) {
|
||||
app.on('activate', function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
|
||||
logger.info('应用初始化完成');
|
||||
})
|
||||
|
||||
// 修改窗口关闭行为
|
||||
app.on('window-all-closed', () => {
|
||||
app.on('window-all-closed', async (event) => {
|
||||
if (process.platform !== 'darwin') {
|
||||
event.preventDefault();
|
||||
if (!app.isQuiting) {
|
||||
|
||||
// 如果不是主动退出,则隐藏所有窗口
|
||||
BrowserWindow.getAllWindows().forEach(window => {
|
||||
window.hide()
|
||||
})
|
||||
|
||||
// 清除所有会话数据
|
||||
try {
|
||||
await clearAllSessionData('window-all-closed');
|
||||
} catch (error) {
|
||||
logger.error('窗口关闭时清理缓存失败:', error);
|
||||
}
|
||||
|
||||
// 清理完成后退出应用
|
||||
app.quit();
|
||||
|
||||
} else {
|
||||
// 如果是主动退出,则销毁托盘并退出应用
|
||||
destroyTray()
|
||||
app.quit()
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.on('before-quit', async (event) => {
|
||||
// 在应用程序即将退出时执行操作,例如保存数据
|
||||
event.preventDefault();
|
||||
|
||||
// 清除所有会话数据
|
||||
try {
|
||||
await clearAllSessionData('before-quit');
|
||||
} catch (error) {
|
||||
logger.error('应用退出时清理缓存失败:', error);
|
||||
}
|
||||
|
||||
// 清理完成后退出应用
|
||||
app.quit();
|
||||
});
|
||||
|
||||
// 在应用退出时注销所有快捷键
|
||||
app.on('will-quit', () => {
|
||||
app.on('will-quit', async (event) => {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// 清除所有会话数据
|
||||
try {
|
||||
await clearAllSessionData('will-quit');
|
||||
} catch (error) {
|
||||
logger.error('应用退出时清理缓存失败:', error);
|
||||
}
|
||||
|
||||
unregisterAllShortcuts()
|
||||
|
||||
// 清理完成后退出应用
|
||||
app.quit();
|
||||
})
|
||||
|
||||
|
||||
|
||||
// 监听进程退出信号,确保在系统强制关闭时也能清理缓存
|
||||
process.on('SIGTERM', async () => {
|
||||
logger.info('收到 SIGTERM 信号,开始清理缓存');
|
||||
try {
|
||||
await clearAllSessionData('SIGTERM');
|
||||
} catch (error) {
|
||||
logger.error('SIGTERM 时清理缓存失败:', error);
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGINT', async () => {
|
||||
logger.info('收到 SIGINT 信号,开始清理缓存');
|
||||
try {
|
||||
await clearAllSessionData('SIGINT');
|
||||
} catch (error) {
|
||||
logger.error('SIGINT 时清理缓存失败:', error);
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// 监听未捕获的异常,确保应用崩溃时也能记录日志
|
||||
process.on('uncaughtException', (error) => {
|
||||
logger.error('未捕获的异常:', error);
|
||||
// 不要立即退出,给应用一个恢复的机会
|
||||
logger.error('应用遇到未捕获的异常,但将继续运行');
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
logger.error('未处理的 Promise 拒绝:', reason);
|
||||
// 不要立即退出,给应用一个恢复的机会
|
||||
logger.error('应用遇到未处理的 Promise 拒绝,但将继续运行');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function checkIsKeepAlive(){
|
||||
|
||||
const checkIsKeepAliveTimer=setInterval(async () => {
|
||||
|
||||
const lastAliveTime = getStoreValue("lastAliveTime")||null
|
||||
|
||||
if (lastAliveTime!=null){
|
||||
const nowTime=dayjs();
|
||||
const lastTime=dayjs(lastAliveTime);
|
||||
const diff=nowTime.diff(lastTime, 'second')
|
||||
|
||||
|
||||
// 上次在线事件在三秒之前,则认为关闭过exe程序
|
||||
if (diff>5){
|
||||
logger.info('上次在线事件在三秒之前,则认为关闭过exe程序')
|
||||
await clearAllSessionData('超时5秒');
|
||||
deleteStore("lastAliveTime")
|
||||
}else{
|
||||
setStoreValue("lastAliveTime",dayjs())
|
||||
}
|
||||
}
|
||||
|
||||
}, 1000 * 2)
|
||||
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import path from 'path';
|
||||
log.initialize();
|
||||
import {checkForUpdates} from "./utils/updateUtils"
|
||||
import logger from './utils/logger'
|
||||
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
function isValidUrl(_url) {
|
||||
const containsApp = _url.includes('/app/');
|
||||
@@ -34,9 +34,8 @@ export function setupIPC() {
|
||||
|
||||
setStoreValue("difySite", data.difySite);
|
||||
|
||||
setStoreValue("test_12222222", "test_12222222");
|
||||
|
||||
|
||||
setStoreValue("lastAliveTime",dayjs())
|
||||
|
||||
difyRetryRequestTimer()
|
||||
|
||||
|
||||
@@ -1,38 +1,22 @@
|
||||
import { Tray, Menu, app, BrowserWindow } from 'electron'
|
||||
import { Tray, Menu, app, BrowserWindow, shell } from 'electron'
|
||||
import { join } from 'path'
|
||||
import icon from '../../resources/icon.png?asset'
|
||||
import { getMainWindow, createWindow, createApiConfigWindow, createConfigWindow ,getDragWindow} from './window.js'
|
||||
import { getStoreValue, setStoreValue, clearStore } from './store.js'
|
||||
import { createDrageWindow, getDrageWindow } from './window.js'
|
||||
import logger from './utils/logger'
|
||||
import { clearAllWindowsCache } from './utils/cacheUtils.js'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
let tray = null
|
||||
|
||||
import {checkForUpdates} from "./utils/updateUtils"
|
||||
|
||||
async function clearBrowserCache() {
|
||||
export async function clearBrowserCache() {
|
||||
try {
|
||||
// 清除所有窗口的浏览器缓存
|
||||
const windows = BrowserWindow.getAllWindows()
|
||||
for (const window of windows) {
|
||||
const session = window.webContents.session
|
||||
await session.clearStorageData({
|
||||
storages: [
|
||||
'appcache',
|
||||
'cookies',
|
||||
'filesystem',
|
||||
'indexdb',
|
||||
'localstorage',
|
||||
'shadercache',
|
||||
'websql',
|
||||
'serviceworkers',
|
||||
'cachestorage'
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
logger.info('浏览器缓存清除成功')
|
||||
// 使用统一的缓存清理函数
|
||||
await clearAllWindowsCache('tray-clear-cache');
|
||||
} catch (error) {
|
||||
logger.error('清除浏览器缓存失败:', error)
|
||||
}
|
||||
@@ -108,6 +92,7 @@ export function createTray() {
|
||||
checkForUpdates(menuItem,true)
|
||||
}
|
||||
},
|
||||
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: '清除缓存',
|
||||
|
||||
@@ -6,16 +6,19 @@ import { Notification } from "electron";
|
||||
|
||||
class WebSocketClient {
|
||||
constructor(options = {}) {
|
||||
this.reconnectInterval = 5000; // 重连间隔时间
|
||||
this.reconnectInterval = options.reconnectInterval || 5000; // 重连间隔时间
|
||||
this.lockReconnect = false;
|
||||
this.ws = null;
|
||||
this.pingTimeout = null;
|
||||
this.reconnectAttempts = 0;
|
||||
this.messageHandlers = new Map();
|
||||
this.isConnected = false;
|
||||
this.options = options;
|
||||
|
||||
// 初始化时立即创建连接
|
||||
this.createConnect();
|
||||
// 延迟创建连接,避免在应用启动时立即连接
|
||||
setTimeout(() => {
|
||||
this.createConnect();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
createConnect() {
|
||||
@@ -26,10 +29,10 @@ class WebSocketClient {
|
||||
|
||||
// 检查必要的连接信息是否存在
|
||||
if (!apiUrl || !userInfo || !token) {
|
||||
logger.error("WebSocket连接信息不完整,等待重试");
|
||||
logger.error("apiUrl:", apiUrl);
|
||||
logger.error("userInfo:", userInfo);
|
||||
logger.error("token:", token);
|
||||
logger.info("WebSocket连接信息不完整,等待重试");
|
||||
logger.info("apiUrl:", apiUrl);
|
||||
logger.info("userInfo:", userInfo);
|
||||
logger.info("token:", token);
|
||||
this.scheduleReconnect();
|
||||
return;
|
||||
}
|
||||
@@ -92,10 +95,8 @@ class WebSocketClient {
|
||||
this.ws = new WebSocket(this.url, token, this.options);
|
||||
|
||||
this.ws.on('open', () => {
|
||||
logger.info('😀😀😀😀 WebSocket connect create ok 😀😀😀😀');
|
||||
logger.info('😀😀😀😀 WebSocket connect create ok 😀😀😀😀');
|
||||
logger.info('😀😀😀😀 WebSocket connect create ok 😀😀😀😀');
|
||||
logger.info('WebSocket protocol:', this.ws.protocol); // 添加协议日志
|
||||
logger.info('WebSocket 连接成功');
|
||||
logger.info('WebSocket protocol:', this.ws.protocol);
|
||||
this.isConnected = true;
|
||||
this.reconnectAttempts = 0;
|
||||
this.lockReconnect = false;
|
||||
@@ -105,8 +106,19 @@ class WebSocketClient {
|
||||
|
||||
this.ws.on('message', (data) => {
|
||||
try {
|
||||
logger.info('收到消息:', data.toString());
|
||||
const message = JSON.parse(data);
|
||||
const messageStr = data.toString();
|
||||
const message = JSON.parse(messageStr);
|
||||
|
||||
// 减少心跳消息的日志打印
|
||||
if (message.cmd === 'heartcheck') {
|
||||
// 心跳消息不打印日志,或者只在调试模式下打印
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
logger.debug('收到心跳消息');
|
||||
}
|
||||
} else {
|
||||
logger.info('收到消息:', messageStr);
|
||||
}
|
||||
|
||||
this.handleMessage(message);
|
||||
} catch (error) {
|
||||
logger.error('消息解析错误:', error);
|
||||
|
||||
93
src/main/utils/cacheUtils.js
Normal file
93
src/main/utils/cacheUtils.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import { session } from 'electron'
|
||||
import logger from './logger.js'
|
||||
|
||||
/**
|
||||
* 清除所有会话数据的统一函数
|
||||
* @param {string} context - 清理上下文,用于日志记录
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function clearAllSessionData(context = 'unknown') {
|
||||
logger.info(`[${context}] 开始清除所有会话数据`);
|
||||
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
session.defaultSession.clearStorageData({
|
||||
storages: [
|
||||
'appcache',
|
||||
'cookies',
|
||||
'filesystem',
|
||||
'indexdb',
|
||||
'localstorage',
|
||||
'shadercache',
|
||||
'websql',
|
||||
'serviceworkers',
|
||||
'cachestorage'
|
||||
]
|
||||
}, (error) => {
|
||||
if (error) {
|
||||
logger.error(`[${context}] 清除会话数据失败:`, error);
|
||||
reject(error);
|
||||
} else {
|
||||
logger.info(`[${context}] 会话数据清除成功`);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 强制同步日志
|
||||
logger.info(`[${context}] 缓存清理完成`);
|
||||
|
||||
// 等待一小段时间确保日志写入
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`[${context}] 清理缓存失败:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有窗口的浏览器缓存
|
||||
* @param {string} context - 清理上下文,用于日志记录
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function clearAllWindowsCache(context = 'unknown') {
|
||||
logger.info(`[${context}] 开始清除所有窗口的浏览器缓存`);
|
||||
|
||||
try {
|
||||
const { BrowserWindow } = require('electron');
|
||||
const windows = BrowserWindow.getAllWindows();
|
||||
|
||||
for (const window of windows) {
|
||||
const session = window.webContents.session;
|
||||
await new Promise((resolve, reject) => {
|
||||
session.clearStorageData({
|
||||
storages: [
|
||||
'appcache',
|
||||
'cookies',
|
||||
'filesystem',
|
||||
'indexdb',
|
||||
'localstorage',
|
||||
'shadercache',
|
||||
'websql',
|
||||
'serviceworkers',
|
||||
'cachestorage'
|
||||
]
|
||||
}, (error) => {
|
||||
if (error) {
|
||||
logger.error(`[${context}] 清除窗口缓存失败:`, error);
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(`[${context}] 所有窗口缓存清除成功`);
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`[${context}] 清除窗口缓存失败:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,72 @@
|
||||
import log from 'electron-log/main';
|
||||
import { app } from 'electron';
|
||||
import path from 'path';
|
||||
|
||||
log.initialize();
|
||||
|
||||
// 配置日志立即写入
|
||||
log.transports.file.level = 'info';
|
||||
log.transports.console.level = 'info';
|
||||
|
||||
// 确保日志立即写入文件
|
||||
log.transports.file.sync = true;
|
||||
|
||||
// 获取日志文件路径
|
||||
const logFilePath = log.transports.file.getFile().path;
|
||||
log.info(`日志文件路径: ${logFilePath}`);
|
||||
|
||||
// 日志级别配置
|
||||
const LOG_LEVELS = {
|
||||
ERROR: 0,
|
||||
WARN: 1,
|
||||
INFO: 2,
|
||||
DEBUG: 3
|
||||
};
|
||||
|
||||
let currentLogLevel = LOG_LEVELS.INFO; // 默认日志级别
|
||||
|
||||
// 设置日志级别
|
||||
export function setLogLevel(level) {
|
||||
if (typeof level === 'string') {
|
||||
currentLogLevel = LOG_LEVELS[level.toUpperCase()] || LOG_LEVELS.INFO;
|
||||
} else {
|
||||
currentLogLevel = level;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否应该打印日志
|
||||
function shouldLog(level) {
|
||||
return level <= currentLogLevel;
|
||||
}
|
||||
|
||||
export const logger = {
|
||||
log: log.info,
|
||||
info: log.info,
|
||||
error: log.error,
|
||||
warn: log.warn,
|
||||
debug: log.debug
|
||||
log: (message, ...args) => {
|
||||
if (shouldLog(LOG_LEVELS.INFO)) {
|
||||
log.info(message, ...args);
|
||||
}
|
||||
},
|
||||
info: (message, ...args) => {
|
||||
if (shouldLog(LOG_LEVELS.INFO)) {
|
||||
log.info(message, ...args);
|
||||
}
|
||||
},
|
||||
error: (message, ...args) => {
|
||||
if (shouldLog(LOG_LEVELS.ERROR)) {
|
||||
log.error(message, ...args);
|
||||
}
|
||||
},
|
||||
warn: (message, ...args) => {
|
||||
if (shouldLog(LOG_LEVELS.WARN)) {
|
||||
log.warn(message, ...args);
|
||||
}
|
||||
},
|
||||
debug: (message, ...args) => {
|
||||
if (shouldLog(LOG_LEVELS.DEBUG)) {
|
||||
log.debug(message, ...args);
|
||||
}
|
||||
},
|
||||
getLogPath: () => logFilePath,
|
||||
setLogLevel: setLogLevel
|
||||
}
|
||||
|
||||
export default logger
|
||||
|
||||
205
src/main/utils/singleInstance.js
Normal file
205
src/main/utils/singleInstance.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import { app } from 'electron'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import logger from './logger.js'
|
||||
|
||||
/**
|
||||
* 单实例锁管理工具
|
||||
*/
|
||||
export class SingleInstanceManager {
|
||||
constructor() {
|
||||
this.lockFile = null
|
||||
this.isLocked = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理可能存在的无效单实例锁
|
||||
*/
|
||||
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) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建锁文件
|
||||
*/
|
||||
createLock() {
|
||||
try {
|
||||
const userDataPath = app.getPath('userData')
|
||||
this.lockFile = path.join(userDataPath, 'single-instance-lock')
|
||||
|
||||
// 写入当前进程的 PID
|
||||
fs.writeFileSync(this.lockFile, process.pid.toString())
|
||||
this.isLocked = true
|
||||
|
||||
logger.info(`创建单实例锁文件: ${this.lockFile}, PID: ${process.pid}`)
|
||||
|
||||
// 监听应用退出事件,清理锁文件
|
||||
app.on('before-quit', () => {
|
||||
this.removeLock()
|
||||
})
|
||||
|
||||
app.on('will-quit', () => {
|
||||
this.removeLock()
|
||||
})
|
||||
|
||||
// 监听进程退出信号
|
||||
process.on('exit', () => {
|
||||
this.removeLock()
|
||||
})
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
this.removeLock()
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
this.removeLock()
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
logger.error('创建锁文件失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除锁文件
|
||||
*/
|
||||
removeLock() {
|
||||
if (this.lockFile && this.isLocked) {
|
||||
try {
|
||||
if (fs.existsSync(this.lockFile)) {
|
||||
fs.unlinkSync(this.lockFile)
|
||||
logger.info(`移除锁文件: ${this.lockFile}`)
|
||||
}
|
||||
this.isLocked = false
|
||||
} catch (error) {
|
||||
logger.error('移除锁文件失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为第一个实例
|
||||
*/
|
||||
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
|
||||
@@ -11,13 +11,12 @@ import { setStoreValue, getStoreValue,deleteStore } from './store.js'
|
||||
import {checkForUpdates} from "./utils/updateUtils"
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
|
||||
let mainWindow = null
|
||||
let difyfullScreenWindow = null
|
||||
let drageWindow = null
|
||||
let apiConfigWindow = null
|
||||
let configWindow = null
|
||||
|
||||
import { clearAllSessionData } from './utils/cacheUtils.js'
|
||||
// 权限授权
|
||||
async function checkMediaAccess(mediaType){
|
||||
const result = systemPreferences.getMediaAccessStatus(mediaType)
|
||||
@@ -109,6 +108,9 @@ export async function createWindow() {
|
||||
mainWindow.webContents.on('before-input-event', (event, input) => {
|
||||
if (input.key === 'F12') {
|
||||
mainWindow.webContents.toggleDevTools()
|
||||
} else if (input.key === 'F5') {
|
||||
logger.info('主窗口 F5 快捷键触发')
|
||||
mainWindow.reload()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -164,9 +166,11 @@ export async function createWindow() {
|
||||
// 加载存储的 URL
|
||||
mainWindow.loadURL(h5_client_url)
|
||||
|
||||
// 超过30分钟不活动,则退出登录
|
||||
// 接口超过30分钟不活动,则退出登录
|
||||
await tokenExpireTimer()
|
||||
|
||||
|
||||
|
||||
setTimeout(()=>{
|
||||
try {
|
||||
// 注册全局快捷键
|
||||
@@ -189,21 +193,44 @@ export async function createWindow() {
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 添加一个专门的快捷键注册函数
|
||||
function registerShortcuts(window) {
|
||||
function registerShortcuts(window=null) {
|
||||
// 先注销可能存在的F5快捷键
|
||||
globalShortcut.unregister('F5')
|
||||
|
||||
// 注册 F5 刷新快捷键
|
||||
globalShortcut.register('F5', () => {
|
||||
const success = globalShortcut.register('F5', () => {
|
||||
logger.info('F5 快捷键触发')
|
||||
if (window && !window.isDestroyed()) {
|
||||
window.reload()
|
||||
|
||||
try {
|
||||
// 获取当前焦点窗口
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow()
|
||||
logger.info('当前焦点窗口:', focusedWindow ? '存在' : '不存在')
|
||||
|
||||
if (focusedWindow && !focusedWindow.isDestroyed()) {
|
||||
logger.info('刷新当前焦点窗口')
|
||||
focusedWindow.reload()
|
||||
} else if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
logger.info('没有焦点窗口,刷新主窗口')
|
||||
mainWindow.reload()
|
||||
} else if (difyfullScreenWindow && !difyfullScreenWindow.isDestroyed()) {
|
||||
logger.info('主窗口不可用,刷新全屏窗口')
|
||||
difyfullScreenWindow.reload()
|
||||
} else {
|
||||
logger.warn('没有可用的窗口进行刷新')
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('F5快捷键执行出错:', error)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
logger.info(`F5快捷键注册${success ? '成功' : '失败'}`)
|
||||
|
||||
const isRegistered_F12 = globalShortcut.isRegistered('F12');
|
||||
logger.info(`Is CommandOrControl+X registered: ${isRegistered_F12}`);
|
||||
logger.info(`Is F12 registered: ${isRegistered_F12}`);
|
||||
|
||||
// 桌面端快要退出的时候,注销快捷键
|
||||
app.on('will-quit', () => {
|
||||
@@ -236,6 +263,8 @@ export async function createNewWindow(url, access_token, refresh_token,sandbox=f
|
||||
|
||||
difyfullScreenWindow.on('ready-to-show', () => {
|
||||
difyfullScreenWindow.show()
|
||||
// 确保快捷键在新窗口显示后也能工作
|
||||
registerShortcuts(difyfullScreenWindow)
|
||||
})
|
||||
|
||||
let code = `localStorage.setItem("IsHsAiApp","IsHsAiApp");localStorage.setItem("console_token","${access_token}");localStorage.setItem("refresh_token","12");`
|
||||
@@ -298,6 +327,9 @@ export async function createNewWindow(url, access_token, refresh_token,sandbox=f
|
||||
difyfullScreenWindow.webContents.on('before-input-event', (event, input) => {
|
||||
if (input.key === 'F12') {
|
||||
difyfullScreenWindow.webContents.toggleDevTools()
|
||||
} else if (input.key === 'F5') {
|
||||
logger.info('全屏窗口 F5 快捷键触发')
|
||||
difyfullScreenWindow.reload()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -467,18 +499,29 @@ export function createDrageWindow() {
|
||||
drageWindow.setPosition(screenWidth - drageWindow.getSize()[0] -100, screenHeight - drageWindow.getSize()[1] - 100)
|
||||
}
|
||||
|
||||
|
||||
export async function tokenExpireTimer(){
|
||||
let lastLogTime = 0; // 记录上次打印日志的时间
|
||||
const LOG_INTERVAL = 60000; // 日志打印间隔,60秒打印一次
|
||||
|
||||
const tokenExpireTimer = setInterval(async () => {
|
||||
logger.info("tokenExpireTimer 触发")
|
||||
const currentTime = Date.now();
|
||||
const lastActiveTime = getStoreValue("lastActiveTime")||null
|
||||
|
||||
if (lastActiveTime!=null){
|
||||
logger.info("tokenExpireTimer 触发 对比时间戳")
|
||||
try {
|
||||
const nowTime=dayjs();
|
||||
const lastTime=dayjs(lastActiveTime);
|
||||
const diff=nowTime.diff(lastTime, 'minute')
|
||||
logger.info("tokenExpireTimer 触发 对比时间戳差距为:"+diff)
|
||||
|
||||
// 只在特定条件下打印日志,减少日志频率
|
||||
if (currentTime - lastLogTime > LOG_INTERVAL) {
|
||||
logger.info(`tokenExpireTimer 检查 - 时间差距: ${diff}分钟`)
|
||||
lastLogTime = currentTime;
|
||||
}
|
||||
|
||||
if ( diff> 30) {
|
||||
logger.info(`用户超时退出登录 - 时间差距: ${diff}分钟`)
|
||||
deleteStore("lastActiveTime")
|
||||
try {
|
||||
// 清除所有窗口的浏览器缓存
|
||||
@@ -512,7 +555,7 @@ export async function tokenExpireTimer(){
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
logger.error('tokenExpireTimer 执行错误:', e)
|
||||
}
|
||||
}
|
||||
}, 1000 * 10)
|
||||
@@ -610,3 +653,32 @@ export function closeConfigWindow() {
|
||||
configWindow = null
|
||||
}
|
||||
}
|
||||
|
||||
// 测试快捷键是否正常工作
|
||||
export function testShortcuts() {
|
||||
logger.info('测试快捷键功能...')
|
||||
|
||||
// 检查F5快捷键是否已注册
|
||||
const isF5Registered = globalShortcut.isRegistered('F5')
|
||||
logger.info(`F5快捷键是否已注册: ${isF5Registered}`)
|
||||
|
||||
// 获取所有已注册的快捷键
|
||||
const allShortcuts = globalShortcut.isRegistered('F5') ? ['F5'] : []
|
||||
logger.info(`已注册的快捷键: ${allShortcuts.join(', ')}`)
|
||||
|
||||
// 获取当前焦点窗口
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow()
|
||||
logger.info(`当前焦点窗口: ${focusedWindow ? '存在' : '不存在'}`)
|
||||
|
||||
if (focusedWindow) {
|
||||
logger.info(`焦点窗口标题: ${focusedWindow.getTitle()}`)
|
||||
logger.info(`焦点窗口是否销毁: ${focusedWindow.isDestroyed()}`)
|
||||
}
|
||||
|
||||
return {
|
||||
f5Registered: isF5Registered,
|
||||
focusedWindow: focusedWindow ? 'exists' : 'none',
|
||||
mainWindow: mainWindow && !mainWindow.isDestroyed() ? 'exists' : 'none',
|
||||
difyWindow: difyfullScreenWindow && !difyfullScreenWindow.isDestroyed() ? 'exists' : 'none'
|
||||
}
|
||||
}
|
||||
|
||||
44
test-cache-clear.js
Normal file
44
test-cache-clear.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const { app, session } = require('electron');
|
||||
|
||||
// 模拟缓存清理测试
|
||||
async function testCacheClear() {
|
||||
console.log('开始测试缓存清理功能...');
|
||||
|
||||
try {
|
||||
// 测试清除所有会话数据
|
||||
await new Promise((resolve, reject) => {
|
||||
session.defaultSession.clearStorageData({
|
||||
storages: [
|
||||
'appcache',
|
||||
'cookies',
|
||||
'filesystem',
|
||||
'indexdb',
|
||||
'localstorage',
|
||||
'shadercache',
|
||||
'websql',
|
||||
'serviceworkers',
|
||||
'cachestorage'
|
||||
]
|
||||
}, (error) => {
|
||||
if (error) {
|
||||
console.error('清除会话数据失败:', error);
|
||||
reject(error);
|
||||
} else {
|
||||
console.log('会话数据清除成功');
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log('缓存清理测试完成');
|
||||
} catch (error) {
|
||||
console.error('缓存清理测试失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
testCacheClear();
|
||||
}
|
||||
|
||||
module.exports = { testCacheClear };
|
||||
138
test-lock-cleanup.js
Normal file
138
test-lock-cleanup.js
Normal file
@@ -0,0 +1,138 @@
|
||||
const { app } = require('electron');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
console.log('开始测试锁文件清理功能...');
|
||||
|
||||
// 测试安全删除文件功能
|
||||
function testSafeDeleteFile(filePath) {
|
||||
console.log(`测试删除文件: ${filePath}`);
|
||||
|
||||
try {
|
||||
// 首先尝试直接删除
|
||||
fs.unlinkSync(filePath);
|
||||
console.log(`成功删除文件: ${filePath}`);
|
||||
} catch (error) {
|
||||
if (error.code === 'EBUSY' || error.code === 'EACCES') {
|
||||
console.log(`文件被占用,无法删除: ${filePath}`);
|
||||
console.log(`错误信息: ${error.message}`);
|
||||
|
||||
// 在 Windows 上尝试使用 del 命令
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
execSync(`del /F /Q "${filePath}"`, { stdio: 'ignore' });
|
||||
console.log(`使用 del 命令删除文件: ${filePath}`);
|
||||
} catch (delError) {
|
||||
console.error(`del 命令删除失败: ${filePath}`, delError);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error(`删除文件失败: ${filePath}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试创建和删除锁文件
|
||||
function testLockFileOperations() {
|
||||
const userDataPath = app.getPath('userData');
|
||||
const testLockFile = path.join(userDataPath, 'test-lockfile');
|
||||
|
||||
console.log('用户数据路径:', userDataPath);
|
||||
console.log('测试锁文件路径:', testLockFile);
|
||||
|
||||
try {
|
||||
// 创建测试锁文件
|
||||
fs.writeFileSync(testLockFile, process.pid.toString());
|
||||
console.log(`创建测试锁文件: ${testLockFile}, PID: ${process.pid}`);
|
||||
|
||||
// 等待一段时间
|
||||
setTimeout(() => {
|
||||
console.log('开始测试删除锁文件...');
|
||||
testSafeDeleteFile(testLockFile);
|
||||
|
||||
// 检查文件是否还存在
|
||||
if (fs.existsSync(testLockFile)) {
|
||||
console.log('文件仍然存在,尝试强制删除...');
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
execSync(`del /F /Q "${testLockFile}"`, { stdio: 'ignore' });
|
||||
console.log('强制删除成功');
|
||||
} catch (error) {
|
||||
console.error('强制删除失败:', error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('文件已成功删除');
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}, 2000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建测试锁文件失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 测试检查现有锁文件
|
||||
function testExistingLockFiles() {
|
||||
const userDataPath = app.getPath('userData');
|
||||
const possibleLockFiles = [
|
||||
path.join(userDataPath, 'single-instance-lock'),
|
||||
path.join(userDataPath, 'lockfile'),
|
||||
path.join(userDataPath, '.lock')
|
||||
];
|
||||
|
||||
console.log('检查现有锁文件...');
|
||||
|
||||
for (const lockFile of possibleLockFiles) {
|
||||
if (fs.existsSync(lockFile)) {
|
||||
console.log(`发现锁文件: ${lockFile}`);
|
||||
try {
|
||||
const content = fs.readFileSync(lockFile, 'utf8');
|
||||
console.log(`锁文件内容: ${content}`);
|
||||
|
||||
const pid = parseInt(content.trim());
|
||||
if (pid) {
|
||||
console.log(`锁文件中的 PID: ${pid}`);
|
||||
|
||||
// 检查进程是否还在运行
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
const result = execSync(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, { encoding: 'utf8' });
|
||||
if (result.includes(pid.toString())) {
|
||||
console.log(`进程 ${pid} 仍在运行`);
|
||||
} else {
|
||||
console.log(`进程 ${pid} 不存在,可以删除锁文件`);
|
||||
testSafeDeleteFile(lockFile);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`进程 ${pid} 不存在,删除锁文件`);
|
||||
testSafeDeleteFile(lockFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`读取锁文件失败: ${lockFile}`, error);
|
||||
testSafeDeleteFile(lockFile);
|
||||
}
|
||||
} else {
|
||||
console.log(`没有找到锁文件: ${lockFile}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
console.log('当前进程 PID:', process.pid);
|
||||
|
||||
// 检查现有锁文件
|
||||
testExistingLockFiles();
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
// 测试锁文件操作
|
||||
testLockFileOperations();
|
||||
}
|
||||
|
||||
module.exports = { testSafeDeleteFile, testExistingLockFiles, testLockFileOperations };
|
||||
87
test-single-instance.js
Normal file
87
test-single-instance.js
Normal file
@@ -0,0 +1,87 @@
|
||||
const { app } = require('electron');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
console.log('开始测试单实例锁功能...');
|
||||
|
||||
// 模拟单实例锁检查
|
||||
function testSingleInstanceLock() {
|
||||
const userDataPath = app.getPath('userData');
|
||||
const lockFile = path.join(userDataPath, 'single-instance-lock');
|
||||
|
||||
console.log('用户数据路径:', userDataPath);
|
||||
console.log('锁文件路径:', lockFile);
|
||||
|
||||
// 检查是否存在锁文件
|
||||
if (fs.existsSync(lockFile)) {
|
||||
console.log('发现锁文件,尝试读取内容...');
|
||||
try {
|
||||
const content = fs.readFileSync(lockFile, 'utf8');
|
||||
console.log('锁文件内容:', content);
|
||||
|
||||
// 检查进程是否还在运行
|
||||
const pid = parseInt(content.trim());
|
||||
console.log('锁文件中的 PID:', pid);
|
||||
|
||||
// 在 Windows 上检查进程
|
||||
if (process.platform === 'win32') {
|
||||
const { execSync } = require('child_process');
|
||||
try {
|
||||
const result = execSync(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, { encoding: 'utf8' });
|
||||
if (result.includes(pid.toString())) {
|
||||
console.log(`进程 ${pid} 仍在运行`);
|
||||
} else {
|
||||
console.log(`进程 ${pid} 不存在,可以清理锁文件`);
|
||||
fs.unlinkSync(lockFile);
|
||||
console.log('锁文件已清理');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`进程 ${pid} 不存在,清理锁文件`);
|
||||
fs.unlinkSync(lockFile);
|
||||
console.log('锁文件已清理');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('读取锁文件失败:', error);
|
||||
}
|
||||
} else {
|
||||
console.log('没有找到锁文件');
|
||||
}
|
||||
}
|
||||
|
||||
// 测试创建锁文件
|
||||
function testCreateLock() {
|
||||
const userDataPath = app.getPath('userData');
|
||||
const lockFile = path.join(userDataPath, 'single-instance-lock');
|
||||
|
||||
try {
|
||||
fs.writeFileSync(lockFile, process.pid.toString());
|
||||
console.log(`创建锁文件: ${lockFile}, PID: ${process.pid}`);
|
||||
} catch (error) {
|
||||
console.error('创建锁文件失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
console.log('当前进程 PID:', process.pid);
|
||||
testSingleInstanceLock();
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
// 创建测试锁文件
|
||||
testCreateLock();
|
||||
|
||||
// 等待一段时间后清理
|
||||
setTimeout(() => {
|
||||
console.log('清理测试锁文件...');
|
||||
const userDataPath = app.getPath('userData');
|
||||
const lockFile = path.join(userDataPath, 'single-instance-lock');
|
||||
if (fs.existsSync(lockFile)) {
|
||||
fs.unlinkSync(lockFile);
|
||||
console.log('测试锁文件已清理');
|
||||
}
|
||||
process.exit(0);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
module.exports = { testSingleInstanceLock, testCreateLock };
|
||||
42
test-startup.js
Normal file
42
test-startup.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const { app } = require('electron');
|
||||
const path = require('path');
|
||||
|
||||
console.log('开始测试应用启动...');
|
||||
|
||||
// 模拟应用启动过程
|
||||
app.on('ready', () => {
|
||||
console.log('应用已准备就绪');
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
console.log('所有窗口已关闭');
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('before-quit', () => {
|
||||
console.log('应用即将退出');
|
||||
});
|
||||
|
||||
app.on('will-quit', () => {
|
||||
console.log('应用将要退出');
|
||||
});
|
||||
|
||||
// 检查单实例锁
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
console.log('单实例锁检查结果:', gotTheLock);
|
||||
|
||||
if (!gotTheLock) {
|
||||
console.log('检测到已有实例运行,退出当前实例');
|
||||
app.quit();
|
||||
} else {
|
||||
console.log('这是第一个实例,继续启动');
|
||||
|
||||
// 模拟异步操作
|
||||
setTimeout(() => {
|
||||
console.log('异步操作完成');
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
console.log('启动脚本执行完成');
|
||||
Reference in New Issue
Block a user