feat: 增加ws重连机制
This commit is contained in:
@@ -102,8 +102,8 @@ export const establishSshConnection = (
|
|||||||
privateKey: connDetails.privateKey,
|
privateKey: connDetails.privateKey,
|
||||||
passphrase: connDetails.passphrase,
|
passphrase: connDetails.passphrase,
|
||||||
readyTimeout: timeout,
|
readyTimeout: timeout,
|
||||||
keepaliveInterval: 30000, // 保持连接
|
keepaliveInterval: 10000, // 保持连接
|
||||||
keepaliveCountMax: 3,
|
keepaliveCountMax: 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
const readyHandler = () => {
|
const readyHandler = () => {
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export const initializeWebSocket = (server: http.Server, sessionParser: RequestH
|
|||||||
extWs.isAlive = false;
|
extWs.isAlive = false;
|
||||||
extWs.ping(() => {});
|
extWs.ping(() => {});
|
||||||
});
|
});
|
||||||
}, 30000); // 30 秒心跳间隔
|
}, 60000); // 增加到 60 秒心跳间隔
|
||||||
|
|
||||||
// --- WebSocket 升级处理 (认证) ---
|
// --- WebSocket 升级处理 (认证) ---
|
||||||
server.on('upgrade', (request: Request, socket, head) => {
|
server.on('upgrade', (request: Request, socket, head) => {
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ const swapDisplay = computed(() => {
|
|||||||
.status-item {
|
.status-item {
|
||||||
display: grid;
|
display: grid;
|
||||||
/* Simplified grid columns: Label | Value Area - Further increased label width */
|
/* Simplified grid columns: Label | Value Area - Further increased label width */
|
||||||
grid-template-columns: 100px 1fr;
|
grid-template-columns: 75px 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.8rem; /* Keep increased gap */
|
gap: 0.8rem; /* Keep increased gap */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ export function createWebSocketConnectionManager(sessionId: string, dbConnection
|
|||||||
const messageHandlers = new Map<string, Set<MessageHandler>>(); // 此实例的消息处理器注册表
|
const messageHandlers = new Map<string, Set<MessageHandler>>(); // 此实例的消息处理器注册表
|
||||||
const instanceSessionId = sessionId; // 保存会话 ID 用于日志
|
const instanceSessionId = sessionId; // 保存会话 ID 用于日志
|
||||||
const instanceDbConnectionId = dbConnectionId; // 保存数据库连接 ID
|
const instanceDbConnectionId = dbConnectionId; // 保存数据库连接 ID
|
||||||
|
let reconnectAttempts = 0; // 重连尝试次数
|
||||||
|
const maxReconnectAttempts = 5; // 最大重连次数
|
||||||
|
let reconnectTimeoutId: ReturnType<typeof setTimeout> | null = null; // 重连定时器 ID
|
||||||
|
let lastUrl = ''; // 保存上次连接的 URL
|
||||||
|
let intentionalDisconnect = false; // 标记是否为用户主动断开
|
||||||
// --- End Instance State ---
|
// --- End Instance State ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,11 +65,47 @@ export function createWebSocketConnectionManager(sessionId: string, dbConnection
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安排 WebSocket 重连尝试
|
||||||
|
*/
|
||||||
|
const scheduleReconnect = () => {
|
||||||
|
if (intentionalDisconnect) return; // 如果是主动断开,则不重连
|
||||||
|
|
||||||
|
if (reconnectAttempts >= maxReconnectAttempts) {
|
||||||
|
console.log(`[WebSocket ${instanceSessionId}] 已达到最大重连次数 (${maxReconnectAttempts}),停止重连。`);
|
||||||
|
statusMessage.value = getStatusText('reconnectFailed');
|
||||||
|
connectionStatus.value = 'error'; // 标记为错误状态
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnectAttempts++;
|
||||||
|
// 指数退避延迟 (例如: 2s, 4s, 8s, 16s, 32s)
|
||||||
|
const delay = Math.pow(2, reconnectAttempts) * 1000;
|
||||||
|
console.log(`[WebSocket ${instanceSessionId}] 连接丢失,将在 ${delay / 1000} 秒后尝试第 ${reconnectAttempts} 次重连...`);
|
||||||
|
statusMessage.value = getStatusText('reconnecting', { attempt: reconnectAttempts, delay: delay / 1000 });
|
||||||
|
connectionStatus.value = 'connecting'; // 更新状态为正在连接
|
||||||
|
|
||||||
|
if (reconnectTimeoutId) clearTimeout(reconnectTimeoutId); // 清除旧的定时器
|
||||||
|
|
||||||
|
reconnectTimeoutId = setTimeout(() => {
|
||||||
|
if (!intentionalDisconnect && lastUrl) { // 再次检查是否主动断开
|
||||||
|
connect(lastUrl);
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 建立 WebSocket 连接
|
* 建立 WebSocket 连接
|
||||||
* @param {string} url - WebSocket 服务器 URL
|
* @param {string} url - WebSocket 服务器 URL
|
||||||
*/
|
*/
|
||||||
const connect = (url: string) => {
|
const connect = (url: string) => {
|
||||||
|
lastUrl = url; // 保存 URL 以便重连
|
||||||
|
intentionalDisconnect = false; // 重置主动断开标记
|
||||||
|
if (reconnectTimeoutId) {
|
||||||
|
clearTimeout(reconnectTimeoutId); // 清除可能存在的重连定时器
|
||||||
|
reconnectTimeoutId = null;
|
||||||
|
}
|
||||||
|
|
||||||
// 防止重复连接同一实例
|
// 防止重复连接同一实例
|
||||||
if (ws.value && (ws.value.readyState === WebSocket.OPEN || ws.value.readyState === WebSocket.CONNECTING)) {
|
if (ws.value && (ws.value.readyState === WebSocket.OPEN || ws.value.readyState === WebSocket.CONNECTING)) {
|
||||||
console.warn(`[WebSocket ${instanceSessionId}] 连接已打开或正在连接中。`);
|
console.warn(`[WebSocket ${instanceSessionId}] 连接已打开或正在连接中。`);
|
||||||
@@ -81,6 +122,7 @@ export function createWebSocketConnectionManager(sessionId: string, dbConnection
|
|||||||
|
|
||||||
ws.value.onopen = () => {
|
ws.value.onopen = () => {
|
||||||
console.log(`[WebSocket ${instanceSessionId}] 连接已打开。`);
|
console.log(`[WebSocket ${instanceSessionId}] 连接已打开。`);
|
||||||
|
reconnectAttempts = 0; // 连接成功,重置尝试次数
|
||||||
statusMessage.value = getStatusText('wsConnected');
|
statusMessage.value = getStatusText('wsConnected');
|
||||||
// 状态保持 'connecting' 直到收到 ssh:connected
|
// 状态保持 'connecting' 直到收到 ssh:connected
|
||||||
// 发送后端所需的初始连接消息,包含数据库连接 ID
|
// 发送后端所需的初始连接消息,包含数据库连接 ID
|
||||||
@@ -140,18 +182,32 @@ export function createWebSocketConnectionManager(sessionId: string, dbConnection
|
|||||||
dispatchMessage('internal:error', event, { type: 'internal:error' });
|
dispatchMessage('internal:error', event, { type: 'internal:error' });
|
||||||
isSftpReady.value = false;
|
isSftpReady.value = false;
|
||||||
ws.value = null; // 清理实例
|
ws.value = null; // 清理实例
|
||||||
|
// 如果不是主动断开,尝试重连
|
||||||
|
if (!intentionalDisconnect) {
|
||||||
|
scheduleReconnect();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.value.onclose = (event) => {
|
ws.value.onclose = (event) => {
|
||||||
console.log(`[WebSocket ${instanceSessionId}] 连接已关闭: Code=${event.code}, Reason=${event.reason}`);
|
console.log(`[WebSocket ${instanceSessionId}] 连接已关闭: Code=${event.code}, Reason=${event.reason}`);
|
||||||
if (connectionStatus.value !== 'disconnected' && connectionStatus.value !== 'error') {
|
// 只有在非错误状态下才更新为 disconnected
|
||||||
|
if (connectionStatus.value !== 'error') {
|
||||||
connectionStatus.value = 'disconnected';
|
connectionStatus.value = 'disconnected';
|
||||||
statusMessage.value = getStatusText('wsClosed', { code: event.code });
|
// 如果不是主动断开,显示尝试重连的消息
|
||||||
|
if (!intentionalDisconnect && event.code !== 1000) {
|
||||||
|
statusMessage.value = getStatusText('wsClosedWillRetry', { code: event.code });
|
||||||
|
} else {
|
||||||
|
statusMessage.value = getStatusText('wsClosed', { code: event.code });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dispatchMessage('internal:closed', { code: event.code, reason: event.reason }, { type: 'internal:closed' });
|
dispatchMessage('internal:closed', { code: event.code, reason: event.reason }, { type: 'internal:closed' });
|
||||||
isSftpReady.value = false;
|
isSftpReady.value = false;
|
||||||
ws.value = null; // 清理实例引用
|
ws.value = null; // 清理实例引用
|
||||||
// 不自动清除处理器,以便在重连时可能复用
|
|
||||||
|
// 如果不是主动断开 (code 1000),尝试重连
|
||||||
|
if (!intentionalDisconnect && event.code !== 1000) {
|
||||||
|
scheduleReconnect();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[WebSocket ${instanceSessionId}] 创建 WebSocket 实例失败:`, err);
|
console.error(`[WebSocket ${instanceSessionId}] 创建 WebSocket 实例失败:`, err);
|
||||||
@@ -166,6 +222,11 @@ export function createWebSocketConnectionManager(sessionId: string, dbConnection
|
|||||||
* 手动断开此 WebSocket 连接
|
* 手动断开此 WebSocket 连接
|
||||||
*/
|
*/
|
||||||
const disconnect = () => {
|
const disconnect = () => {
|
||||||
|
intentionalDisconnect = true; // 标记为主动断开
|
||||||
|
if (reconnectTimeoutId) {
|
||||||
|
clearTimeout(reconnectTimeoutId); // 清除重连定时器
|
||||||
|
reconnectTimeoutId = null;
|
||||||
|
}
|
||||||
if (ws.value) {
|
if (ws.value) {
|
||||||
console.log(`[WebSocket ${instanceSessionId}] 手动关闭连接...`);
|
console.log(`[WebSocket ${instanceSessionId}] 手动关闭连接...`);
|
||||||
if (connectionStatus.value !== 'disconnected') {
|
if (connectionStatus.value !== 'disconnected') {
|
||||||
|
|||||||
Reference in New Issue
Block a user