Commit fe77884cc37bbff30da4442319ee1e79ca7130c9

Authored by YunaiV
1 parent bcac553c

✨ feat(friend): 重构好友状态管理逻辑

优化好友状态的获取和验证逻辑,将 ImFriendStateEnum 的使用改为整型状态值,简化了好友关系的校验过程。新增 validateFriend 方法,统一处理好友和黑名单的校验,提升代码可读性和维护性。
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendStateEnum.java
... ... @@ -5,6 +5,7 @@ import lombok.Getter;
5 5 import lombok.RequiredArgsConstructor;
6 6  
7 7 import java.util.Arrays;
  8 +import java.util.Objects;
8 9  
9 10 /**
10 11 * IM 好友关系状态(合并「是否好友」+「是否拉黑」两态)
... ... @@ -39,4 +40,16 @@ public enum ImFriendStateEnum implements ArrayValuable<Integer> {
39 40 return ARRAYS;
40 41 }
41 42  
  43 + public static boolean isNone(Integer state) {
  44 + return Objects.equals(NONE.state, state);
  45 + }
  46 +
  47 + public static boolean isFriend(Integer state) {
  48 + return Objects.equals(FRIEND.state, state);
  49 + }
  50 +
  51 + public static boolean isBlocked(Integer state) {
  52 + return Objects.equals(BLOCKED.state, state);
  53 + }
  54 +
42 55 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java
... ... @@ -71,15 +71,15 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService {
71 71 // 1.2 校验对方存在且启用
72 72 adminUserApi.validateUser(toUserId);
73 73 // 1.3 已是好友 / 被对方拉黑:直接报错(state 一次拿到双向状态,省两次单边查询)
74   - ImFriendStateEnum state = friendService.getFriendState(fromUserId, toUserId);
75   - if (state == ImFriendStateEnum.FRIEND) {
  74 + // 错误码与 ImFriendService#validateFriend 不同(语义为「申请被拒」),故保留 inline
  75 + Integer state = friendService.getFriendState(fromUserId, toUserId);
  76 + if (ImFriendStateEnum.isFriend(state)) {
76 77 throw exception(FRIEND_REQUEST_ALREADY_FRIEND);
77 78 }
78   - if (state == ImFriendStateEnum.BLOCKED) {
  79 + if (ImFriendStateEnum.isBlocked(state)) {
79 80 throw exception(FRIEND_REQUEST_BLOCKED_BY_PEER);
80 81 }
81 82 // 1.4 单向好友(我已删 + 对方仍把我当好友):静默重新启用我侧关系,避免对方感知我曾删除
82   - // TODO DONE @AI:前端 FriendAddDialog 按 requestId 是否为 null 区分提示文案:null → 「已添加为好友」;非 null → 「等待对方验证」
83 83 ImFriendDO peerFriend = friendService.getFriend(toUserId, fromUserId);
84 84 if (peerFriend != null && CommonStatusEnum.isEnable(peerFriend.getStatus())) {
85 85 friendService.silentReAddFriend(fromUserId, toUserId, reqVO.getDisplayName(), reqVO.getAddSource());
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java
... ... @@ -23,9 +23,19 @@ public interface ImFriendService {
23 23 /**
24 24 * 获取 userId 视角下与 friendUserId 的好友关系状态(私聊发送热点路径)
25 25 * <p>
26   - * 合并「是否好友」+「是否被自己拉黑」两态,供调用方一次缓存查询完成判定,详见 {@link ImFriendStateEnum}
  26 + * 参见 {@link ImFriendStateEnum} 枚举类
27 27 */
28   - ImFriendStateEnum getFriendState(Long userId, Long friendUserId);
  28 + Integer getFriendState(Long userId, Long friendUserId);
  29 +
  30 + /**
  31 + * 校验「能否对 peerUserId 发起私聊语义动作」(消息发送 / RTC 邀请)
  32 + * <p>
  33 + * 好友 / 黑名单校验:和私聊消息发送同一套语义;NONE 已删 / 未加,BLOCKED 被对方拉黑
  34 + *
  35 + * @param userId 当前用户编号
  36 + * @param peerUserId 对方用户编号
  37 + */
  38 + void validateFriend(Long userId, Long peerUserId);
