/** * 用户在线状态管理器 * 基于WebSocket心跳检测实现 */ class PresenceManager { constructor() { this.ws = null; this.heartbeatTimer = null; this.reconnectTimer = null; this.heartbeatInterval = 30000; // 30秒心跳 this.reconnectInterval = 5000; // 5秒重连 this.isConnected = false; this.userId = null; this.statusCallbacks = new Map(); // 存储状态变化回调 this.onlineStatusCache = new Map(); // 缓存在线状态 this.messageCallbacks = []; // 存储消息回调(用于已读回执等) // WebSocket服务器地址(使用实际的聊天WebSocket服务) this.wsUrl = 'ws://localhost:8083/ws/chat'; this.httpUrl = 'http://localhost:8083/api/online'; // HTTP API地址 } /** * 连接WebSocket * @param {String} userId 当前用户ID */ connect(userId) { if (this.isConnected || !userId) { return; } this.userId = userId; try { // uni-app的WebSocket API this.ws = uni.connectSocket({ url: `${this.wsUrl}?userId=${userId}`, success: () => { console.log('🔌 WebSocket连接请求已发送'); }, fail: (err) => { console.error('❌ WebSocket连接失败:', err); this.scheduleReconnect(); } }); // 监听连接打开 uni.onSocketOpen(() => { console.log('✅ WebSocket已连接'); this.isConnected = true; this.startHeartbeat(); }); // 监听消息 uni.onSocketMessage((res) => { this.handleMessage(res.data); }); // 监听错误 uni.onSocketError((err) => { console.error('❌ WebSocket错误:', err); this.isConnected = false; this.scheduleReconnect(); }); // 监听关闭 uni.onSocketClose(() => { console.log('🔌 WebSocket已关闭'); this.isConnected = false; this.stopHeartbeat(); this.scheduleReconnect(); }); } catch (error) { console.error('❌ WebSocket连接异常:', error); this.scheduleReconnect(); } } /** * 处理接收到的消息 */ handleMessage(data) { try { const message = typeof data === 'string' ? JSON.parse(data) : data; switch (message.type) { case 'PONG': // 心跳响应 console.log('💓 收到心跳响应'); break; case 'ONLINE': // 用户上线通知 if (message.fromUserId) { this.onlineStatusCache.set(String(message.fromUserId), true); this.notifyStatusChange(String(message.fromUserId), 'online'); } break; case 'OFFLINE': // 用户离线通知 if (message.fromUserId) { this.onlineStatusCache.set(String(message.fromUserId), false); this.notifyStatusChange(String(message.fromUserId), 'offline'); } break; case 'STATUS_UPDATE': // 用户状态更新 if (message.userId) { const status = message.online ? 'online' : 'offline'; this.onlineStatusCache.set(String(message.userId), message.online); this.notifyStatusChange(String(message.userId), status); } break; default: // 其他消息类型,调用注册的回调 if (this.messageCallbacks && this.messageCallbacks.length > 0) { this.messageCallbacks.forEach(callback => { try { callback(message); } catch (err) { console.error('❌ 消息回调执行失败:', err); } }); } break; } } catch (error) { console.error('❌ 消息解析失败:', error); } } /** * 通过HTTP API查询用户在线状态 * @param {String} userId 目标用户ID * @return {Promise} 是否在线 */ async queryOnlineStatus(userId) { try { const [err, res] = await uni.request({ url: `${this.httpUrl}/checkStatus`, method: 'GET', data: { userId: userId } }); if (err) { console.error('❌ 查询在线状态失败:', err); return false; } if (res.data && res.data.code === 200) { const isOnline = res.data.data.online || false; this.onlineStatusCache.set(String(userId), isOnline); return isOnline; } return false; } catch (error) { console.error('❌ 查询在线状态异常:', error); return false; } } /** * 获取缓存的在线状态 * @param {String} userId 目标用户ID * @return {Boolean|null} 在线状态,null表示未知 */ getCachedStatus(userId) { return this.onlineStatusCache.get(String(userId)) || null; } /** * 启动心跳 */ startHeartbeat() { this.stopHeartbeat(); this.heartbeatTimer = setInterval(() => { if (this.isConnected) { this.sendMessage({ type: 'PING', fromUserId: this.userId, timestamp: Date.now() }); } }, this.heartbeatInterval); } /** * 停止心跳 */ stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } } /** * 发送消息 */ sendMessage(data) { if (!this.isConnected) { console.warn('⚠️ WebSocket未连接,无法发送消息'); return; } try { uni.sendSocketMessage({ data: JSON.stringify(data), fail: (err) => { console.error('❌ 发送消息失败:', err); } }); } catch (error) { console.error('❌ 发送消息异常:', error); } } /** * 订阅用户状态 * @param {String} targetUserId 目标用户ID */ subscribeUser(targetUserId) { if (!this.isConnected || !targetUserId) { return; } this.sendMessage({ type: 'SUBSCRIBE', fromUserId: this.userId, toUserId: targetUserId }); } /** * 取消订阅用户状态 * @param {String} targetUserId 目标用户ID */ unsubscribeUser(targetUserId) { if (!this.isConnected || !targetUserId) { return; } this.sendMessage({ type: 'UNSUBSCRIBE', fromUserId: this.userId, toUserId: targetUserId }); } /** * 监听用户状态变化 * @param {String} targetUserId 目标用户ID * @param {Function} callback 回调函数 (status) => {} */ onStatusChange(targetUserId, callback) { if (!this.statusCallbacks.has(targetUserId)) { this.statusCallbacks.set(targetUserId, []); } this.statusCallbacks.get(targetUserId).push(callback); // 订阅该用户 this.subscribeUser(targetUserId); } /** * 移除状态监听 * @param {String} targetUserId 目标用户ID * @param {Function} callback 回调函数 */ offStatusChange(targetUserId, callback) { if (!this.statusCallbacks.has(targetUserId)) { return; } const callbacks = this.statusCallbacks.get(targetUserId); const index = callbacks.indexOf(callback); if (index > -1) { callbacks.splice(index, 1); } // 如果没有回调了,取消订阅 if (callbacks.length === 0) { this.statusCallbacks.delete(targetUserId); this.unsubscribeUser(targetUserId); } } /** * 通知状态变化 */ notifyStatusChange(userId, status) { console.log(`👤 用户 ${userId} 状态变更: ${status}`); const callbacks = this.statusCallbacks.get(userId); if (callbacks && callbacks.length > 0) { callbacks.forEach(callback => { try { callback(status); } catch (error) { console.error('❌ 状态回调执行失败:', error); } }); } } /** * 计划重连 */ scheduleReconnect() { if (this.reconnectTimer) { return; } console.log('🔄 将在5秒后重连...'); this.reconnectTimer = setTimeout(() => { this.reconnectTimer = null; if (!this.isConnected && this.userId) { console.log('🔄 尝试重连...'); this.connect(this.userId); } }, this.reconnectInterval); } /** * 断开连接 */ disconnect() { this.stopHeartbeat(); if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } if (this.isConnected) { uni.closeSocket(); this.isConnected = false; } this.statusCallbacks.clear(); this.messageCallbacks = []; // 清空消息回调 this.userId = null; } /** * 获取连接状态 */ getConnectionStatus() { return this.isConnected; } } // 导出单例 export default new PresenceManager();