// WebSocket全局配置 const WS_CONFIG = { heartbeatInterval: 10000, // 30秒心跳间隔 heartbeatTimeout: 10000, // 心跳响应超时时间(10秒,给服务器更多响应时间) maxReconnectAttempts: 5, // 最大重连次数 reconnectBaseDelay: 500, // 重连基础延迟0.5秒(更快重连) maxReconnectDelay: 10000, // 最大重连延迟10秒(减少等待时间) heartbeatMessage: 'ping', // 心跳消息 heartbeatResponseType: 'pong', // 心跳响应类型 }; // WebSocket关闭码说明 const WS_CLOSE_CODES: Record = { 1000: '正常关闭', 1001: '端点离开(如页面关闭)', 1002: '协议错误', 1003: '接收到不支持的数据类型', 1004: '保留码', 1005: '未收到预期的状态码', 1006: '连接异常关闭', 1007: '接收到无效的frame payload数据', 1008: '策略违规(服务器主动关闭)', 1009: '消息过大', 1010: '扩展协商失败', 1011: '服务器遇到意外情况', 1012: '服务重启', 1013: '稍后重试', 1014: '网关超时', 1015: 'TLS握手失败', }; // 获取关闭原因描述 function getCloseReasonDescription(code: number, reason: string): string { const codeDescription = WS_CLOSE_CODES[code] || '未知关闭码'; const reasonText = reason ? ` (原因: ${reason})` : ''; return `${code} - ${codeDescription}${reasonText}`; } // 增强的WebSocket包装器 class EnhancedWebSocket { private ws: WebSocket; private path: string; private baseUrl: string; private heartbeatTimer?: NodeJS.Timeout; private heartbeatTimeoutTimer?: NodeJS.Timeout; private reconnectTimer?: NodeJS.Timeout; private reconnectAttempts: number = 0; private isManualClose: boolean = false; private isHeartbeatTimeout: boolean = false; private userOnMessage: ((event: MessageEvent) => void) | null = null; private userOnClose: ((event: CloseEvent) => void) | null = null; private userOnError: ((event: Event) => void) | null = null; private userOnOpen: ((event: Event) => void) | null = null; private connectionStartTime: number = 0; private lastHeartbeatTime: number = 0; private heartbeatSentCount: number = 0; private heartbeatReceivedCount: number = 0; constructor(path: string, baseUrl: string) { this.path = path; this.baseUrl = baseUrl; this.connectionStartTime = Date.now(); console.log(`🔗 开始创建WebSocket连接: ${this.path}, 基础URL: ${baseUrl}`); this.ws = new WebSocket(baseUrl + path); this.setupHandlers(); } // 设置事件处理器 private setupHandlers(): void { this.ws.onopen = (event) => { const connectionDuration = Date.now() - this.connectionStartTime; console.log(`✅ WebSocket连接已建立: ${this.path}, 耗时: ${connectionDuration}ms`); this.reconnectAttempts = 0; this.heartbeatSentCount = 0; this.heartbeatReceivedCount = 0; this.clearReconnectTimer(); // 🔧 优化:连接建立后立即发送一次心跳,然后开始定期心跳 if (this.ws.readyState === WebSocket.OPEN) { console.log(`💓 连接建立后发送初始心跳: ${this.path}`); this.ws.send(WS_CONFIG.heartbeatMessage); this.heartbeatSentCount++; this.lastHeartbeatTime = Date.now(); this.startHeartbeatTimeout(); } this.startHeartbeat(); if (this.userOnOpen) { this.userOnOpen(event); } }; this.ws.onmessage = (event) => { const messageData = event.data; // 🔧 优化:收到任何消息都说明连接正常,清除心跳超时检测 this.clearHeartbeatTimeout(); // 检查是否为心跳响应(支持字符串和JSON格式) let isHeartbeatResponse = false; // 1. 检查是否为简单字符串心跳响应 if (typeof messageData === 'string' && messageData === WS_CONFIG.heartbeatResponseType) { isHeartbeatResponse = true; } // 2. 检查是否为JSON格式心跳响应 if (!isHeartbeatResponse && typeof messageData === 'string') { try { const data = JSON.parse(messageData); if (data.type === WS_CONFIG.heartbeatResponseType) { isHeartbeatResponse = true; } } catch { // JSON解析失败,不是JSON格式的心跳响应 } } if (isHeartbeatResponse) { this.heartbeatReceivedCount++; const responseTime = Date.now() - this.lastHeartbeatTime; console.log( `💗 收到心跳响应: ${this.path}, 响应时间: ${responseTime}ms, 已发送: ${this.heartbeatSentCount}, 已接收: ${this.heartbeatReceivedCount}`, ); // 心跳响应,不传递给业务代码 return; } // 传递给业务代码 if (this.userOnMessage) { this.userOnMessage(event); } }; this.ws.onclose = (event) => { const connectionDuration = Date.now() - this.connectionStartTime; const closeReason = getCloseReasonDescription(event.code, event.reason); console.log(`❌ WebSocket连接关闭: ${this.path}`); console.log(` └─ 关闭原因: ${closeReason}`); console.log(` └─ 连接持续时间: ${connectionDuration}ms`); console.log(` └─ 心跳统计: 发送${this.heartbeatSentCount}次, 接收${this.heartbeatReceivedCount}次`); console.log(` └─ 是否手动关闭: ${this.isManualClose}`); console.log(` └─ 是否心跳超时: ${this.isHeartbeatTimeout}`); // 分析断连原因 this.analyzeDisconnectionReason(event.code, event.reason); this.stopHeartbeat(); // 先调用业务代码的关闭处理 if (this.userOnClose) { this.userOnClose(event); } // 如果不是手动关闭,或者是心跳超时导致的关闭,则重连 if (!this.isManualClose || this.isHeartbeatTimeout) { this.scheduleReconnect(); } // 重置心跳超时标志 this.isHeartbeatTimeout = false; }; this.ws.onerror = (event) => { console.error(`🔥 WebSocket连接错误: ${this.path}`, event); console.log(` └─ 连接状态: ${this.getReadyStateText()}`); console.log(` └─ 连接URL: ${this.ws.url}`); this.stopHeartbeat(); if (this.userOnError) { this.userOnError(event); } }; } // 分析断连原因 private analyzeDisconnectionReason(code: number, reason: string): void { console.log(`🔍 断连原因分析: ${this.path}`); if (reason) { console.log(` └─ 服务器提供的关闭原因: ${reason}`); } switch (code) { case 1000: console.log(' └─ 正常关闭,可能是服务器主动关闭或客户端主动关闭'); break; case 1001: console.log(' └─ 端点离开,可能是页面关闭或刷新'); break; case 1006: console.log(' └─ 连接异常关闭,可能是网络问题或服务器崩溃'); break; case 1008: console.log(' └─ 策略违规,服务器主动关闭连接'); console.log(' └─ 可能原因: 1) 服务器负载过高 2) 连接频率过快 3) 服务器重启 4) 业务逻辑限制'); break; case 1011: console.log(' └─ 服务器遇到意外情况,可能是服务器内部错误'); break; case 1012: console.log(' └─ 服务重启,服务器正在重启'); break; case 1013: console.log(' └─ 稍后重试,服务器暂时无法处理请求'); break; default: if (code >= 3000 && code <= 3999) { console.log(' └─ 应用程序定义的关闭码,可能是业务逻辑关闭'); } else if (code >= 4000 && code <= 4999) { console.log(' └─ 私有关闭码,可能是框架或库定义的关闭原因'); } else { console.log(' └─ 未知关闭码,需要进一步调查'); } } // 心跳统计分析 if (this.heartbeatSentCount > this.heartbeatReceivedCount) { const missedHeartbeats = this.heartbeatSentCount - this.heartbeatReceivedCount; console.log(` └─ 心跳分析: 有${missedHeartbeats}个心跳未收到响应,可能存在网络延迟或服务器响应问题`); } } // 获取连接状态文本 private getReadyStateText(): string { switch (this.ws.readyState) { case WebSocket.CONNECTING: return 'CONNECTING(0)'; case WebSocket.OPEN: return 'OPEN(1)'; case WebSocket.CLOSING: return 'CLOSING(2)'; case WebSocket.CLOSED: return 'CLOSED(3)'; default: return `UNKNOWN(${this.ws.readyState})`; } } // 开始心跳检测 private startHeartbeat(): void { this.stopHeartbeat(); console.log(`💓 开始心跳检测: ${this.path}, 间隔: ${WS_CONFIG.heartbeatInterval}ms`); this.heartbeatTimer = setInterval(() => { if (this.ws.readyState === WebSocket.OPEN) { this.heartbeatSentCount++; this.lastHeartbeatTime = Date.now(); console.log(`💓 发送心跳: ${this.path} (#${this.heartbeatSentCount})`); this.ws.send(WS_CONFIG.heartbeatMessage); // 只有在没有进行超时检测时才设置新的超时检测 if (!this.heartbeatTimeoutTimer) { this.startHeartbeatTimeout(); } } else { console.log(`⚠️ 心跳检测时发现连接状态异常: ${this.path}, 状态: ${this.getReadyStateText()}`); } }, WS_CONFIG.heartbeatInterval); } // 停止心跳检测 private stopHeartbeat(): void { if (this.heartbeatTimer) { console.log(`🛑 停止心跳检测: ${this.path}`); clearInterval(this.heartbeatTimer); this.heartbeatTimer = undefined; } this.clearHeartbeatTimeout(); } // 开始心跳响应超时检测 private startHeartbeatTimeout(): void { // 不再自动清除,只在收到响应时清除 this.heartbeatTimeoutTimer = setTimeout(() => { console.log(`💔 心跳响应超时: ${this.path}, ${WS_CONFIG.heartbeatTimeout}ms内未收到响应,主动断开连接`); console.log(` └─ 心跳统计: 发送${this.heartbeatSentCount}次, 接收${this.heartbeatReceivedCount}次`); // 设置心跳超时标志,触发重连 this.isHeartbeatTimeout = true; this.ws.close(1000, 'Heartbeat timeout'); // 使用正常关闭状态码,通过标志来判断是否重连 }, WS_CONFIG.heartbeatTimeout); } // 清除心跳响应超时检测 private clearHeartbeatTimeout(): void { if (this.heartbeatTimeoutTimer) { clearTimeout(this.heartbeatTimeoutTimer); this.heartbeatTimeoutTimer = undefined; } } // 安排重连 private scheduleReconnect(): void { if (this.isManualClose || this.reconnectAttempts >= WS_CONFIG.maxReconnectAttempts) { console.log(`🚫 停止重连: ${this.path}, 手动关闭: ${this.isManualClose}, 重连次数: ${this.reconnectAttempts}`); return; } this.reconnectAttempts++; // 指数退避重连策略 const delay = Math.min( WS_CONFIG.reconnectBaseDelay * Math.pow(2, this.reconnectAttempts - 1), WS_CONFIG.maxReconnectDelay, ); console.log( `🔄 WebSocket将在${delay}ms后重连: ${this.path} (${this.reconnectAttempts}/${WS_CONFIG.maxReconnectAttempts})`, ); this.reconnectTimer = setTimeout(() => { this.reconnect(); }, delay); } // 重连逻辑 private reconnect(): void { if (this.isManualClose) return; console.log(`🔄 WebSocket重连尝试: ${this.path} (${this.reconnectAttempts}/${WS_CONFIG.maxReconnectAttempts})`); this.connectionStartTime = Date.now(); // 创建新的WebSocket连接 this.ws = new WebSocket(this.baseUrl + this.path); this.setupHandlers(); } // 清理重连定时器 private clearReconnectTimer(): void { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = undefined; } } // 公开的WebSocket属性和方法 get readyState(): number { return this.ws.readyState; } get url(): string { return this.ws.url; } get protocol(): string { return this.ws.protocol; } get extensions(): string { return this.ws.extensions; } get bufferedAmount(): number { return this.ws.bufferedAmount; } get binaryType(): BinaryType { return this.ws.binaryType; } set binaryType(value: BinaryType) { this.ws.binaryType = value; } // 事件处理器属性 get onopen(): ((event: Event) => void) | null { return this.userOnOpen; } set onopen(handler: ((event: Event) => void) | null) { this.userOnOpen = handler; } get onmessage(): ((event: MessageEvent) => void) | null { return this.userOnMessage; } set onmessage(handler: ((event: MessageEvent) => void) | null) { this.userOnMessage = handler; } get onclose(): ((event: CloseEvent) => void) | null { return this.userOnClose; } set onclose(handler: ((event: CloseEvent) => void) | null) { this.userOnClose = handler; } get onerror(): ((event: Event) => void) | null { return this.userOnError; } set onerror(handler: ((event: Event) => void) | null) { this.userOnError = handler; } // WebSocket方法 send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void { this.ws.send(data); } close(code?: number, reason?: string): void { console.log(`👋 手动关闭WebSocket: ${this.path}`); this.isManualClose = true; this.isHeartbeatTimeout = false; // 手动关闭时重置心跳超时标志 this.stopHeartbeat(); this.clearReconnectTimer(); this.ws.close(code, reason); } addEventListener( type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => void, options?: boolean | AddEventListenerOptions, ): void { this.ws.addEventListener(type, listener, options); } removeEventListener( type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => void, options?: boolean | EventListenerOptions, ): void { this.ws.removeEventListener(type, listener, options); } dispatchEvent(event: Event): boolean { return this.ws.dispatchEvent(event); } // 常量 static readonly CONNECTING = WebSocket.CONNECTING; static readonly OPEN = WebSocket.OPEN; static readonly CLOSING = WebSocket.CLOSING; static readonly CLOSED = WebSocket.CLOSED; readonly CONNECTING = WebSocket.CONNECTING; readonly OPEN = WebSocket.OPEN; readonly CLOSING = WebSocket.CLOSING; readonly CLOSED = WebSocket.CLOSED; } function create(path: string): Promise { let baseUrl = ''; if (path.includes(import.meta.env.ENV_STORAGE_WEBSOCKET_BASE)) { baseUrl = ''; } else { baseUrl = import.meta.env.ENV_WEBSOCKET_BASE ?? ''; } const ws = new EnhancedWebSocket(path, baseUrl) as WebSocket; return new Promise((resolve, reject) => { const timeout = setTimeout(() => { ws.close(); reject(new Error('WebSocket connection timeout')); }, 10000); // 10秒连接超时 ws.addEventListener('open', () => { clearTimeout(timeout); resolve(ws); }); ws.addEventListener('error', (error: Event) => { clearTimeout(timeout); reject(error); }); }); } export default { create };