Эх сурвалжийг харах

用户在线状态修复消息已读未读状态

mazhenhang 1 сар өмнө
parent
commit
0ccdbca0ec

+ 44 - 3
LiangZhiYUMao/App.vue

@@ -1,14 +1,55 @@
 <script>
+import presenceManager from '@/utils/presence-manager.js';
+
 export default {
 	onLaunch: function () {
-
+		console.log('=== App启动 ===');
+		
+		// 初始化全局在线状态
+		this.initGlobalPresence();
 	},
+	
 	onShow: function () {
-
+		console.log('=== App显示 ===');
+		
+		// App从后台切换到前台时,重新连接
+		this.initGlobalPresence();
 	},
+	
 	onHide: function () {
-
+		console.log('=== App隐藏 ===');
+		// App切换到后台时,不断开连接,保持在线状态
+	},
+	
+	methods: {
+		/**
+		 * 初始化全局在线状态
+		 */
+		initGlobalPresence() {
+			try {
+				// 获取当前登录用户ID
+				const userInfo = uni.getStorageSync('userInfo');
+				const userId = uni.getStorageSync('userId') || userInfo?.userId || userInfo?.id;
+				
+				if (userId) {
+					console.log('🌐 初始化全局在线状态,用户ID:', userId);
+					
+					// 如果未连接,则连接
+					if (!presenceManager.getConnectionStatus()) {
+						presenceManager.connect(String(userId));
+						console.log('✅ 全局在线状态WebSocket已连接');
+					} else {
+						console.log('✅ 全局在线状态WebSocket已存在');
+					}
+				} else {
+					console.log('⚠️ 未登录,跳过在线状态初始化');
+				}
+			} catch (error) {
+				console.error('❌ 初始化全局在线状态失败:', error);
+			}
+		}
 	},
+	
 	globalData: {
 	
 	}

+ 37 - 29
LiangZhiYUMao/pages/message/chat.vue

@@ -545,7 +545,7 @@ export default {
         
         // 导入当前用户(确保userId是字符串)
         const currentUserRes = await uni.request({
-          url: 'http://localhost:1004/api/im/importUser',
+          url: 'http://localhost:8083/api/im/importUser',
           method: 'POST',
           data: {
             userId: String(this.userId),
@@ -560,7 +560,7 @@ export default {
         
         // 导入目标用户(确保userId是字符串)
         const targetUserRes = await uni.request({
-          url: 'http://localhost:1004/api/im/importUser',
+          url: 'http://localhost:8083/api/im/importUser',
           method: 'POST',
           data: {
             userId: String(this.targetUserId),
@@ -585,7 +585,7 @@ export default {
     async getUserSig() {
       try {
         const [err, res] = await uni.request({
-          url: 'http://localhost:1004/api/im/getUserSig',
+          url: 'http://localhost:8083/api/im/getUserSig',
           method: 'GET',
           data: {
             userId: this.userId
@@ -771,15 +771,16 @@ export default {
         
         // 通过WebSocket发送已读回执
         const message = {
-          type: 'read',  // 已读回执类型
+          type: 'READ_RECEIPT',  // 已读回执类型
           fromUserId: parseInt(this.userId),
           toUserId: parseInt(this.targetUserId),
           timestamp: Date.now()
         };
         
-        // 发送WebSocket消息
-        if (timManager.websocket && timManager.websocket.readyState === 1) {
-          timManager.websocket.send(JSON.stringify(message));
+        // 使用presenceManager的WebSocket发送
+        const presenceManager = require('@/utils/presence-manager.js').default;
+        if (presenceManager.isConnected) {
+          presenceManager.sendMessage(message);
           console.log('✅ 已读回执发送成功');
         } else {
           console.warn('⚠️ WebSocket未连接,无法发送已读回执');
@@ -1138,7 +1139,7 @@ export default {
         
         // 调用后端同步接口
         const res = await uni.request({
-          url: 'http://localhost:1004/api/chat/syncTIMMessage',
+          url: 'http://localhost:8083/api/chat/syncTIMMessage',
           method: 'POST',
           data: syncData,
           header: {
@@ -1540,26 +1541,20 @@ export default {
       console.log('✅ 已订阅对方用户状态');
       
       // 4. 监听已读回执(通过WebSocket)
-      if (presenceManager.websocket) {
-        const originalOnMessage = presenceManager.websocket.onmessage;
-        presenceManager.websocket.onmessage = (event) => {
-          // 先调用原有的消息处理
-          if (originalOnMessage) {
-            originalOnMessage.call(presenceManager.websocket, event);
-          }
-          
-          // 处理已读回执
-          try {
-            const data = JSON.parse(event.data);
-            if (data.type === 'read' && data.fromUserId == this.targetUserId) {
-              this.handleReadReceipt(data);
-            }
-          } catch (error) {
-            console.error('❌ 解析WebSocket消息失败:', error);
-          }
-        };
-        console.log('✅ 已监听已读回执');
+      // 注册已读回执的回调
+      this.readReceiptCallback = (data) => {
+        if (data.type === 'READ_RECEIPT' && data.fromUserId == this.targetUserId) {
+          console.log('📬 收到已读回执:', data);
+          this.handleReadReceipt(data);
+        }
+      };
+      
+      // 将回调添加到presenceManager的消息处理中
+      if (!presenceManager.messageCallbacks) {
+        presenceManager.messageCallbacks = [];
       }
+      presenceManager.messageCallbacks.push(this.readReceiptCallback);
+      console.log('✅ 已监听已读回执');
       
       // 4. 定期轮询在线状态(作为补充,每30秒查询一次)
       this.onlineStatusPollingTimer = setInterval(async () => {
@@ -1603,17 +1598,30 @@ export default {
     cleanupPresence() {
       console.log('🔌 清理在线状态监听');
       
+      const presenceManager = require('@/utils/presence-manager.js').default;
+      
       // 清除轮询定时器
       if (this.onlineStatusPollingTimer) {
         clearInterval(this.onlineStatusPollingTimer);
         this.onlineStatusPollingTimer = null;
       }
       
+      // 移除在线状态监听
       if (this.handleStatusChange) {
         presenceManager.offStatusChange(this.targetUserId, this.handleStatusChange);
         this.handleStatusChange = null;
       }
       
+      // 移除已读回执回调
+      if (this.readReceiptCallback && presenceManager.messageCallbacks) {
+        const index = presenceManager.messageCallbacks.indexOf(this.readReceiptCallback);
+        if (index > -1) {
+          presenceManager.messageCallbacks.splice(index, 1);
+          console.log('✅ 已移除已读回执监听');
+        }
+        this.readReceiptCallback = null;
+      }
+      
       // 注意:不要断开WebSocket连接,因为其他页面可能还在使用
       // presenceManager.disconnect();
     },
@@ -1937,7 +1945,7 @@ export default {
         
         // 使用uni.uploadFile上传到后端MinIO接口
         const [err, res] = await uni.uploadFile({
-          url: 'http://localhost:1004/api/voice/upload',
+          url: 'http://localhost:8083/api/voice/upload',
           filePath: this.voiceTempPath,
           name: 'file',
           header: {
@@ -2142,7 +2150,7 @@ export default {
 		    async getUserMessageLimit() {
 		      try {
 		        const [err, res] = await uni.request({
-		          url: 'http://localhost:1004/api/chat/getUserMessageLimit',
+		          url: 'http://localhost:8083/api/chat/getUserMessageLimit',
 		          method: 'GET',
 		          data: {
 		            userId: this.userId ,// 已在onLoad中初始化的当前用户ID

+ 141 - 5
LiangZhiYUMao/pages/message/index.vue

@@ -331,6 +331,9 @@ export default {
     // 监听刷新事件(从聊天页面返回时触发)
     uni.$on('refreshConversations', this.refreshConversations);
 	uni.$on('blacklistUpdated', this.handleBlacklistUpdate);
+	
+	// 初始化在线状态监听
+	this.initOnlineStatusListener();
   },
   
   onShow() {
@@ -378,6 +381,9 @@ export default {
     // 页面显示时刷新会话列表
     this.refreshConversations();
     this.loadSystemUnread();
+	
+	// 刷新在线状态
+	this.refreshOnlineStatus();
   },
   
   onUnload() {
@@ -389,7 +395,15 @@ export default {
     // 移除事件监听
     uni.$off('refreshConversations', this.refreshConversations);
 	// 移除拉黑状态变更监听
-	  uni.$off('blacklistUpdated', this.handleBlacklistUpdate);
+	uni.$off('blacklistUpdated', this.handleBlacklistUpdate);
+	
+	// 清理在线状态监听
+	const presenceManager = require('@/utils/presence-manager.js').default;
+	this.conversations.forEach(conv => {
+	  if (conv.onlineStatusCallback) {
+	    presenceManager.offStatusChange(conv.targetUserId, conv.onlineStatusCallback);
+	  }
+	});
   },
   
   methods: {
@@ -493,7 +507,7 @@ export default {
     async importUser() {
       try {
         await uni.request({
-          url: 'http://localhost:1004/api/im/importUser',
+          url: 'http://localhost:8083/api/im/importUser',
           method: 'POST',
           data: {
             userId: String(this.userId)
@@ -512,7 +526,7 @@ export default {
     async getUserSig() {
       try {
         const res = await uni.request({
-          url: `http://localhost:1004/api/im/getUserSig?userId=${this.userId}`,
+          url: `http://localhost:8083/api/im/getUserSig?userId=${this.userId}`,
           method: 'GET'
         });
         
@@ -722,8 +736,11 @@ export default {
             userMap[user.userId] = user;
           });
           
+          // 收集数据库中不存在的用户ID
+          const deletedUserIds = [];
+          
           // 更新会话列表中的头像和昵称
-          this.conversations.forEach(conv => {
+          for (const conv of this.conversations) {
             const userInfo = userMap[conv.targetUserId];
             if (userInfo) {
               // 更新头像(如果数据库中有头像)
@@ -739,8 +756,18 @@ export default {
                 昵称: conv.targetUserName,
                 头像: conv.targetUserAvatar
               });
+            } else {
+              // 数据库中不存在该用户,标记为需要删除
+              console.warn(`⚠️ 用户 ${conv.targetUserId} 在数据库中不存在`);
+              deletedUserIds.push(conv.targetUserId);
             }
-          });
+          }
+          
+          // 如果有不存在的用户,删除TIM中的会话和好友
+          if (deletedUserIds.length > 0) {
+            console.log(`🗑️ 发现 ${deletedUserIds.length} 个不存在的用户,开始清理TIM数据...`);
+            await this.cleanupDeletedUsers(deletedUserIds);
+          }
         } else {
           console.error('❌ 批量获取用户信息失败:', res[1].data);
         }
@@ -1191,6 +1218,115 @@ export default {
       });
     },
     
+    /**
+     * 初始化在线状态监听
+     */
+    initOnlineStatusListener() {
+      console.log('👂 初始化在线状态监听');
+      
+      const presenceManager = require('@/utils/presence-manager.js').default;
+      
+      // 监听所有用户的在线状态变化
+      this.conversations.forEach(conv => {
+        // 创建回调并保存引用
+        const callback = (status) => {
+          console.log(`👤 用户 ${conv.targetUserId} 状态变更: ${status}`);
+          
+          // 更新会话列表中的在线状态
+          const conversation = this.conversations.find(c => c.targetUserId === conv.targetUserId);
+          if (conversation) {
+            this.$set(conversation, 'isOnline', status === 'online');
+          }
+        };
+        
+        // 保存回调引用以便后续清理
+        conv.onlineStatusCallback = callback;
+        
+        // 注册监听
+        presenceManager.onStatusChange(conv.targetUserId, callback);
+      });
+    },
+    
+    /**
+     * 刷新在线状态
+     */
+    async refreshOnlineStatus() {
+      console.log('🔄 刷新在线状态');
+      
+      const presenceManager = require('@/utils/presence-manager.js').default;
+      
+      // 批量查询所有会话用户的在线状态
+      for (const conv of this.conversations) {
+        try {
+          const isOnline = await presenceManager.queryOnlineStatus(conv.targetUserId);
+          this.$set(conv, 'isOnline', isOnline);
+        } catch (error) {
+          console.error(`❌ 查询用户 ${conv.targetUserId} 在线状态失败:`, error);
+        }
+      }
+      
+      console.log('✅ 在线状态刷新完成');
+    },
+    
+    /**
+     * 清理已删除用户的TIM数据
+     * @param {Array} deletedUserIds 已删除的用户ID列表
+     */
+    async cleanupDeletedUsers(deletedUserIds) {
+      try {
+        console.log('🗑️ 开始清理已删除用户的TIM数据:', deletedUserIds);
+        
+        let successCount = 0;
+        let failCount = 0;
+        
+        for (const userId of deletedUserIds) {
+          try {
+            // 1. 删除TIM会话
+            const conversationID = `C2C${userId}`;
+            await timManager.tim.deleteConversation(conversationID);
+            console.log(`✅ 已删除会话: ${conversationID}`);
+            
+            // 2. 删除TIM好友关系
+            await timManager.tim.deleteFriend({
+              userIDList: [String(userId)],
+              type: timManager.tim.TYPES.SNS_DELETE_TYPE_BOTH // 双向删除
+            });
+            console.log(`✅ 已删除好友: ${userId}`);
+            
+            // 3. 从本地会话列表中移除
+            const index = this.conversations.findIndex(conv => conv.targetUserId === userId);
+            if (index > -1) {
+              this.conversations.splice(index, 1);
+              console.log(`✅ 已从本地列表移除: ${userId}`);
+            }
+            
+            successCount++;
+          } catch (error) {
+            console.error(`❌ 清理用户 ${userId} 失败:`, error);
+            failCount++;
+          }
+        }
+        
+        // 重新计算总未读数
+        const allUnreadCount = this.conversations.reduce((total, conv) => total + conv.unreadCount, 0);
+        this.totalUnread = allUnreadCount + this.systemUnread;
+        this.$store.dispatch('updateTotalUnread', this.totalUnread);
+        
+        console.log(`✅ TIM数据清理完成: 成功${successCount}个, 失败${failCount}个`);
+        
+        // 显示提示
+        if (successCount > 0) {
+          uni.showToast({
+            title: `已清理${successCount}个无效会话`,
+            icon: 'success',
+            duration: 2000
+          });
+        }
+      } catch (error) {
+        console.error('❌ 清理TIM数据失败:', error);
+      }
+    },
+    
     /**
      * 切换标签页
      */

+ 27 - 4
LiangZhiYUMao/pages/mine/index.vue

@@ -1070,7 +1070,30 @@
 				// 延迟执行,确保UI更新
 				setTimeout(() => {
 					try {
-						console.log('步骤1: 清除登录数据')
+						console.log('步骤1: 断开WebSocket连接')
+						// 断开在线状态WebSocket
+						try {
+							const presenceManager = require('@/utils/presence-manager.js').default;
+							if (presenceManager) {
+								presenceManager.disconnect();
+								console.log('✅ 在线状态WebSocket已断开');
+							}
+						} catch (error) {
+							console.error('❌ 断开在线状态WebSocket失败:', error);
+						}
+						
+						// 断开TIM WebSocket
+						try {
+							const timManager = require('@/utils/tim-manager.js').default;
+							if (timManager && timManager.isLogin) {
+								timManager.logout();
+								console.log('✅ TIM已登出');
+							}
+						} catch (error) {
+							console.error('❌ TIM登出失败:', error);
+						}
+						
+						console.log('步骤2: 清除登录数据')
 						
 						// 方法1: 逐个清除
 						uni.removeStorageSync('token')
@@ -1078,7 +1101,7 @@
 						uni.removeStorageSync('userId')
 						uni.removeStorageSync('rememberedAccount')
 						
-						console.log('步骤2: 验证数据清除')
+						console.log('步骤3: 验证数据清除')
 						const remainToken = uni.getStorageSync('token')
 						const remainUser = uni.getStorageSync('userInfo')
 						console.log('清除后检查 - token:', remainToken, 'userInfo:', remainUser)
@@ -1089,7 +1112,7 @@
 							uni.clearStorageSync()
 						}
 						
-						console.log('步骤3: 重置页面状态')
+						console.log('步骤4: 重置页面状态')
 						this.currentUserId = null
 						this.userInfo = {
 							userId: null,
@@ -1107,7 +1130,7 @@
 						
 						// 延迟跳转
 						setTimeout(() => {
-							console.log('步骤4: 跳转到登录页面')
+							console.log('步骤5: 跳转到登录页面')
 							uni.reLaunch({
 								url: '/pages/page3/page3'
 							})

+ 2 - 2
LiangZhiYUMao/pages/recommend/index.vue

@@ -791,7 +791,7 @@ export default {
 
 				// 导入当前用户
 				await uni.request({
-					url: 'http://localhost:1004/api/im/importUser',
+					url: 'http://localhost:8083/api/im/importUser',
 					method: 'POST',
 					data: {
 						userId: String(currentUserId),
@@ -804,7 +804,7 @@ export default {
 
 				// 导入目标用户
 				await uni.request({
-					url: 'http://localhost:1004/api/im/importUser',
+					url: 'http://localhost:8083/api/im/importUser',
 					method: 'POST',
 					data: {
 						userId: String(targetUserId),

+ 24 - 2
LiangZhiYUMao/pages/settings/index.vue

@@ -172,10 +172,32 @@
 					content: '确定要退出登录吗?',
 					success: (res) => {
 						if (res.confirm) {
-							// 清除登录信息
+							// 1. 断开WebSocket连接
+							try {
+								const presenceManager = require('@/utils/presence-manager.js').default;
+								if (presenceManager) {
+									presenceManager.disconnect();
+									console.log('✅ 在线状态WebSocket已断开');
+								}
+							} catch (error) {
+								console.error('❌ 断开在线状态WebSocket失败:', error);
+							}
+							
+							// 2. 断开TIM WebSocket
+							try {
+								const timManager = require('@/utils/tim-manager.js').default;
+								if (timManager && timManager.isLogin) {
+									timManager.logout();
+									console.log('✅ TIM已登出');
+								}
+							} catch (error) {
+								console.error('❌ TIM登出失败:', error);
+							}
+							
+							// 3. 清除登录信息
 							uni.clearStorageSync()
 							
-							// 跳转到登录页
+							// 4. 跳转到登录页
 							uni.reLaunch({
 								url: '/pages/page3/page3'
 							})

+ 1 - 1
LiangZhiYUMao/utils/chat-api.js

@@ -2,7 +2,7 @@
  * 聊天相关API接口封装
  */
 
-const CHAT_BASE_URL = 'http://localhost:1004/api/chat';
+const CHAT_BASE_URL = 'http://localhost:8083/api/chat';
 
 /**
  * 统一的聊天请求封装

+ 14 - 3
LiangZhiYUMao/utils/presence-manager.js

@@ -14,10 +14,11 @@ class PresenceManager {
     this.userId = null;
     this.statusCallbacks = new Map(); // 存储状态变化回调
     this.onlineStatusCache = new Map(); // 缓存在线状态
+    this.messageCallbacks = []; // 存储消息回调(用于已读回执等)
     
     // WebSocket服务器地址(使用实际的聊天WebSocket服务)
-    this.wsUrl = 'ws://localhost:1004/ws/chat';
-    this.httpUrl = 'http://localhost:1004/api/online'; // HTTP API地址
+    this.wsUrl = 'ws://localhost:8083/ws/chat';
+    this.httpUrl = 'http://localhost:8083/api/online'; // HTTP API地址
   }
   
   /**
@@ -116,7 +117,16 @@ class PresenceManager {
           break;
           
         default:
-          // 忽略其他消息类型(如聊天消息等)
+          // 其他消息类型,调用注册的回调
+          if (this.messageCallbacks && this.messageCallbacks.length > 0) {
+            this.messageCallbacks.forEach(callback => {
+              try {
+                callback(message);
+              } catch (err) {
+                console.error('❌ 消息回调执行失败:', err);
+              }
+            });
+          }
           break;
       }
     } catch (error) {
@@ -337,6 +347,7 @@ class PresenceManager {
     }
     
     this.statusCallbacks.clear();
+    this.messageCallbacks = []; // 清空消息回调
     this.userId = null;
   }
   

+ 1 - 1
LiangZhiYUMao/utils/tim-manager.js

@@ -181,7 +181,7 @@ class TIMManager {
       
       // 调用后端同步接口
       const res = await uni.request({
-        url: 'http://localhost:1004/api/chat/syncTIMMessage',
+        url: 'http://localhost:8083/api/chat/syncTIMMessage',
         method: 'POST',
         data: syncData,
         header: {

+ 1 - 1
LiangZhiYUMao/utils/websocket.js

@@ -36,7 +36,7 @@ class WebSocketManager {
    * @param {Number} userId 用户ID
    * @param {String} baseUrl WebSocket服务地址
    */
-  connect(userId, baseUrl = 'ws://localhost:1004') {
+  connect(userId, baseUrl = 'ws://localhost:8083') {
     if (this.isConnected) {
       console.log('WebSocket已连接');
       return;

+ 32 - 0
gateway/src/main/resources/application.yml

@@ -38,6 +38,38 @@ spring:
           filters:
             - StripPrefix=0
         
+        # 在线状态API路由
+        - id: online-api-route
+          uri: http://localhost:1004
+          predicates:
+            - Path=/api/online/**
+          filters:
+            - StripPrefix=0
+        
+        # IM服务路由(腾讯云IM相关)
+        - id: im-api-route
+          uri: http://localhost:1004
+          predicates:
+            - Path=/api/im/**
+          filters:
+            - StripPrefix=0
+        
+        # 好友服务路由(拉黑等功能)
+        - id: chatfriend-api-route
+          uri: http://localhost:1004
+          predicates:
+            - Path=/api/chatfriend/**
+          filters:
+            - StripPrefix=0
+        
+        # 语音上传服务路由
+        - id: voice-api-route
+          uri: http://localhost:1004
+          predicates:
+            - Path=/api/voice/**
+          filters:
+            - StripPrefix=0
+        
         # 聊天REST API路由
         - id: chat-api-route
           uri: http://localhost:1004