const config = require('../config/config.js'); /** * 微信小程序专用WebSocket管理器 V2 * 针对小程序环境优化,解决连接问题 */ class WebSocketManagerV2 { constructor() { this.ws = null; this.isConnected = false; this.isConnecting = false; this.reconnectAttempts = 0; this.maxReconnectAttempts = 8; // 增加重连次数 this.reconnectInterval = 2000; // 2秒 this.heartbeatInterval = null; this.heartbeatTimeout = 30000; // 30秒心跳 this.connectionTimeout = null; // 认证信息 this.token = null; this.deviceId = null; // 事件处理器 this.messageHandlers = new Map(); this.eventHandlers = new Map(); // 消息队列 this.messageQueue = []; this.messageIdCounter = 0; // 性能监控 this.performanceStats = { connectionAttempts: 0, successfulConnections: 0, failedConnections: 0, messagesSent: 0, messagesReceived: 0, reconnectCount: 0, lastConnectTime: null, lastDisconnectTime: null, averageLatency: 0, latencyHistory: [] }; // 连接质量监控 this.connectionQuality = { score: 100, // 0-100分 factors: { stability: 100, // 连接稳定性 latency: 100, // 延迟 reliability: 100 // 可靠性 } }; // 智能重连配置 this.smartReconnect = { enabled: true, adaptiveDelay: true, maxDelay: 60000, // 最大延迟60秒 backoffFactor: 1.5, jitterRange: 0.1 // 10%的随机抖动 }; // 初始化设备ID this.initDeviceId(); // 绑定方法上下文 this.onOpen = this.onOpen.bind(this); this.onMessage = this.onMessage.bind(this); this.onError = this.onError.bind(this); this.onClose = this.onClose.bind(this); } // 初始化设备ID initDeviceId() { try { let deviceId = wx.getStorageSync('device_id'); if (!deviceId) { deviceId = 'mp_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); wx.setStorageSync('device_id', deviceId); } else { } this.deviceId = deviceId; } catch (error) { console.error('❌ 设备ID初始化失败:', error); this.deviceId = 'mp_unknown_' + Date.now(); } } // 设置认证token setToken(token) { this.token = token; } // 获取认证token getToken() { if (this.token) { return this.token; } try { // 方式1:从app.globalData获取 const app = getApp(); if (app?.globalData?.userInfo?.token) { this.token = app.globalData.userInfo.token; return this.token; } // 方式2:从本地存储获取userInfo const userInfo = wx.getStorageSync('userInfo'); if (userInfo?.token) { this.token = userInfo.token; return this.token; } // 方式3:直接从本地存储获取token const directToken = wx.getStorageSync('token'); if (directToken) { this.token = directToken; return this.token; } console.warn('⚠️ 未找到有效的token'); return null; } catch (error) { console.error('❌ 获取token失败:', error); return null; } } // 连接WebSocket async connect() { if (this.ws) { // 0: CONNECTING, 1: OPEN, 2: CLOSING, 3: CLOSED } if (this.isConnected) { return Promise.resolve(); } if (this.isConnecting) { return this.waitForConnection(); } return new Promise((resolve, reject) => { this.isConnecting = true; // 记录连接尝试 this.performanceStats.connectionAttempts++; const connectStartTime = Date.now(); // 获取token const token = this.getToken(); if (!token) { console.error('❌ 缺少认证token,无法连接WebSocket'); this.isConnecting = false; this.performanceStats.failedConnections++; reject(new Error('缺少认证token')); return; } // 构建WebSocket URL const wsUrl = config.websocket?.url || 'wss://api.faxianwo.me/api/v1/ws'; const fullUrl = `${wsUrl}?device_id=${encodeURIComponent(this.deviceId)}`; try { // 小程序WebSocket连接配置 const connectOptions = { url: fullUrl, header: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, timeout: 15000 }; this.ws = wx.connectSocket(connectOptions); // 设置连接超时 this.connectionTimeout = setTimeout(() => { if (this.isConnecting && !this.isConnected) { console.error('❌ WebSocket连接超时'); this.cleanup(); reject(new Error('连接超时')); } }, 20000); // 事件监听 this.ws.onOpen((res) => { const connectTime = Date.now() - connectStartTime; console.log(`🎉 WebSocket连接成功 (${connectTime}ms)`); // 更新性能统计 this.performanceStats.successfulConnections++; this.performanceStats.lastConnectTime = Date.now(); this.updateConnectionQuality('connect_success', connectTime); this.onOpen(res); resolve(); }); this.ws.onMessage((res) => { this.performanceStats.messagesReceived++; console.log('🔥 WebSocket原始接收:', { data: res.data, dataType: typeof res.data, dataLength: res.data ? res.data.length : 0, timestamp: new Date().toLocaleTimeString() }); try { this.onMessage(res); } catch (error) { console.error('🔥 this.onMessage调用失败:', error); } }); this.ws.onError((error) => { console.error('💥 WebSocket连接错误:', error); this.performanceStats.failedConnections++; this.updateConnectionQuality('connect_error'); this.onError(error); if (this.isConnecting) { reject(error); } }); this.ws.onClose((res) => { this.performanceStats.lastDisconnectTime = Date.now(); this.updateConnectionQuality('disconnect', res.code); this.onClose(res); if (this.isConnecting) { reject(new Error('连接被关闭')); } }); } catch (error) { console.error('❌ WebSocket连接创建失败:', error); this.cleanup(); // 检查域名配置问题 if (error.errMsg?.includes('url not in domain list')) { console.error('💡 请在微信公众平台配置WebSocket域名: wss://api.faxianwo.me'); } reject(error); } }); } // 等待连接完成 waitForConnection() { return new Promise((resolve, reject) => { const checkConnection = () => { if (this.isConnected) { resolve(); } else if (!this.isConnecting) { reject(new Error('连接失败')); } else { setTimeout(checkConnection, 100); } }; checkConnection(); }); } // 清理连接状态 cleanup() { this.isConnecting = false; if (this.connectionTimeout) { clearTimeout(this.connectionTimeout); this.connectionTimeout = null; } } // 连接成功处理 onOpen(res) { console.log('📊 连接详情:', { deviceId: this.deviceId, hasToken: !!this.token, reconnectAttempts: this.reconnectAttempts, timestamp: new Date().toLocaleTimeString() }); this.isConnected = true; this.isConnecting = false; this.reconnectAttempts = 0; this.cleanup(); this.startHeartbeat(); this.processMessageQueue(); this.triggerEvent('connected', res); } // 消息处理 onMessage(event) { try { const message = JSON.parse(event.data); console.log('📨 WebSocket收到原始消息:', { type: message.type || 'unknown', hasData: !!message.data, messageKeys: Object.keys(message), deviceId: this.deviceId, timestamp: new Date().toLocaleTimeString() }); // 心跳响应 if (message.type === 'pong' || message.type === 'heartbeat_response') { return; } // 🔥 根据消息类型分发到对应的事件处理器 switch (message.type) { case 'new_message': this.triggerEvent('new_message', message); break; case 'message_status': this.triggerEvent('message_status', message); break; case 'message_sent': // 标准响应:消息发送成功(包括撤回成功) this.triggerEvent('message_sent', message); break; case 'message_recalled': // 收到其他用户撤回消息的通知 this.triggerEvent('message_recalled', message); break; case 'error': // 错误响应(包括撤回失败) this.triggerEvent('error', message); break; case 'unread_count_update': this.triggerEvent('unread_count_update', message); break; // 🔥 在线状态相关(presence) case 'user_online': this.triggerEvent('user_online', message); this.triggerEvent('message', message); // 兼容通用监听 break; case 'user_offline': this.triggerEvent('user_offline', message); this.triggerEvent('message', message); // 兼容通用监听 break; case 'presence_update': this.triggerEvent('presence_update', message); this.triggerEvent('message', message); // 兼容通用监听 break; case 'friend_request': this.triggerEvent('friend_request', message); break; case 'notification': this.triggerEvent('notification', message); break; case 'chat_message': this.triggerEvent('chat_message', message); break; default: // 尝试查找特定类型的处理器 const handler = this.messageHandlers.get(message.type); if (handler) { handler(message); } else { // 触发通用消息事件 this.triggerEvent('message', message); } } } catch (error) { console.error('❌ 消息解析失败:', error); } } // 错误处理 onError(error) { console.error('❌ WebSocket错误:', error); this.isConnected = false; this.cleanup(); this.triggerEvent('error', error); this.scheduleReconnect(); } // 连接关闭处理 onClose(event) { this.isConnected = false; this.cleanup(); this.stopHeartbeat(); this.triggerEvent('disconnected', event); // 非正常关闭时重连 if (event.code !== 1000) { this.scheduleReconnect(); } } // 发送消息(带性能监控) send(message) { if (!this.isConnected) { console.warn('⚠️ WebSocket未连接,消息加入队列'); this.messageQueue.push(message); this.connect().catch(console.error); return false; } try { const messageStr = typeof message === 'string' ? message : JSON.stringify(message); const sendTime = Date.now(); this.ws.send({ data: messageStr, success: () => { this.performanceStats.messagesSent++; // 如果是心跳消息,记录延迟 if (message.type === 'heartbeat') { this.recordLatency(sendTime); } }, fail: (error) => { console.error('❌ 消息发送失败:', error); this.updateConnectionQuality('send_error'); } }); return true; } catch (error) { console.error('❌ 消息发送失败:', error); this.updateConnectionQuality('send_exception'); return false; } } // 处理消息队列 processMessageQueue() { while (this.messageQueue.length > 0 && this.isConnected) { const message = this.messageQueue.shift(); this.send(message); } } // 开始心跳 - 使用WebSocket原生ping/pong startHeartbeat() { this.stopHeartbeat(); this.heartbeatInterval = setInterval(() => { console.log("发送心跳---->"); if (this.isConnected && this.ws) { try { // 使用微信小程序的ping方法(如果支持) if (typeof this.ws.ping === 'function') { this.ws.ping(); } else { // 降级:发送应用层心跳(使用正确的格式) const heartbeatMessage = { type: 'heartbeat', id: `heartbeat_${Date.now()}`, data: { timestamp: Date.now() } }; this.send(heartbeatMessage); } } catch (error) { console.error('❌ 心跳发送失败:', error); } } }, this.heartbeatTimeout); } // 停止心跳 stopHeartbeat() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } } // 智能重连机制 scheduleReconnect() { if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error('❌ 达到最大重连次数,停止重连'); this.triggerEvent('max_reconnect_reached'); return; } // 智能延迟计算 let delay; if (this.smartReconnect.adaptiveDelay) { // 基于连接质量调整延迟 const qualityFactor = this.connectionQuality.score / 100; const baseDelay = this.reconnectInterval * Math.pow(this.smartReconnect.backoffFactor, this.reconnectAttempts); delay = Math.min(baseDelay / qualityFactor, this.smartReconnect.maxDelay); // 添加随机抖动,避免雷群效应 const jitter = delay * this.smartReconnect.jitterRange * (Math.random() - 0.5); delay = Math.max(1000, delay + jitter); // 最小1秒 } else { delay = this.reconnectInterval * Math.pow(2, this.reconnectAttempts); } console.log(`🔄 ${Math.round(delay)}ms后尝试第${this.reconnectAttempts + 1}次重连 (质量分数: ${this.connectionQuality.score})`); // 更新统计 this.performanceStats.reconnectCount++; setTimeout(() => { this.reconnectAttempts++; this.connect().catch((error) => { console.error('重连失败:', error); this.scheduleReconnect(); // 继续重连 }); }, delay); } // 断开连接 disconnect() { this.stopHeartbeat(); this.cleanup(); if (this.ws) { this.ws.close(); this.ws = null; } this.isConnected = false; this.reconnectAttempts = this.maxReconnectAttempts; // 阻止重连 } // 注册消息处理器 registerMessageHandler(type, handler) { this.messageHandlers.set(type, handler); } // 发送聊天消息 - 符合API文档规范 sendChatMessage(receiverId, content, msgType = 0, chatType = 0, options = {}) { // 生成客户端消息ID const clientMsgId = this.generateMessageId(); // 🔥 消息类型映射 - 修正为与API文档一致 const msgTypeMap = { 'text': 0, // 文字消息 'image': 1, // 图片消息 'voice': 2, // 语音消息 'video': 3, // 视频消息 'file': 4, // 文件消息 'emoji': 6, // 表情消息 'location': 5 // 位置消息 }; // 🔥 详细记录消息类型转换过程 // 如果msgType是字符串,转换为数字 if (typeof msgType === 'string') { const convertedMsgType = msgTypeMap[msgType] || 0; msgType = convertedMsgType; } const message = { type: 'send_message', id: clientMsgId, data: { receiverId: receiverId, chatType: chatType, msgType: msgType, content: content, atUsers: options.atUsers || [], replyTo: options.replyTo || '', extra: options.extra || '' } }; console.log('📤 发送聊天消息:', { type: 'send_message', id: clientMsgId, receiverId, chatType, msgType, contentLength: content ? content.length : 0, contentPreview: content ? (content.substring(0, 50) + (content.length > 50 ? '...' : '')) : 'null' }); return this.send(message); } // 生成消息ID generateMessageId() { this.messageIdCounter++; return `client_msg_${Date.now()}_${this.messageIdCounter}`; } // 发送图片消息 sendImageMessage(receiverId, imageUrl, chatType = 0, options = {}) { return this.sendChatMessage(receiverId, imageUrl, 1, chatType, options); } // 发送语音消息 sendVoiceMessage(receiverId, voiceUrl, duration, chatType = 0, options = {}) { const content = JSON.stringify({ url: voiceUrl, duration: duration }); return this.sendChatMessage(receiverId, content, 2, chatType, options); } // 发送视频消息 sendVideoMessage(receiverId, videoUrl, duration, thumbnail, chatType = 0, options = {}) { const content = JSON.stringify({ url: videoUrl, duration: duration, thumbnail: thumbnail }); return this.sendChatMessage(receiverId, content, 3, chatType, options); } // 发送位置消息 sendLocationMessage(receiverId, latitude, longitude, address, locationName, chatType = 0, options = {}) { const message = { type: 'send_location', id: this.generateMessageId(), data: { receiverId: receiverId, chatType: chatType, latitude: latitude, longitude: longitude, address: address, locationName: locationName } }; return this.send(message); } // 撤回消息 recallMessage(messageId) { const message = { type: 'recall_message', id: this.generateMessageId(), data: { messageId: messageId } }; return this.send(message); } // 标记消息已读 markMessageRead(messageIds) { const message = { type: 'mark_read', id: this.generateMessageId(), data: { messageIds: Array.isArray(messageIds) ? messageIds : [messageIds] } }; return this.send(message); } // 注册事件处理器 on(event, handler) { if (!this.eventHandlers.has(event)) { this.eventHandlers.set(event, []); } this.eventHandlers.get(event).push(handler); } // 移除事件处理器 off(event, handler) { if (!this.eventHandlers.has(event)) { return; } const handlers = this.eventHandlers.get(event); if (handler) { // 移除特定的处理器 const index = handlers.indexOf(handler); if (index > -1) { handlers.splice(index, 1); } } else { // 移除所有处理器 this.eventHandlers.set(event, []); } // 如果没有处理器了,删除整个事件 if (handlers.length === 0) { this.eventHandlers.delete(event); } } // 触发事件 triggerEvent(event, data) { const handlers = this.eventHandlers.get(event); const handlerCount = Array.isArray(handlers) ? handlers.length : 0; if (handlerCount > 0) { handlers.forEach((handler, index) => { try { handler(data); } catch (error) { console.error(`❌ 事件处理器错误 [${event}]:`, error); } }); } else { } } // 获取连接状态 getStatus() { return { isConnected: this.isConnected, isConnecting: this.isConnecting, reconnectAttempts: this.reconnectAttempts, hasToken: !!this.token, deviceId: this.deviceId }; } // 兼容性方法 - 为了向后兼容 getConnectionStatus() { return { connected: this.isConnected, connecting: this.isConnecting }; } // ===== 聊天功能相关方法 ===== // 发送输入状态 sendTypingStatus(conversationId, isTyping) { try { if (!this.isConnected) { console.warn('⚠️ WebSocket未连接,无法发送输入状态'); return false; } const message = { type: 'typing_status', id: `typing_${Date.now()}`, data: { conversationId: conversationId, isTyping: isTyping, timestamp: Date.now() } }; console.log('📝 发送输入状态:', { conversationId, isTyping, timestamp: Date.now() }); return this.send(message); } catch (error) { console.error('❌ 发送输入状态失败:', error); return false; } } // 发送已读回执 sendReadReceipt(messageId) { try { if (!this.isConnected) { console.warn('⚠️ WebSocket未连接,无法发送已读回执'); return false; } const message = { type: 'mark_read', id: `read_${Date.now()}`, data: { messageId: messageId, timestamp: Date.now() } }; console.log('✅ 发送已读回执:', { messageId, timestamp: Date.now() }); return this.send(message); } catch (error) { console.error('❌ 发送已读回执失败:', error); return false; } } // 聊天消息处理器注册 onChatMessage(handler) { this.on('new_message', handler); this.on('chat_message', handler); } // 未读数更新处理器注册 onUnreadCountUpdate(handler) { this.on('unread_count_update', handler); this.on('unread_update', handler); } // 通知处理器注册 onNotification(handler) { this.on('notification', handler); } // 好友请求处理器注册 onFriendRequest(handler) { this.on('friend_request', handler); } // 发送聊天消息(兼容旧版本接口)- 重定向到标准方法 sendChatMessageLegacy(targetId, content, messageType = 'text', extra = {}) { // 转换消息类型 const msgTypeMap = { 'text': 1, 'image': 2, 'voice': 3, 'video': 4, 'file': 5, 'emoji': 6, 'location': 7 }; const msgType = msgTypeMap[messageType] || 1; const chatType = extra.chatType || 0; return this.sendChatMessage(targetId, content, msgType, chatType, extra); } // 生成消息ID generateMessageId() { return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } // ===== 性能监控方法 ===== // 记录延迟 recordLatency(sendTime) { const latency = Date.now() - sendTime; this.performanceStats.latencyHistory.push(latency); // 只保留最近50次的延迟记录 if (this.performanceStats.latencyHistory.length > 50) { this.performanceStats.latencyHistory.shift(); } // 计算平均延迟 const sum = this.performanceStats.latencyHistory.reduce((a, b) => a + b, 0); this.performanceStats.averageLatency = sum / this.performanceStats.latencyHistory.length; // 更新连接质量 this.updateConnectionQuality('latency', latency); } // 更新连接质量评分 updateConnectionQuality(event, value = null) { const factors = this.connectionQuality.factors; switch (event) { case 'connect_success': factors.reliability = Math.min(100, factors.reliability + 5); if (value < 3000) factors.latency = Math.min(100, factors.latency + 2); break; case 'connect_error': factors.reliability = Math.max(0, factors.reliability - 10); factors.stability = Math.max(0, factors.stability - 5); break; case 'disconnect': if (value !== 1000) { // 非正常关闭 factors.stability = Math.max(0, factors.stability - 8); } break; case 'send_error': case 'send_exception': factors.reliability = Math.max(0, factors.reliability - 3); break; case 'latency': if (value > 5000) { factors.latency = Math.max(0, factors.latency - 5); } else if (value < 1000) { factors.latency = Math.min(100, factors.latency + 1); } break; } // 计算总分 this.connectionQuality.score = Math.round( (factors.stability + factors.latency + factors.reliability) / 3 ); } // 获取性能统计 getPerformanceStats() { return { ...this.performanceStats, connectionQuality: this.connectionQuality, uptime: this.performanceStats.lastConnectTime ? Date.now() - this.performanceStats.lastConnectTime : 0 }; } // 重置性能统计 resetPerformanceStats() { this.performanceStats = { connectionAttempts: 0, successfulConnections: 0, failedConnections: 0, messagesSent: 0, messagesReceived: 0, reconnectCount: 0, lastConnectTime: null, lastDisconnectTime: null, averageLatency: 0, latencyHistory: [] }; this.connectionQuality = { score: 100, factors: { stability: 100, latency: 100, reliability: 100 } }; } // ===== 兼容性别名方法 ===== // 兼容旧版本的 sendMessage 方法 sendMessage(message) { return this.send(message); } } // 创建全局实例 const wsManager = new WebSocketManagerV2(); module.exports = wsManager;