29 39  
30 40 /**
31 41 * 获得当前用户的好友列表(含已删除状态)
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
1 1 package cn.iocoder.yudao.module.im.service.friend;
2 2  
3 3 import cn.hutool.core.util.BooleanUtil;
  4 +import cn.hutool.extra.spring.SpringUtil;
4 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 7 import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO;
... ... @@ -31,6 +32,7 @@ import java.util.List;
31 32  
32 33 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
33 34 import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.FRIEND_STATE;
  35 +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_BLOCKED_BY_PEER;
34 36 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_BLOCKED;
35 37 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND;
36 38  
... ... @@ -55,19 +57,35 @@ public class ImFriendServiceImpl implements ImFriendService {
55 57  
56 58 @Override
57 59 @Cacheable(cacheNames = FRIEND_STATE, key = "#userId + '_' + #friendUserId", unless = "#result == null")
58   - public ImFriendStateEnum getFriendState(Long userId, Long friendUserId) {
  60 + public Integer getFriendState(Long userId, Long friendUserId) {
59 61 // 1.1 我侧记录:我方删了,都算非好友
60 62 ImFriendDO mine = friendMapper.selectByUserIdAndFriendUserId(userId, friendUserId);
61 63 if (mine == null || !CommonStatusEnum.isEnable(mine.getStatus())) {
62   - return ImFriendStateEnum.NONE;
  64 + return ImFriendStateEnum.NONE.getState();
63 65 }
64 66 // 1.2 对方侧记录:对方删了 = 不是好友
65 67 ImFriendDO peer = friendMapper.selectByUserIdAndFriendUserId(friendUserId, userId);
66 68 if (peer == null || !CommonStatusEnum.isEnable(peer.getStatus())) {
67   - return ImFriendStateEnum.NONE;
  69 + return ImFriendStateEnum.NONE.getState();
68 70 }
69 71 // 2. 仅当双方都是 ENABLE 状态,才算好友关系;此时对方拉黑我,则是 BLOCKED
70   - return BooleanUtil.isTrue(peer.getBlocked()) ? ImFriendStateEnum.BLOCKED : ImFriendStateEnum.FRIEND;
  72 + return BooleanUtil.isTrue(peer.getBlocked()) ? ImFriendStateEnum.BLOCKED.getState() : ImFriendStateEnum.FRIEND.getState();
  73 + }
  74 +
  75 + @Override
  76 + public void validateFriend(Long userId, Long peerUserId) {
  77 + // 好友 / 黑名单校验:和私聊消息发送同一套语义;NONE 已删 / 未加,BLOCKED 被对方拉黑
  78 + Integer state = getSelf().getFriendState(userId, peerUserId);
  79 + if (ImFriendStateEnum.isNone(state)) {
  80 + throw exception(FRIEND_NOT_FRIEND);
  81 + }
  82 + if (ImFriendStateEnum.isBlocked(state)) {
  83 + throw exception(FRIEND_BLOCKED_BY_PEER);
  84 + }
  85 + }
  86 +
  87 + private ImFriendService getSelf() {
  88 + return SpringUtil.getBean(getClass());
71 89 }
72 90  
73 91 @Override
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
... ... @@ -14,7 +14,6 @@ import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
14 14 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper;
15 15 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
16 16 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
17   -import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum;
18 17 import cn.iocoder.yudao.module.im.framework.config.ImProperties;
19 18 import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
20 19 import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO;
... ... @@ -71,13 +70,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
71 70 return existing;
72 71 }
73 72 // 1.2 好友校验
74   - ImFriendStateEnum state = friendService.getFriendState(senderId, reqVO.getReceiverId());
75   - if (state == ImFriendStateEnum.NONE) {
76   - throw exception(FRIEND_NOT_FRIEND);
77   - }
78   - if (state == ImFriendStateEnum.BLOCKED) {
79   - throw exception(FRIEND_BLOCKED_BY_PEER);
80   - }
  73 + friendService.validateFriend(senderId, reqVO.getReceiverId());
81 74 // 1.3 文本消息敏感词过滤
82 75 if (ImMessageTypeEnum.TEXT.getType().equals(reqVO.getType())) {
83 76 sensitiveWordService.validateText(reqVO.getContent());
... ...
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java
... ... @@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivate
6 6 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO;
7 7 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
8 8 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper;
9   -import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum;
10 9 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
11 10 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
12 11 import cn.iocoder.yudao.module.im.framework.config.ImProperties;
... ... @@ -70,7 +69,6 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
70 69 ImPrivateMessageSendReqVO reqVO = buildSendReqVO();
71 70 when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001"))
72 71 .thenReturn(null);
73   - when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.FRIEND);
74 72 when(privateMessageMapper.insert(any(ImPrivateMessageDO.class))).thenAnswer(invocation -> {
75 73 ImPrivateMessageDO msg = invocation.getArgument(0);
76 74 msg.setId(99L);
... ... @@ -89,7 +87,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
89 87 assertNotNull(result.getSendTime());
90 88  
91 89 // 验证调用
92   - verify(friendService).getFriendState(1L, 2L);
  90 + verify(friendService).validateFriend(1L, 2L);
93 91 verify(sensitiveWordService).validateText(reqVO.getContent());
94 92 verify(privateMessageMapper).insert(any(ImPrivateMessageDO.class));
95 93 // 验证推送给接收方和发送方
... ... @@ -123,7 +121,8 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
123 121 ImPrivateMessageSendReqVO reqVO = buildSendReqVO();
124 122 when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001"))
125 123 .thenReturn(null);
126   - when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.NONE);
  124 + doThrow(new ServiceException(FRIEND_NOT_FRIEND))
  125 + .when(friendService).validateFriend(1L, 2L);
127 126  
128 127 // 调用并断言
129 128 ServiceException exception = assertThrows(ServiceException.class,
... ... @@ -286,7 +285,6 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
286 285 ImPrivateMessageSendReqVO reqVO = buildSendReqVO();
287 286 when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001"))
288 287 .thenReturn(null);
289   - when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.FRIEND);
290 288 doThrow(new ServiceException(MESSAGE_SENSITIVE_WORD_BLOCKED))
291 289 .when(sensitiveWordService).validateText(reqVO.getContent());
292 290  
... ...