const DEFAULT_LOGIN_WEBSOCKET = import.meta.env.VITE_LOGIN_WEBSOCKET || 'ws://localhost:8080/ws' class WebSocketClient { constructor(options) { this.ws = null this.url = options.url || DEFAULT_LOGIN_WEBSOCKET this.messageHandler = options.messageHandler || (() => {}) this.reconnectInterval = options.reconnectInterval ?? 20 * 1e3 this.heartbeatInterval = options.heartbeatInterval ?? 5 * 1e3 this.pingInterval = options.pingInterval ?? 10 * 1e3 this.reconnectTimeout = options.reconnectTimeout ?? 30 * 1e3 this.maxReconnectAttempts = options.maxReconnectAttempts ?? 10 this.connectionTimeout = options.connectionTimeout ?? 10 * 1e3 this.reconnectAttempts = 0 this.messageQueue = [] this.detectionTimer = null this.timeoutTimer = null this.reconnectTimer = null this.pingTimer = null this.connectionTimer = null this.isConnected = false this.isConnecting = false this.stopReconnect = false this.isReconnecting = false } // 单例模式获取实例 static getInstance(options) { if (!WebSocketClient.instance) { WebSocketClient.instance = new WebSocketClient(options) } else { WebSocketClient.instance.messageHandler = options.messageHandler || (() => {}) if (options.url && WebSocketClient.instance.url !== options.url) { WebSocketClient.instance.url = options.url WebSocketClient.instance.reconnectAttempts = 0 WebSocketClient.instance.init() } } return WebSocketClient.instance } // 初始化连接 init() { this.connect(true) } connect(resetReconnectAttempts = false) { if (this.isConnecting) { console.log('正在建立WebSocket连接中...') return } if (this.ws?.readyState === WebSocket.OPEN) { console.warn('WebSocket连接已存在') this.flushMessageQueue() return } try { this.isConnecting = true this.stopReconnect = false if (resetReconnectAttempts) { this.reconnectAttempts = 0 this.isReconnecting = false this.clearTimer('reconnectTimer') } this.ws = new WebSocket(this.url) this.clearTimer('connectionTimer') this.connectionTimer = setTimeout(() => { console.error(`WebSocket连接超时 (${this.connectionTimeout}ms):${this.url}`) this.handleConnectionTimeout() }, this.connectionTimeout) this.ws.onopen = (event) => this.handleOpen(event) this.ws.onmessage = (event) => this.handleMessage(event) this.ws.onclose = (event) => this.handleClose(event) this.ws.onerror = (event) => this.handleError(event) } catch (error) { console.error('WebSocket初始化失败:', error) this.isConnecting = false this.reconnect() } } // 处理连接超时 handleConnectionTimeout() { if (this.ws?.readyState !== WebSocket.OPEN) { console.error('WebSocket连接超时,强制关闭连接') this.ws?.close(1e3, 'Connection timeout') this.isConnecting = false this.reconnect() } } // 关闭连接 close(force) { this.clearAllTimers() this.stopReconnect = true this.isReconnecting = false this.isConnecting = false if (this.ws) { this.ws.close(force ? 1001 : 1e3, force ? 'Force closed' : 'Normal close') this.ws = null } this.isConnected = false } // 发送消息 - 增加消息队列 send(data, immediate = false) { if (immediate && (!this.ws || this.ws.readyState !== WebSocket.OPEN)) { console.error('WebSocket未连接,无法立即发送消息') return } if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { console.log('WebSocket未连接,消息已加入队列等待发送') this.messageQueue.push(data) if (!this.isConnecting && !this.stopReconnect) { this.init() } return } try { this.ws.send(data) } catch (error) { console.error('WebSocket发送消息失败:', error) this.messageQueue.push(data) this.reconnect() } } // 发送队列中的消息 flushMessageQueue() { if (this.messageQueue.length > 0 && this.ws?.readyState === WebSocket.OPEN) { console.log(`发送队列中的${this.messageQueue.length}条消息`) while (this.messageQueue.length > 0) { const data = this.messageQueue.shift() if (data) { try { this.ws?.send(data) } catch (error) { console.error('发送队列消息失败:', error) if (data) this.messageQueue.unshift(data) break } } } } } // 处理连接打开 handleOpen(event) { console.log('WebSocket连接成功', event) this.clearTimer('connectionTimer') this.isConnected = true this.isConnecting = false this.isReconnecting = false this.stopReconnect = false this.reconnectAttempts = 0 this.startHeartbeat() this.startPing() this.flushMessageQueue() } // 处理收到的消息 handleMessage(event) { console.log('收到WebSocket消息:', event) this.resetHeartbeat() this.messageHandler(event) } // 处理连接关闭 handleClose(event) { console.log( `WebSocket断开: 代码=${event.code}, 原因=${event.reason}, 干净关闭=${event.wasClean}` ) const isNormalClose = event.code === 1e3 this.isConnected = false this.isConnecting = false this.clearConnectionTimers() this.ws = null if (!this.stopReconnect && !isNormalClose) { this.reconnect() } } // 处理错误 - 增加详细错误信息 handleError(event) { console.error('WebSocket连接错误:') console.error('错误事件:', event) console.error( '当前连接状态:', this.ws?.readyState ? this.getReadyStateText(this.ws.readyState) : '未初始化' ) this.isConnected = false this.isConnecting = false if (!this.stopReconnect) { this.reconnect() } } closeCurrentSocketForReconnect() { this.clearConnectionTimers() this.isConnected = false this.isConnecting = false if (this.ws) { this.ws.onopen = null this.ws.onmessage = null this.ws.onclose = null this.ws.onerror = null if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) { this.ws.close(1001, 'Reconnect') } this.ws = null } } // 转换连接状态为文本描述 getReadyStateText(state) { switch (state) { 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 `未知状态 (${state})` } } // 开始心跳检测 startHeartbeat() { this.clearTimer('detectionTimer') this.clearTimer('timeoutTimer') this.detectionTimer = setTimeout(() => { this.isConnected = this.ws?.readyState === WebSocket.OPEN if (!this.isConnected) { console.warn('WebSocket心跳检测失败,尝试重连') this.reconnect() this.timeoutTimer = setTimeout(() => { console.warn('WebSocket重连超时') this.close() }, this.reconnectTimeout) } }, this.heartbeatInterval) } // 重置心跳检测 resetHeartbeat() { this.clearTimer('detectionTimer') this.clearTimer('timeoutTimer') this.startHeartbeat() } // 开始发送ping消息 startPing() { this.clearTimer('pingTimer') this.pingTimer = setInterval(() => { if (this.ws?.readyState !== WebSocket.OPEN) { console.warn('WebSocket未连接,停止发送ping') this.clearTimer('pingTimer') this.reconnect() return } try { this.ws.send('ping') console.log('发送ping消息') } catch (error) { console.error('发送ping消息失败:', error) this.clearTimer('pingTimer') this.reconnect() } }, this.pingInterval) } // 重连 - 增加重连次数限制 reconnect() { if (this.stopReconnect || this.isConnecting || this.reconnectInterval <= 0) { return } if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error(`已达到最大重连次数(${this.maxReconnectAttempts}),停止重连`) this.close(true) return } this.reconnectAttempts++ this.isReconnecting = true this.closeCurrentSocketForReconnect() const delay = this.calculateReconnectDelay() console.log( `将在${delay / 1e3}秒后尝试重新连接(第${this.reconnectAttempts}/${this.maxReconnectAttempts}次)` ) this.clearTimer('reconnectTimer') this.reconnectTimer = setTimeout(() => { console.log(`尝试重新连接WebSocket(第${this.reconnectAttempts}次)`) this.connect(false) }, delay) } // 计算重连延迟 - 指数退避策略 calculateReconnectDelay() { const jitter = Math.random() * 1e3 const baseDelay = Math.min( this.reconnectInterval * Math.pow(1.5, this.reconnectAttempts - 1), this.reconnectInterval * 5 ) return baseDelay + jitter } // 清除指定定时器 clearTimer(timerName) { if (this[timerName]) { clearTimeout(this[timerName]) this[timerName] = null } } // 清除所有定时器 clearAllTimers() { this.clearConnectionTimers() this.clearTimer('reconnectTimer') } clearConnectionTimers() { this.clearTimer('detectionTimer') this.clearTimer('timeoutTimer') this.clearTimer('pingTimer') this.clearTimer('connectionTimer') } // 获取当前连接状态 get isWebSocketConnected() { return this.isConnected } // 获取当前连接状态文本 get connectionStatusText() { if (this.isConnecting) return '正在连接' if (this.isConnected) return '已连接' if (this.isReconnecting && this.reconnectAttempts > 0) return `重连中(${this.reconnectAttempts}/${this.maxReconnectAttempts})` return '已断开' } // 销毁实例 static destroyInstance() { if (WebSocketClient.instance) { WebSocketClient.instance.close() WebSocketClient.instance = null } } } WebSocketClient.instance = null export default WebSocketClient