Commit fe77884cc37bbff30da4442319ee1e79ca7130c9
1 parent
bcac553c
✨ feat(friend): 重构好友状态管理逻辑
优化好友状态的获取和验证逻辑,将 ImFriendStateEnum 的使用改为整型状态值,简化了好友关系的校验过程。新增 validateFriend 方法,统一处理好友和黑名单的校验,提升代码可读性和维护性。
Showing
6 changed files
with
55 additions
and
23 deletions
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 | ... | ... |