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,6 +5,7 @@ import lombok.Getter;
5 import lombok.RequiredArgsConstructor; 5 import lombok.RequiredArgsConstructor;
6 6
7 import java.util.Arrays; 7 import java.util.Arrays;
  8 +import java.util.Objects;
8 9
9 /** 10 /**
10 * IM 好友关系状态(合并「是否好友」+「是否拉黑」两态) 11 * IM 好友关系状态(合并「是否好友」+「是否拉黑」两态)
@@ -39,4 +40,16 @@ public enum ImFriendStateEnum implements ArrayValuable<Integer> { @@ -39,4 +40,16 @@ public enum ImFriendStateEnum implements ArrayValuable<Integer> {
39 return ARRAYS; 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,15 +71,15 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService {
71 // 1.2 校验对方存在且启用 71 // 1.2 校验对方存在且启用
72 adminUserApi.validateUser(toUserId); 72 adminUserApi.validateUser(toUserId);
73 // 1.3 已是好友 / 被对方拉黑:直接报错(state 一次拿到双向状态,省两次单边查询) 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 throw exception(FRIEND_REQUEST_ALREADY_FRIEND); 77 throw exception(FRIEND_REQUEST_ALREADY_FRIEND);
77 } 78 }
78 - if (state == ImFriendStateEnum.BLOCKED) { 79 + if (ImFriendStateEnum.isBlocked(state)) {
79 throw exception(FRIEND_REQUEST_BLOCKED_BY_PEER); 80 throw exception(FRIEND_REQUEST_BLOCKED_BY_PEER);
80 } 81 }
81 // 1.4 单向好友(我已删 + 对方仍把我当好友):静默重新启用我侧关系,避免对方感知我曾删除 82 // 1.4 单向好友(我已删 + 对方仍把我当好友):静默重新启用我侧关系,避免对方感知我曾删除
82 - // TODO DONE @AI:前端 FriendAddDialog 按 requestId 是否为 null 区分提示文案:null → 「已添加为好友」;非 null → 「等待对方验证」  
83 ImFriendDO peerFriend = friendService.getFriend(toUserId, fromUserId); 83 ImFriendDO peerFriend = friendService.getFriend(toUserId, fromUserId);
84 if (peerFriend != null && CommonStatusEnum.isEnable(peerFriend.getStatus())) { 84 if (peerFriend != null && CommonStatusEnum.isEnable(peerFriend.getStatus())) {
85 friendService.silentReAddFriend(fromUserId, toUserId, reqVO.getDisplayName(), reqVO.getAddSource()); 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,9 +23,19 @@ public interface ImFriendService {
23 /** 23 /**
24 * 获取 userId 视角下与 friendUserId 的好友关系状态(私聊发送热点路径) 24 * 获取 userId 视角下与 friendUserId 的好友关系状态(私聊发送热点路径)
25 * <p> 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 package cn.iocoder.yudao.module.im.service.friend; 1 package cn.iocoder.yudao.module.im.service.friend;
2 2
3 import cn.hutool.core.util.BooleanUtil; 3 import cn.hutool.core.util.BooleanUtil;
  4 +import cn.hutool.extra.spring.SpringUtil;
4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; 7 import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO;
@@ -31,6 +32,7 @@ import java.util.List; @@ -31,6 +32,7 @@ import java.util.List;
31 32
32 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 33 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
33 import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.FRIEND_STATE; 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 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_BLOCKED; 36 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_BLOCKED;
35 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND; 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,19 +57,35 @@ public class ImFriendServiceImpl implements ImFriendService {
55 57
56 @Override 58 @Override
57 @Cacheable(cacheNames = FRIEND_STATE, key = "#userId + '_' + #friendUserId", unless = "#result == null") 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 // 1.1 我侧记录:我方删了,都算非好友 61 // 1.1 我侧记录:我方删了,都算非好友
60 ImFriendDO mine = friendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); 62 ImFriendDO mine = friendMapper.selectByUserIdAndFriendUserId(userId, friendUserId);
61 if (mine == null || !CommonStatusEnum.isEnable(mine.getStatus())) { 63 if (mine == null || !CommonStatusEnum.isEnable(mine.getStatus())) {
62 - return ImFriendStateEnum.NONE; 64 + return ImFriendStateEnum.NONE.getState();
63 } 65 }
64 // 1.2 对方侧记录:对方删了 = 不是好友 66 // 1.2 对方侧记录:对方删了 = 不是好友
65 ImFriendDO peer = friendMapper.selectByUserIdAndFriendUserId(friendUserId, userId); 67 ImFriendDO peer = friendMapper.selectByUserIdAndFriendUserId(friendUserId, userId);
66 if (peer == null || !CommonStatusEnum.isEnable(peer.getStatus())) { 68 if (peer == null || !CommonStatusEnum.isEnable(peer.getStatus())) {
67 - return ImFriendStateEnum.NONE; 69 + return ImFriendStateEnum.NONE.getState();
68 } 70 }
69 // 2. 仅当双方都是 ENABLE 状态,才算好友关系;此时对方拉黑我,则是 BLOCKED 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 @Override 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,7 +14,6 @@ import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
14 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; 14 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper;
15 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 15 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
16 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 16 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
17 -import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum;  
18 import cn.iocoder.yudao.module.im.framework.config.ImProperties; 17 import cn.iocoder.yudao.module.im.framework.config.ImProperties;
19 import cn.iocoder.yudao.module.im.service.friend.ImFriendService; 18 import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
20 import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; 19 import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO;
@@ -71,13 +70,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @@ -71,13 +70,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
71 return existing; 70 return existing;
72 } 71 }
73 // 1.2 好友校验 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 // 1.3 文本消息敏感词过滤 74 // 1.3 文本消息敏感词过滤
82 if (ImMessageTypeEnum.TEXT.getType().equals(reqVO.getType())) { 75 if (ImMessageTypeEnum.TEXT.getType().equals(reqVO.getType())) {
83 sensitiveWordService.validateText(reqVO.getContent()); 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,7 +6,6 @@ import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivate
6 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; 6 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO;
7 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; 7 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
8 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; 8 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper;
9 -import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum;  
10 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 9 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
11 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 10 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
12 import cn.iocoder.yudao.module.im.framework.config.ImProperties; 11 import cn.iocoder.yudao.module.im.framework.config.ImProperties;
@@ -70,7 +69,6 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -70,7 +69,6 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
70 ImPrivateMessageSendReqVO reqVO = buildSendReqVO(); 69 ImPrivateMessageSendReqVO reqVO = buildSendReqVO();
71 when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001")) 70 when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001"))
72 .thenReturn(null); 71 .thenReturn(null);
73 - when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.FRIEND);  
74 when(privateMessageMapper.insert(any(ImPrivateMessageDO.class))).thenAnswer(invocation -> { 72 when(privateMessageMapper.insert(any(ImPrivateMessageDO.class))).thenAnswer(invocation -> {
75 ImPrivateMessageDO msg = invocation.getArgument(0); 73 ImPrivateMessageDO msg = invocation.getArgument(0);
76 msg.setId(99L); 74 msg.setId(99L);
@@ -89,7 +87,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -89,7 +87,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
89 assertNotNull(result.getSendTime()); 87 assertNotNull(result.getSendTime());
90 88
91 // 验证调用 89 // 验证调用
92 - verify(friendService).getFriendState(1L, 2L); 90 + verify(friendService).validateFriend(1L, 2L);
93 verify(sensitiveWordService).validateText(reqVO.getContent()); 91 verify(sensitiveWordService).validateText(reqVO.getContent());
94 verify(privateMessageMapper).insert(any(ImPrivateMessageDO.class)); 92 verify(privateMessageMapper).insert(any(ImPrivateMessageDO.class));
95 // 验证推送给接收方和发送方 93 // 验证推送给接收方和发送方
@@ -123,7 +121,8 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -123,7 +121,8 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
123 ImPrivateMessageSendReqVO reqVO = buildSendReqVO(); 121 ImPrivateMessageSendReqVO reqVO = buildSendReqVO();
124 when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001")) 122 when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001"))
125 .thenReturn(null); 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 ServiceException exception = assertThrows(ServiceException.class, 128 ServiceException exception = assertThrows(ServiceException.class,
@@ -286,7 +285,6 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -286,7 +285,6 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
286 ImPrivateMessageSendReqVO reqVO = buildSendReqVO(); 285 ImPrivateMessageSendReqVO reqVO = buildSendReqVO();
287 when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001")) 286 when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001"))
288 .thenReturn(null); 287 .thenReturn(null);
289 - when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.FRIEND);  
290 doThrow(new ServiceException(MESSAGE_SENSITIVE_WORD_BLOCKED)) 288 doThrow(new ServiceException(MESSAGE_SENSITIVE_WORD_BLOCKED))
291 .when(sensitiveWordService).validateText(reqVO.getContent()); 289 .when(sensitiveWordService).validateText(reqVO.getContent());
292 290