|
|
@@ -1,363 +0,0 @@
|
|
|
-/**
|
|
|
- * 用户在线状态管理器
|
|
|
- * 基于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<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.messageCallbacks = []; // 清空消息回调
|
|
|
- this.userId = null;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 获取连接状态
|
|
|
- */
|
|
|
- getConnectionStatus() {
|
|
|
- return this.isConnected;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 导出单例
|
|
|
-export default new PresenceManager();
|