Bläddra i källkod

非VIP发信息限制5条

mazhenhang 1 månad sedan
förälder
incheckning
dbdcfa392e

+ 137 - 4
LiangZhiYUMao/pages/message/chat.vue

@@ -149,7 +149,15 @@
         </view>
       </view>
     </scroll-view>
-
+		<!-- 新增:消息发送限制提示 -->
+		<view class="message-limit-tip">
+		  <!-- 同时判断isVip和hasMessageLimit,增强可靠性     -->
+		  <text v-if="isVip || !hasMessageLimit" class="vip-tip">✨ VIP特权:无发送次数限制</text>
+		  <text v-else class="limit-tip">
+		    今日剩余可发送消息:{{ Math.max(remainingCount, 0) }} 条<text class="vip-link" @click="goToVipPage">(非VIP每日限5条)</text>
+		  </text>
+		</view>
+		
     <!-- 输入框 -->
     <view class="input-bar">
       <!-- 语音按钮 -->
@@ -276,8 +284,10 @@ export default {
       
       emojis: ['😀', '😃', '😄', '😁', '😆', '😅', '😂', '🤣', '😊', '😇', '🙂', '🙃', '😉', '😌', '😍', '🥰', '😘', '😗', '😙', '😚', '😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🤩', '🥳'],
 		showMoreOptionsModal: false, // 控制更多选项弹窗显示
-	    showBlockConfirmModal: false // 控制拉黑确认弹窗显示
-	
+	    showBlockConfirmModal: false, // 控制拉黑确认弹窗显示
+		isVip: false, // 是否VIP用户
+	    remainingCount: 5, // 剩余可发送消息数(非VIP默认5)
+	    hasMessageLimit: true // 是否有发送限制(VIP为false)
 	};
   },
   
