Commit ee01a16f8b358e4b7149822fd1bcb1fdbfc4d433

Authored by YunaiV
1 parent eb2d3c39

🐛 fix(im):好友模块 codex 评审修复——1209 改 @TransactionalEventListener(AFTER_COMMIT) 避免幽灵…

…通知、Consumer 过滤下推 SQL、申请表索引带 id 后缀覆盖游标排序、limit 加 @Max(200) 上限
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendRequestController.java
@@ -16,6 +16,7 @@ import io.swagger.v3.oas.annotations.Parameter; @@ -16,6 +16,7 @@ import io.swagger.v3.oas.annotations.Parameter;
16 import io.swagger.v3.oas.annotations.tags.Tag; 16 import io.swagger.v3.oas.annotations.tags.Tag;
17 import jakarta.annotation.Resource; 17 import jakarta.annotation.Resource;
18 import jakarta.validation.Valid; 18 import jakarta.validation.Valid;
  19 +import jakarta.validation.constraints.Max;
19 import jakarta.validation.constraints.Min; 20 import jakarta.validation.constraints.Min;
20 import jakarta.validation.constraints.NotNull; 21 import jakarta.validation.constraints.NotNull;
21 import jakarta.validation.constraints.Size; 22 import jakarta.validation.constraints.Size;
@@ -81,7 +82,8 @@ public class ImFriendRequestController { @@ -81,7 +82,8 @@ public class ImFriendRequestController {
81 public CommonResult<List<ImFriendRequestRespVO>> getMyFriendRequestList( 82 public CommonResult<List<ImFriendRequestRespVO>> getMyFriendRequestList(
82 @Parameter(description = "当前列表最旧记录的 id;首页不传") 83 @Parameter(description = "当前列表最旧记录的 id;首页不传")
83 @RequestParam(value = "lastRequestId", required = false) Long lastRequestId, 84 @RequestParam(value = "lastRequestId", required = false) Long lastRequestId,
84 - @Parameter(description = "单次拉取条数", required = true) @RequestParam("limit") @Min(1) Integer limit) { 85 + @Parameter(description = "单次拉取条数", required = true)
  86 + @RequestParam("limit") @Min(1) @Max(200) Integer limit) {
85 List<ImFriendRequestDO> list = friendRequestService.getMyFriendRequestList(getLoginUserId(), lastRequestId, limit); 87 List<ImFriendRequestDO> list = friendRequestService.getMyFriendRequestList(getLoginUserId(), lastRequestId, limit);
86 return success(buildList(list)); 88 return success(buildList(list));
87 } 89 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/mq/consumer/friend/AdminUserProfileUpdateConsumer.java
1 package cn.iocoder.yudao.module.im.mq.consumer.friend; 1 package cn.iocoder.yudao.module.im.mq.consumer.friend;
2 2
3 -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;  
4 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; 3 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
5 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 4 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
6 import cn.iocoder.yudao.module.im.service.friend.ImFriendService; 5 import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
@@ -10,9 +9,10 @@ import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.Frie @@ -10,9 +9,10 @@ import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.Frie
10 import cn.iocoder.yudao.module.system.api.message.user.AdminUserProfileUpdateMessage; 9 import cn.iocoder.yudao.module.system.api.message.user.AdminUserProfileUpdateMessage;
11 import jakarta.annotation.Resource; 10 import jakarta.annotation.Resource;
12 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
13 -import org.springframework.context.event.EventListener;  
14 import org.springframework.scheduling.annotation.Async; 12 import org.springframework.scheduling.annotation.Async;
15 import org.springframework.stereotype.Component; 13 import org.springframework.stereotype.Component;
  14 +import org.springframework.transaction.event.TransactionPhase;
  15 +import org.springframework.transaction.event.TransactionalEventListener;
16 16
17 import java.util.List; 17 import java.util.List;
18 18
@@ -30,30 +30,25 @@ public class AdminUserProfileUpdateConsumer { @@ -30,30 +30,25 @@ public class AdminUserProfileUpdateConsumer {
30 @Resource 30 @Resource
31 private ImWebSocketService websocketService; 31 private ImWebSocketService websocketService;
32 32
33 - @EventListener  
34 - @Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步 33 + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
  34 + @Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步;事务提交后触发,避免回滚误推幽灵通知 / Consumer 抢在 commit 前读旧值
35 public void onMessage(AdminUserProfileUpdateMessage message) { 35 public void onMessage(AdminUserProfileUpdateMessage message) {
36 log.info("[onMessage][消息内容({})]", message); 36 log.info("[onMessage][消息内容({})]", message);
37 Long userId = message.getUserId(); 37 Long userId = message.getUserId();
38 - // 1. 找当前用户的所有有效好友(DISABLE 已删的不推)  
39 - List<ImFriendDO> friends = friendService.getFriendList(userId); 38 + // 1. SQL 层过滤 ENABLE 好友
  39 + List<ImFriendDO> friends = friendService.getEnableFriendList(userId);
40 if (friends.isEmpty()) { 40 if (friends.isEmpty()) {
41 return; 41 return;
42 } 42 }
43 43
44 // 2. 给每个好友的多端推 FRIEND_INFO_UPDATED;payload 里 operatorUserId / friendUserId 都是「资料被改的人」 44 // 2. 给每个好友的多端推 FRIEND_INFO_UPDATED;payload 里 operatorUserId / friendUserId 都是「资料被改的人」
45 - int notifyCount = 0;  
46 for (ImFriendDO friend : friends) { 45 for (ImFriendDO friend : friends) {
47 - if (CommonStatusEnum.isDisable(friend.getStatus())) {  
48 - continue;  
49 - }  
50 FriendInfoUpdatedNotification payload = (FriendInfoUpdatedNotification) new FriendInfoUpdatedNotification() 46 FriendInfoUpdatedNotification payload = (FriendInfoUpdatedNotification) new FriendInfoUpdatedNotification()
51 .setOperatorUserId(userId).setFriendUserId(userId); 47 .setOperatorUserId(userId).setFriendUserId(userId);
52 websocketService.sendPrivateMessageAsync(friend.getFriendUserId(), ImPrivateMessageDTO.ofFriendNotification( 48 websocketService.sendPrivateMessageAsync(friend.getFriendUserId(), ImPrivateMessageDTO.ofFriendNotification(
53 ImMessageTypeEnum.FRIEND_INFO_UPDATED.getType(), userId, friend.getFriendUserId(), payload)); 49 ImMessageTypeEnum.FRIEND_INFO_UPDATED.getType(), userId, friend.getFriendUserId(), payload));
54 - notifyCount++;  
55 } 50 }
56 - log.info("[onMessage][userId({}) 推送 FRIEND_INFO_UPDATED 给 {} 位好友]", userId, notifyCount); 51 + log.info("[onMessage][userId({}) 推送 FRIEND_INFO_UPDATED 给 {} 位好友]", userId, friends.size());
57 } 52 }
58 53
59 } 54 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java
@@ -33,6 +33,11 @@ public interface ImFriendService { @@ -33,6 +33,11 @@ public interface ImFriendService {
33 List<ImFriendDO> getFriendList(Long userId); 33 List<ImFriendDO> getFriendList(Long userId);
34 34
35 /** 35 /**
  36 + * 获得当前用户的有效好友列表(仅 ENABLE 状态)
  37 + */
  38 + List<ImFriendDO> getEnableFriendList(Long userId);
  39 +
  40 + /**
36 * 获得当前用户与指定用户之间的有效好友列表(仅 ENABLE 状态) 41 * 获得当前用户与指定用户之间的有效好友列表(仅 ENABLE 状态)
37 */ 42 */
38 List<ImFriendDO> getActiveFriendList(Long userId, Collection<Long> friendUserIds); 43 List<ImFriendDO> getActiveFriendList(Long userId, Collection<Long> friendUserIds);
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
@@ -76,6 +76,11 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -76,6 +76,11 @@ public class ImFriendServiceImpl implements ImFriendService {
76 } 76 }
77 77
78 @Override 78 @Override
  79 + public List<ImFriendDO> getEnableFriendList(Long userId) {
  80 + return friendMapper.selectListByUserIdAndStatus(userId, CommonStatusEnum.ENABLE.getStatus());
  81 + }
  82 +
  83 + @Override
79 public List<ImFriendDO> getActiveFriendList(Long userId, Collection<Long> friendUserIds) { 84 public List<ImFriendDO> getActiveFriendList(Long userId, Collection<Long> friendUserIds) {
80 return friendMapper.selectListByUserIdAndFriendUserIdsAndStatus(userId, friendUserIds, 85 return friendMapper.selectListByUserIdAndFriendUserIdsAndStatus(userId, friendUserIds,
81 CommonStatusEnum.ENABLE.getStatus()); 86 CommonStatusEnum.ENABLE.getStatus());