Explorar o código

解决package-lock.json冲突,合并yh分支代码到test

YH_0525 hai 1 mes
pai
achega
9579ac2afc
Modificáronse 24 ficheiros con 1282 adicións e 196 borrados
  1. 2 0
      LiangZhiYUMao/main.js
  2. 7 2
      LiangZhiYUMao/manifest.json
  3. 1 1
      LiangZhiYUMao/package-lock.json
  4. 67 66
      LiangZhiYUMao/pages/index/index.vue
  5. 278 5
      LiangZhiYUMao/pages/message/chat.vue
  6. 133 15
      LiangZhiYUMao/pages/message/index.vue
  7. 57 40
      LiangZhiYUMao/pages/mine/index.vue
  8. 56 34
      LiangZhiYUMao/pages/plaza/index.vue
  9. 80 13
      LiangZhiYUMao/pages/recommend/index.vue
  10. 26 0
      LiangZhiYUMao/store/index.js
  11. 10 1
      gateway/src/main/resources/application.yml
  12. 10 1
      gateway/target/classes/application.yml
  13. 121 15
      service/login/src/main/java/com/zhentao/service/impl/WeChatServiceImpl.java
  14. 3 3
      service/login/src/main/resources/application.yml
  15. 27 0
      service/websocket/src/main/java/com/zhentao/controller/ChatFriendController.java
  16. 24 0
      service/websocket/src/main/java/com/zhentao/dto/BlockDto.java
  17. 120 0
      service/websocket/src/main/java/com/zhentao/entity/ChatFriend.java
  18. 20 0
      service/websocket/src/main/java/com/zhentao/mapper/ChatFriendMapper.java
  19. 9 0
      service/websocket/src/main/java/com/zhentao/service/ChatConversationService.java
  20. 18 0
      service/websocket/src/main/java/com/zhentao/service/ChatFriendService.java
  21. 20 0
      service/websocket/src/main/java/com/zhentao/service/impl/ChatConversationServiceImpl.java
  22. 101 0
      service/websocket/src/main/java/com/zhentao/service/impl/ChatFriendServiceImpl.java
  23. 11 0
      service/websocket/src/main/java/com/zhentao/vo/GetblockVo.java
  24. 81 0
      service/websocket/src/main/java/com/zhentao/vo/ResultVo.java

+ 2 - 0
LiangZhiYUMao/main.js

@@ -1,11 +1,13 @@
 import Vue from 'vue'
 import App from './App'
+import store from './store'
 
 Vue.config.productionTip = false
 
 App.mpType = 'app'
 
 const app = new Vue({
+	store,
 	...App
 })
 app.$mount()

+ 7 - 2
LiangZhiYUMao/manifest.json

@@ -46,7 +46,7 @@
     },
     "quickapp" : {},
     "mp-weixin" : {
-        "appid" : "",
+        "appid" : "wx3e90d662a801266e",
         "setting" : {
             "urlCheck" : false,
             "postcss" : true,
@@ -54,7 +54,12 @@
             "es6" : true
         },
         "usingComponents" : true,
-        "lazyCodeLoading" : "requiredComponents"
+        "lazyCodeLoading" : "requiredComponents",
+        "permission" : {
+            "scope.userLocation" : {
+                "desc" : "用于获取用户位置"
+            }
+        }
     },
     "mp-alipay" : {
         "usingComponents" : true

+ 1 - 1
LiangZhiYUMao/package-lock.json

@@ -395,7 +395,7 @@
         },
         "node_modules/tim-wx-sdk": {
             "version": "2.27.6",
-            "resolved": "https://registry.npmmirror.com/tim-wx-sdk/-/tim-wx-sdk-2.27.6.tgz",
+
             "integrity": "sha512-zB+eRdmigdhEDeqrXC0bLJonUQZzS5uKNPLFtrje503WAnmuxVQjq/n4Zle4FYHG4FiKHKhsrVd0aCYXABlFEg==",
             "license": "ISC"
         }

+ 67 - 66
LiangZhiYUMao/pages/index/index.vue

@@ -206,7 +206,7 @@
 			<view class="tabbar-item" @click="switchTab('message')">
 				<text class="tabbar-icon">💬</text>
 				<text class="tabbar-text">消息</text>
-				<view v-if="unreadCount > 0" class="tabbar-badge">{{ unreadCount }}</view>
+				<view v-if="unreadCount >= 1" class="tabbar-badge">{{ unreadCount }}</view>
 			</view>
 			<view class="tabbar-item" @click="switchTab('mine')">
 				<text class="tabbar-icon">👤</text>
@@ -230,7 +230,7 @@
 					userId: null
 				},
 				matchCount: 3,
-				unreadCount: 2,
+			
 
 				// 轮播图数据
 				bannerList: [
@@ -356,7 +356,12 @@
 				imageErrorCount: 0
 			}
 		},
