From cee0688c30313accfe4c1f0450b721facffd5ad0 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 4 May 2026 11:08:03 +0800 Subject: [PATCH] ✨ feat(im): 增加好友申请的逻辑(v1.3:修复各种边界情况,包括静默添加好友) --- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java | 2 ++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendAddSourceEnum.java | 3 +-- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java | 26 +++++++++++++------------- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java | 13 +++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java | 15 +++++++++++++-- 5 files changed, 42 insertions(+), 17 deletions(-) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java index 1ccb953..b04fd61 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java @@ -53,6 +53,8 @@ public interface ErrorCodeConstants { ErrorCode FRIEND_REQUEST_HANDLED = new ErrorCode(1_040_610_002, "好友申请已处理"); ErrorCode FRIEND_REQUEST_NOT_TO_ME = new ErrorCode(1_040_610_003, "不能处理别人的好友申请"); ErrorCode FRIEND_REQUEST_TARGET_INVALID = new ErrorCode(1_040_610_004, "好友申请的对方不存在或已禁用"); + ErrorCode FRIEND_REQUEST_ALREADY_FRIEND = new ErrorCode(1_040_610_005, "您已是 TA 的好友,无需重复添加"); + ErrorCode FRIEND_REQUEST_BLOCKED_BY_PEER = new ErrorCode(1_040_610_006, "您已被对方拉入黑名单,无法添加为好友"); // ========== 敏感词 (1-040-700-000) ========== ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1_040_700_000, "敏感词不存在"); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendAddSourceEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendAddSourceEnum.java index 9295b6d..55ad7ac 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendAddSourceEnum.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendAddSourceEnum.java @@ -17,9 +17,8 @@ import java.util.Arrays; @Getter public enum ImFriendAddSourceEnum implements ArrayValuable { - // TODO DONE @AI:GROUP 提前到 source=2(先实现);BUSINESS_CARD 简化为 CARD SEARCH(1, "搜索"), // FriendAddDialog 搜索流程 - GROUP(2, "群聊"), // TODO @AI:群成员主页加好友,待落 UI 入口 + GROUP(2, "群聊"), // 群成员主页 → UserInfo「加为好友」入口 QR_CODE(3, "扫码"), // TODO @芋艿:后续实现扫码加好友 CARD(4, "名片"); // TODO @芋艿:后续实现通过名片加好友,类似微信的「扫一扫 - 名片」功能,或者「通讯录 - 推荐好友」功能 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 9f519bb..9fc8b17 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 @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.im.service.friend; -import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; @@ -10,6 +9,7 @@ import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO; import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendRequestMapper; import cn.iocoder.yudao.module.im.enums.friend.ImFriendRequestHandleResultEnum; +import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; import cn.iocoder.yudao.module.im.framework.config.ImProperties; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; @@ -67,21 +67,21 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { } // 1.2 校验对方存在且启用 adminUserApi.validateUser(toUserId); - // 1.3 已是好友:直接幂等返回 - ImFriendDO friend = friendService.getFriend(fromUserId, toUserId); - // TODO @AI:已经是好友,直接提示报错;不用触发; - if (friend != null && CommonStatusEnum.isEnable(friend.getStatus())) { - return null; + // 1.3 已是好友 / 被对方拉黑:直接报错(state 一次拿到双向状态,省两次单边查询) + ImFriendStateEnum state = friendService.getFriendState(fromUserId, toUserId); + if (state == ImFriendStateEnum.FRIEND) { + throw exception(FRIEND_REQUEST_ALREADY_FRIEND); + } + if (state == ImFriendStateEnum.BLOCKED) { + throw exception(FRIEND_REQUEST_BLOCKED_BY_PEER); } - // 1.4 被对方拉黑:静默成功,不暴露 - // TODO @AI:是不是 peerFriend?这样更好理解。 - // TODO @AI:是不是应该提示被拉黑了?返回 null 给前端很奇怪。 - ImFriendDO reverse = friendService.getFriend(toUserId, fromUserId); - if (reverse != null && BooleanUtil.isTrue(reverse.getBlocked())) { - log.info("[applyFriend][fromUserId({}) 被 toUserId({}) 拉黑,静默忽略]", fromUserId, toUserId); + // 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()); return null; } - // TODO @AI:如果是单向好友(对方没删除);我删除了;是不是直接更新状态就好了;(不然会被对方发现!) // 2. 落库:复用最新一条未处理记录;否则新建 ImFriendRequestDO request = friendRequestMapper.selectLatestByFromUserIdAndToUserId(fromUserId, toUserId); 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 b39e24a..81b9851 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 @@ -55,6 +55,19 @@ public interface ImFriendService { */ void becomeFriends(ImFriendRequestDO request); + /** + * 单向静默重新建立好友关系 + *

+ * 仅用于 {@link ImFriendRequestService#applyFriend} 在「我已删除 + 对方仍把我当好友」场景: + * 直接恢复 userId 这边的 friend 记录,不走申请审批;不下发 TIP / 不通知对方,仅 FRIEND_ADD 给 userId 多端,避免对方感知我曾删除。 + * + * @param userId 当前用户编号 + * @param friendUserId 对方用户编号 + * @param displayName 备注(取自申请 VO) + * @param addSource 添加来源(取自申请 VO) + */ + void silentReAddFriend(Long userId, Long friendUserId, String displayName, Integer addSource); + // ==================== 用户端 ==================== /** 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 fd550a9..0bd8200 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 @@ -123,8 +123,6 @@ public class ImFriendServiceImpl implements ImFriendService { getSelf().addFriend0(fromUserId, toUserId, request.getDisplayName(), request.getAddSource()); getSelf().addFriend0(toUserId, fromUserId, null, request.getAddSource()); - // TODO DONE @AI:删除「在 openim 里」措辞;TIP 与 FRIEND_ADD 目的不同保持双发 - // TODO @AI:在确认一次;是否需要发送 tip?而是通过 FRIEND_ADD 可以满足了。。。(讨论一下) // 2.1 推 TIP 系统消息(双方私聊会话里看到「你们已成为好友」);TIP 走会话入库,FRIEND_ADD 走事件通知 privateMessageService.sendTipPrivateMessage(fromUserId, toUserId, FRIEND_ADD_TIP_MESSAGE); @@ -142,6 +140,19 @@ public class ImFriendServiceImpl implements ImFriendService { @Override @Transactional(rollbackFor = Exception.class) + public void silentReAddFriend(Long userId, Long friendUserId, String displayName, Integer addSource) { + // 1. 单边重新启用我侧好友关系;addFriend0 内部已做 @CacheEvict 双向失效 + getSelf().addFriend0(userId, friendUserId, displayName, addSource); + + // 2. 仅推 FRIEND_ADD 给 userId 多端(不通知对方,保持「对方一直把我当好友」的错觉) + FriendAddNotification payload = (FriendAddNotification) new FriendAddNotification() + .setOperatorUserId(friendUserId).setFriendUserId(friendUserId); + websocketService.sendPrivateMessageAsync(userId, ImPrivateMessageDTO.ofFriendNotification( + ImMessageTypeEnum.FRIEND_ADD.getType(), friendUserId, userId, payload)); + } + + @Override + @Transactional(rollbackFor = Exception.class) public void deleteFriend(Long userId, Long friendUserId) { // 1. 单边软删:仅 userId 视角的关系置 DISABLE;friendUserId 视角不动 getSelf().deleteFriend0(userId, friendUserId); -- libgit2 0.21.4