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,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 |