-
+			computed: {
+			    // 从Vuex获取全局未读数
+			    unreadCount() {
+			      return this.$store.getters.getTotalUnread || 0;
+			    }
+			  },
 		onLoad() {
 			this.loadUserInfo()
 			this.loadBannerData()
@@ -370,7 +375,10 @@
 		onUnload() {
 			// 清理资源
 		},
-
+onShow() {
+  // 如果需要主动刷新未读数,可以在这里触发
+  this.$store.dispatch('updateTotalUnread', this.unreadCount);
+},
 		methods: {
 			// 加载用户信息
 			loadUserInfo() {
@@ -1551,70 +1559,46 @@
 
 	/* 底部导航栏 */
 	.tabbar {
-		position: fixed;
-		bottom: 0;
-		left: 0;
-		right: 0;
-		display: flex;
-		background: #FFFFFF;
-		border-top: 2rpx solid #E0E0E0;
-		padding-bottom: constant(safe-area-inset-bottom);
-		padding-bottom: env(safe-area-inset-bottom);
-		z-index: 999;
-
-		.tabbar-item {
-			flex: 1;
-			display: flex;
-			flex-direction: column;
-			align-items: center;
-			justify-content: center;
-			padding: 18rpx 0;
-			position: relative;
-			transition: all 0.2s ease;
-			
-			&:active {
-				background: #F5F5F5;
-			}
-
-			.tabbar-icon {
-				font-size: 46rpx;
-				margin-bottom: 8rpx;
-				transition: all 0.2s ease;
-			}
-
-			.tabbar-text {
-				font-size: 22rpx;
-				color: #666666;
-				font-weight: 500;
-				transition: all 0.2s ease;
-			}
-
-			.tabbar-badge {
-				position: absolute;
-				top: 8rpx;
-				right: 25%;
-				min-width: 36rpx;
-				height: 36rpx;
-				line-height: 36rpx;
-				padding: 0 8rpx;
-				background: #FF6B6B;
-				color: #FFFFFF;
-				font-size: 20rpx;
-				font-weight: bold;
-				border-radius: 18rpx;
-				text-align: center;
-			}
-
-			&.active {
-				background: #FFE5F1;
-				
-				.tabbar-text {
-					color: #E91E63;
-					font-weight: 700;
+				position: fixed;
+				bottom: 0;
+				left: 0;
+				right: 0;
+				display: flex;
+				background-color: #FFFFFF;
+				border-top: 1rpx solid #F0F0F0;
+				padding-bottom: constant(safe-area-inset-bottom);
+				padding-bottom: env(safe-area-inset-bottom);
+				z-index: 999;
+				box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+		
+				.tabbar-item {
+					flex: 1;
+					display: flex;
+					flex-direction: column;
+					align-items: center;
+					justify-content: center;
+					padding: 15rpx 0;
+					position: relative;
+		
+					.tabbar-icon {
+						font-size: 44rpx;
+						margin-bottom: 5rpx;
+					}
+		
+					.tabbar-text {
+						font-size: 22rpx;
+						color: #666666;
+					}
+		
+					&.active {
+						.tabbar-text {
+							color: #E91E63;
+							font-weight: bold;
+						}
+					}
 				}
 			}
-		}
-	}
+
 
 	/* 成功案例 */
 	.success-case-section {
@@ -1690,5 +1674,22 @@
 			transform: translateX(-50%);
 		}
 	}
+	
+	.tabbar-badge {
+	      position: absolute;
+	      top: 8rpx;
+	      right: 50%;
+	      margin-right: -40rpx;
+	      min-width: 32rpx;
+	      height: 32rpx;
+	      line-height: 32rpx;
+	      padding: 0 6rpx;
+	      background-color: #FA5151;
+	      border-radius: 16rpx;
+	      font-size: 20rpx;
+	      color: #FFFFFF;
+	      text-align: center;
+	    }
+
 </style>
 

+ 278 - 5
LiangZhiYUMao/pages/message/chat.vue

@@ -1,5 +1,48 @@
 <template>
   <view class="chat-page">
+	  <!-- 更多选项弹窗 -->
+	  <view v-if="showMoreOptionsModal" class="modal-mask" @click="closeMoreOptions">
+	    <view class="modal-content" @click.stop>
+	      <view class="modal-header">
+	        <text class="modal-title">{{ targetUserName }}</text>
+	        <text class="modal-subtitle">好友操作</text>
+	      </view>
+	      <view class="modal-body">
+	        <!-- <view class="modal-item" @click="goToUserProfile">
+	          <text class="item-icon">👤</text>
+	          <text class="item-text">查看资料</text>
+	        </view> -->
+	        <view class="modal-item" @click="blockFriend" :class="{ danger: true }">
+	          <text class="item-icon">🚫</text>
+	          <text class="item-text">拉黑好友</text>
+	        </view>
+	       <!-- <view class="modal-item" @click="reportUser" :class="{ danger: true }">
+	          <text class="item-icon">🚨</text>
+	          <text class="item-text">举报用户</text>
+	        </view> -->
+	      </view>
+	      <view class="modal-footer">
+	        <button class="modal-close-btn" @click="closeMoreOptions">取消</button>
+	      </view>
+	    </view>
+	  </view>
+	  
+	  <!-- 拉黑确认弹窗 -->
+	  <view v-if="showBlockConfirmModal" class="modal-mask" @click="closeBlockConfirm">
+	    <view class="confirm-modal" @click.stop>
+	      <view class="confirm-title">确认拉黑</view>
+	      <view class="confirm-content">
+	        <text>拉黑后将无法接收该用户的消息,是否确定?</text>
+	      </view>
+	      <view class="confirm-buttons">
+	        <button class="confirm-btn cancel" @click="closeBlockConfirm">取消</button>
+	        <button class="confirm-btn confirm" @click="confirmBlockFriend">确认</button>
+	      </view>
+	    </view>
+	  </view>
+	  
+	  
+	  
     <!-- 顶部导航 -->
     <view class="chat-header">
       <view class="header-left" @click="goBack">
@@ -11,7 +54,7 @@
           {{ isTargetOnline ? '在线' : '离线' }}
         </text>
       </view>
-      <view class="header-right">
+      <view class="header-right" @click="showMoreOptions">
         <text class="icon-more">⋯</text>
       </view>
     </view>
@@ -230,8 +273,11 @@ export default {
       nextReqMessageID: '',
       isTargetOnline: false,
       
-      emojis: ['😀', '😃', '😄', '😁', '😆', '😅', '😂', '🤣', '😊', '😇', '🙂', '🙃', '😉', '😌', '😍', '🥰', '😘', '😗', '😙', '😚', '😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🤩', '🥳']
-    };
+      emojis: ['😀', '😃', '😄', '😁', '😆', '😅', '😂', '🤣', '😊', '😇', '🙂', '🙃', '😉', '😌', '😍', '🥰', '😘', '😗', '😙', '😚', '😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🤩', '🥳'],
+		showMoreOptionsModal: false, // 控制更多选项弹窗显示
+	    showBlockConfirmModal: false // 控制拉黑确认弹窗显示
+	
+	};
   },
   
   async onLoad(options) {
@@ -287,6 +333,7 @@ export default {
       });
       return;
     }
+	
     
     // 保存用户ID(TIM需要字符串格式)
     this.userId = String(rawUserId);
@@ -354,7 +401,23 @@ export default {
         });
       }
     },
-    
+    more(userid) {
+      console.log('点击了更多按钮,开始跳转...', userid);
+      // 如果目标页面是普通页面,用navigateTo(保留当前页面);如果是tabbar页面,用switchTab
+      uni.navigateTo({
+        url: `/pages/message/more?userid=${userid}`,
+        success: () => {
+          console.log('跳转成功');
+        },
+        fail: (err) => {
+          console.error('跳转失败:', err);
+          uni.showToast({
+            title: '页面不存在或路径错误',
+            icon: 'none'
+          });
+        }
+      });
+    },
     /**
      * 导入用户到腾讯云IM
      */
@@ -1046,12 +1109,222 @@ export default {
         title: '语音播放功能开发中',
         icon: 'none'
       });
-    }
+    },
+	showMoreOptions() {
+	    this.showMoreOptionsModal = true;
+	  },
+	  closeMoreOptions() {
+	      this.showMoreOptionsModal = false;
+	    },
+		/**
+		   * 查看用户资料(跳转到原有more页面)
+		   */
+		  goToUserProfile() {
+		    this.closeMoreOptions();
+		    uni.navigateTo({
+		      url: `/pages/message/more?userid=${this.targetUserId}`,
+		      fail: (err) => {
+		        console.error('跳转失败:', err);
+		        uni.showToast({
+		          title: '页面不存在',
+		          icon: 'none'
+		        });
+		      }
+		    });
+		  },
+		  /**
+		     * 显示拉黑确认弹窗
+		     */
+		    blockFriend() {
+		      this.closeMoreOptions();
+		      this.showBlockConfirmModal = true;
+		    },
+		    
+		    /**
+		     * 关闭拉黑确认弹窗
+		     */
+		    closeBlockConfirm() {
+		      this.showBlockConfirmModal = false;
+		    },
+		    
+		    /**
+		     * 确认拉黑好友
+		     */
+		    async confirmBlockFriend() {
+		      try {
+		        uni.showLoading({
+		          title: '处理中...'
+		        });
+		        
+		        // 调用后端拉黑接口(根据实际接口调整)
+		        const [err, res] = await uni.request({
+		          url: 'http://localhost:8083/api/chatfriend/block',
+		          method: 'POST',
+		          data: {
+		            userId: this.userId,
+		            targetUserId: this.targetUserId,
+		            targetUserName: this.targetUserName,
+		            targetUserAvatar: this.targetUserAvatar
+		          },
+		          header: {
+		            'Content-Type': 'application/json',
+		            // 'Authorization': 'Bearer ' + uni.getStorageSync('token')
+		          }
+		        });
+		        
+		        uni.hideLoading();
+		        
+		        if (err) throw new Error('网络请求失败');
+		        
+		        if (res.data && res.data.code === 200) {
+		          uni.showToast({ title: '拉黑成功', icon: 'success' });
+		          this.closeBlockConfirm();
+		          
+		          // 触发拉黑列表更新事件,通知消息页面实时刷新
+		          uni.$emit('blacklistUpdated');
+		          
+		          // 拉黑成功后立即返回消息列表页面(优化体验)
+		          setTimeout(() => {
+		            uni.navigateBack({
+		              delta: 1,
+		              success: () => {
+		                // 返回成功后再次触发一次刷新,确保万无一失
+		                uni.$emit('blacklistUpdated');
+		              }
+		            });
+		          }, 1500);
+		        } else {
+		          throw new Error(res.data?.message || '拉黑失败');
+		        }
+		      } catch (error) {
+		        console.error('拉黑失败:', error);
+		        uni.showToast({ title: error.message || '拉黑失败', icon: 'none' });
+		      }
+		    },
   }
 };
 </script>
 
 <style scoped>
