| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- /**
- * 用户在线状态管理器
- * 基于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(); // 缓存在线状态
-
- // WebSocket服务器地址(使用实际的聊天WebSocket服务)
- this.wsUrl = 'ws://localhost:1004/ws/chat';
- this.httpUrl = 'http://localhost:1004/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:
- // 忽略其他消息类型(如聊天消息等)
- break;
- }
- } catch (error) {
- console.error('❌ 消息解析失败:', error);
- }
- }
-
- /**
- * 通过HTTP API查询用户在线状态
- * @param {String} userId 目标用户ID
- * @return {Promise<Boolean>} 是否在线
- */
- 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.userId = null;
- }
-
- /**
- * 获取连接状态
- */
- getConnectionStatus() {
- return this.isConnected;
- }
- }
- // 导出单例
- export default new PresenceManager();
|