/** * WebSocket 管理器 * * 功能: * 1. 自动重连 * 2. 心跳检测 * 3. 消息队列 * 4. 事件监听 * * 使用示例: * import wsManager from '@/utils/websocket-manager.js' * * // 连接 * wsManager.connect('wss://example.com/chat') * * // 监听消息 * wsManager.on('message', (data) => { * console.log('收到消息:', data) * }) * * // 发送消息 * wsManager.send({ type: 'text', content: 'Hello' }) */ class WebSocketManager { constructor() { // WebSocket 实例 this.socketTask = null // 连接状态 this.isConnected = false this.isConnecting = false // 配置 this.url = '' this.reconnectAttempts = 0 this.maxReconnectAttempts = 5 this.reconnectInterval = 5000 this.heartbeatInterval = 30000 // 定时器 this.reconnectTimer = null this.heartbeatTimer = null // 消息队列(连接未建立时暂存) this.messageQueue = [] // 事件监听器 this.listeners = { message: [], // 收到消息 open: [], // 连接成功 close: [], // 连接关闭 error: [], // 连接错误 reconnect: [] // 重连中 } } /** * 连接 WebSocket * @param {String} url WebSocket 服务器地址 * @param {Object} options 配置选项 */ connect(url, options = {}) { // 如果已经在连接中,直接返回 if (this.isConnecting || this.isConnected) { console.log('[WebSocket] 已在连接中或已连接') return } this.url = url this.isConnecting = true // 合并配置 Object.assign(this, options) console.log(`[WebSocket] 开始连接: ${url}`) // 创建连接 this.socketTask = uni.connectSocket({ url: this.url, success: () => { console.log('[WebSocket] 连接请求已发送') }, fail: (err) => { console.error('[WebSocket] 连接失败:', err) this.isConnecting = false this.handleError(err) this.handleReconnect() } }) // 监听连接打开 this.socketTask.onOpen(() => { console.log('[WebSocket] 连接成功') this.isConnected = true this.isConnecting = false this.reconnectAttempts = 0 // 触发 open 事件 this.emit('open') // 启动心跳 this.startHeartbeat() // 发送队列中的消息 this.flushMessageQueue() }) // 监听消息 this.socketTask.onMessage((res) => { console.log('[WebSocket] 收到消息:', res.data) try { const data = JSON.parse(res.data) // 处理 pong(心跳响应) if (data.type === 'pong') { console.log('[WebSocket] 收到心跳响应') return } // 触发 message 事件 this.emit('message', data) } catch (e) { console.error('[WebSocket] 消息解析失败:', e) // 如果不是 JSON,直接传递原始数据 this.emit('message', res.data) } }) // 监听连接关闭 this.socketTask.onClose((res) => { console.log('[WebSocket] 连接已关闭:', res) this.isConnected = false this.isConnecting = false // 停止心跳 this.stopHeartbeat() // 触发 close 事件 this.emit('close', res) // 尝试重连 this.handleReconnect() }) // 监听错误 this.socketTask.onError((err) => { console.error('[WebSocket] 连接错误:', err) this.isConnected = false this.isConnecting = false // 触发 error 事件 this.handleError(err) }) } /** * 发送消息 * @param {Object|String} data 要发送的数据 */ send(data) { // 如果未连接,加入队列 if (!this.isConnected) { console.warn('[WebSocket] 未连接,消息加入队列') this.messageQueue.push(data) return } const message = typeof data === 'string' ? data : JSON.stringify(data) this.socketTask.send({ data: message, success: () => { console.log('[WebSocket] 消息发送成功:', data) }, fail: (err) => { console.error('[WebSocket] 消息发送失败:', err) } }) } /** * 关闭连接 * @param {Boolean} manual 是否手动关闭(手动关闭不重连) */ close(manual = true) { console.log(`[WebSocket] 关闭连接 (手动: ${manual})`) // 如果是手动关闭,清除重连定时器 if (manual) { this.clearReconnectTimer() this.reconnectAttempts = this.maxReconnectAttempts // 阻止重连 } this.stopHeartbeat() if (this.socketTask) { this.socketTask.close({ code: 1000, reason: manual ? '手动关闭' : '自动关闭' }) this.socketTask = null } this.isConnected = false this.isConnecting = false } /** * 启动心跳 */ startHeartbeat() { console.log('[WebSocket] 启动心跳') this.stopHeartbeat() this.heartbeatTimer = setInterval(() => { if (this.isConnected) { console.log('[WebSocket] 发送心跳') this.send({ type: 'ping', timestamp: Date.now() }) } }, this.heartbeatInterval) } /** * 停止心跳 */ stopHeartbeat() { if (this.heartbeatTimer) { console.log('[WebSocket] 停止心跳') clearInterval(this.heartbeatTimer) this.heartbeatTimer = null } } /** * 处理重连 */ handleReconnect() { // 如果达到最大重连次数,停止重连 if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error('[WebSocket] 达到最大重连次数,停止重连') return } // 如果已经在重连中,直接返回 if (this.reconnectTimer) { return } this.reconnectAttempts++ console.log(`[WebSocket] 准备重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`) // 触发 reconnect 事件 this.emit('reconnect', this.reconnectAttempts) this.reconnectTimer = setTimeout(() => { this.reconnectTimer = null this.connect(this.url) }, this.reconnectInterval) } /** * 清除重连定时器 */ clearReconnectTimer() { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer) this.reconnectTimer = null } } /** * 发送队列中的消息 */ flushMessageQueue() { if (this.messageQueue.length === 0) { return } console.log(`[WebSocket] 发送队列中的 ${this.messageQueue.length} 条消息`) while (this.messageQueue.length > 0) { const message = this.messageQueue.shift() this.send(message) } } /** * 处理错误 */ handleError(err) { this.emit('error', err) } /** * 监听事件 * @param {String} event 事件名称 * @param {Function} callback 回调函数 */ on(event, callback) { if (this.listeners[event]) { this.listeners[event].push(callback) } } /** * 移除事件监听 * @param {String} event 事件名称 * @param {Function} callback 回调函数 */ off(event, callback) { if (this.listeners[event]) { const index = this.listeners[event].indexOf(callback) if (index > -1) { this.listeners[event].splice(index, 1) } } } /** * 触发事件 * @param {String} event 事件名称 * @param {*} data 数据 */ emit(event, data) { if (this.listeners[event]) { this.listeners[event].forEach(callback => { try { callback(data) } catch (e) { console.error(`[WebSocket] 事件 ${event} 回调执行失败:`, e) } }) } } /** * 获取连接状态 */ getState() { return { isConnected: this.isConnected, isConnecting: this.isConnecting, reconnectAttempts: this.reconnectAttempts, queueLength: this.messageQueue.length } } } // 导出单例 export default new WebSocketManager()