+	/* 弹窗遮罩 */
+	.modal-mask {
+	  position: fixed;
+	  top: 0;
+	  left: 0;
+	  right: 0;
+	  bottom: 0;
+	  background-color: rgba(0, 0, 0, 0.5);
+	  z-index: 10000;
+	  display: flex;
+	  align-items: center;
+	  justify-content: center;
+	}
+	
+	/* 更多选项弹窗内容 */
+	.modal-content {
+	  width: 70%;
+	  background-color: #fff;
+	  border-radius: 20rpx;
+	  overflow: hidden;
+	}
+	
+	.modal-header {
+	  padding: 30rpx;
+	  text-align: center;
+	  border-bottom: 1px solid #eee;
+	}
+	
+	.modal-title {
+	  font-size: 32rpx;
+	  font-weight: bold;
+	  display: block;
+	}
+	
+	.modal-subtitle {
+	  font-size: 24rpx;
+	  color: #999;
+	  display: block;
+	  margin-top: 10rpx;
+	}
+	
+	.modal-body {
+	  padding: 20rpx 0;
+	}
+	
+	.modal-item {
+	  display: flex;
+	  align-items: center;
+	  padding: 25rpx 40rpx;
+	  font-size: 30rpx;
+	}
+	
+	.modal-item.danger {
+	  color: #fa5151;
+	}
+	
+	.item-icon {
+	  font-size: 32rpx;
+	  margin-right: 20rpx;
+	}
+	
+	.modal-footer {
+	  padding: 20rpx;
+	  border-top: 1px solid #eee;
+	}
+	
+	.modal-close-btn {
+	  width: 100%;
+	  background-color: #f5f5f5;
+	  border: none;
+	  border-radius: 10rpx;
+	  padding: 20rpx;
+	  font-size: 30rpx;
+	}
+	
+	/* 确认弹窗样式 */
+	.confirm-modal {
+	  width: 60%;
+	  background-color: #fff;
+	  border-radius: 20rpx;
+	  padding: 40rpx;
+	  text-align: center;
+	}
+	
+	.confirm-title {
+	  font-size: 32rpx;
+	  font-weight: bold;
+	  margin-bottom: 30rpx;
+	}
+	
+	.confirm-content {
+	  font-size: 28rpx;
+	  color: #666;
+	  margin-bottom: 40rpx;
+	  line-height: 1.5;
+	}
+	
+	.confirm-buttons {
+	  display: flex;
+	  justify-content: space-between;
+	}
+	
+	.confirm-btn {
+	  flex: 1;
+	  margin: 0 10rpx;
+	  padding: 20rpx;
+	  border-radius: 10rpx;
+	  border: none;
+	  font-size: 28rpx;
+	}
+	
+	.confirm-btn.cancel {
+	  background-color: #f5f5f5;
+	}
+	
+	.confirm-btn.confirm {
+	  background-color: #fa5151;
+	  color: #fff;
+	}
 .chat-page {
   display: flex;
   flex-direction: column;

+ 133 - 15
LiangZhiYUMao/pages/message/index.vue

@@ -199,6 +199,8 @@ import TIM from 'tim-wx-sdk';
 export default {
   data() {
     return {
+	  blacklistUsers: [], // 拉黑用户ID列表
+	  blacklistLoaded: false, // 拉黑列表是否加载完成
       userId: null,
       conversations: [],
       loading: false,
@@ -328,6 +330,7 @@ export default {
     
     // 监听刷新事件(从聊天页面返回时触发)
     uni.$on('refreshConversations', this.refreshConversations);
+	uni.$on('blacklistUpdated', this.handleBlacklistUpdate);
   },
   
   onShow() {
@@ -385,9 +388,37 @@ export default {
     
     // 移除事件监听
     uni.$off('refreshConversations', this.refreshConversations);
+	// 移除拉黑状态变更监听
+	  uni.$off('blacklistUpdated', this.handleBlacklistUpdate);
   },
   
   methods: {
+	  
+	  /**
+	     * 处理拉黑列表更新事件
+	     */
+	    async handleBlacklistUpdate() {
+	      console.log('🔄 检测到拉黑列表更新,开始刷新会话列表');
+	      
+	      // 重置拉黑列表加载状态
+	      this.blacklistLoaded = false;
+	      
+	      // 重新加载拉黑列表
+	      await this.loadBlacklist();
+	      
+	      // 重新加载系统消息未读
+	      await this.loadSystemUnread();
+	      
+	      // 重新加载会话列表(会自动过滤拉黑用户)
+	      this.loadConversations();
+	      
+	      // 显示提示
+	      uni.showToast({
+	        title: '已更新会话列表',
+	        icon: 'success',
+	        duration: 1500
+	      });
+	    },
     /**
      * 初始化腾讯云IM
      */
@@ -495,7 +526,6 @@ export default {
         throw error;
       }
     },
-    
     /**
      * 加载会话列表(从腾讯云IM)
      */
@@ -505,6 +535,11 @@ export default {
         return;
       }
       
+      // 确保先加载拉黑列表
+      if (!this.blacklistLoaded) {
+        await this.loadBlacklist();
+      }
+      
       // 验证登录状态
       if (!timManager.isLogin || !timManager.userId) {
         console.error('❌ TIM 未登录或用户ID为空');
@@ -546,7 +581,7 @@ export default {
         console.log('📋 从TIM获取到会话数:', conversationList.length);
         
         // 转换为UI需要的格式
-        this.conversations = conversationList.map(conv => {
+        let formattedConversations = conversationList.map(conv => {
           const formatted = this.formatConversation(conv);
           // 验证会话是否属于当前用户
           if (formatted.userId !== this.userId) {
@@ -556,27 +591,47 @@ export default {
         });
         
         // 过滤掉不属于当前用户的会话(双重保险)
-        this.conversations = this.conversations.filter(conv => conv.userId === this.userId);
+        formattedConversations = formattedConversations.filter(conv => conv.userId === this.userId);
+        
+        // 先计算所有会话的未读数(包括拉黑用户)
+        const allUnreadCount = formattedConversations.reduce((total, conv) => total + conv.unreadCount, 0);
+        
+        // 过滤掉拉黑用户的会话 🌟
+        formattedConversations = formattedConversations.filter(conv => {
+          return !this.blacklistUsers.includes(conv.targetUserId.toString());
+        });
+        
+        this.conversations = formattedConversations;
         
         // 批量获取用户头像信息
         await this.loadUserAvatars();
         
-        // 计算总未读数
-        this.totalUnread = this.conversations.reduce((total, conv) => total + conv.unreadCount, 0);
+        // 计算总未读数(包括所有会话的未读 + 系统消息未读)
+        this.totalUnread = allUnreadCount + this.systemUnread;
+        
+        // 同步到Vuex
+        this.$store.dispatch('updateTotalUnread', this.totalUnread);
         
         console.log('✅ 会话列表加载成功');
         console.log('   - 当前用户ID:', this.userId);
         console.log('   - 会话数量:', this.conversations.length);
+        console.log('   - 拉黑用户数:', this.blacklistUsers.length);
+        console.log('   - 会话未读数:', allUnreadCount);
+        console.log('   - 系统消息未读数:', this.systemUnread);
         console.log('   - 总未读数:', this.totalUnread);
         
         // 输出前3个会话的详细信息(调试用)
         if (this.conversations.length > 0) {
           console.log('📋 会话预览(前3个):');
           this.conversations.slice(0, 3).forEach((conv, idx) => {
-            console.log(`   ${idx + 1}. ${conv.targetUserName} (ID:${conv.targetUserId}) 头像: ${conv.targetUserAvatar}`);
+            console.log(`   ${idx + 1}. ${conv.targetUserName} (ID:${conv.targetUserId}) 头像: ${conv.targetUserAvatar}, 未读: ${conv.unreadCount}`);
           });
         }
         
+        if (this.blacklistUsers.length > 0) {
+          console.log('🚫 已过滤拉黑用户:', this.blacklistUsers);
+        }
+        
       } catch (e) {
         console.error('❌ 加载会话列表失败:', e);
         console.error('❌ 错误详情:', e.message || e);
@@ -591,6 +646,41 @@ export default {
       }
     },
     
+    /**
+     * 获取拉黑用户列表
+     */
+    async loadBlacklist() {
+      try {
+        console.log('🔄 加载拉黑用户列表,用户ID:', this.userId);
+        
+        const res = await uni.request({
+          url: 'http://localhost:8083/api/chatfriend/getBlacklist', // 替换为你的后端接口
+          method: 'GET',
+          data: {
+            userId: this.userId
+          }
+        });
+        
+        if (res[1].statusCode === 200 && res[1].data.code === 200) {
+          // 假设返回格式: {data: [{targetUserId: 'xxx'}, ...]}
+          const blacklistData = res[1].data.data || [];
+          this.blacklistUsers = blacklistData.map(item => {
+            // 确保用户ID为字符串格式,避免类型不一致问题
+            return String(item.targetUserId || item.userId || item.id);
+          });
+          
+          console.log('✅ 拉黑列表加载成功,共', this.blacklistUsers.length, '个拉黑用户:', this.blacklistUsers);
+        } else {
+          console.warn('⚠️ 拉黑列表接口返回异常:', res[1].data);
+        }
+      } catch (error) {
+        console.error('❌ 加载拉黑列表失败:', error);
+        // 加载失败不影响会话列表显示,只记录错误
+      } finally {
+        this.blacklistLoaded = true; // 标记为已加载,避免重复请求
+      }
+    },
+    
     /**
      * 批量获取用户头像信息
      */
@@ -741,6 +831,9 @@ export default {
       // TIM SDK 一次性返回所有会话,不需要分页加载
     },
 
+    /**
+     * 加载系统消息未读
+     */
     async loadSystemUnread() {
       try {
         const userId = this.userId;
@@ -753,6 +846,7 @@ export default {
           const r = await api.message.getSystemUnreadCount(userId);
           this.systemUnread = r.data || 0;
         }
+        
         // 预览最新一条
         const api2 = require('@/utils/api.js').default;
         const listRes = await api2.message.getSystemList(userId, 1, 1);
@@ -761,7 +855,17 @@ export default {
           this.systemLatestTime = list[0].createdAt || list[0].created_at;
           this.systemLatestPreview = list[0].title || '系统通知';
         }
-      } catch (e) { console.log('加载系统未读失败', e); }
+        
+        // 更新总未读数
+        const allUnreadCount = this.conversations.reduce((total, conv) => total + conv.unreadCount, 0);
+        this.totalUnread = allUnreadCount + this.systemUnread;
+        
+        // 同步到Vuex
+        this.$store.dispatch('updateTotalUnread', this.totalUnread);
+        
+      } catch (e) { 
+        console.log('加载系统未读失败', e); 
+      }
     },
 
     openSystemMessages() {
@@ -773,6 +877,9 @@ export default {
      */
     refreshConversations() {
       if (this.timInitialized) {
+        // 先刷新系统消息未读
+        this.loadSystemUnread();
+        // 再刷新会话列表
         this.loadConversations();
       }
     },
@@ -817,8 +924,12 @@ export default {
         // 更新本地显示
         this.selectedConversation.unreadCount = 0;
         
-        // 重新计算总未读数
-        this.totalUnread = this.conversations.reduce((total, conv) => total + conv.unreadCount, 0);
+        // 重新计算总未读数(包含所有会话+系统消息)
+        const allUnreadCount = this.conversations.reduce((total, conv) => total + conv.unreadCount, 0);
+        this.totalUnread = allUnreadCount + this.systemUnread;
+        
+        // 同步到Vuex
+        this.$store.dispatch('updateTotalUnread', this.totalUnread);
         
         uni.showToast({
           title: '已标记为已读',
@@ -854,8 +965,12 @@ export default {
                 this.conversations.splice(index, 1);
               }
               
-              // 重新计算总未读数
-              this.totalUnread = this.conversations.reduce((total, conv) => total + conv.unreadCount, 0);
+              // 重新计算总未读数(包含所有会话+系统消息)
+              const allUnreadCount = this.conversations.reduce((total, conv) => total + conv.unreadCount, 0);
+              this.totalUnread = allUnreadCount + this.systemUnread;
+              
+              // 同步到Vuex
+              this.$store.dispatch('updateTotalUnread', this.totalUnread);
               
               uni.showToast({
                 title: '删除成功',
@@ -992,8 +1107,12 @@ export default {
           this.conversations.splice(index, 1);
         }
         
-        // 重新计算总未读数
-        this.totalUnread = this.conversations.reduce((total, c) => total + c.unreadCount, 0);
+        // 重新计算总未读数(包含所有会话+系统消息)
+        const allUnreadCount = this.conversations.reduce((total, c) => total + c.unreadCount, 0);
+        this.totalUnread = allUnreadCount + this.systemUnread;
+        
+        // 同步到Vuex
+        this.$store.dispatch('updateTotalUnread', this.totalUnread);
         
         uni.showToast({
           title: '删除成功',
@@ -1008,7 +1127,6 @@ export default {
       }
     },
     
-    
     /**
      * 格式化最后一条消息
      */
@@ -1526,4 +1644,4 @@ export default {
     }
   }
 }
-</style>
+</style>

+ 57 - 40
LiangZhiYUMao/pages/mine/index.vue

@@ -219,6 +219,7 @@
 			<view class="tabbar-item" @click="switchTab('message')">
 				<text class="tabbar-icon">💬</text>
 				<text class="tabbar-text">消息</text>
+				<view v-if="unreadCount > 0" class="tabbar-badge">{{ unreadCount }}</view>
 			</view>
 			<view class="tabbar-item active" @click="switchTab('mine')">
 				<text class="tabbar-icon">👤</text>
@@ -277,6 +278,11 @@
 				checkinRewards: []
 			}
 		},
+		computed: {
+		  unreadCount() {
+		    return this.$store.getters.getTotalUnread || 0;
+		  }
+		},
 	onLoad() {
 		console.log('=== 我的页面加载开始 ===')
 		
@@ -1797,50 +1803,61 @@
 	
 	/* 底部导航栏 - 扁平化 */
 	.tabbar {
-		position: fixed;
-		bottom: 0;
-		left: 0;
-		right: 0;
-		display: flex;
-		background-color: #FFFFFF;
-		border-top: 2rpx solid #E0E0E0;
-		padding-bottom: constant(safe-area-inset-bottom);
-		padding-bottom: env(safe-area-inset-bottom);
-		z-index: 999;
-
-		.tabbar-item {
-			flex: 1;
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			right: 0;
 			display: flex;
-			flex-direction: column;
-			align-items: center;
-			justify-content: center;
-			padding: 18rpx 0;
-			transition: all 0.2s ease;
-
-			&:active {
-				background: #F5F5F5;
-			}
-
-			.tabbar-icon {
-				font-size: 46rpx;
-				margin-bottom: 8rpx;
-			}
-
-			.tabbar-text {
-				font-size: 22rpx;
-				color: #666666;
-				font-weight: 500;
-			}
-
-			&.active {
-				background: #FFE5F1;
-				
+			background-color: #FFFFFF;
+			border-top: 1rpx solid #F0F0F0;
+			padding-bottom: constant(safe-area-inset-bottom);
+			padding-bottom: env(safe-area-inset-bottom);
+			z-index: 999;
+			box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+	
+			.tabbar-item {
+				flex: 1;
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				justify-content: center;
+				padding: 15rpx 0;
+				position: relative;
+	
+				.tabbar-icon {
+					font-size: 44rpx;
+					margin-bottom: 5rpx;
+				}
+	
 				.tabbar-text {
-					color: #E91E63;
-					font-weight: 700;
+					font-size: 22rpx;
+					color: #666666;
+				}
+	
+				&.active {
+					.tabbar-text {
+						color: #E91E63;
+						font-weight: bold;
+					}
 				}
 			}
 		}
-	}
+	
+	
+	.tabbar-badge {
+	      position: absolute;
+	      top: 8rpx;
+	      right: 50%;
+	      margin-right: -40rpx;
+	      min-width: 32rpx;
+	      height: 32rpx;
+	      line-height: 32rpx;
+	      padding: 0 6rpx;
+	      background-color: #FA5151;
+	      border-radius: 16rpx;
+	      font-size: 20rpx;
+	      color: #FFFFFF;
+	      text-align: center;
+	    }
 </style>
 

+ 56 - 34
LiangZhiYUMao/pages/plaza/index.vue

@@ -153,6 +153,7 @@
 			<view class="tabbar-item" @click="switchTab('message')">
 				<text class="tabbar-icon">💬</text>
 				<text class="tabbar-text">消息</text>
+				<view v-if="unreadCount > 0" class="tabbar-badge">{{ unreadCount }}</view>
 			</view>
 			<view class="tabbar-item" @click="switchTab('mine')">
 				<text class="tabbar-icon">👤</text>
@@ -182,7 +183,11 @@ export default {
 			favoritingMap: {}  // 记录正在收藏的动态ID,防止重复点击
 		}
 	},
-	
+	computed: {
+    unreadCount() {
+      return this.$store.getters.getTotalUnread || 0;
+    }
+  },
 	onLoad() {
 		this.loadDynamicList()
 		// 监听详情页更新事件,实时同步点赞/收藏状态
@@ -833,44 +838,45 @@ export default {
 	
 	// 底部导航栏
 	.tabbar {
-		position: fixed;
-		bottom: 0;
-		left: 0;
-		right: 0;
-		display: flex;
-		background-color: #FFFFFF;
-		border-top: 1rpx solid #F0F0F0;
-		padding-bottom: constant(safe-area-inset-bottom);
-		padding-bottom: env(safe-area-inset-bottom);
-		z-index: 999;
-		box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
-
-		.tabbar-item {
-			flex: 1;
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			right: 0;
 			display: flex;
-			flex-direction: column;
-			align-items: center;
-			justify-content: center;
-			padding: 15rpx 0;
-
-			.tabbar-icon {
-				font-size: 44rpx;
-				margin-bottom: 5rpx;
-			}
-
-			.tabbar-text {
-				font-size: 22rpx;
-				color: #666666;
-			}
-
-			&.active {
+			background-color: #FFFFFF;
+			border-top: 1rpx solid #F0F0F0;
+			padding-bottom: constant(safe-area-inset-bottom);
+			padding-bottom: env(safe-area-inset-bottom);
+			z-index: 999;
+			box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+	
+			.tabbar-item {
+				flex: 1;
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				justify-content: center;
+				padding: 15rpx 0;
+				position: relative;
+	
+				.tabbar-icon {
+					font-size: 44rpx;
+					margin-bottom: 5rpx;
+				}
+	
 				.tabbar-text {
-					color: #E91E63;
-					font-weight: bold;
+					font-size: 22rpx;
+					color: #666666;
+				}
+	
+				&.active {
+					.tabbar-text {
+						color: #E91E63;
+						font-weight: bold;
+					}
 				}
 			}
 		}
-	}
 }
 
 // 动画
@@ -900,4 +906,20 @@ export default {
 		transform: translateY(0);
 	}
 }
+
+.tabbar-badge {
+      position: absolute;
+      top: 8rpx;
+      right: 50%;
+      margin-right: -40rpx;
+      min-width: 32rpx;
+      height: 32rpx;
+      line-height: 32rpx;
+      padding: 0 6rpx;
+      background-color: #FA5151;
+      border-radius: 16rpx;
+      font-size: 20rpx;
+      color: #FFFFFF;
+      text-align: center;
+    }
 </style>

+ 80 - 13
LiangZhiYUMao/pages/recommend/index.vue

@@ -127,6 +127,7 @@
 			<view class="tabbar-item" @click="switchTab('message')">
 				<text class="tabbar-icon">💬</text>
 				<text class="tabbar-text">消息</text>
+				<view v-if="unreadCount > 0" class="tabbar-badge">{{ unreadCount }}</view>
 			</view>
 			<view class="tabbar-item" @click="switchTab('mine')">
 				<text class="tabbar-icon">👤</text>
@@ -166,6 +167,12 @@ export default {
       provinceList: [], cityList: [], areaList: [], currentProvinceName: '', currentCityName: '', currentAreaName: ''
     }
 	},
+	computed: {
+	    unreadCount() {
+	      return this.$store.getters.getTotalUnread || 0;
+	    }
+	  },
+	
 	onLoad() { this.refresh() },
 	methods: {
 		// 切换选项卡
@@ -657,7 +664,21 @@ export default {
 			opacity: 0.8;
 		}
 	}
-	
+	.tabbar-badge {
+	  position: absolute;
+	  top: 8rpx;
+	  right: 25%;
+	  min-width: 36rpx;
+	  height: 36rpx;
+	  line-height: 36rpx;
+	  padding: 0 8rpx;
+	  background: #FF6B6B;
+	  color: #FFFFFF;
+	  font-size: 20rpx;
+	  font-weight: bold;
+	  border-radius: 18rpx;
+	  text-align: center;
+	}
 	.act-btn.ghost{ 
 		background: #F5F5F5; 
 		color: #666;
@@ -671,18 +692,46 @@ export default {
 	}
 
 	/* 底部导航栏 - 扁平化 */
-	.tabbar { 
-		position: fixed; 
-		bottom: 0; 
-		left: 0; 
-		right: 0; 
-		display: flex; 
-		background: #FFFFFF; 
-		border-top: 2rpx solid #E0E0E0; 
-		padding-bottom: constant(safe-area-inset-bottom); 
-		padding-bottom: env(safe-area-inset-bottom); 
-		z-index: 999;
-	}
+	.tabbar {
+				position: fixed;
+				bottom: 0;
+				left: 0;
+				right: 0;
+				display: flex;
+				background-color: #FFFFFF;
+				border-top: 1rpx solid #F0F0F0;
+				padding-bottom: constant(safe-area-inset-bottom);
+				padding-bottom: env(safe-area-inset-bottom);
+				z-index: 999;
+				box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+		
+				.tabbar-item {
+					flex: 1;
+					display: flex;
+					flex-direction: column;
+					align-items: center;
+					justify-content: center;
+					padding: 15rpx 0;
+					position: relative;
+		
+					.tabbar-icon {
+						font-size: 44rpx;
+						margin-bottom: 5rpx;
+					}
+		
+					.tabbar-text {
+						font-size: 22rpx;
+						color: #666666;
+					}
+		
+					&.active {
+						.tabbar-text {
+							color: #E91E63;
+							font-weight: bold;
+						}
+					}
+				}
+			}
 	
 	.tabbar-item { 
 		flex: 1; 
@@ -850,5 +899,23 @@ export default {
 		color: #FFFFFF;
 		border: none;
 	}
+	
+	
+	.tabbar-badge {
+	      position: absolute;
+	      top: 8rpx;
+	      right: 50%;
+	      margin-right: -40rpx;
+	      min-width: 32rpx;
+	      height: 32rpx;
+	      line-height: 32rpx;
+	      padding: 0 6rpx;
+	      background-color: #FA5151;
+	      border-radius: 16rpx;
+	      font-size: 20rpx;
+	      color: #FFFFFF;
+	      text-align: center;
+	    }
+
 </style>
 

+ 26 - 0
LiangZhiYUMao/store/index.js

@@ -0,0 +1,26 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+Vue.use(Vuex)
+
+export default new Vuex.Store({
+  state: {
+    totalUnread: 0 // 全局未读数(存储所有页面需要共享的值)
+  },
+  mutations: {
+    // 更新未读数
+    SET_TOTAL_UNREAD(state, count) {
+      state.totalUnread = count
+    }
+  },
+  actions: {
+    // 触发更新未读数的动作
+    updateTotalUnread({ commit }, count) {
+      commit('SET_TOTAL_UNREAD', count)
+    }
+  },
+  getters: {
+    // 获取全局未读数
+    getTotalUnread: state => state.totalUnread
+  }
+})

+ 10 - 1
gateway/src/main/resources/application.yml

@@ -103,6 +103,14 @@ spring:
           filters:
             - StripPrefix=0
 
+        # 聊天REST API路由
+        - id: chat-api-routev
+          uri: http://localhost:1004
+          predicates:
+            - Path=/api/chatfriend/**
+          filters:
+            - StripPrefix=0
+
         # 首页服务路由(兜底路由)
         - id: homepage-route
           uri: http://localhost:8081
@@ -110,7 +118,8 @@ spring:
             - Path=/api/**
           filters:
             - StripPrefix=0
-      
+
+
       # WebSocket支持配置
       websocket:
         enabled: true

+ 10 - 1
gateway/target/classes/application.yml

@@ -103,6 +103,14 @@ spring:
           filters:
             - StripPrefix=0
 
+        # 聊天REST API路由
+        - id: chat-api-routev
+          uri: http://localhost:1004
+          predicates:
+            - Path=/api/chatfriend/**
+          filters:
+            - StripPrefix=0
+
         # 首页服务路由(兜底路由)
         - id: homepage-route
           uri: http://localhost:8081
@@ -110,7 +118,8 @@ spring:
             - Path=/api/**
           filters:
             - StripPrefix=0
-      
+
+
       # WebSocket支持配置
       websocket:
         enabled: true

+ 121 - 15
service/login/src/main/java/com/zhentao/service/impl/WeChatServiceImpl.java

@@ -42,14 +42,22 @@ public class WeChatServiceImpl implements WeChatService {
 
     @Override
     public Map<String, Object> code2Session(String code) {
+        log.info("=== 开始调用微信 code2Session ===");
+        log.info("接收到的 code: {}", code);
+        log.info("code 长度: {}", code != null ? code.length() : 0);
+        log.info("Mock 模式: {}", mockEnabled);
+        
         Map<String, Object> result = new HashMap<>();
         if (code == null || code.trim().isEmpty()) {
+            log.error("❌ code 为空");
             result.put("errcode", 400);
             result.put("errmsg", "code 不能为空");
             return result;
         }
+        
         // 开发环境 Mock:无需真实 appid/secret 与外网请求
         if (mockEnabled) {
+            log.info("✅ 使用 Mock 模式");
             Map<String, Object> mock = new HashMap<>();
             mock.put("errcode", 0);
             mock.put("openid", "mock_openid_" + code.trim());
@@ -57,29 +65,74 @@ public class WeChatServiceImpl implements WeChatService {
             mock.put("unionid", null);
             return mock;
         }
+        
         if (appId == null || appId.isEmpty() || secret == null || secret.isEmpty()) {
+            log.error("❌ 未配置 appid 或 secret");
+            log.error("appId: {}", appId != null ? (appId.isEmpty() ? "空字符串" : "已配置") : "null");
+            log.error("secret: {}", secret != null ? (secret.isEmpty() ? "空字符串" : "已配置") : "null");
             result.put("errcode", 500);
             result.put("errmsg", "未配置 wechat.appid/secret");
             return result;
         }
+        
+        log.info("✅ 配置检查通过");
+        log.info("appId: {}", appId);
+        log.info("secret: {}...", secret.substring(0, Math.min(8, secret.length())));
+        
         String url = String.format("%s?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
-                jscode2sessionUrl, appId, secret, code);
+                jscode2sessionUrl, appId, secret, code.trim());
+        log.info("请求URL: {}", url.replace(secret, "***"));
+        
         try (CloseableHttpClient client = HttpClients.createDefault()) {
             HttpGet get = new HttpGet(url);
+            log.info("发送HTTP请求到微信服务器...");
+            
             try (CloseableHttpResponse resp = client.execute(get)) {
+                int statusCode = resp.getStatusLine().getStatusCode();
+                log.info("HTTP状态码: {}", statusCode);
+                
                 String body = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8);
+                log.info("微信返回原始响应: {}", body);
+                
                 if (body == null || body.isEmpty()) {
+                    log.error("❌ 微信返回空响应");
                     Map<String, Object> err = new HashMap<>();
                     err.put("errcode", 500);
                     err.put("errmsg", "微信返回空响应");
                     return err;
                 }
+                
                 ObjectMapper mapper = new ObjectMapper();
                 Map<String, Object> parsed = mapper.readValue(body, new TypeReference<Map<String, Object>>(){});
+                
+                // 检查是否有错误
+                if (parsed.containsKey("errcode")) {
+                    Integer errcode = (Integer) parsed.get("errcode");
+                    if (errcode != 0) {
+                        String errmsg = (String) parsed.get("errmsg");
+                        log.error("❌ 微信接口返回错误");
+                        log.error("错误码: {}", errcode);
+                        log.error("错误信息: {}", errmsg);
+                        
+                        // 针对常见错误给出提示
+                        if (errcode == 40029) {
+                            log.error("提示: code 无效,可能原因:");
+                            log.error("  1. code 已被使用过(每个 code 只能使用一次)");
+                            log.error("  2. code 已过期(5分钟有效期)");
+                            log.error("  3. code 与 appid 不匹配");
+                        } else if (errcode == 40163) {
+                            log.error("提示: code 已被使用");
+                        }
+                    } else {
+                        log.info("✅ 微信接口调用成功");
+                        log.info("openid: {}", parsed.get("openid"));
+                    }
+                }
+                
                 return parsed != null ? parsed : new HashMap<>();
             }
         } catch (Exception e) {
-            log.error("调用 jscode2session 失败", e);
+            log.error("❌ 调用 jscode2session 异常", e);
             Map<String, Object> err = new HashMap<>();
             err.put("errcode", 500);
             err.put("errmsg", "调用 jscode2session 异常:" + e.getMessage());
@@ -122,38 +175,91 @@ public class WeChatServiceImpl implements WeChatService {
 
     @Override
     public String getUserPhoneByCode(String code) {
+        log.info("=== 开始获取微信手机号 ===");
+        log.info("接收到的 phoneCode: {}", code);
+        log.info("Mock 模式: {}", mockEnabled);
+        
         // 开发环境 Mock:返回演示手机号
         if (mockEnabled) {
+            log.info("✅ 使用 Mock 模式,返回测试手机号");
             return "13800138000";
         }
-        if (code == null || code.trim().isEmpty()) return null;
+        
+        if (code == null || code.trim().isEmpty()) {
+            log.error("❌ phoneCode 为空");
+            return null;
+        }
+        
+        log.info("获取 access_token...");
         String accessToken = getAccessToken();
-        if (accessToken == null) return null;
+        if (accessToken == null) {
+            log.error("❌ 获取 access_token 失败");
+            return null;
+        }
+        log.info("✅ access_token 获取成功: {}...", accessToken.substring(0, Math.min(20, accessToken.length())));
+        
         String url = String.format("%s?access_token=%s", getPhoneUrl, accessToken);
+        log.info("请求URL: {}", url.replace(accessToken, "***"));
+        
         try (CloseableHttpClient client = HttpClients.createDefault()) {
             // 微信该接口要求 POST JSON:{"code":"xxx"}
             org.apache.http.client.methods.HttpPost post = new org.apache.http.client.methods.HttpPost(url);
             post.setHeader("Content-Type", "application/json;charset=UTF-8");
-            String payload = "{\"code\":\"" + code + "\"}";
+            String payload = "{\"code\":\"" + code.trim() + "\"}";
+            log.info("请求体: {}", payload);
             post.setEntity(new org.apache.http.entity.StringEntity(payload, StandardCharsets.UTF_8));
+            
+            log.info("发送HTTP POST请求到微信服务器...");
             try (CloseableHttpResponse resp = client.execute(post)) {
+                int statusCode = resp.getStatusLine().getStatusCode();
+                log.info("HTTP状态码: {}", statusCode);
+                
                 String body = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8);
-                if (body == null || body.isEmpty()) return null;
+                log.info("微信返回原始响应: {}", body);
+                
+                if (body == null || body.isEmpty()) {
+                    log.error("❌ 微信返回空响应");
+                    return null;
+                }
+                
                 ObjectMapper mapper = new ObjectMapper();
                 Map<String, Object> parsed = mapper.readValue(body, new TypeReference<Map<String, Object>>(){});
-                // 期望结构:{"code":0,"data":{"phoneNumber":"xxxx","purePhoneNumber":"xxxx"}}
-                Object codeField = parsed.get("code");
-                if (codeField instanceof Number && ((Number) codeField).intValue() == 0) {
-                    Object data = parsed.get("data");
-                    if (data instanceof Map) {
-                        Object phone = ((Map) data).get("phoneNumber");
-                        if (phone == null) phone = ((Map) data).get("purePhoneNumber");
-                        return phone != null ? String.valueOf(phone) : null;
+                
+                // 检查错误码
+                Object errcode = parsed.get("errcode");
+                if (errcode != null) {
+                    int errcodeInt = ((Number) errcode).intValue();
+                    if (errcodeInt != 0) {
+                        String errmsg = parsed.get("errmsg") != null ? String.valueOf(parsed.get("errmsg")) : "未知错误";
+                        log.error("❌ 微信接口返回错误");
+                        log.error("错误码: {}", errcodeInt);
+                        log.error("错误信息: {}", errmsg);
+                        return null;
+                    }
+                }
+                
+                // 微信返回结构:{"errcode":0,"errmsg":"ok","phone_info":{"phoneNumber":"xxxx","purePhoneNumber":"xxxx","countryCode":"86","watermark":{...}}}
+                Object phoneInfo = parsed.get("phone_info");
+                if (phoneInfo instanceof Map) {
+                    Map<String, Object> phoneMap = (Map<String, Object>) phoneInfo;
+                    Object phone = phoneMap.get("phoneNumber");
+                    if (phone == null) phone = phoneMap.get("purePhoneNumber");
+                    
+                    if (phone != null) {
+                        String phoneStr = String.valueOf(phone);
+                        log.info("✅ 成功获取手机号: {}", phoneStr);
+                        return phoneStr;
+                    } else {
+                        log.error("❌ phone_info 中没有 phoneNumber 或 purePhoneNumber");
+                        log.error("phone_info 内容: {}", phoneMap);
                     }
+                } else {
+                    log.error("❌ 响应中没有 phone_info 字段或格式错误");
+                    log.error("响应内容: {}", parsed);
                 }
             }
         } catch (Exception e) {
-            log.error("通过 code 获取手机号失败", e);
+            log.error("❌ 通过 code 获取手机号异常", e);
         }
         return null;
     }

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

@@ -62,9 +62,9 @@ mybatis-plus:
 # --- WeChat 集成配置 ---
 wechat:
   mock:
-    enabled: true   # 暂时启用 Mock,避免未提供 secret 导致 502
-  appid: wx8454588818fa7b30
-  secret: ${WECHAT_SECRET:}  # 出于安全,默认从环境变量注入,未提供则为空
+    enabled: false   # 暂时启用 Mock,避免未提供 secret 导致 502
+  appid: wx3e90d662a801266e
+  secret: d82ce405f04a47de14382bef4180239d  # 出于安全,默认从环境变量注入,未提供则为空
 
 # --- MinIO 配置 ---
 minio:

+ 27 - 0
service/websocket/src/main/java/com/zhentao/controller/ChatFriendController.java

@@ -0,0 +1,27 @@
+package com.zhentao.controller;
+
+import com.zhentao.dto.BlockDto;
+import com.zhentao.service.ChatFriendService;
+import com.zhentao.vo.ResultVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/chatfriend")
+public class ChatFriendController {
+    @Autowired
+    private ChatFriendService chatFriendService;
+
+    @PostMapping("/block")
+    public ResultVo block(@RequestBody BlockDto blockDto){
+        return chatFriendService.block(blockDto);
+    }
+
+    @GetMapping("/getBlacklist")
+    public ResultVo getBlacklist(@RequestParam("userId") Long userId){
+        return chatFriendService.getBlacklist(userId);
+    }
+
+}

+ 24 - 0
service/websocket/src/main/java/com/zhentao/dto/BlockDto.java

@@ -0,0 +1,24 @@
+package com.zhentao.dto;
+
+import afu.org.checkerframework.checker.igj.qual.I;
+import lombok.Data;
+
+@Data
+public class BlockDto {
+    /**
+     * 用户id
+     */
+    private Long userId;
+    /**
+     * 好友id
+     */
+    private Long targetUserId;
+    /**
+     * 好友的备注名
+     */
+    private String targetUserName;
+    /**
+     * 好友头像
+     */
+    private String targetUserAvatar;
+}

+ 120 - 0
service/websocket/src/main/java/com/zhentao/entity/ChatFriend.java

@@ -0,0 +1,120 @@
+package com.zhentao.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 好友关系表
+ * @TableName chat_friend
+ */
+@TableName(value ="chat_friend")
+@Data
+public class ChatFriend {
+    /**
+     * 好友关系ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 好友ID
+     */
+    private Long friendId;
+
+    /**
+     * 好友备注名
+     */
+    private String friendName;
+
+    /**
+     * 好友头像
+     */
+    private String friendAvatar;
+
+    /**
+     * 好友状态(1:正常 2:已拉黑)
+     */
+    private Integer status;
+
+    /**
+     * 是否删除(0:否 1:是)
+     */
+    private Integer isDeleted;
+
+    /**
+     * 添加时间
+     */
+    private Date createTime;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        ChatFriend other = (ChatFriend) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
+            && (this.getFriendId() == null ? other.getFriendId() == null : this.getFriendId().equals(other.getFriendId()))
+            && (this.getFriendName() == null ? other.getFriendName() == null : this.getFriendName().equals(other.getFriendName()))
+            && (this.getFriendAvatar() == null ? other.getFriendAvatar() == null : this.getFriendAvatar().equals(other.getFriendAvatar()))
+            && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
+            && (this.getIsDeleted() == null ? other.getIsDeleted() == null : this.getIsDeleted().equals(other.getIsDeleted()))
+            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
+            && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
+        result = prime * result + ((getFriendId() == null) ? 0 : getFriendId().hashCode());
+        result = prime * result + ((getFriendName() == null) ? 0 : getFriendName().hashCode());
+        result = prime * result + ((getFriendAvatar() == null) ? 0 : getFriendAvatar().hashCode());
+        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
+        result = prime * result + ((getIsDeleted() == null) ? 0 : getIsDeleted().hashCode());
+        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
+        result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", userId=").append(userId);
+        sb.append(", friendId=").append(friendId);
+        sb.append(", friendName=").append(friendName);
+        sb.append(", friendAvatar=").append(friendAvatar);
+        sb.append(", status=").append(status);
+        sb.append(", isDeleted=").append(isDeleted);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 20 - 0
service/websocket/src/main/java/com/zhentao/mapper/ChatFriendMapper.java

@@ -0,0 +1,20 @@
+package com.zhentao.mapper;
+
+import com.zhentao.entity.ChatFriend;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* @author 26621
+* @description 针对表【chat_friend(好友关系表)】的数据库操作Mapper
+* @createDate 2025-11-27 14:04:39
+* @Entity com.zhentao.entity.ChatFriend
+*/
+@Mapper
+public interface ChatFriendMapper extends BaseMapper<ChatFriend> {
+
+}
+
+
+
+

+ 9 - 0
service/websocket/src/main/java/com/zhentao/service/ChatConversationService.java

@@ -0,0 +1,9 @@
+package com.zhentao.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.dto.BlockDto;
+import com.zhentao.entity.ChatConversation;
+
+public interface ChatConversationService extends IService<ChatConversation> {
+    void deleteChatConversation(BlockDto dto);
+}

+ 18 - 0
service/websocket/src/main/java/com/zhentao/service/ChatFriendService.java

@@ -0,0 +1,18 @@
+package com.zhentao.service;
+
+import com.zhentao.dto.BlockDto;
+import com.zhentao.entity.ChatFriend;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.vo.ResultVo;
+
+/**
+* @author 26621
+* @description 针对表【chat_friend(好友关系表)】的数据库操作Service
+* @createDate 2025-11-27 14:04:39
+*/
+public interface ChatFriendService extends IService<ChatFriend> {
+
+    ResultVo block(BlockDto blockDto);
+
+    ResultVo getBlacklist(Long userId);
+}

+ 20 - 0
service/websocket/src/main/java/com/zhentao/service/impl/ChatConversationServiceImpl.java

@@ -0,0 +1,20 @@
+package com.zhentao.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.dto.BlockDto;
+import com.zhentao.entity.ChatConversation;
+import com.zhentao.repository.ChatConversationMapper;
+import com.zhentao.service.ChatConversationService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ChatConversationServiceImpl extends ServiceImpl<ChatConversationMapper,ChatConversation> implements ChatConversationService {
+
+    @Override
+    public void deleteChatConversation(BlockDto dto) {
+        QueryWrapper<ChatConversation> chatConversationQueryWrapper = new QueryWrapper<>();
+        chatConversationQueryWrapper.lambda().eq(ChatConversation::getUserId,dto.getUserId()).eq(ChatConversation::getTargetUserId,dto.getTargetUserId());
+        this.baseMapper.delete(chatConversationQueryWrapper);
+    }
+}

+ 101 - 0
service/websocket/src/main/java/com/zhentao/service/impl/ChatFriendServiceImpl.java

@@ -0,0 +1,101 @@
+package com.zhentao.service.impl;
+
+import cn.hutool.Hutool;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.IdUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.dto.BlockDto;
+import com.zhentao.entity.ChatFriend;
+import com.zhentao.service.ChatConversationService;
+import com.zhentao.service.ChatFriendService;
+import com.zhentao.mapper.ChatFriendMapper;
+import com.zhentao.vo.GetblockVo;
+import com.zhentao.vo.ResultVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+* @author 26621
+* @description 针对表【chat_friend(好友关系表)】的数据库操作Service实现
+* @createDate 2025-11-27 14:04:39
+*/
+@Service
+public class ChatFriendServiceImpl extends ServiceImpl<ChatFriendMapper, ChatFriend>
+    implements ChatFriendService{
+    @Autowired
+    private ChatConversationService chatConversationService;
+
+
+    @Override
+    public ResultVo block(BlockDto blockDto) {
+        QueryWrapper<ChatFriend> chatFriendQueryWrapper = new QueryWrapper<>();
+        chatFriendQueryWrapper.lambda().eq(ChatFriend::getUserId,blockDto.getUserId()).eq(ChatFriend::getFriendId,blockDto.getTargetUserId());
+        ChatFriend chatFriends = this.baseMapper.selectOne(chatFriendQueryWrapper);
+
+        if (chatFriends == null){
+            ChatFriend chatFriend = new ChatFriend();
+            chatFriend.setId(IdUtil.getSnowflake().nextId());
+            chatFriend.setUserId(blockDto.getUserId());
+            chatFriend.setFriendId(blockDto.getTargetUserId());
+            chatFriend.setFriendAvatar(blockDto.getTargetUserAvatar());
+            chatFriend.setFriendName(blockDto.getTargetUserName());
+            chatFriend.setCreateTime(new Date());
+            chatFriend.setUpdateTime(new Date());
+            chatFriend.setStatus(2);
+
+            int insert = this.baseMapper.insert(chatFriend);
+            if (insert > 0){
+                deleteChatConversation(blockDto);
+                return ResultVo.success("拉黑成功");
+            }
+        }
+        if (chatFriends.getStatus() == 2){
+            deleteChatConversation(blockDto);
+
+            return ResultVo.success("拉黑成功");
+
+        }
+        chatFriends.setStatus(2);
+        int i = this.baseMapper.updateById(chatFriends);
+        if (i > 0){
+            deleteChatConversation(blockDto);
+
+            return ResultVo.success("拉黑成功");
+        }
+        return ResultVo.fail("拉黑失败");
+    }
+
+    @Override
+    public ResultVo getBlacklist(Long userId) {
+        QueryWrapper<ChatFriend> chatFriendQueryWrapper = new QueryWrapper<>();
+        chatFriendQueryWrapper.lambda().eq(ChatFriend::getUserId,userId).eq(ChatFriend::getStatus,2);
+        List<ChatFriend> chatFriends = this.baseMapper.selectList(chatFriendQueryWrapper);
+        if (CollUtil.isEmpty(chatFriends)){
+            return ResultVo.success("success");
+        }
+        List<GetblockVo> getblockVoList = new ArrayList<>();
+        for (ChatFriend chatFriend : chatFriends) {
+            GetblockVo getblockVo = new GetblockVo();
+            getblockVo.setUserId(chatFriend.getFriendId());
+            getblockVo.setCreateTime(chatFriend.getCreateTime());
+            getblockVoList.add(getblockVo);
+        }
+        return ResultVo.success("success",getblockVoList);
+    }
+
+
+    public void deleteChatConversation(BlockDto dto){
+        chatConversationService.deleteChatConversation(dto);
+    }
+}
+
+
+
+

+ 11 - 0
service/websocket/src/main/java/com/zhentao/vo/GetblockVo.java

@@ -0,0 +1,11 @@
+package com.zhentao.vo;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class GetblockVo {
+    private Long userId;
+    private Date createTime;
+}

+ 81 - 0
service/websocket/src/main/java/com/zhentao/vo/ResultVo.java

@@ -0,0 +1,81 @@
+package com.zhentao.vo;
+
+
+import lombok.Data;
+
+/**
+ * 通用接口返回结果封装类
+ * @param <T> 数据泛型类型
+ */
+@Data
+public class ResultVo<T> {
+
+    private Integer code;
+
+    private String message;
+
+    private T data;
+
+    /** 私有构造方法,禁止外部直接实例化 */
+    private ResultVo() {}
+
+    /**
+     * 成功返回(无数据)
+     * @param message 提示消息
+     * @return 成功结果对象
+     */
+    public static ResultVo<Object> success(String message) {
+        ResultVo<Object> result = new ResultVo<>();
+        result.setCode(200);
+        result.setMessage(message);
+        result.setData(null);
+        return result;
+    }
+
+    /**
+     * 成功返回(带数据)
+     * @param message 提示消息
+     * @param data 返回数据
+     * @param <T> 数据类型
+     * @return 成功结果对象
+     */
+    public static <T> ResultVo<T> success(String message, T data) {
+        ResultVo<T> result = new ResultVo<>();
+        result.setCode(200);
+        result.setMessage(message);
+        result.setData(data);
+        return result;
+    }
+
+    /**
+     * 失败返回(自定义状态码和消息)
+     * @param code 状态码
+     * @param message 提示消息
+     * @return 失败结果对象
+     */
+    public static ResultVo<Object> fail(Integer code, String message) {
+        ResultVo<Object> result = new ResultVo<>();
+        result.setCode(code);
+        result.setMessage(message);
+        result.setData(null);
+        return result;
+    }
+
+    /**
+     * 失败返回(默认状态码500)
+     * @param message 提示消息
+     * @return 失败结果对象
+     */
+    public static ResultVo<Object> fail(String message) {
+        return fail(500, message);
+    }
+
+    /**
+     * 参数错误返回(默认状态码400)
+     * @param message 提示消息
+     * @return 参数错误结果对象
+     */
+    public static ResultVo<Object> paramError(String message) {
+        return fail(400, message);
+    }
+}