优化重启逻辑

This commit is contained in:
2025-07-18 18:27:37 +08:00
parent 6fe479448c
commit f667c26650
42 changed files with 362 additions and 1370 deletions

View File

@@ -1,7 +1,7 @@
import { app, shell, BrowserWindow, ipcMain, Menu, session, screen, dialog } from 'electron'
import { electronApp, optimizer } from '@electron-toolkit/utils'
import Store from 'electron-store'
import { createWindow, createDrageWindow, unregisterAllShortcuts } from './window.js'
import { createWindow, createDrageWindow, unregisterAllShortcuts, getMainWindow } from './window.js'
import { setupIPC } from './ipc.js'
import { createTray, destroyTray,clearBrowserCache } from './tray.js'
import XEUtils from 'xe-utils'
@@ -13,6 +13,7 @@ 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 axios from 'axios'
import WebSocketClient from './utils/WebSocketClient';
@@ -273,6 +274,12 @@ if (!isFirstInstance) {
app.on('window-all-closed', async (event) => {
if (process.platform !== 'darwin') {
event.preventDefault();
// 如果是重启过程,不执行任何清理操作
if (app.isRestarting) {
logger.info('检测到重启过程,跳过窗口关闭处理')
return;
}
if (!app.isQuiting) {
// 如果不是主动退出,则隐藏所有窗口
@@ -300,6 +307,12 @@ if (!isFirstInstance) {
})
app.on('before-quit', async (event) => {
// 如果是重启过程,不执行任何清理操作
if (app.isRestarting) {
logger.info('检测到重启过程跳过before-quit处理')
return;
}
// 在应用程序即将退出时执行操作,例如保存数据
event.preventDefault();
@@ -317,6 +330,11 @@ if (!isFirstInstance) {
// 在应用退出时注销所有快捷键
app.on('will-quit', async (event) => {
// 如果是重启过程,不执行任何清理操作
if (app.isRestarting) {
logger.info('检测到重启过程跳过will-quit处理')
return;
}
event.preventDefault();

View File

@@ -334,4 +334,50 @@ export function setupIPC() {
throw error;
}
});
// 重启软件
ipcMain.handle('restartApp', async (event) => {
logger.info("=============================重启软件");
try {
// 标记为正在重启,避免其他退出逻辑干扰
app.isRestarting = true;
// 确保所有窗口都被正确关闭
const windows = BrowserWindow.getAllWindows();
logger.info(`准备关闭 ${windows.length} 个窗口`);
windows.forEach(window => {
if (!window.isDestroyed()) {
try {
window.close();
logger.info('窗口关闭成功');
} catch (error) {
logger.warn('关闭窗口时出错:', error);
}
}
});
// 延迟执行重启,确保窗口关闭完成
setTimeout(() => {
try {
logger.info('执行应用重启');
app.relaunch();
app.exit(0);
} catch (error) {
logger.error('重启应用失败,尝试强制退出:', error);
try {
app.quit();
} catch (quitError) {
logger.error('强制退出也失败:', quitError);
process.exit(0);
}
}
}, 1000);
return { success: true };
} catch (error) {
logger.error(`重启软件失败: ${error.message}`);
throw error;
}
});
}

View File

@@ -13,6 +13,62 @@ let tray = null
import {checkForUpdates} from "./utils/updateUtils"
// 安全重启应用函数
function safeRestartApp() {
return new Promise((resolve) => {
try {
logger.info('开始安全重启应用')
// 标记为正在重启,避免其他退出逻辑干扰
app.isRestarting = true
// 确保所有窗口都被正确关闭
const windows = BrowserWindow.getAllWindows()
logger.info(`准备关闭 ${windows.length} 个窗口`)
windows.forEach(window => {
if (!window.isDestroyed()) {
try {
window.close()
logger.info('窗口关闭成功')
} catch (error) {
logger.warn('关闭窗口时出错:', error)
}
}
})
// 延迟执行重启,确保窗口关闭完成
setTimeout(() => {
try {
logger.info('执行应用重启')
app.relaunch()
app.exit(0)
resolve(true)
} catch (error) {
logger.error('重启应用失败,尝试强制退出:', error)
try {
app.quit()
} catch (quitError) {
logger.error('强制退出也失败:', quitError)
process.exit(0)
}
resolve(false)
}
}, 1000) // 增加延迟时间到1秒
} catch (error) {
logger.error('安全重启失败:', error)
try {
app.quit()
} catch (quitError) {
logger.error('强制退出失败:', quitError)
process.exit(0)
}
resolve(false)
}
})
}
export async function clearBrowserCache() {
try {
// 使用统一的缓存清理函数
@@ -100,31 +156,7 @@ export function createTray() {
}
},
{ type: 'separator' },
{
label: '清除缓存',
click: async () => {
await clearBrowserCache()
// 删除配置文件
try {
const userDataPath = app.getPath('userData')
const configPath = join(userDataPath, 'config.json')
if (fs.existsSync(configPath)) {
fs.unlinkSync(configPath)
logger.info('配置文件删除成功')
}
} catch (error) {
logger.error('删除配置文件失败:', error)
}
// 重新加载所有窗口
const windows = BrowserWindow.getAllWindows()
for (const window of windows) {
window.reload()
}
}
},
{ type: 'separator' },
{
label: '退出登录',

View File

@@ -0,0 +1,76 @@
import axios from 'axios';
import logger from './logger';
import { getStoreValue } from '../store.js';
/**
* 检测网络连接是否正常
* @param {string} url - 要检测的URL如果不提供则使用默认的客户端地址
* @param {number} timeout - 超时时间默认5秒
* @returns {Promise<boolean>} - 返回网络连接状态
*/
export async function checkNetworkConnection(url = null, timeout = 5000) {
try {
// 如果没有提供URL则使用存储的客户端地址
if (!url) {
const h5ClientUrl = getStoreValue("h5_client_url");
if (!h5ClientUrl) {
logger.warn('网络检测:未找到客户端地址配置');
return false;
}
url = h5ClientUrl;
}
logger.info(`网络检测:开始检测 ${url} 的连接状态`);
// 发送HEAD请求检测网络连接
const response = await axios.head(url, {
timeout: timeout,
validateStatus: function (status) {
// 接受2xx和3xx的状态码
return status >= 200 && status < 400;
}
});
logger.info(`网络检测:连接成功,状态码 ${response.status}`);
return true;
} catch (error) {
logger.warn(`网络检测:连接失败 - ${error.message}`);
// 如果是超时错误,记录更详细的信息
if (error.code === 'ECONNABORTED') {
logger.warn(`网络检测:请求超时 (${timeout}ms)`);
} else if (error.code === 'ENOTFOUND') {
logger.warn('网络检测:无法解析主机名');
} else if (error.code === 'ECONNREFUSED') {
logger.warn('网络检测:连接被拒绝');
}
return false;
}
}
/**
* 检测多个URL的网络连接
* @param {Array<string>} urls - 要检测的URL数组
* @param {number} timeout - 超时时间默认5秒
* @returns {Promise<Object>} - 返回每个URL的检测结果
*/
export async function checkMultipleNetworkConnections(urls, timeout = 5000) {
const results = {};
for (const url of urls) {
results[url] = await checkNetworkConnection(url, timeout);
}
return results;
}
/**
* 检测默认客户端地址的网络连接
* @param {number} timeout - 超时时间默认5秒
* @returns {Promise<boolean>} - 返回网络连接状态
*/
export async function checkDefaultClientConnection(timeout = 5000) {
return await checkNetworkConnection(null, timeout);
}

View File

@@ -5,6 +5,7 @@ import icon from '../../resources/icon.png?asset'
import { isApiDatasetsDocumentsPath, extractIdFromPath } from './utils/difyUtils.js'
import XEUtils from 'xe-utils'
import logger from './utils/logger'
import { checkDefaultClientConnection } from './utils/networkUtils.js'
import { createMenu } from './menu.js'
import { setStoreValue, getStoreValue,deleteStore } from './store.js'
@@ -166,16 +167,38 @@ export async function createWindow() {
// 加载存储的 URL
mainWindow.loadURL(h5_client_url)
// 监听页面加载失败事件
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL) => {
logger.error(`主窗口页面加载失败: code=${errorCode}, desc=${errorDescription}, url=${validatedURL}`)
// 跳转到网络错误页面
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '/#/network_error')
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'), { hash: '/network_error' })
}
})
// 接口超过30分钟不活动则退出登录
await tokenExpireTimer()
setTimeout(()=>{
setTimeout(async ()=>{
try {
// 注册全局快捷键
registerShortcuts(mainWindow)
checkForUpdates({},false)
// 先检测网络连接,如果网络正常再调用版本检测升级
const isNetworkConnected = await checkDefaultClientConnection(5000);
if (isNetworkConnected) {
logger.info('网络连接正常,开始检查版本更新');
checkForUpdates({},false)
} else {
logger.warn('网络连接异常,跳过版本检测升级');
}
}catch (e) {
logger.info(e)
}

View File

@@ -2,7 +2,7 @@
<html>
<head>
<meta charset="UTF-8" />
<title>Dify Market Manager</title>
<title>百千万AI</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"

View File

@@ -29,9 +29,9 @@ onMounted(async () => {
const saveConfig = async () => {
try {
await window.electron.ipcRenderer.invoke('setStoreValue', 'h5_client_url', h5ClientUrl.value)
alert('保存成功')
// 关闭当前窗口
window.electron.ipcRenderer.send('closeConfigWindow')
// 重启应用
await window.electron.ipcRenderer.invoke('restartApp')
} catch (error) {
alert('保存失败:' + error.message)
}

View File

@@ -0,0 +1,130 @@
<template>
<div class="network-error">
<div class="error-container">
<div class="error-icon">
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" fill="#f44336"/>
</svg>
</div>
<h1 class="error-title">网络连接错误</h1>
<p class="error-message">无法连接到服务器请检查您的网络连接</p>
<div class="error-actions">
<button @click="retryConnection" class="retry-btn">重启服务</button>
</div>
<div class="tray-tip">
<p class="tip-text">
💡 提示在系统托盘中找到软件图标右键单击 <strong>配置 -> 配置客户端地址</strong>查看客户端地址是否正确以及<strong>是否有网络访问权限</strong>
</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'NetworkError',
methods: {
async retryConnection() {
try {
// 调用重启软件的方法
await window.electron.ipcRenderer.invoke('restartApp');
} catch (error) {
console.error('重启软件失败:', error);
// 如果重启失败,回退到页面刷新
window.location.reload();
}
},
goToConfig() {
// 跳转到配置页面
this.$router.push('/config')
}
}
}
</script>
<style scoped>
.network-error {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f5f5f5;
}
.error-container {
text-align: center;
padding: 2rem;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
max-width: 400px;
}
.error-icon {
margin-bottom: 1rem;
}
.error-title {
color: #333;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.error-message {
color: #666;
margin-bottom: 2rem;
line-height: 1.5;
}
.error-actions {
display: flex;
gap: 1rem;
justify-content: center;
}
.retry-btn, .config-btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
transition: background-color 0.2s;
}
.retry-btn {
background-color: #2196f3;
color: white;
}
.retry-btn:hover {
background-color: #1976d2;
}
.config-btn {
background-color: #f5f5f5;
color: #333;
border: 1px solid #ddd;
}
.config-btn:hover {
background-color: #e0e0e0;
}
.tray-tip {
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid #eee;
}
.tip-text {
color: #666;
font-size: 0.9rem;
line-height: 1.4;
margin: 0;
}
.tip-text strong {
color: #333;
font-weight: 600;
}
</style>

View File

@@ -1,6 +1,7 @@
import { createRouter, createWebHashHistory } from 'vue-router'
import App from '../App.vue'
import ApiConfig from '../components/ApiConfig.vue'
import NetworkError from '../components/network_error.vue'
const routes = [
{
@@ -12,6 +13,11 @@ const routes = [
path: '/config',
name: 'config',
component: ApiConfig
},
{
path: '/network_error',
name: 'network_error',
component: NetworkError
}
]