Kaynağa Gözat

Merge remote-tracking branch 'origin/test_dev' into test_dev

yuxy 1 ay önce
ebeveyn
işleme
ba84af6ba7
22 değiştirilmiş dosya ile 1087 ekleme ve 364 silme
  1. 295 86
      LiangZhiYUMao/pages/matchmaker-workbench/message.vue
  2. 26 3
      LiangZhiYUMao/pages/matchmakers/detail.vue
  3. 153 187
      LiangZhiYUMao/pages/message/chat.vue
  4. 91 59
      LiangZhiYUMao/pages/message/index.vue
  5. 14 0
      LiangZhiYUMao/utils/tim-manager.js
  6. 1 1
      gateway/src/main/java/com/zhentao/filter/GatewayRoutes.java
  7. 98 11
      service/admin/src/main/java/com/zhentao/service/impl/MarrApplyServiceImpl.java
  8. 4 1
      service/homePage/src/main/java/com/zhentao/constant/RedisKeyConstants.java
  9. 31 3
      service/homePage/src/main/java/com/zhentao/controller/ActivityController.java
  10. 177 0
      service/homePage/src/main/java/com/zhentao/controller/MatchmakerController.java
  11. 8 0
      service/homePage/src/main/java/com/zhentao/mapper/MatchmakerMapper.java
  12. 15 1
      service/homePage/src/main/java/com/zhentao/service/ActivityService.java
  13. 16 0
      service/homePage/src/main/java/com/zhentao/service/MatchmakerService.java
  14. 39 1
      service/homePage/src/main/java/com/zhentao/service/impl/ActivityServiceImpl.java
  15. 26 1
      service/homePage/src/main/java/com/zhentao/service/impl/MatchmakerServiceImpl.java
  16. 33 0
      service/homePage/src/main/resources/mapper/MatchmakerMapper.xml
  17. 12 1
      service/websocket/src/main/java/com/zhentao/WebSocketApplication.java
  18. 17 0
      service/websocket/src/main/java/com/zhentao/config/MinioConfig.java
  19. 5 0
      service/websocket/src/main/java/com/zhentao/controller/ChatController.java
  20. 21 4
      service/websocket/src/main/java/com/zhentao/controller/OnlineStatusController.java
  21. 2 2
      service/websocket/src/main/java/com/zhentao/controller/TIMController.java
  22. 3 3
      service/websocket/src/main/resources/application.yml

+ 295 - 86
LiangZhiYUMao/pages/matchmaker-workbench/message.vue

@@ -8,67 +8,43 @@
 		</view>
 
 		<scroll-view scroll-y class="content">
-			<!-- 系统通知 -->
-			<view class="message-item system-notification active">
-				<text class="message-type">系统通知</text>
-				<text class="message-time">刚刚</text>
-				<text class="message-content">您的线索审核已通过,获得20积分奖励,当前积分可兑换【资源查看权限】x1</text>
-				<text class="message-footer">完成线索采集,积分+20 (距黄金级还差72分)</text>
+			<!-- 加载中 -->
+			<view v-if="loading" class="loading-container">
+				<text>加载中...</text>
 			</view>
-
-			<!-- 撮合成功通知 -->
-			<view class="message-item match-success">
-				<view class="message-icon heart"></view>
-				<view class="message-body">
-					<text class="message-type">撮合成功通知</text>
-					<text class="message-content">您推荐的王先生和刘女士已成功匹配,获得50积分+100元现金奖励</text>
-				</view>
-				<text class="message-time">10分钟前</text>
-			</view>
-
-			<!-- 今天分组 -->
-			<view class="time-group">
-				<text class="time-label">今天</text>
-			</view>
-
-			<!-- 魏先生消息 -->
-			<view class="message-item user-message">
-				<view class="message-avatar">魏</view>
-				<view class="message-body">
-					<text class="message-type">魏先生</text>
-					<text class="message-content">您好,想了解一下李女士的详细情况,方便沟通吗?</text>
+			
+			<!-- 会话列表 -->
+			<view v-else-if="conversationList.length > 0">
+				<view 
+					v-for="conversation in conversationList" 
+					:key="conversation.conversationID"
+					class="message-item user-message"
+					@click="openChat(conversation)"
+				>
+					<image 
+						v-if="conversation.userProfile.avatar" 
+						:src="conversation.userProfile.avatar" 
+						class="message-avatar-img"
+					/>
+					<view v-else class="message-avatar">
+						{{ conversation.userProfile.nick || conversation.userProfile.userID.charAt(0) }}
+					</view>
+					<view class="message-body">
+						<text class="message-type">{{ conversation.userProfile.nick || conversation.userProfile.userID }}</text>
+						<text class="message-content">{{ getLastMessageText(conversation.lastMessage) }}</text>
+					</view>
+					<view class="message-right">
+						<text class="message-time">{{ formatTime(conversation.lastMessage.lastTime) }}</text>
+						<view v-if="conversation.unreadCount > 0" class="unread-badge">
+							{{ conversation.unreadCount > 99 ? '99+' : conversation.unreadCount }}
+						</view>
+					</view>
 				</view>
-				<text class="message-time">1小时前</text>
-			</view>
-
-			<!-- 昨日分组 -->
-			<view class="time-group">
-				<text class="time-label">昨日</text>
 			</view>
-
-			<!-- 张先生消息 -->
-			<view class="message-item user-message">
-				<view class="message-avatar">张</view>
-				<view class="message-body">
-					<text class="message-type">张先生</text>
-					<text class="message-content">您好,想了解一下王女士的详细情况,方便沟通吗?</text>
-				</view>
-				<text class="message-time">1小时前</text>
-			</view>
-
-			<!-- 更早分组 -->
-			<view class="time-group">
-				<text class="time-label">更早</text>
-			</view>
-
-			<!-- 高先生消息 -->
-			<view class="message-item user-message">
-				<view class="message-avatar">高</view>
-				<view class="message-body">
-					<text class="message-type">高先生</text>
-					<text class="message-content">您好,想了解一下陈女士的详细情况,方便沟通吗?</text>
-				</view>
-				<text class="message-time">1小时前</text>
+			
+			<!-- 空状态 -->
+			<view v-else class="empty-container">
+				<text>暂无消息</text>
 			</view>
 		</scroll-view>
 
@@ -101,65 +77,256 @@
 </template>
 
 <script>