@@ -355,7 +365,8 @@ export default {
     
     // 初始化 TIM
     await this.initTIM();
-    
+	// 获取用户消息发送限制(VIP状态+剩余次数)
+    await this.getUserMessageLimit();
     // 等待 SDK Ready 后再加载消息
     await this.waitForSDKReady();
     // 先检查拉黑状态
@@ -410,6 +421,26 @@ 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'
+	        });
+	      }
+	    });
+	  },
     more(userid) {
       console.log('点击了更多按钮,开始跳转...', userid);
       // 如果目标页面是普通页面,用navigateTo(保留当前页面);如果是tabbar页面,用switchTab
@@ -725,6 +756,16 @@ export default {
      * 发送文本消息
      */
     async sendTextMessage() {
+		
+		if (this.hasMessageLimit && this.remainingCount <= 0) {
+		    uni.showToast({
+		      title: '今日消息发送次数已用完,开通VIP无限制',
+		      icon: 'none',
+		      duration: 2000
+		    });
+		    return;
+		  }
+		  
       if (!this.inputText.trim()) {
         return;
       }
@@ -794,6 +835,9 @@ export default {
         
         console.log('✅ 消息发送成功');
         this.syncMessageToMySQL(message);
+		if (!this.isVip) {
+		    await this.updateMessageCount();
+		  }
       } catch (error) {
         console.error('❌ 消息发送失败:', error);
         
@@ -1315,7 +1359,69 @@ export default {
 		    closeBlockConfirm() {
 		      this.showBlockConfirmModal = false;
 		    },
+		    /**
+		     * 从后端获取VIP状态和今日剩余发送次数
+		     */
+		    async getUserMessageLimit() {
+		      try {
+		        const [err, res] = await uni.request({
+		          url: 'http://localhost:1004/api/chat/getUserMessageLimit',
+		          method: 'GET',
+		          data: {
+		            userId: this.userId ,// 已在onLoad中初始化的当前用户ID
+					targetUserId: this.targetUserId
+		          },
+		          header: {
+		            'Content-Type': 'application/json'
+		          }
+		        });
+		    
+		        if (err) throw new Error('网络请求失败');
+		        if (res.data.code !== 200) throw new Error(res.data.message || '获取限制信息失败');
+		    
+		        const { isVip, remainingCount, hasMessageLimit } = res.data.data;
+		        this.isVip = isVip;
+		        this.remainingCount = remainingCount;
+		        this.hasMessageLimit = hasMessageLimit;
 		    
+		        console.log('✅ 用户消息限制信息:', { isVip, remainingCount, hasMessageLimit });
+		      } catch (error) {
+		        console.error('❌ 获取消息限制失败:', error);
+		        // 异常降级:默认非VIP,剩余5次
+		        this.isVip = false;
+		        this.remainingCount = 5;
+		        this.hasMessageLimit = true;
+		      }
+		    },
+			/**
+			 * 发送成功后,更新后端计数并同步前端剩余次数
+			 */
+			async updateMessageCount() {
+			  try {
+			    const [err, res] = await uni.request({
+			      url: 'http://localhost:8083/api/chat/updateMessageCount',
+			      method: 'get',
+			      data: {
+			        userId: this.userId ,// 已在onLoad中初始化的当前用户ID
+			        targetUserId: this.targetUserId
+			      },
+			      header: {
+			        'Content-Type': 'application/json'
+			      }
+			    });
+			
+			    if (err) throw new Error('更新计数失败');
+			    if (res.data.code === 200) {
+			      this.remainingCount = res.data.data.remainingCount; // 同步后端返回的剩余次数
+			    }
+			  } catch (error) {
+			    console.error('❌ 更新发送次数失败:', error);
+			    // 前端本地降级:避免影响用户体验,本地暂减1(刷新页面后同步真实数据)
+			    if (this.remainingCount > 0) {
+			      this.remainingCount--;
+			    }
+			  }
+			},
 		    /**
 		     * 确认拉黑好友
 		     */
@@ -1828,6 +1934,33 @@ export default {
 .message-action-menu .menu-icon {
   font-size: 36rpx;
 }
+
+/* 消息发送限制提示 */
+.message-limit-tip {
+  padding: 10rpx 20rpx;
+  font-size: 24rpx;
+  text-align: center;
+  background-color: #fff;
+  border-bottom: 1px solid #f5f5f5;
+}
+.vip-tip {
+  color: #ff9500; /* VIP橙色提示 */
+}
+.limit-tip {
+  color: #666; /* 普通灰色提示 */
+}
+
+.vip-link {
+  color: #2c9fff; /* 蓝色文字 */
+  text-decoration: underline; /* 下划线 */
+  cursor: pointer; /* 鼠标悬浮时显示手型 */
+  margin-left: 5rpx;
+}
+
+/* 点击时添加轻微反馈 */
+.vip-link:active {
+  opacity: 0.7; /* 点击时透明度降低 */
+}
 </style>
 
 

+ 10 - 82
service/admin/src/main/java/com/zhentao/controller/UserController.java

@@ -6,7 +6,6 @@ import com.zhentao.common.Result;
 import com.zhentao.entity.*;
 import com.zhentao.mapper.*;
 import com.zhentao.vo.UserVO;
-import com.zhentao.vo.UserVipListVO;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -40,9 +39,6 @@ public class UserController {
     @Autowired
     private MatchmakerMapper matchmakerMapper;
     
-    @Autowired
-    private VipPackageMapper vipPackageMapper;
-    
     /**
      * 用户列表(分页)
      */
@@ -184,7 +180,7 @@ public class UserController {
     }
     
     /**
-     * VIP用户列表(查询有生效中VIP记录的用户)
+     * 认证用户列表(资料完整的用户)
      */
     @GetMapping("/vip/list")
     public Result<Map<String, Object>> vipList(
@@ -192,86 +188,18 @@ public class UserController {
             @RequestParam(defaultValue = "10") Integer pageSize) {
         
         try {
-            // 先查询所有生效中的VIP记录
-            QueryWrapper<UserVip> vipWrapper = new QueryWrapper<>();
-            vipWrapper.eq("status", 1)  // 状态:生效中
-                    .gt("end_time", LocalDateTime.now())  // 未过期
-                    .orderByDesc("end_time");
-            
-            List<UserVip> vipList = userVipMapper.selectList(vipWrapper);
-            
-            if (vipList == null || vipList.isEmpty()) {
-                Map<String, Object> data = new HashMap<>();
-                data.put("list", new ArrayList<>());
-                data.put("total", 0);
-                data.put("page", page);
-                data.put("pageSize", pageSize);
-                return Result.success(data);
-            }
-            
-            // 获取所有用户ID(去重)
-            List<Long> userIds = vipList.stream()
-                    .map(UserVip::getUserId)
-                    .distinct()
-                    .collect(Collectors.toList());
-            
-            // 计算总数
-            int total = userIds.size();
-            
-            // 分页处理
-            int start = (page - 1) * pageSize;
-            int end = Math.min(start + pageSize, userIds.size());
-            List<Long> pageUserIds = userIds.subList(start, end);
+            Page<Users> pageInfo = new Page<>(page, pageSize);
+            QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
             
-            // 查询用户信息
-            List<Users> users = new ArrayList<>();
-            if (!pageUserIds.isEmpty()) {
-                QueryWrapper<Users> userWrapper = new QueryWrapper<>();
-                userWrapper.in("user_id", pageUserIds);
-                users = usersMapper.selectList(userWrapper);
-            }
+            // 只查询资料完整的用户
+            queryWrapper.eq("is_profile_complete", 1);
+            queryWrapper.orderByDesc("created_at");
             
-            // 转换为UserVipListVO
-            List<UserVipListVO> voList = new ArrayList<>();
-            for (Users user : users) {
-                // 查找该用户最新的VIP记录(按结束时间排序,取最新的)
-                UserVip userVip = vipList.stream()
-                        .filter(vip -> vip.getUserId().equals(Long.valueOf(user.getUserId())))
-                        .max((v1, v2) -> v1.getEndTime().compareTo(v2.getEndTime()))
-                        .orElse(null);
-                
-                if (userVip != null) {
-                    UserVipListVO vo = new UserVipListVO();
-                    vo.setUserId(user.getUserId());
-                    vo.setNickname(user.getNickname());
-                    
-                    // 查询VIP套餐信息
-                    VipPackage vipPackage = vipPackageMapper.selectById(userVip.getPackageId());
-                    if (vipPackage != null) {
-                        vo.setVipLevel(vipPackage.getPackageName());
-                    } else {
-                        vo.setVipLevel("VIP会员");
-                    }
-                    
-                    vo.setVipStartTime(userVip.getStartTime());
-                    vo.setVipEndTime(userVip.getEndTime());
-                    
-                    // 计算剩余天数
-                    LocalDateTime now = LocalDateTime.now();
-                    if (userVip.getEndTime().isAfter(now)) {
-                        long days = Duration.between(now, userVip.getEndTime()).toDays();
-                        vo.setRemainingDays((int) Math.max(0, days));
-                    } else {
-                        vo.setRemainingDays(0);
-                    }
-                    
-                    voList.add(vo);
-                }
-            }
+            Page<Users> result = usersMapper.selectPage(pageInfo, queryWrapper);
             
             Map<String, Object> data = new HashMap<>();
-            data.put("list", voList);
-            data.put("total", total);
+            data.put("list", result.getRecords());
+            data.put("total", result.getTotal());
             data.put("page", page);
             data.put("pageSize", pageSize);
             
@@ -348,7 +276,7 @@ public class UserController {
             
             // 活跃用户数(最近7天登录)
             QueryWrapper<Users> activeWrapper = new QueryWrapper<>();
-            activeWrapper.gt("last_login_at", java.time.LocalDateTime.now().minusDays(7));
+            activeWrapper.gt("last_login_at", LocalDateTime.now().minusDays(7));
             Long activeUsers = usersMapper.selectCount(activeWrapper);
             
             Map<String, Object> stats = new HashMap<>();

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

@@ -5,6 +5,8 @@ import com.zhentao.entity.ChatConversation;
 import com.zhentao.entity.ChatMessage;
 import com.zhentao.service.ChatMessageService;
 import com.zhentao.service.OnlineUserService;
+import com.zhentao.service.UserVipService;
+import com.zhentao.vo.ResultVo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
@@ -29,6 +31,8 @@ public class ChatController {
     @Autowired
     private com.zhentao.repository.ChatConversationMapper conversationMapper;
 
+    @Autowired
+    private UserVipService userVipService;
     /**
      * 获取会话消息列表(优化版:添加更多参数支持)
      * GET /api/chat/messages?userId=10001&targetUserId=10002&page=0&size=20&lastMessageId=xxx
@@ -358,4 +362,15 @@ public class ChatController {
         result.put("timestamp", System.currentTimeMillis());
         return result;
     }
+
+//    获取用户消息限制
+    @GetMapping("/getUserMessageLimit")
+    public ResultVo getUserMessageLimit(@RequestParam("userId")Long userId,@RequestParam("targetUserId")Long targetUserId){
+        return userVipService.getUserMessageLimit(userId,targetUserId);
+    }
+
+    @GetMapping("/updateMessageCount")
+    public ResultVo updateMessageCount(@RequestParam("userId")Long userId,@RequestParam("targetUserId")Long targetUserId){
+        return userVipService.updateMessageCount(userId,targetUserId);
+    }
 }

+ 153 - 0
service/websocket/src/main/java/com/zhentao/entity/UserVip.java

@@ -0,0 +1,153 @@
+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.math.BigDecimal;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 用户VIP记录表
+ * @TableName user_vip
+ */
+@TableName(value ="user_vip")
+@Data
+public class UserVip {
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 套餐ID
+     */
+    private Long packageId;
+
+    /**
+     * 开始时间
+     */
+    private Date startTime;
+
+    /**
+     * 结束时间
+     */
+    private Date endTime;
+
+    /**
+     * 购买天数
+     */
+    private Integer durationDays;
+
+    /**
+     * 支付金额
+     */
+    private BigDecimal paymentAmount;
+
+    /**
+     * 支付方式
+     */
+    private String paymentMethod;
+
+    /**
+     * 订单号
+     */
+    private String orderNo;
+
+    /**
+     * 状态 0-已过期 1-生效中 2-已取消
+     */
+    private Integer status;
+
+    /**
+     * 来源(签到奖励/购买)
+     */
+    private String source;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 支付完成时间
+     */
+    private Date paymentTime;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        UserVip other = (UserVip) 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.getPackageId() == null ? other.getPackageId() == null : this.getPackageId().equals(other.getPackageId()))
+            && (this.getStartTime() == null ? other.getStartTime() == null : this.getStartTime().equals(other.getStartTime()))
+            && (this.getEndTime() == null ? other.getEndTime() == null : this.getEndTime().equals(other.getEndTime()))
+            && (this.getDurationDays() == null ? other.getDurationDays() == null : this.getDurationDays().equals(other.getDurationDays()))
+            && (this.getPaymentAmount() == null ? other.getPaymentAmount() == null : this.getPaymentAmount().equals(other.getPaymentAmount()))
+            && (this.getPaymentMethod() == null ? other.getPaymentMethod() == null : this.getPaymentMethod().equals(other.getPaymentMethod()))
+            && (this.getOrderNo() == null ? other.getOrderNo() == null : this.getOrderNo().equals(other.getOrderNo()))
+            && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
+            && (this.getSource() == null ? other.getSource() == null : this.getSource().equals(other.getSource()))
+            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
+            && (this.getPaymentTime() == null ? other.getPaymentTime() == null : this.getPaymentTime().equals(other.getPaymentTime()));
+    }
+
+    @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 + ((getPackageId() == null) ? 0 : getPackageId().hashCode());
+        result = prime * result + ((getStartTime() == null) ? 0 : getStartTime().hashCode());
+        result = prime * result + ((getEndTime() == null) ? 0 : getEndTime().hashCode());
+        result = prime * result + ((getDurationDays() == null) ? 0 : getDurationDays().hashCode());
+        result = prime * result + ((getPaymentAmount() == null) ? 0 : getPaymentAmount().hashCode());
+        result = prime * result + ((getPaymentMethod() == null) ? 0 : getPaymentMethod().hashCode());
+        result = prime * result + ((getOrderNo() == null) ? 0 : getOrderNo().hashCode());
+        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
+        result = prime * result + ((getSource() == null) ? 0 : getSource().hashCode());
+        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
+        result = prime * result + ((getPaymentTime() == null) ? 0 : getPaymentTime().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(", packageId=").append(packageId);
+        sb.append(", startTime=").append(startTime);
+        sb.append(", endTime=").append(endTime);
+        sb.append(", durationDays=").append(durationDays);
+        sb.append(", paymentAmount=").append(paymentAmount);
+        sb.append(", paymentMethod=").append(paymentMethod);
+        sb.append(", orderNo=").append(orderNo);
+        sb.append(", status=").append(status);
+        sb.append(", source=").append(source);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", paymentTime=").append(paymentTime);
+        sb.append("]");
+        return sb.toString();
+    }
+}

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

@@ -0,0 +1,18 @@
+package com.zhentao.mapper;
+
+import com.zhentao.entity.UserVip;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author 26621
+* @description 针对表【user_vip(用户VIP记录表)】的数据库操作Mapper
+* @createDate 2025-12-01 19:18:32
+* @Entity com.zhentao.entity.UserVip
+*/
+public interface UserVipMapper extends BaseMapper<UserVip> {
+
+}
+
+
+
+

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

@@ -0,0 +1,18 @@
+package com.zhentao.service;
+
+import com.zhentao.entity.UserVip;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.vo.ResultVo;
+
+/**
+* @author 26621
+* @description 针对表【user_vip(用户VIP记录表)】的数据库操作Service
+* @createDate 2025-12-01 19:18:32
+*/
+public interface UserVipService extends IService<UserVip> {
+
+    ResultVo getUserMessageLimit(Long userId, Long targetUserId);
+
+    ResultVo updateMessageCount(Long userId, Long targetUserId);
+
+}

+ 128 - 0
service/websocket/src/main/java/com/zhentao/service/impl/UserVipServiceImpl.java

@@ -0,0 +1,128 @@
+package com.zhentao.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.entity.UserVip;
+import com.zhentao.service.UserVipService;
+import com.zhentao.mapper.UserVipMapper;
+import com.zhentao.vo.ResultVo;
+import com.zhentao.vo.UserVipVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class UserVipServiceImpl extends ServiceImpl<UserVipMapper, UserVip>
+        implements UserVipService {
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
+    @Override
+    public ResultVo getUserMessageLimit(Long userId, Long targetUserId) {
+        // 1. 先查Redis:判断用户是否有有效VIP(缓存中存在即有效)
+        String vipRedisKey = "user:vip:" + userId;
+        if (redisTemplate.hasKey(vipRedisKey)) {
+            UserVipVo vipVo = new UserVipVo();
+            vipVo.setVip(true);
+            vipVo.setRemainingCount(-1);
+            vipVo.setHasMessageLimit(false);
+            return ResultVo.success("success", vipVo);
+        }
+
+        // 2. Redis无缓存,查数据库:查询用户未过期的VIP
+        QueryWrapper<UserVip> queryWrapper = new QueryWrapper<>();
+        Date nowDate = new Date(); // 与endTime(Date类型)匹配
+        queryWrapper.lambda()
+                .eq(UserVip::getUserId, userId)
+                .gt(UserVip::getEndTime, nowDate); // 类型匹配,避免SQL错误
+
+        List<UserVip> userVips = baseMapper.selectList(queryWrapper);
+        if (!CollUtil.isEmpty(userVips)) {
+            for (UserVip vip : userVips) {
+                Date endTime = vip.getEndTime();
+                if (endTime == null) continue; // 跳过无效数据
+
+                // 计算过期时间(毫秒差)
+                long expireMillis = endTime.getTime() - nowDate.getTime();
+                if (expireMillis > 0) {
+                    // 缓存VIP标记(无需存整个对象),到期自动删除
+                    redisTemplate.opsForValue().set(vipRedisKey, Boolean.TRUE, expireMillis, TimeUnit.MILLISECONDS);
+                    // 返回VIP权限
+                    UserVipVo vipVo = new UserVipVo();
+                    vipVo.setVip(true);
+                    vipVo.setRemainingCount(-1);
+                    vipVo.setHasMessageLimit(false);
+                    return ResultVo.success("success", vipVo);
+                }
+            }
+        }
+
+        // 3. 非VIP用户:处理消息次数限制
+        UserVipVo normalVo = new UserVipVo();
+        normalVo.setVip(false);
+        normalVo.setHasMessageLimit(true);
+
+        String msgCountRedisKey = "msg:count:" + userId + ":" + targetUserId;
+        // 安全获取缓存(避免类型转换异常)
+//        Integer remainingCount = redisTemplate.opsForValue().get(msgCountRedisKey, Integer.class);
+        Object value = redisTemplate.opsForValue().get(msgCountRedisKey);
+        Integer remainingCount = (value != null) ? (Integer) value : null;
+
+        if (remainingCount == null) {
+            // 初始化缓存:当前时间到明天凌晨的秒数
+            LocalDateTime now = LocalDateTime.now();
+            LocalDateTime midnight = LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.MIN);
+            long expireSeconds = ChronoUnit.SECONDS.between(now, midnight);
+
+            // 时间单位改为SECONDS,与计算结果一致
+            redisTemplate.opsForValue().set(msgCountRedisKey, 5, expireSeconds, TimeUnit.SECONDS);
+            remainingCount = 5;
+        }
+
+        normalVo.setRemainingCount(remainingCount);
+        return ResultVo.success("success", normalVo);
+    }
+
+    @Override
+    public ResultVo updateMessageCount(Long userId, Long targetUserId) {
+        // 1. 先判断用户是否为VIP(从Redis缓存获取,与getUserMessageLimit逻辑一致)
+        Boolean isVip = (Boolean) redisTemplate.opsForValue().get("user:vip:" + userId);
+        if (isVip != null && isVip) {
+            // VIP用户无限制,直接返回
+            return ResultVo.success("VIP用户无次数限制",
+                    new UserVipVo( true,-1,false));
+        }
+
+        // 2. 非VIP用户:构造Redis键(与前端一致:msg:count:userId:targetUserId)
+        String redisKey = "msg:count:" + userId + ":" + targetUserId;
+
+        // 3. 获取当前剩余次数(不存在则默认5次,防止缓存穿透)
+        Integer remainingCount = (Integer) redisTemplate.opsForValue().get(redisKey);
+        if (remainingCount == null) {
+            remainingCount = 5;
+        }
+
+        // 4. 递减次数(最小为0,不能为负数)
+        remainingCount = Math.max(remainingCount - 1, 0);
+
+        // 5. 更新Redis缓存(保持与前端一致的过期时间:到次日凌晨)
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime midnight = LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.MIN);
+        long expireSeconds = ChronoUnit.SECONDS.between(now, midnight);
+        redisTemplate.opsForValue().set(redisKey, remainingCount, expireSeconds, TimeUnit.SECONDS);
+
+        // 6. 返回更新后的结果
+        return ResultVo.success("更新成功",
+                new UserVipVo( false,remainingCount, true));
+}
+}

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

@@ -70,6 +70,7 @@ public class ResultVo<T> {
         return fail(500, message);
     }
 
+
     /**
      * 参数错误返回(默认状态码400)
      * @param message 提示消息

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

@@ -0,0 +1,18 @@
+package com.zhentao.vo;
+
+import afu.org.checkerframework.checker.igj.qual.I;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+
+public class UserVipVo {
+    private boolean isVip;
+    private Integer remainingCount;
+    private boolean hasMessageLimit;
+
+
+}

+ 28 - 0
service/websocket/src/main/resources/com/zhentao/mapper/UserVipMapper.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.mapper.UserVipMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.entity.UserVip">
+            <id property="id" column="id" />
+            <result property="userId" column="user_id" />
+            <result property="packageId" column="package_id" />
+            <result property="startTime" column="start_time" />
+            <result property="endTime" column="end_time" />
+            <result property="durationDays" column="duration_days" />
+            <result property="paymentAmount" column="payment_amount" />
+            <result property="paymentMethod" column="payment_method" />
+            <result property="orderNo" column="order_no" />
+            <result property="status" column="status" />
+            <result property="source" column="source" />
+            <result property="createTime" column="create_time" />
+            <result property="paymentTime" column="payment_time" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,user_id,package_id,start_time,end_time,duration_days,
+        payment_amount,payment_method,order_no,status,source,
+        create_time,payment_time
+    </sql>
+</mapper>