优化重启逻辑
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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: '退出登录',
|
||||
|
||||
76
src/main/utils/networkUtils.js
Normal file
76
src/main/utils/networkUtils.js
Normal 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);
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
130
src/renderer/src/components/network_error.vue
Normal file
130
src/renderer/src/components/network_error.vue
Normal 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>
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user