+	import timManager from '@/utils/tim-manager.js'
+	import TIM from 'tim-wx-sdk'
+	
 	export default {
 		data() {
 			return {
-				messages: [
-					{
-						id: 1,
-						type: 'system',
-						title: '系统通知',
-						content: '您的线索审核已通过,获得20积分奖励,当前积分可兑换【资源查看权限】x1',
-						footer: '完成线索采集,积分+20 (距黄金级还差72分)',
-						time: '刚刚',
-						isNew: true
-					},
-					{
-						id: 2,
-						type: 'matchSuccess',
-						title: '撮合成功通知',
-						content: '您推荐的王先生和刘女士已成功匹配,获得50积分+100元现金奖励',
-						time: '10分钟前',
-						isNew: false
-					},
-					{
-						id: 3,
-						type: 'user',
-						title: '王先生',
-						content: '您好,想了解一下李女士的详细情况,方便沟通吗?',
-						time: '1小时前',
-						isNew: false
-					}
-				]
+				loading: true,
+				conversationList: [],
+				matchmakerInfo: null,
+				imUserId: '',
+				totalUnreadCount: 0
+			}
+		},
+		
+		async onLoad() {
+			await this.initIM()
+		},
+		
+		onShow() {
+			// 页面显示时刷新会话列表
+			if (this.imUserId) {
+				this.loadConversationList()
 			}
 		},
+		
+		onUnload() {
+			// 页面卸载时移除监听
+			this.removeListeners()
+		},
+		
 		methods: {
+			// 初始化 IM
+			async initIM() {
+				try {
+					this.loading = true
+					
+					// 1. 获取当前登录用户ID
+					const userId = uni.getStorageSync('userId')
+					if (!userId) {
+						uni.showToast({ title: '请先登录', icon: 'none' })
+						return
+					}
+					
+					// 2. 获取红娘信息
+					const res = await uni.request({
+						url: 'http://localhost:8081/api/matchmaker/current',
+						method: 'GET',
+						data: { userId }
+					})
+					
+					if (res[1].data.code !== 200) {
+						uni.showToast({ title: '获取红娘信息失败', icon: 'none' })
+						return
+					}
+					
+					this.matchmakerInfo = res[1].data.data
+					this.imUserId = this.matchmakerInfo.imUserId  // m_1
+					
+					console.log('✅ 红娘信息:', this.matchmakerInfo)
+					
+					// 3. 获取 UserSig
+					const sigRes = await uni.request({
+						url: `http://localhost:8083/api/im/getUserSig?userId=${this.imUserId}`,
+						method: 'GET'
+					})
+					
+					if (sigRes[1].data.code !== 200) {
+						uni.showToast({ title: '获取UserSig失败', icon: 'none' })
+						return
+					}
+					
+					const userSig = sigRes[1].data.data.userSig
+					console.log('✅ 获取到 UserSig:', userSig ? '成功' : '失败')
+					
+					// 4. 先登出当前账号(如果已登录)
+					try {
+						await timManager.logout()
+						console.log('✅ 已登出之前的账号')
+					} catch (e) {
+						console.log('⚠️ 登出失败或未登录:', e.message)
+					}
+					
+					// 5. 以红娘身份登录 IM
+					await timManager.login(this.imUserId, userSig)
+					console.log('✅ 红娘已登录 IM:', this.imUserId)
+					
+					// 6. 监听事件
+					this.addListeners()
+					
+					// 7. 加载会话列表
+					// 6. 加载会话列表
+					await this.loadConversationList()
+					
+				} catch (error) {
+					console.error('❌ 初始化 IM 失败:', error)
+					uni.showToast({ title: '初始化失败', icon: 'none' })
+				} finally {
+					this.loading = false
+				}
+			},
+			
+			// 加载会话列表
+			async loadConversationList() {
+				try {
+					const tim = timManager.getTim()
+					const { data } = await tim.getConversationList()
+					this.conversationList = data.conversationList
+					
+					// 计算总未读数
+					this.totalUnreadCount = this.conversationList.reduce((total, conv) => {
+						return total + conv.unreadCount
+					}, 0)
+					
+					console.log('✅ 会话列表:', this.conversationList)
+				} catch (error) {
+					console.error('❌ 加载会话列表失败:', error)
+				}
+			},
+			
+			// 添加事件监听
+			addListeners() {
+				const tim = timManager.getTim()
+				
+				// 监听新消息
+				tim.on(TIM.EVENT.MESSAGE_RECEIVED, this.onMessageReceived)
+				
+				// 监听会话列表更新
+				tim.on(TIM.EVENT.CONVERSATION_LIST_UPDATED, this.onConversationListUpdated)
+			},
+			
+			// 移除事件监听
+			removeListeners() {
+				const tim = timManager.getTim()
+				tim.off(TIM.EVENT.MESSAGE_RECEIVED, this.onMessageReceived)
+				tim.off(TIM.EVENT.CONVERSATION_LIST_UPDATED, this.onConversationListUpdated)
+			},
+			
+			// 收到新消息
+			onMessageReceived(event) {
+				console.log('📩 收到新消息:', event.data)
+				// 刷新会话列表
+				this.loadConversationList()
+			},
+			
+			// 会话列表更新
+			onConversationListUpdated(event) {
+				console.log('🔄 会话列表更新:', event.data)
+				this.conversationList = event.data
+				
+				// 更新总未读数
+				this.totalUnreadCount = this.conversationList.reduce((total, conv) => {
+					return total + conv.unreadCount
+				}, 0)
+			},
+			
+			// 打开聊天页面
+			openChat(conversation) {
+				const targetUserId = conversation.userProfile.userID
+				const targetUserName = encodeURIComponent(conversation.userProfile.nick || `用户${targetUserId}`)
+				const targetUserAvatar = encodeURIComponent(conversation.userProfile.avatar || '')
+				uni.navigateTo({
+					url: `/pages/message/chat?targetUserId=${targetUserId}&targetUserName=${targetUserName}&targetUserAvatar=${targetUserAvatar}&fromMatchmaker=true`
+				})
+			},
+			
+			// 获取最后一条消息的文本
+			getLastMessageText(lastMessage) {
+				if (!lastMessage) return ''
+				
+				switch (lastMessage.type) {
+					case TIM.TYPES.MSG_TEXT:
+						return lastMessage.payload.text
+					case TIM.TYPES.MSG_IMAGE:
+						return '[图片]'
+					case TIM.TYPES.MSG_AUDIO:
+						return '[语音]'
+					case TIM.TYPES.MSG_VIDEO:
+						return '[视频]'
+					case TIM.TYPES.MSG_FILE:
+						return '[文件]'
+					default:
+						return '[消息]'
+				}
+			},
+			
+			// 格式化时间
+			formatTime(timestamp) {
+				if (!timestamp) return ''
+				
+				const now = new Date()
+				const msgTime = new Date(timestamp * 1000)
+				const diff = now - msgTime
+				
+				// 一分钟内
+				if (diff < 60000) {
+					return '刚刚'
+				}
+				
+				// 一小时内
+				if (diff < 3600000) {
+					return Math.floor(diff / 60000) + '分钟前'
+				}
+				
+				// 今天
+				if (msgTime.toDateString() === now.toDateString()) {
+					return msgTime.getHours() + ':' + String(msgTime.getMinutes()).padStart(2, '0')
+				}
+				
+				// 昨天
+				const yesterday = new Date(now)
+				yesterday.setDate(yesterday.getDate() - 1)
+				if (msgTime.toDateString() === yesterday.toDateString()) {
+					return '昨天'
+				}
+				
+				// 更早
+				return (msgTime.getMonth() + 1) + '-' + msgTime.getDate()
+			},
+			
 			// 返回上一页
 			goBack() {
 				uni.navigateBack()
 			},
+			
 			// 导航到工作台
 			navigateToWorkbench() {
 				uni.redirectTo({
 					url: '/pages/matchmaker-workbench/index'
 				})
 			},
+			
 			// 导航到我的资源
 			navigateToMyResources() {
 				uni.redirectTo({
 					url: '/pages/matchmaker-workbench/my-resources'
 				})
 			},
+			
 			// 导航到排行榜
 			navigateToRanking() {
 				uni.redirectTo({
 					url: '/pages/matchmaker-workbench/ranking'
 				})
 			},
+			
 			// 导航到消息
 			navigateToMessage() {
 				// 已在消息页面,无需跳转
 			},
+			
 			// 导航到我的
 			navigateToMine() {
 				uni.redirectTo({
@@ -317,6 +484,7 @@
 			display: flex;
 			background: #FFFFFF;
 			border: 2rpx solid #E0E0E0;
+			align-items: center;
 
 			.message-avatar {
 				width: 60rpx;
@@ -330,10 +498,20 @@
 				align-items: center;
 				justify-content: center;
 				margin-right: 20rpx;
+				flex-shrink: 0;
+			}
+			
+			.message-avatar-img {
+				width: 60rpx;
+				height: 60rpx;
+				border-radius: 50%;
+				margin-right: 20rpx;
+				flex-shrink: 0;
 			}
 
 			.message-body {
 				flex: 1;
+				min-width: 0;
 			}
 
 			.message-type {
@@ -349,15 +527,46 @@
 				font-size: 26rpx;
 				color: #666;
 				line-height: 1.4;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+			}
+			
+			.message-right {
+				display: flex;
+				flex-direction: column;
+				align-items: flex-end;
+				margin-left: 20rpx;
 			}
 
 			.message-time {
 				font-size: 24rpx;
 				color: #999;
-				margin-top: auto;
+				margin-bottom: 10rpx;
+			}
+			
+			.unread-badge {
+				background: #E91E63;
+				color: #FFFFFF;
+				font-size: 20rpx;
+				padding: 4rpx 10rpx;
+				border-radius: 20rpx;
+				min-width: 36rpx;
+				text-align: center;
 			}
 		}
 	}
+	
+	/* 加载和空状态 */
+	.loading-container,
+	.empty-container {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		padding: 100rpx 0;
+		color: #999;
+		font-size: 28rpx;
+	}
 
 	/* 时间分组 */
 	.time-group {

+ 26 - 3
LiangZhiYUMao/pages/matchmakers/detail.vue

@@ -306,9 +306,32 @@ export default {
 		 * 在线咨询
 		 */
 		consultMatchmaker() {
-			uni.showToast({
-				title: '功能开发中',
-				icon: 'none'
+			if (!this.matchmaker) {
+				uni.showToast({
+					title: '红娘信息未加载',
+					icon: 'none'
+				})
+				return
+			}
+			
+			// 目标用户信息(红娘)
+			// 给红娘 ID 加上 m_ 前缀,避免与普通用户 ID 冲突
+			const matchmakerId = this.matchmaker.id || this.matchmakerId
+			const targetUserId = 'm_' + matchmakerId
+			const targetUserName = this.matchmaker.real_name || '红娘'
+			const targetUserAvatar = this.getAvatarUrl(this.matchmaker.avatar_url)
+			
+			if (!targetUserId) {
+				uni.showToast({
+					title: '缺少红娘ID',
+					icon: 'none'
+				})
+				return
+			}
+			
+			// 跳转到聊天页面,携带对方用户信息和红娘标记
+			uni.navigateTo({
+				url: `/pages/message/chat?targetUserId=${encodeURIComponent(targetUserId)}&targetUserName=${encodeURIComponent(targetUserName)}&targetUserAvatar=${encodeURIComponent(targetUserAvatar)}&fromMatchmaker=1`
 			})
 		},
 

+ 153 - 187
LiangZhiYUMao/pages/message/chat.vue

@@ -162,8 +162,8 @@
         </view>
       </view>
     </scroll-view>
-		<!-- 新增:消息发送限制提示 -->
-		<view class="message-limit-tip">
+		<!-- 新增:消息发送限制提示(双方都不是红娘,且不是从红娘工作台进入时才显示) -->
+		<view class="message-limit-tip" v-if="!(String(userId).startsWith('m_') || String(targetUserId).startsWith('m_') || fromMatchmaker)">
 		  <!-- 同时判断isVip和hasMessageLimit,增强可靠性     -->
 		  <text v-if="isVip || !hasMessageLimit" class="vip-tip">✨ VIP特权:无发送次数限制</text>
 		  <text v-else class="limit-tip">
@@ -307,6 +307,7 @@ export default {
 		isVip: false, // 是否VIP用户
 	    remainingCount: 5, // 剩余可发送消息数(非VIP默认5)
 	    hasMessageLimit: true, // 是否有发送限制(VIP为false)
+	    fromMatchmaker: false, // 是否来自红娘详情页的会话(用户与红娘聊天)
 	    
 	    // 语音录制相关
 	    recorderManager: null, // 录音管理器
@@ -362,27 +363,42 @@ export default {
       rawUserId = parseInt(rawUserId);
     }
     
-    if (!rawUserId || isNaN(rawUserId)) {
-      console.error('❌ 无法获取有效的用户ID');
-      uni.showModal({
-        title: '用户信息错误',
-        content: '无法获取用户ID,请重新登录',
-        showCancel: false,
-        success: () => {
-          uni.removeStorageSync('token');
-          uni.removeStorageSync('userInfo');
-          uni.removeStorageSync('userId');
-          uni.reLaunch({
-            url: '/pages/page3/page3'
-          });
-        }
-      });
-      return;
+    // 标记是否为用户与红娘的聊天,用于跳过每日5条限制和文本审核
+    // 支持两种格式:'1' 或 'true'
+    this.fromMatchmaker = options.fromMatchmaker === '1' || options.fromMatchmaker === 'true';
+    
+    // 根据入口来源确定当前会话中的“自己”是谁
+    if (this.fromMatchmaker) {
+      // 红娘工作台入口:使用当前 TIM 登录账号作为 userId(例如 m_22)
+      const imUserId = timManager.getCurrentUserId();
+      if (!imUserId) {
+        console.error('❌ TIM 未登录,无法获取红娘IM账号');
+        return;
+      }
+      this.userId = String(imUserId);
+    } else {
+      // 普通用户入口:仍然使用本地存储的 userId
+      if (!rawUserId || isNaN(rawUserId)) {
+        console.error('❌ 无法获取有效的用户ID');
+        uni.showModal({
+          title: '用户信息错误',
+          content: '无法获取用户ID,请重新登录',
+          showCancel: false,
+          success: () => {
+            uni.removeStorageSync('token');
+            uni.removeStorageSync('userInfo');
+            uni.removeStorageSync('userId');
+            uni.reLaunch({
+              url: '/pages/page3/page3'
+            });
+          }
+        });
+        return;
+      }
+      this.userId = String(rawUserId);
     }
-	
     
-    // 保存用户ID(TIM需要字符串格式)
-    this.userId = String(rawUserId);
+    // 头像:默认先用用户信息中的头像(红娘入口会被 loadMatchmakerAvatarByImId 覆盖)
     this.userAvatar = userInfo.avatar || userInfo.avatarUrl || '/static/default-avatar.svg';
     
     // 获取对方用户信息(确保是字符串格式)
@@ -394,125 +410,56 @@ export default {
     this.conversationID = `C2C${this.targetUserId}`;
     
     console.log('✅ 聊天页面初始化成功:');
-    console.log('   - 当前用户ID:', rawUserId, '(TIM格式:', this.userId, ')');
+    console.log('   - 当前用户ID:', this.userId);
     console.log('   - 对方用户ID:', this.targetUserId);
     console.log('   - 会话ID:', this.conversationID);
-    
+
     // 初始化 TIM
     await this.initTIM();
-	// 获取用户消息发送限制(VIP状态+剩余次数)
+
+    // 获取用户消息发送限制(VIP 状态 + 剩余次数)
     await this.getUserMessageLimit();
+
     // 等待 SDK Ready 后再加载消息
     await this.waitForSDKReady();
+
     // 先检查拉黑状态
-      this.isBlockedByTarget = await this.checkIsBlockedByTarget();
-      if (this.isBlockedByTarget) {
-        uni.showToast({
-          title: '你已被对方拉黑,无法发送消息',
-          icon: 'none',
-          duration: 3000
-        });
-      }
+    this.isBlockedByTarget = await this.checkIsBlockedByTarget();
+    if (this.isBlockedByTarget) {
+      uni.showToast({
+        title: '你已被对方拉黑,无法发送消息',
+        icon: 'none',
+        duration: 3000
+      });
+    }
+
     // 加载历史消息
     await this.loadMessages();
-    
+
     // 监听新消息
     this.listenMessages();
-    
-    // 监听已读回执(确保在 SDK Ready 之后调用)
-    this.listenMessageReadReceipt();
-    
+
+    // 监听已读回执
+    this.listenMessageReadReceipt && this.listenMessageReadReceipt();
+
     // 标记当前会话的消息为已读
-    this.markConversationRead();
-    
-    // 初始化在线状态监听(HTTP 轮询方式)
-    this.initOnlineStatusPolling();
-    
+    this.markConversationRead && this.markConversationRead();
+
+    // 初始化在线状态轮询
+    this.initOnlineStatusPolling && this.initOnlineStatusPolling();
+
     // 如果有预设消息,自动发送
     if (options.message) {
       const message = decodeURIComponent(options.message);
       console.log('检测到预设消息,准备自动发送:', message);
-      // 等待一下再发送,确保TIM已经初始化完成
       setTimeout(() => {
         this.inputText = message;
         this.sendTextMessage();
       }, 1500);
     }
   },
-  
-  /**
-   * 页面显示时(从其他页面返回)
-   */
-  async onShow() {
-    console.log('=== 聊天页面显示 ===');
-    
-    // 🔥 重新查询会话的 peerReadTime,更新已读状态
-    // 这样即使 A 离开页面期间 B 阅读了消息,A 返回时也能看到最新的已读状态
-    if (this.conversationID && timManager.tim) {
-      try {
-        console.log('🔄 重新查询会话已读状态...');
-        const conversationRes = await timManager.tim.getConversationProfile(this.conversationID);
-        
-        if (conversationRes && conversationRes.data && conversationRes.data.conversation) {
-          const peerReadTime = conversationRes.data.conversation.peerReadTime;
-          console.log('   - 对方最后阅读时间:', peerReadTime, peerReadTime > 0 ? new Date(peerReadTime * 1000).toLocaleString() : '未读');
-          
-          if (peerReadTime && peerReadTime > 0) {
-            let updatedCount = 0;
-            
-            // 更新所有发送时间 <= peerReadTime 的消息为已读
-            this.messages.forEach((msg, index) => {
-              if (msg.fromUserId === this.userId && !msg.isPeerRead && msg.sendStatus !== 4) {
-                const msgTime = Math.floor(msg.sendTime.getTime() / 1000);
-                
-                if (msgTime <= peerReadTime) {
-                  this.$set(this.messages[index], 'isPeerRead', true);
-                  updatedCount++;
-                  console.log(`   - 消息 ${msg.messageId} 已标记为已读`);
-                }
-              }
-            });
-            
-            console.log(`✅ 页面显示时更新了 ${updatedCount} 条消息为已读状态`);
-          }
-        }
-      } catch (error) {
-        console.error('❌ 重新查询会话已读状态失败:', error);
-      }
-    }
-    
-    // 重新标记当前会话为已读(通知对方)
-    this.markConversationRead();
-  },
-  
-  onUnload() {
-    // 页面卸载时移除监听
-    timManager.offMessage(this.handleNewMessage);
-    
-    // 移除消息状态变更监听(已注释)
-    /*
-    if (timManager.tim && this.handleStatusChange) {
-      timManager.tim.off(TIM.EVENT.MESSAGE_STATUS_CHANGED, this.handleStatusChange);
-    }
-    */
-  },
-  
+
   methods: {
-    /**
-     * 计算语音消息宽度(根据时长动态变化)
-     * @param {Number} duration 语音时长(秒)
-     * @return {String} 宽度值
-     */
-    getVoiceWidth(duration) {
-      // 基础宽度 100rpx,每秒增加 10rpx,最大 400rpx
-      const baseWidth = 100;
-      const widthPerSecond = 10;
-      const maxWidth = 400;
-      
-      const width = Math.min(baseWidth + (duration * widthPerSecond), maxWidth);
-      return width + 'rpx';
-    },
-    
     /**
      * 初始化 TIM
      */
@@ -520,19 +467,19 @@ export default {
       try {
         // 如果未初始化,先初始化
         if (!timManager.tim) {
-          timManager.init(1600109674);  // ✅ 已更新为正确的 SDKAppID
+          timManager.init(1600109674); // 使用正确的 SDKAppID
         }
-        
+
         // 如果未登录,获取 userSig 并登录
         if (!timManager.isLogin) {
-          // 先导入当前用户和目标用户到腾讯云IM
+          // 先导入当前用户和目标用户到腾讯云 IM
           await this.importUsers();
-          
+
           // 从后端获取 userSig
           const userSig = await this.getUserSig();
           await timManager.login(this.userId, userSig);
         }
-        
+
         this.isLogin = true;
         console.log('✅ TIM 初始化完成');
       } catch (error) {
@@ -543,26 +490,27 @@ export default {
         });
       }
     },
-	/**
-	   * 跳转到VIP页面
-	   */
-	  goToVipPage() {
-	    console.log('点击跳转VIP页面');
-	    // 替换为你的实际VIP页面路径(例如会员开通页面)
-	    uni.navigateTo({
-	      url: '/pages/vip/index', // 请根据项目实际路径修改
-	      success: () => {
-	        console.log('跳转VIP页面成功');
-	      },
-	      fail: (err) => {
-	        console.error('跳转VIP页面失败:', err);
-	        uni.showToast({
-	          title: 'VIP页面不存在',
-	          icon: 'none'
-	        });
-	      }
-	    });
-	  },
+
+    /**
+     * 跳转到VIP页面
+     */
+    goToVipPage() {
+      console.log('点击跳转VIP页面');
+      // 替换为你的实际VIP页面路径(例如会员开通页面)
+      uni.navigateTo({
+        url: '/pages/vip/index', // 请根据项目实际路径修改
+        success: () => {
+          console.log('跳转VIP页面成功');
+        },
+        fail: (err) => {
+          console.error('跳转VIP页面失败:', err);
+          uni.showToast({
+            title: 'VIP页面不存在',
+            icon: 'none'
+          });
+        }
+      });
+    },
     more(userid) {
       console.log('点击了更多按钮,开始跳转...', userid);
       // 如果目标页面是普通页面,用navigateTo(保留当前页面);如果是tabbar页面,用switchTab
@@ -599,7 +547,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),
@@ -614,7 +562,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),
@@ -639,7 +587,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
@@ -1095,8 +1043,11 @@ export default {
      * 发送文本消息
      */
     async sendTextMessage() {
+		  // 判断是否为涉及红娘的会话:任一方ID以 m_ 开头,或来自红娘工作台
+		  const isMatchmakerChat = this.fromMatchmaker || String(this.userId).startsWith('m_') || String(this.targetUserId).startsWith('m_');
 		
-		if (this.hasMessageLimit && this.remainingCount <= 0) {
+		  // 仅普通用户之间的会话才做消息次数限制
+		  if (!isMatchmakerChat && this.hasMessageLimit && this.remainingCount <= 0) {
 		    uni.showToast({
 		      title: '今日消息发送次数已用完,开通VIP无限制',
 		      icon: 'none',
@@ -1112,48 +1063,52 @@ export default {
       const content = this.inputText;
       this.inputText = '';
       
-      // 1. 先进行消息内容审核
-      try {
-        const checkRes = await uni.request({
-          url: 'http://localhost:1004/api/chat/checkMessage',
-          method: 'POST',
-          data: {
-            userId: String(this.userId),
-            content: content
-          },
-          header: {
-            'Content-Type': 'application/json'
-          }
-        });
-        
-        if (checkRes[1].data.code !== 200) {
-          // 审核未通过,显示失败消息
-          const failedMessage = {
-            messageId: 'failed_' + Date.now(),
-            fromUserId: this.userId,
-            toUserId: this.targetUserId,
-            messageType: 1,
-            content: content,
-            sendStatus: 4, // 4表示发送失败
-            failReason: 'audit_failed', // 标记失败原因:审核失败
-            sendTime: new Date(),
-            fromUserName: '我'
-          };
-          
-          this.messages.push(failedMessage);
-          this.scrollToBottom();
-          
-          uni.showToast({
-            title: checkRes[1].data.message || '消息发送失败',
-            icon: 'none',
-            duration: 2000
+      // 1. 先进行消息内容审核(只要有一方是红娘就跳过审核)
+      if (!isMatchmakerChat) {
+        try {
+          const checkRes = await uni.request({
+            url: 'http://localhost:8083/api/chat/checkMessage',
+            method: 'POST',
+            data: {
+              userId: String(this.userId),
+              content: content
+            },
+            header: {
+              'Content-Type': 'application/json'
+            }
           });
           
-          return;
+          if (checkRes[1].data.code !== 200) {
+            // 审核未通过,显示失败消息
+            const failedMessage = {
+              messageId: 'failed_' + Date.now(),
+              fromUserId: this.userId,
+              toUserId: this.targetUserId,
+              messageType: 1,
+              content: content,
+              sendStatus: 4, // 4表示发送失败
+              failReason: 'audit_failed', // 标记失败原因:审核失败
+              sendTime: new Date(),
+              fromUserName: '我'
+            };
+            
+            this.messages.push(failedMessage);
+            this.scrollToBottom();
+            
+            uni.showToast({
+              title: checkRes[1].data.message || '消息发送失败',
+              icon: 'none',
+              duration: 2000
+            });
+            
+            return;
+          }
+        } catch (error) {
+          console.error('❌ 消息审核失败:', error);
+          // 审核接口失败,为了不影响用户体验,继续发送
         }
-      } catch (error) {
-        console.error('❌ 消息审核失败:', error);
-        // 审核接口失败,为了不影响用户体验,继续发送
+      } else {
+        console.log('✅ 红娘聊天模式:跳过文本审核');
       }
       
       // 2. 检查是否被拉黑
@@ -1218,7 +1173,8 @@ export default {
         
         console.log('✅ 消息发送成功');
         this.syncMessageToMySQL(message);
-		if (!this.isVip) {
+		  // 仅普通用户之间的会话才扣减消息次数
+		  if (!isMatchmakerChat && !this.isVip) {
 		    await this.updateMessageCount();
 		  }
       } catch (error) {
@@ -1338,7 +1294,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: {
@@ -2084,7 +2040,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: {
@@ -2287,6 +2243,16 @@ export default {
 		     * 从后端获取VIP状态和今日剩余发送次数
 		     */
 		    async getUserMessageLimit() {
+		      // 只要一方是红娘(ID 以 m_ 开头),就不做每日5条限制,直接视为无限制
+		      const isMatchmakerChat = String(this.userId).startsWith('m_') || String(this.targetUserId).startsWith('m_');
+		      if (isMatchmakerChat || this.fromMatchmaker) {
+		        this.hasMessageLimit = false;
+		        this.isVip = true;
+		        this.remainingCount = 999;
+		        console.log('✅ 红娘聊天模式:无消息限制(跳过 getUserMessageLimit 接口)');
+		        return;
+		      }
+		      
 		      try {
 		        const [err, res] = await uni.request({
 		          url: 'http://localhost:1004/api/chat/getUserMessageLimit',

+ 91 - 59
LiangZhiYUMao/pages/message/index.vue

@@ -711,76 +711,109 @@ export default {
      */
     async loadUserAvatars() {
       try {
-        if (this.conversations.length === 0) {
-          return;
-        }
+        // 分离普通用户和红娘ID
+        const normalUserIds = [];
+        const matchmakerIds = [];
         
-        // 收集所有需要获取头像的用户ID
-        const userIds = this.conversations
-          .map(conv => conv.targetUserId)
-          .filter(id => id)  // 过滤掉空值
-          .join(',');
+        this.conversations.forEach(conv => {
+          if (!conv.targetUserId) return;
+          
+          if (conv.targetUserId.startsWith('m_')) {
+            // 红娘ID,去掉 m_ 前缀
+            const matchmakerId = conv.targetUserId.substring(2);
+            matchmakerIds.push(matchmakerId);
+          } else {
+            // 普通用户ID
+            normalUserIds.push(conv.targetUserId);
+          }
+        });
         
-        if (!userIds) {
-          console.log('⚠️ 没有需要获取头像的用户');
-          return;
-        }
+        console.log('🔄 开始批量获取信息 - 用户:', normalUserIds.length, '个, 红娘:', matchmakerIds.length, '个');
         
-        console.log('🔄 开始批量获取用户头像,用户ID列表:', userIds);
+        // 创建ID到信息的映射
+        const userInfoMap = {};
         
-        // 调用批量获取用户信息接口
-        const res = await uni.request({
-          url: 'http://localhost:8083/api/user/batch',
-          method: 'GET',
-          data: {
-            userIds: userIds
+        // 1. 批量查询普通用户
+        if (normalUserIds.length > 0) {
+          const userRes = await uni.request({
+            url: 'http://localhost:8083/api/user/batch',
+            method: 'GET',
+            data: {
+              userIds: normalUserIds.join(',')
+            }
+          });
+          
+          if (userRes[1].statusCode === 200 && userRes[1].data.code === 200) {
+            const userList = userRes[1].data.data || [];
+            userList.forEach(user => {
+              userInfoMap[user.userId] = {
+                nickname: user.nickname,
+                avatarUrl: user.avatarUrl
+              };
+            });
+            console.log('✅ 批量获取用户信息成功,数量:', userList.length);
           }
-        });
+        }
         
-        if (res[1].statusCode === 200 && res[1].data.code === 200) {
-          const userList = res[1].data.data || [];
-          console.log('✅ 批量获取用户信息成功,数量:', userList.length);
-          
-          // 创建用户ID到用户信息的映射
-          const userMap = {};
-          userList.forEach(user => {
-            userMap[user.userId] = user;
+        // 2. 批量查询红娘
+        if (matchmakerIds.length > 0) {
+          const matchmakerRes = await uni.request({
+            url: 'http://localhost:8081/api/matchmaker/batch',
+            method: 'GET',
+            data: {
+              matchmakerIds: matchmakerIds.join(',')
+            }
           });
           
-          // 收集数据库中不存在的用户ID
-          const deletedUserIds = [];
-          
-          // 更新会话列表中的头像和昵称
-          for (const conv of this.conversations) {
-            const userInfo = userMap[conv.targetUserId];
-            if (userInfo) {
-              // 更新头像(如果数据库中有头像)
-              if (userInfo.avatarUrl) {
-                conv.targetUserAvatar = userInfo.avatarUrl;
-              }
-              // 更新昵称(如果数据库中的昵称更准确)
-              if (userInfo.nickname && userInfo.nickname !== `用户${conv.targetUserId}`) {
-                conv.targetUserName = userInfo.nickname;
-              }
-              
-              console.log(`🖼️ 更新用户 ${conv.targetUserId} 的信息:`, {
-                昵称: conv.targetUserName,
-                头像: conv.targetUserAvatar
-              });
-            } else {
-              // 数据库中不存在该用户,标记为需要删除
+          if (matchmakerRes[1].statusCode === 200 && matchmakerRes[1].data.code === 200) {
+            const matchmakerList = matchmakerRes[1].data.data || [];
+            matchmakerList.forEach(matchmaker => {
+              // 红娘的 IM ID 是 m_ + matchmakerId
+              const imUserId = 'm_' + matchmaker.matchmakerId;
+              userInfoMap[imUserId] = {
+                nickname: matchmaker.realName,
+                avatarUrl: matchmaker.avatarUrl
+              };
+            });
+            console.log('✅ 批量获取红娘信息成功,数量:', matchmakerList.length);
+          }
+        }
+        
+        // 收集数据库中不存在的用户ID(排除红娘)
+        const deletedUserIds = [];
+        
+        // 3. 更新会话列表中的头像和昵称
+        for (const conv of this.conversations) {
+          const userInfo = userInfoMap[conv.targetUserId];
+          if (userInfo) {
+            // 更新头像
+            if (userInfo.avatarUrl) {
+              conv.targetUserAvatar = userInfo.avatarUrl;
+            }
+            // 更新昵称
+            if (userInfo.nickname && userInfo.nickname !== `用户${conv.targetUserId}`) {
+              conv.targetUserName = userInfo.nickname;
+            }
+            
+            console.log(`🖼️ 更新用户 ${conv.targetUserId} 的信息:`, {
+              昵称: conv.targetUserName,
+              头像: conv.targetUserAvatar
+            });
+          } else {
+            // 只有普通用户不存在时才标记为删除(红娘不删除)
+            if (!conv.targetUserId.startsWith('m_')) {
               console.warn(`⚠️ 用户 ${conv.targetUserId} 在数据库中不存在`);
               deletedUserIds.push(conv.targetUserId);
+            } else {
+              console.warn(`⚠️ 红娘 ${conv.targetUserId} 在数据库中不存在`);
             }
           }
-          
-          // 如果有不存在的用户,删除TIM中的会话和好友
-          if (deletedUserIds.length > 0) {
-            console.log(`🗑️ 发现 ${deletedUserIds.length} 个不存在的用户,开始清理TIM数据...`);
-            await this.cleanupDeletedUsers(deletedUserIds);
-          }
-        } else {
-          console.error('❌ 批量获取用户信息失败:', res[1].data);
+        }
+        
+        // 4. 如果有不存在的普通用户,删除TIM中的会话和好友
+        if (deletedUserIds.length > 0) {
+          console.log(`🗑️ 发现 ${deletedUserIds.length} 个不存在的用户,开始清理TIM数据...`);
+          await this.cleanupDeletedUsers(deletedUserIds);
         }
       } catch (error) {
         console.error('❌ 批量获取用户头像失败:', error);
@@ -790,7 +823,6 @@ export default {
     
     /**
      * 格式化会话数据
-     * 将TIM的会话格式转换为UI需要的格式
      */
     formatConversation(timConv) {
       // conversationID 格式: C2C{userId}

+ 14 - 0
LiangZhiYUMao/utils/tim-manager.js

@@ -524,6 +524,20 @@ class TIMManager {
       console.log('✅ 已移除已读回执回调,剩余回调数:', this.messageReadCallbacks.length);
     }
   }
+
+  /**
+   * 获取 TIM 实例
+   */
+  getTim() {
+    return this.tim;
+  }
+
+  /**
+   * 获取当前登录的 IM 用户ID
+   */
+  getCurrentUserId() {
+    return this.userId ? String(this.userId) : null;
+  }
 }
 
 // 导出单例

+ 1 - 1
gateway/src/main/java/com/zhentao/filter/GatewayRoutes.java

@@ -43,7 +43,7 @@ public class GatewayRoutes {
                 // 首页服务路由 - 课程接口(端口8081)
                 .route("homepage_course_route", r -> r.path("/api/course/**")
                         .uri("http://localhost:8081"))
-                .route("homepage_chatfriend_route", r -> r.path("/api/chatfriend/**")
+                .route("homepage_chatfriend_route", r -> r.path("/api/chatfriend/**","/api/online/**","/api/im/**")
                         .uri("http://localhost:1004"))
                 
                 // 首页服务路由 - 我的资源接口(端口8081)

+ 98 - 11
service/admin/src/main/java/com/zhentao/service/impl/MarrApplyServiceImpl.java

@@ -2,17 +2,26 @@ package com.zhentao.service.impl;
 
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.zhentao.entity.MarrApply;
+import com.zhentao.entity.Matchmaker;
 import com.zhentao.entity.Users;
 import com.zhentao.service.MarrApplyService;
 import com.zhentao.mapper.MarrApplyMapper;
+import com.zhentao.mapper.MatchmakerMapper;
 import com.zhentao.mapper.UsersMapper;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
 
+import java.time.LocalDateTime;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
 * @author 联想
@@ -25,6 +34,14 @@ public class MarrApplyServiceImpl extends ServiceImpl<MarrApplyMapper, MarrApply
 
     @Autowired
     private UsersMapper usersMapper;
+    
+    @Autowired
+    private MatchmakerMapper matchmakerMapper;
+    
+    private final RestTemplate restTemplate = new RestTemplate();
+    
+    // websocket 服务的 IM 接口地址
+    private static final String IM_SERVICE_URL = "http://localhost:1004/api/im";
 
     @Override
     public Page<MarrApply> pageQuery(Integer pageNum, Integer pageSize, String name, String phone) {
@@ -38,28 +55,98 @@ public class MarrApplyServiceImpl extends ServiceImpl<MarrApplyMapper, MarrApply
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean approve(Long applyId, Integer userId) {
-        // 1. 查询用户是否存在
+        System.out.println("========== 开始审核红娘申请 ==========");
+        System.out.println("申请ID: " + applyId + ", 用户ID: " + userId);
+        
+        // 1. 查询申请信息
+        MarrApply apply = this.getById(applyId);
+        if (apply == null) {
+            throw new RuntimeException("申请记录不存在");
+        }
+        
+        // 2. 查询用户是否存在
         Users user = usersMapper.selectById(userId);
         if (user == null) {
             throw new RuntimeException("用户不存在");
         }
         
-        // 2. 更新用户的isMatchmaker为1
-        user.setIsMatchmaker(1);
-        int userUpdateResult = usersMapper.updateById(user);
+        // 3. 更新用户的isMatchmaker为1
+        // 直接使用 MyBatis-Plus 的 update 方法
+        Users updateUser = new Users();
+        updateUser.setUserId(userId);
+        updateUser.setIsMatchmaker(1);
+        int userUpdateResult = usersMapper.updateById(updateUser);
         if (userUpdateResult <= 0) {
             throw new RuntimeException("更新用户红娘状态失败");
         }
+        System.out.println("✅ 已更新 users 表,is_matchmaker = 1");
         
-        // 3. 更新申请记录的更新人和更新时间
-        MarrApply apply = this.getById(applyId);
-        if (apply != null) {
-            apply.setUpdateTime(new Date());
-            // 这里可以从当前登录用户获取,暂时设置为"系统"
-            apply.setUpdateMan("系统");
-            this.updateById(apply);
+        // 4. 创建 matchmakers 记录
+        Matchmaker matchmaker = new Matchmaker();
+        
+        // 从 marr_apply 表获取数据
+        matchmaker.setRealName(apply.getName());           // 真实姓名
+        matchmaker.setPhone(apply.getPhone());             // 手机号
+        matchmaker.setEmail(apply.getEmail());             // 邮箱
+        matchmaker.setGender(apply.getGender());           // 性别
+        matchmaker.setProfile(apply.getIntroduction());    // 个人简介
+        
+        // 从 users 表获取数据
+        matchmaker.setUsername(user.getNickname() != null ? user.getNickname() : user.getPhone()); // 用户名(优先使用昵称,否则使用手机号)
+        matchmaker.setPassword(user.getPassword());        // 密码(使用用户的密码)
+        matchmaker.setBirthDate(user.getBirthDate());      // 出生日期(更准确)
+        
+        // 头像:根据性别强制设置默认头像
+        if (apply.getGender() != null && apply.getGender() == 1) {
+            // 男性默认头像
+            matchmaker.setAvatarUrl("http://115.190.125.125:9000/dynamic-comments/dynamics/5c645152-9940-41d3-83a9-69ee6e0c0aaa.png");
+        } else {
+            // 女性默认头像(包括性别为2或null的情况)
+            matchmaker.setAvatarUrl("http://115.190.125.125:9000/dynamic-comments/dynamics/c7fb04d7-ee4d-4b3d-bcef-f246da9c841f.png");
+        }
+        
+        // 设置默认值(无法从其他表获取的字段)
+        matchmaker.setMatchmakerType(1);                   // 默认类型:1-普通红娘
+        matchmaker.setLevel(1);                            // 默认等级:1级
+        matchmaker.setSuccessCouples(0);                   // 初始成功撮合数:0
+        matchmaker.setStatus(1);                           // 状态:1-正常
+        matchmaker.setCreateTime(LocalDateTime.now());
+        matchmaker.setUpdateTime(LocalDateTime.now());
+        
+        int insertResult = matchmakerMapper.insert(matchmaker);
+        if (insertResult <= 0) {
+            throw new RuntimeException("创建红娘记录失败");
         }
+        System.out.println("✅ 已创建 matchmakers 记录,matchmaker_id = " + matchmaker.getMatchmakerId());
+        
+        // 5. 导入到腾讯云 IM(使用 m_ + matchmaker_id)
+        String imUserId = "m_" + matchmaker.getMatchmakerId();
+        try {
+            Map<String, String> imParams = new HashMap<>();
+            imParams.put("userId", imUserId);
+            imParams.put("nickname", matchmaker.getRealName());
+            if (matchmaker.getAvatarUrl() != null && !matchmaker.getAvatarUrl().isEmpty()) {
+                imParams.put("faceUrl", matchmaker.getAvatarUrl());
+            }
+            
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            HttpEntity<Map<String, String>> request = new HttpEntity<>(imParams, headers);
+            
+            restTemplate.postForObject(IM_SERVICE_URL + "/importUser", request, Map.class);
+            System.out.println("✅ 已导入到腾讯云 IM: imUserId = " + imUserId);
+        } catch (Exception e) {
+            System.err.println("❌ 导入到 IM 失败: " + e.getMessage());
+            e.printStackTrace();
+            // 导入失败不影响审核流程,只记录日志
+        }
+        
+        // 6. 更新申请记录的更新人和更新时间
+        apply.setUpdateTime(new Date());
+        apply.setUpdateMan("系统");
+        this.updateById(apply);
         
+        System.out.println("========== 审核完成 ==========");
         return true;
     }
 

+ 4 - 1
service/homePage/src/main/java/com/zhentao/constant/RedisKeyConstants.java

@@ -54,12 +54,15 @@ public class RedisKeyConstants {
      * 构建红娘列表缓存Key
      */
     public static String buildMatchmakerListKey(Integer type, Integer level, Integer provinceId, 
-                                                 Integer cityId, Integer pageNum, Integer pageSize) {
+                                                 Integer cityId, String keyword, Integer pageNum, Integer pageSize) {
+        // 将keyword转换为缓存键的一部分,如果为空则使用"all"
+        String keywordPart = (keyword != null && !keyword.trim().isEmpty()) ? keyword.trim() : "all";
         return MATCHMAKER_LIST + 
                (type != null ? type : "all") + ":" +
                (level != null ? level : "all") + ":" +
                (provinceId != null ? provinceId : "all") + ":" +
                (cityId != null ? cityId : "all") + ":" +
+               keywordPart + ":" +
                pageNum + ":" + pageSize;
     }
     

+ 31 - 3
service/homePage/src/main/java/com/zhentao/controller/ActivityController.java

@@ -1,5 +1,6 @@
 package com.zhentao.controller;
 
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.zhentao.common.Result;
 import com.zhentao.entity.Activity;
 import com.zhentao.service.ActivityService;
@@ -52,15 +53,42 @@ public class ActivityController {
      * 
      * @param type 活动类型(可选,支持数字类型或 "hot" 表示热门活动)
      * @param status 活动状态(可选)
-     * @param limit 限制数量(可选)
+     * @param keyword 搜索关键词(可选,用于活动名称模糊查询)
+     * @param page 页码(可选,用于分页)
+     * @param pageSize 每页大小(可选,用于分页)
+     * @param limit 限制数量(可选,当使用分页时此参数无效)
      * @return 活动列表
      */
     @GetMapping("/list")
-    public Result<List<Activity>> getActivityList(
+    public Result<?> getActivityList(
             @RequestParam(required = false) String type,
             @RequestParam(required = false) Integer status,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Integer page,
+            @RequestParam(required = false) Integer pageSize,
             @RequestParam(required = false) Integer limit) {
         try {
+            // 如果提供了分页参数,使用分页查询
+            if (page != null && pageSize != null && page > 0 && pageSize > 0) {
+                Integer typeInt = null;
+                if (type != null && !type.isEmpty() && !"hot".equalsIgnoreCase(type)) {
+                    try {
+                        typeInt = Integer.parseInt(type);
+                    } catch (NumberFormatException e) {
+                        // 忽略无法转换的类型
+                    }
+                }
+                Page<Activity> pageResult = activityService.getActivityPage(typeInt, status, keyword, page, pageSize);
+                // 将分页结果转换为前端需要的格式
+                Map<String, Object> data = new HashMap<>();
+                data.put("list", pageResult.getRecords());
+                data.put("total", pageResult.getTotal());
+                data.put("page", page);
+                data.put("pageSize", pageSize);
+                return Result.success(data);
+            }
+            
+            // 否则使用原来的逻辑(不分页)
             List<Activity> activityList;
             
             // 如果是热门活动
@@ -76,7 +104,7 @@ public class ActivityController {
                         // 忽略无法转换的类型
                     }
                 }
-                activityList = activityService.getActivityList(typeInt, status, limit);
+                activityList = activityService.getActivityList(typeInt, status, keyword, limit);
             }
             
             return Result.success(activityList);

+ 177 - 0
service/homePage/src/main/java/com/zhentao/controller/MatchmakerController.java

@@ -7,9 +7,16 @@ import com.zhentao.entity.Matchmaker;
 import com.zhentao.service.MatchmakerService;
 import com.zhentao.vo.MatchmakerVO;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.client.RestTemplate;
 
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 红娘控制器
@@ -21,6 +28,11 @@ public class MatchmakerController {
     @Autowired
     private MatchmakerService matchmakerService;
     
+    private final RestTemplate restTemplate = new RestTemplate();
+    
+    // websocket 服务的 IM 接口地址
+    private static final String IM_SERVICE_URL = "http://localhost:1004/api/im";
+    
     /**
      * 分页查询红娘列表
      * 
@@ -65,6 +77,34 @@ public class MatchmakerController {
         }
     }
     
+    /**
+     * 获取当前登录红娘的信息(通过 userId 查询)
+     * 
+     * @param userId 用户ID
+     * @return 红娘信息
+     */
+    @GetMapping("/current")
+    public Result<Map<String, Object>> getCurrentMatchmaker(@RequestParam Integer userId) {
+        try {
+            Matchmaker matchmaker = matchmakerService.getMatchmakerByUserId(userId);
+            if (matchmaker == null) {
+                return Result.error("该用户不是红娘");
+            }
+            
+            Map<String, Object> result = new HashMap<>();
+            result.put("matchmakerId", matchmaker.getMatchmakerId());
+            result.put("realName", matchmaker.getRealName());
+            result.put("avatarUrl", matchmaker.getAvatarUrl());
+            result.put("userId", userId);
+            result.put("imUserId", "m_" + matchmaker.getMatchmakerId());  // IM 用户 ID
+            
+            return Result.success(result);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取红娘信息失败:" + e.getMessage());
+        }
+    }
+    
     /**
      * 获取全职红娘列表(首页展示)
      * 
@@ -109,8 +149,32 @@ public class MatchmakerController {
                 matchmaker.setStatus(1); // 1-正常
             }
             
+            // 保存红娘到数据库
             boolean success = matchmakerService.save(matchmaker);
             if (success) {
+                // 导入红娘到腾讯云 IM(使用 m_ + matchmaker_id)
+                String imUserId = "m_" + matchmaker.getMatchmakerId();
+                try {
+                    // 调用 websocket 服务的 IM 导入接口
+                    Map<String, String> imParams = new HashMap<>();
+                    imParams.put("userId", imUserId);
+                    imParams.put("nickname", matchmaker.getRealName());
+                    if (matchmaker.getAvatarUrl() != null && !matchmaker.getAvatarUrl().isEmpty()) {
+                        imParams.put("faceUrl", matchmaker.getAvatarUrl());
+                    }
+                    
+                    HttpHeaders headers = new HttpHeaders();
+                    headers.setContentType(MediaType.APPLICATION_JSON);
+                    HttpEntity<Map<String, String>> request = new HttpEntity<>(imParams, headers);
+                    
+                    restTemplate.postForObject(IM_SERVICE_URL + "/importUser", request, Map.class);
+                    System.out.println("✅ 红娘已导入到腾讯云 IM: matchmakerId=" + matchmaker.getMatchmakerId() + ", imUserId=" + imUserId);
+                } catch (Exception e) {
+                    System.err.println("❌ 导入红娘到 IM 失败: " + e.getMessage());
+                    e.printStackTrace();
+                    // 导入失败不影响红娘创建,只记录日志
+                }
+                
                 // 清除列表缓存
                 matchmakerService.clearMatchmakerCache(null);
                 return Result.success(matchmaker);
@@ -159,8 +223,27 @@ public class MatchmakerController {
     @DeleteMapping("/delete/{matchmakerId}")
     public Result<Void> deleteMatchmaker(@PathVariable Integer matchmakerId) {
         try {
+            // 先查询红娘信息(用于后续删除 IM 账号)
+            Matchmaker matchmaker = matchmakerService.getById(matchmakerId);
+            if (matchmaker == null) {
+                return Result.error("红娘不存在");
+            }
+            
+            // 删除数据库记录
             boolean success = matchmakerService.removeById(matchmakerId);
             if (success) {
+                // 从腾讯云 IM 删除红娘账号(使用 m_ + matchmaker_id)
+                String imUserId = "m_" + matchmakerId;
+                try {
+                    // 调用 websocket 服务的 IM 删除接口
+                    restTemplate.delete(IM_SERVICE_URL + "/deleteAccount?userId=" + imUserId);
+                    System.out.println("✅ 已从腾讯云 IM 删除红娘账号: matchmakerId=" + matchmakerId + ", imUserId=" + imUserId);
+                } catch (Exception e) {
+                    System.err.println("❌ 从 IM 删除账号失败: " + e.getMessage());
+                    e.printStackTrace();
+                    // 删除失败不影响数据库删除,只记录日志
+                }
+                
                 // 清除相关缓存
                 matchmakerService.clearMatchmakerCache(matchmakerId);
                 return Result.success(null);
@@ -190,5 +273,99 @@ public class MatchmakerController {
             return Result.error("获取排行榜失败:" + e.getMessage());
         }
     }
+    
+    /**
+     * 批量查询红娘信息
+     * 
+     * @param matchmakerIds 红娘ID列表,逗号分隔(例如:22,23,24)
+     * @return 红娘信息列表
+     */
+    @GetMapping("/batch")
+    public Result<List<MatchmakerVO>> batchGetMatchmakers(@RequestParam String matchmakerIds) {
+        try {
+            if (matchmakerIds == null || matchmakerIds.trim().isEmpty()) {
+                return Result.success(Collections.emptyList());
+            }
+            
+            // 解析ID列表
+            String[] idArray = matchmakerIds.split(",");
+            List<Integer> ids = new java.util.ArrayList<>();
+            for (String id : idArray) {
+                try {
+                    ids.add(Integer.parseInt(id.trim()));
+                } catch (NumberFormatException e) {
+                    System.err.println("⚠️ 无效的红娘ID: " + id);
+                }
+            }
+            
+            if (ids.isEmpty()) {
+                return Result.success(Collections.emptyList());
+            }
+            
+            // 批量查询
+            List<MatchmakerVO> matchmakers = matchmakerService.batchGetMatchmakers(ids);
+            return Result.success(matchmakers);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("批量查询红娘信息失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 批量导入现有红娘到腾讯云 IM(一次性任务)
+     * 
+     * @return 导入结果
+     */
+    @PostMapping("/batch-import-to-im")
+    public Result<Map<String, Object>> batchImportMatchmakersToIM() {
+        try {
+            List<Matchmaker> matchmakers = matchmakerService.list();
+            
+            int successCount = 0;
+            int failCount = 0;
+            StringBuilder failedIds = new StringBuilder();
+            
+            for (Matchmaker matchmaker : matchmakers) {
+                String imUserId = "m_" + matchmaker.getMatchmakerId();
+                try {
+                    // 调用 websocket 服务的 IM 导入接口
+                    Map<String, String> imParams = new HashMap<>();
+                    imParams.put("userId", imUserId);
+                    imParams.put("nickname", matchmaker.getRealName());
+                    if (matchmaker.getAvatarUrl() != null && !matchmaker.getAvatarUrl().isEmpty()) {
+                        imParams.put("faceUrl", matchmaker.getAvatarUrl());
+                    }
+                    
+                    HttpHeaders headers = new HttpHeaders();
+                    headers.setContentType(MediaType.APPLICATION_JSON);
+                    HttpEntity<Map<String, String>> request = new HttpEntity<>(imParams, headers);
+                    
+                    restTemplate.postForObject(IM_SERVICE_URL + "/importUser", request, Map.class);
+                    successCount++;
+                    System.out.println("✅ 导入成功: matchmakerId=" + matchmaker.getMatchmakerId() + ", imUserId=" + imUserId);
+                    
+                    // 避免请求过快
+                    Thread.sleep(100);
+                } catch (Exception e) {
+                    failCount++;
+                    failedIds.append(matchmaker.getMatchmakerId()).append(",");
+                    System.err.println("❌ 导入失败: matchmakerId=" + matchmaker.getMatchmakerId() + ", error=" + e.getMessage());
+                }
+            }
+            
+            Map<String, Object> result = new HashMap<>();
+            result.put("total", matchmakers.size());
+            result.put("successCount", successCount);
+            result.put("failCount", failCount);
+            if (failCount > 0) {
+                result.put("failedIds", failedIds.toString());
+            }
+            
+            return Result.success(result);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("批量导入失败:" + e.getMessage());
+        }
+    }
 }
 

+ 8 - 0
service/homePage/src/main/java/com/zhentao/mapper/MatchmakerMapper.java

@@ -54,5 +54,13 @@ public interface MatchmakerMapper extends BaseMapper<Matchmaker> {
      * @return 红娘排行榜列表
      */
     List<MatchmakerVO> selectRankingList(@Param("limit") Integer limit);
+    
+    /**
+     * 批量查询红娘信息
+     * 
+     * @param matchmakerIds 红娘ID列表
+     * @return 红娘VO列表
+     */
+    List<MatchmakerVO> selectBatchMatchmakers(@Param("matchmakerIds") List<Integer> matchmakerIds);
 }
 

+ 15 - 1
service/homePage/src/main/java/com/zhentao/service/ActivityService.java

@@ -1,5 +1,6 @@
 package com.zhentao.service;
 
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.zhentao.entity.Activity;
 import java.util.List;
 
@@ -13,10 +14,23 @@ public interface ActivityService {
      * 
      * @param type 活动类型(null表示全部)
      * @param status 活动状态(null表示全部)
+     * @param keyword 搜索关键词(可选,用于活动名称模糊查询)
      * @param limit 限制数量(可选)
      * @return 活动列表
      */
-    List<Activity> getActivityList(Integer type, Integer status, Integer limit);
+    List<Activity> getActivityList(Integer type, Integer status, String keyword, Integer limit);
+    
+    /**
+     * 分页获取活动列表
+     * 
+     * @param type 活动类型(null表示全部)
+     * @param status 活动状态(null表示全部)
+     * @param keyword 搜索关键词(可选,用于活动名称模糊查询)
+     * @param pageNum 页码
+     * @param pageSize 每页大小
+     * @return 分页结果
+     */
+    Page<Activity> getActivityPage(Integer type, Integer status, String keyword, Integer pageNum, Integer pageSize);
     
     /**
      * 获取活动详情

+ 16 - 0
service/homePage/src/main/java/com/zhentao/service/MatchmakerService.java

@@ -48,5 +48,21 @@ public interface MatchmakerService extends IService<Matchmaker> {
      * @return 红娘排行榜列表
      */
     java.util.List<MatchmakerVO> getRankingList(Integer limit);
+    
+    /**
+     * 根据用户ID查询红娘信息
+     * 
+     * @param userId 用户ID
+     * @return 红娘实体
+     */
+    Matchmaker getMatchmakerByUserId(Integer userId);
+    
+    /**
+     * 批量查询红娘信息
+     * 
+     * @param matchmakerIds 红娘ID列表
+     * @return 红娘VO列表
+     */
+    java.util.List<MatchmakerVO> batchGetMatchmakers(java.util.List<Integer> matchmakerIds);
 }
 

+ 39 - 1
service/homePage/src/main/java/com/zhentao/service/impl/ActivityServiceImpl.java

@@ -2,6 +2,7 @@ package com.zhentao.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.zhentao.entity.Activity;
 import com.zhentao.entity.ActivityRegistration;
 import com.zhentao.mapper.ActivityMapper;
@@ -30,7 +31,7 @@ public class ActivityServiceImpl implements ActivityService {
      * 获取活动列表
      */
     @Override
-    public List<Activity> getActivityList(Integer type, Integer status, Integer limit) {
+    public List<Activity> getActivityList(Integer type, Integer status, String keyword, Integer limit) {
         QueryWrapper<Activity> queryWrapper = new QueryWrapper<>();
         
         // 只查询未删除的活动
@@ -46,6 +47,11 @@ public class ActivityServiceImpl implements ActivityService {
             queryWrapper.eq("status", status);
         }
         
+        // 关键词模糊查询(活动名称)
+        if (keyword != null && !keyword.trim().isEmpty()) {
+            queryWrapper.like("name", keyword.trim());
+        }
+        
         // 按开始时间降序排序
         queryWrapper.orderByDesc("start_time");
         
@@ -57,6 +63,38 @@ public class ActivityServiceImpl implements ActivityService {
         return activityMapper.selectList(queryWrapper);
     }
     
+    /**
+     * 分页获取活动列表
+     */
+    @Override
+    public Page<Activity> getActivityPage(Integer type, Integer status, String keyword, Integer pageNum, Integer pageSize) {
+        Page<Activity> page = new Page<>(pageNum, pageSize);
+        QueryWrapper<Activity> queryWrapper = new QueryWrapper<>();
+        
+        // 只查询未删除的活动
+        queryWrapper.eq("is_deleted", 0);
+        
+        // 按类型筛选
+        if (type != null) {
+            queryWrapper.eq("type", type);
+        }
+        
+        // 按状态筛选
+        if (status != null) {
+            queryWrapper.eq("status", status);
+        }
+        
+        // 关键词模糊查询(活动名称)
+        if (keyword != null && !keyword.trim().isEmpty()) {
+            queryWrapper.like("name", keyword.trim());
+        }
+        
+        // 按开始时间降序排序
+        queryWrapper.orderByDesc("start_time");
+        
+        return activityMapper.selectPage(page, queryWrapper);
+    }
+    
     /**
      * 获取活动详情
      */

+ 26 - 1
service/homePage/src/main/java/com/zhentao/service/impl/MatchmakerServiceImpl.java

@@ -15,6 +15,7 @@ import org.springframework.stereotype.Service;
 
 import java.time.LocalDate;
 import java.time.Period;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
@@ -38,12 +39,13 @@ public class MatchmakerServiceImpl extends ServiceImpl<MatchmakerMapper, Matchma
     @Override
     @SuppressWarnings("unchecked")
     public Page<MatchmakerVO> getMatchmakerPage(MatchmakerQueryDTO queryDTO) {
-        // 1. 构建缓存Key
+        // 1. 构建缓存Key(包含keyword参数)
         String cacheKey = RedisKeyConstants.buildMatchmakerListKey(
                 queryDTO.getMatchmakerType(),
                 queryDTO.getLevel(),
                 queryDTO.getProvinceId(),
                 queryDTO.getCityId(),
+                queryDTO.getKeyword(),
                 queryDTO.getPageNum(),
                 queryDTO.getPageSize()
         );
@@ -328,4 +330,27 @@ public class MatchmakerServiceImpl extends ServiceImpl<MatchmakerMapper, Matchma
         
         return rankingList;
     }
+    
+    @Override
+    public Matchmaker getMatchmakerByUserId(Integer userId) {
+        // 根据 user_id 查询红娘信息
+        return this.lambdaQuery()
+                .eq(Matchmaker::getUserId, userId.longValue())
+                .one();
+    }
+    
+    @Override
+    public List<MatchmakerVO> batchGetMatchmakers(List<Integer> matchmakerIds) {
+        if (matchmakerIds == null || matchmakerIds.isEmpty()) {
+            return new ArrayList<>();
+        }
+        
+        // 批量查询红娘详情
+        List<MatchmakerVO> matchmakers = matchmakerMapper.selectBatchMatchmakers(matchmakerIds);
+        
+        // 处理数据(计算年龄、处理头像等)
+        matchmakers.forEach(this::processMatchmakerVO);
+        
+        return matchmakers;
+    }
 }

+ 33 - 0
service/homePage/src/main/resources/mapper/MatchmakerMapper.xml

@@ -141,5 +141,38 @@
         ORDER BY m.success_couples DESC, m.level DESC
         LIMIT #{limit}
     </select>
+    
+    <!-- 批量查询红娘信息 -->
+    <select id="selectBatchMatchmakers" resultMap="MatchmakerVOMap">
+        SELECT 
+            m.matchmaker_id,
+            m.real_name,
+            m.phone,
+            m.email,
+            m.gender,
+            m.birth_date,
+            m.avatar_url,
+            m.matchmaker_type,
+            m.level,
+            m.success_couples,
+            m.address_detail,
+            m.profile,
+            m.status,
+            m.created_at,
+            m.province_id,
+            p.name AS province_name,
+            m.city_id,
+            c.name AS city_name,
+            m.area_id,
+            a.name AS area_name
+        FROM matchmakers m
+        LEFT JOIN province p ON m.province_id = p.id
+        LEFT JOIN city c ON m.city_id = c.id
+        LEFT JOIN area a ON m.area_id = a.id
+        WHERE m.matchmaker_id IN
+        <foreach collection="matchmakerIds" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </select>
 </mapper>
 

+ 12 - 1
service/websocket/src/main/java/com/zhentao/WebSocketApplication.java

@@ -18,8 +18,19 @@ import org.springframework.context.annotation.ComponentScan;
 @MapperScan({"com.zhentao.repository", "com.zhentao.mapper"})
 public class WebSocketApplication {
     public static void main(String[] args) {
-        SpringApplication.run(WebSocketApplication.class, args);
+        org.springframework.context.ConfigurableApplicationContext context = SpringApplication.run(WebSocketApplication.class, args);
         System.out.println("WebSocket聊天服务启动成功!");
         System.out.println("WebSocket连接地址: ws://localhost:1004/ws/chat?userId={userId}");
+        
+        System.out.println("====== 检查 Bean 是否存在 ======");
+        boolean hasChatController = context.containsBean("chatController");
+        System.out.println("ChatController 存在: " + hasChatController);
+        
+        if (hasChatController) {
+            System.out.println("ChatController Bean: " + context.getBean("chatController"));
+        } else {
+            System.err.println("❌ 严重错误: ChatController 未被注册!请检查包扫描路径或编译输出。");
+        }
+        System.out.println("==============================");
     }
 }

+ 17 - 0
service/websocket/src/main/java/com/zhentao/config/MinioConfig.java

@@ -3,6 +3,7 @@ package com.zhentao.config;
 import io.minio.MinioClient;
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -10,6 +11,7 @@ import org.springframework.context.annotation.Configuration;
  * MinIO配置类
  */
 @Configuration
+@EnableConfigurationProperties
 @ConfigurationProperties(prefix = "minio")
 @Data
 public class MinioConfig {
@@ -39,6 +41,21 @@ public class MinioConfig {
      */
     @Bean
     public MinioClient minioClient() {
+        if (endpoint == null || endpoint.isEmpty()) {
+            throw new IllegalStateException("MinIO endpoint 未配置!请检查 application.yml 中的 minio.endpoint 配置");
+        }
+        if (accessKey == null || accessKey.isEmpty()) {
+            throw new IllegalStateException("MinIO accessKey 未配置!请检查 application.yml 中的 minio.access-key 配置");
+        }
+        if (secretKey == null || secretKey.isEmpty()) {
+            throw new IllegalStateException("MinIO secretKey 未配置!请检查 application.yml 中的 minio.secret-key 配置");
+        }
+        
+        System.out.println("✅ MinIO 配置加载成功:");
+        System.out.println("   - endpoint: " + endpoint);
+        System.out.println("   - accessKey: " + accessKey);
+        System.out.println("   - bucketName: " + bucketName);
+        
         return MinioClient.builder()
                 .endpoint(endpoint)
                 .credentials(accessKey, secretKey)

+ 5 - 0
service/websocket/src/main/java/com/zhentao/controller/ChatController.java

@@ -34,6 +34,11 @@ public class ChatController {
 
     @Autowired
     private UserVipService userVipService;
+    @GetMapping("/test")
+    public String test() {
+        return "ChatController is working!";
+    }
+
     /**
      * 获取会话消息列表(优化版:添加更多参数支持)
      * GET /api/chat/messages?userId=10001&targetUserId=10002&page=0&size=20&lastMessageId=xxx

+ 21 - 4
service/websocket/src/main/java/com/zhentao/controller/OnlineStatusController.java

@@ -20,18 +20,30 @@ public class OnlineStatusController {
 
     /**
      * 检查用户是否在线
-     * @param userId 用户ID
+     * @param userId 用户ID(支持普通用户ID和红娘IM ID,如 "123" 或 "m_22")
      * @return 在线状态
      */
     @GetMapping("/checkStatus")
-    public Map<String, Object> checkUserOnlineStatus(@RequestParam("userId") Long userId) {
+    public Map<String, Object> checkUserOnlineStatus(@RequestParam("userId") String userId) {
         Map<String, Object> result = new HashMap<>();
         
         try {
-            boolean isOnline = onlineUserService.isUserOnline(userId);
+            // 将字符串 userId 转换为 Long(如果是红娘ID "m_22",需要特殊处理)
+            Long userIdLong;
+            if (userId.startsWith("m_")) {
+                // 红娘ID格式:m_22 -> 提取数字部分
+                // 注意:这里假设 OnlineUserService 使用的是红娘的 matchmaker_id
+                // 如果需要使用完整的 IM ID,需要修改 OnlineUserService
+                userIdLong = Long.parseLong(userId.substring(2));
+            } else {
+                // 普通用户ID
+                userIdLong = Long.parseLong(userId);
+            }
+            
+            boolean isOnline = onlineUserService.isUserOnline(userIdLong);
             
             Map<String, Object> data = new HashMap<>();
-            data.put("userId", userId);
+            data.put("userId", userId);  // 返回原始 userId
             data.put("online", isOnline);
             data.put("timestamp", System.currentTimeMillis());
             
@@ -40,6 +52,11 @@ public class OnlineStatusController {
             result.put("data", data);
             
             System.out.println("查询用户 " + userId + " 在线状态: " + (isOnline ? "在线" : "离线"));
+        } catch (NumberFormatException e) {
+            System.err.println("无效的用户ID格式: " + userId);
+            result.put("code", 400);
+            result.put("message", "无效的用户ID格式");
+            result.put("data", null);
         } catch (Exception e) {
             System.err.println("查询在线状态失败: " + e.getMessage());
             result.put("code", 500);

+ 2 - 2
service/websocket/src/main/java/com/zhentao/controller/TIMController.java

@@ -33,11 +33,11 @@ public class TIMController {
      * GET /api/im/getUserSig?userId=1
      */
     @GetMapping("/getUserSig")
-    public Map<String, Object> getUserSig(@RequestParam Long userId) {
+    public Map<String, Object> getUserSig(@RequestParam String userId) {
         Map<String, Object> result = new HashMap<>();
         
         try {
-            String userSig = timUtils.generateUserSig(String.valueOf(userId));
+            String userSig = timUtils.generateUserSig(userId);
             
             Map<String, String> data = new HashMap<>();
             data.put("userSig", userSig);

+ 3 - 3
service/websocket/src/main/resources/application.yml

@@ -81,6 +81,6 @@ tim:
 # MinIO 对象存储配置
 minio:
   endpoint: http://115.190.125.125:9000
-  access-key: minioadmin
-  secret-key: minioadmin
-  bucket-name: minion-voice-files
+  accessKey: minioadmin
+  secretKey: minioadmin
+  bucketName: minion-voice-files