From fe77884cc37bbff30da4442319ee1e79ca7130c9 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 11 May 2026 14:25:25 +0800 Subject: [PATCH] ✨ feat(friend): 重构好友状态管理逻辑 --- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendStateEnum.java | 13 +++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java | 8 ++++---- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java | 14 ++++++++++++-- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java | 26 ++++++++++++++++++++++---- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java | 9 +-------- yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java | 8 +++----- 6 files changed, 55 insertions(+), 23 deletions(-) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendStateEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendStateEnum.java index 747e253..888f6a9 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendStateEnum.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendStateEnum.java @@ -5,6 +5,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import java.util.Arrays; +import java.util.Objects; /** * IM 好友关系状态(合并「是否好友」+「是否拉黑」两态) @@ -39,4 +40,16 @@ public enum ImFriendStateEnum implements ArrayValuable { return ARRAYS; } + public static boolean isNone(Integer state) { + return Objects.equals(NONE.state, state); + } + + public static boolean isFriend(Integer state) { + return Objects.equals(FRIEND.state, state); + } + + public static boolean isBlocked(Integer state) { + return Objects.equals(BLOCKED.state, state); + } + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java index 08592de..e4d67dd 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java @@ -71,15 +71,15 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { // 1.2 校验对方存在且启用 adminUserApi.validateUser(toUserId); // 1.3 已是好友 / 被对方拉黑:直接报错(state 一次拿到双向状态,省两次单边查询) - ImFriendStateEnum state = friendService.getFriendState(fromUserId, toUserId); - if (state == ImFriendStateEnum.FRIEND) { + // 错误码与 ImFriendService#validateFriend 不同(语义为「申请被拒」),故保留 inline + Integer state = friendService.getFriendState(fromUserId, toUserId); + if (ImFriendStateEnum.isFriend(state)) { throw exception(FRIEND_REQUEST_ALREADY_FRIEND); } - if (state == ImFriendStateEnum.BLOCKED) { + if (ImFriendStateEnum.isBlocked(state)) { throw exception(FRIEND_REQUEST_BLOCKED_BY_PEER); } // 1.4 单向好友(我已删 + 对方仍把我当好友):静默重新启用我侧关系,避免对方感知我曾删除 - // TODO DONE @AI:前端 FriendAddDialog 按 requestId 是否为 null 区分提示文案:null → 「已添加为好友」;非 null → 「等待对方验证」 ImFriendDO peerFriend = friendService.getFriend(toUserId, fromUserId); if (peerFriend != null && CommonStatusEnum.isEnable(peerFriend.getStatus())) { friendService.silentReAddFriend(fromUserId, toUserId, reqVO.getDisplayName(), reqVO.getAddSource()); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java index eb6a5e6..f3ed003 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java @@ -23,9 +23,19 @@ public interface ImFriendService { /** * 获取 userId 视角下与 friendUserId 的好友关系状态(私聊发送热点路径) *

- * 合并「是否好友」+「是否被自己拉黑」两态,供调用方一次缓存查询完成判定,详见 {@link ImFriendStateEnum} + * 参见 {@link ImFriendStateEnum} 枚举类 */ - ImFriendStateEnum getFriendState(Long userId, Long friendUserId); + Integer getFriendState(Long userId, Long friendUserId); + + /** + * 校验「能否对 peerUserId 发起私聊语义动作」(消息发送 / RTC 邀请) + *

+ * 好友 / 黑名单校验:和私聊消息发送同一套语义;NONE 已删 / 未加,BLOCKED 被对方拉黑 + * + * @param userId 当前用户编号 + * @param peerUserId 对方用户编号 + */ + void validateFriend(Long userId, Long peerUserId); /** * 获得当前用户的好友列表(含已删除状态) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java index 65283e0..8f2a35a 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.im.service.friend; import cn.hutool.core.util.BooleanUtil; +import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; @@ -31,6 +32,7 @@ import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.FRIEND_STATE; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_BLOCKED_BY_PEER; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_BLOCKED; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND; @@ -55,19 +57,35 @@ public class ImFriendServiceImpl implements ImFriendService { @Override @Cacheable(cacheNames = FRIEND_STATE, key = "#userId + '_' + #friendUserId", unless = "#result == null") - public ImFriendStateEnum getFriendState(Long userId, Long friendUserId) { + public Integer getFriendState(Long userId, Long friendUserId) { // 1.1 我侧记录:我方删了,都算非好友 ImFriendDO mine = friendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); if (mine == null || !CommonStatusEnum.isEnable(mine.getStatus())) { - return ImFriendStateEnum.NONE; + return ImFriendStateEnum.NONE.getState(); } // 1.2 对方侧记录:对方删了 = 不是好友 ImFriendDO peer = friendMapper.selectByUserIdAndFriendUserId(friendUserId, userId); if (peer == null || !CommonStatusEnum.isEnable(peer.getStatus())) { - return ImFriendStateEnum.NONE; + return ImFriendStateEnum.NONE.getState(); } // 2. 仅当双方都是 ENABLE 状态,才算好友关系;此时对方拉黑我,则是 BLOCKED - return BooleanUtil.isTrue(peer.getBlocked()) ? ImFriendStateEnum.BLOCKED : ImFriendStateEnum.FRIEND; + return BooleanUtil.isTrue(peer.getBlocked()) ? ImFriendStateEnum.BLOCKED.getState() : ImFriendStateEnum.FRIEND.getState(); + } + + @Override + public void validateFriend(Long userId, Long peerUserId) { + // 好友 / 黑名单校验:和私聊消息发送同一套语义;NONE 已删 / 未加,BLOCKED 被对方拉黑 + Integer state = getSelf().getFriendState(userId, peerUserId); + if (ImFriendStateEnum.isNone(state)) { + throw exception(FRIEND_NOT_FRIEND); + } + if (ImFriendStateEnum.isBlocked(state)) { + throw exception(FRIEND_BLOCKED_BY_PEER); + } + } + + private ImFriendService getSelf() { + return SpringUtil.getBean(getClass()); } @Override diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java index a8d78fa..1ac9600 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java +++ b/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; import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; -import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; import cn.iocoder.yudao.module.im.framework.config.ImProperties; import cn.iocoder.yudao.module.im.service.friend.ImFriendService; import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; @@ -71,13 +70,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { return existing; } // 1.2 好友校验 - ImFriendStateEnum state = friendService.getFriendState(senderId, reqVO.getReceiverId()); - if (state == ImFriendStateEnum.NONE) { - throw exception(FRIEND_NOT_FRIEND); - } - if (state == ImFriendStateEnum.BLOCKED) { - throw exception(FRIEND_BLOCKED_BY_PEER); - } + friendService.validateFriend(senderId, reqVO.getReceiverId()); // 1.3 文本消息敏感词过滤 if (ImMessageTypeEnum.TEXT.getType().equals(reqVO.getType())) { sensitiveWordService.validateText(reqVO.getContent()); diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java index ac912ff..3bf4b0f 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java +++ b/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 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; -import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; import cn.iocoder.yudao.module.im.framework.config.ImProperties; @@ -70,7 +69,6 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { ImPrivateMessageSendReqVO reqVO = buildSendReqVO(); when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001")) .thenReturn(null); - when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.FRIEND); when(privateMessageMapper.insert(any(ImPrivateMessageDO.class))).thenAnswer(invocation -> { ImPrivateMessageDO msg = invocation.getArgument(0); msg.setId(99L); @@ -89,7 +87,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { assertNotNull(result.getSendTime()); // 验证调用 - verify(friendService).getFriendState(1L, 2L); + verify(friendService).validateFriend(1L, 2L); verify(sensitiveWordService).validateText(reqVO.getContent()); verify(privateMessageMapper).insert(any(ImPrivateMessageDO.class)); // 验证推送给接收方和发送方 @@ -123,7 +121,8 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { ImPrivateMessageSendReqVO reqVO = buildSendReqVO(); when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001")) .thenReturn(null); - when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.NONE); + doThrow(new ServiceException(FRIEND_NOT_FRIEND)) + .when(friendService).validateFriend(1L, 2L); // 调用并断言 ServiceException exception = assertThrows(ServiceException.class, @@ -286,7 +285,6 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { ImPrivateMessageSendReqVO reqVO = buildSendReqVO(); when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001")) .thenReturn(null); - when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.FRIEND); doThrow(new ServiceException(MESSAGE_SENSITIVE_WORD_BLOCKED)) .when(sensitiveWordService).validateText(reqVO.getContent()); -- libgit2 0.21.4