重启之后清除登录信息
This commit is contained in:
@@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user