Commit 15a1bca721b15cd1dbdcd9503cedef6042e4edff

Authored by YunaiV
1 parent affc4090

♻️ refactor(friend): 重构好友关系相关逻辑,优化查询与缓存

yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendController.java
1 package cn.iocoder.yudao.module.im.controller.admin.friend; 1 package cn.iocoder.yudao.module.im.controller.admin.friend;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendRespVO; 7 import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendRespVO;
5 import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; 8 import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO;
  9 +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
6 import cn.iocoder.yudao.module.im.service.friend.ImFriendService; 10 import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
  11 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  12 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
7 import io.swagger.v3.oas.annotations.Operation; 13 import io.swagger.v3.oas.annotations.Operation;
8 import io.swagger.v3.oas.annotations.Parameter; 14 import io.swagger.v3.oas.annotations.Parameter;
9 import io.swagger.v3.oas.annotations.tags.Tag; 15 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -13,9 +19,14 @@ import jakarta.validation.constraints.NotNull; @@ -13,9 +19,14 @@ import jakarta.validation.constraints.NotNull;
13 import org.springframework.validation.annotation.Validated; 19 import org.springframework.validation.annotation.Validated;
14 import org.springframework.web.bind.annotation.*; 20 import org.springframework.web.bind.annotation.*;
15 21
  22 +import java.util.Collection;
  23 +import java.util.Collections;
16 import java.util.List; 24 import java.util.List;
  25 +import java.util.Map;
17 26
18 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 27 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.singleton;
19 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; 30 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
20 31
21 @Tag(name = "管理后台 - IM 好友") 32 @Tag(name = "管理后台 - IM 好友")
@@ -27,17 +38,22 @@ public class ImFriendController { @@ -27,17 +38,22 @@ public class ImFriendController {
27 @Resource 38 @Resource
28 private ImFriendService friendService; 39 private ImFriendService friendService;
29 40
  41 + @Resource
  42 + private AdminUserApi adminUserApi;
  43 +
30 @GetMapping("/list") 44 @GetMapping("/list")
31 @Operation(summary = "获得当前登录用户的好友列表") 45 @Operation(summary = "获得当前登录用户的好友列表")
32 public CommonResult<List<ImFriendRespVO>> getMyFriendList() { 46 public CommonResult<List<ImFriendRespVO>> getMyFriendList() {
33 - return success(friendService.getMyFriendList(getLoginUserId())); 47 + List<ImFriendDO> friends = friendService.getFriendList(getLoginUserId());
  48 + return success(buildFriendRespVOList(friends));
34 } 49 }
35 50
36 @GetMapping("/get") 51 @GetMapping("/get")
37 @Operation(summary = "获得好友详情") 52 @Operation(summary = "获得好友详情")
38 @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048") 53 @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048")
39 public CommonResult<ImFriendRespVO> getFriend(@RequestParam("friendUserId") Long friendUserId) { 54 public CommonResult<ImFriendRespVO> getFriend(@RequestParam("friendUserId") Long friendUserId) {
40 - return success(friendService.getFriend(getLoginUserId(), friendUserId)); 55 + ImFriendDO friend = friendService.getFriend(getLoginUserId(), friendUserId);
  56 + return success(buildFriendRespVO(friend));
41 } 57 }
42 58
43 @PostMapping("/add") 59 @PostMapping("/add")
@@ -65,4 +81,28 @@ public class ImFriendController { @@ -65,4 +81,28 @@ public class ImFriendController {
65 return success(true); 81 return success(true);
66 } 82 }
67 83
  84 + // ========== 私有方法:VO 组装 ==========
  85 +
  86 + private List<ImFriendRespVO> buildFriendRespVOList(Collection<ImFriendDO> friends) {
  87 + if (CollUtil.isNotEmpty(friends)) {
  88 + return Collections.emptyList();
  89 + }
  90 + // 批量聚合 AdminUser 信息(昵称 / 头像),避免 N+1
  91 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  92 + convertList(friends, ImFriendDO::getFriendUserId));
  93 + return convertList(friends, f -> {
  94 + ImFriendRespVO vo = BeanUtils.toBean(f, ImFriendRespVO.class);
  95 + MapUtils.findAndThen(userMap, f.getFriendUserId(), user ->
  96 + vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
  97 + return vo;
  98 + });
  99 + }
  100 +
  101 + private ImFriendRespVO buildFriendRespVO(ImFriendDO friend) {
  102 + if (friend == null) {
  103 + return null;
  104 + }
  105 + return CollUtil.getFirst(buildFriendRespVOList(singleton(friend)));
  106 + }
  107 +
68 } 108 }
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java
@@ -15,4 +15,12 @@ public interface RedisKeyConstants { @@ -15,4 +15,12 @@ public interface RedisKeyConstants {
15 */ 15 */
16 String GROUP_MESSAGE_READ = "im:group:message:read:%s"; 16 String GROUP_MESSAGE_READ = "im:group:message:read:%s";
17 17
  18 + /**
  19 + * 好友关系是否存在的缓存
  20 + * <p>
  21 + * KEY 格式:friend:{userId}_{friendUserId}
  22 + * VALUE 数据类型:Boolean
  23 + */
  24 + String FRIEND = "friend";
  25 +
18 } 26 }
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java
1 package cn.iocoder.yudao.module.im.service.friend; 1 package cn.iocoder.yudao.module.im.service.friend;
2 2
3 -import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendRespVO;  
4 import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; 3 import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO;
5 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; 4 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
6 5
@@ -14,35 +13,33 @@ import java.util.List; @@ -14,35 +13,33 @@ import java.util.List;
14 public interface ImFriendService { 13 public interface ImFriendService {
15 14
16 /** 15 /**
17 - * 校验两个用户是否是好友关系(未删除状态) 16 + * 判断两个用户是否是好友关系(未删除状态)
18 * 17 *
19 * @param userId 用户编号 18 * @param userId 用户编号
20 * @param friendUserId 好友用户编号 19 * @param friendUserId 好友用户编号
  20 + * @return 是否好友
21 */ 21 */
22 - void validateFriendExists(Long userId, Long friendUserId); 22 + boolean isFriend(Long userId, Long friendUserId);
23 23
24 /** 24 /**
25 - * 获得当前用户的好友列表(含昵称/头像聚合) 25 + * 获得当前用户的好友列表(含已删除状态)
26 * <p> 26 * <p>
27 - * 返回的列表里**包含已删除状态**,前端按 status 区分; 27 + * 返回的列表里**包含已删除状态**,前端按 status 区分展示(已删除的置灰);
28 * 原因是本地已经建立会话的好友被单向删除后,前端仍需要展示头像/昵称。 28 * 原因是本地已经建立会话的好友被单向删除后,前端仍需要展示头像/昵称。
29 * 29 *
30 * @param userId 用户编号 30 * @param userId 用户编号
31 - * @return 好友列表(含聚合后的用户信息) 31 + * @return 好友关系列表(含已删除的记录)
32 */ 32 */
33 - List<ImFriendRespVO> getMyFriendList(Long userId); 33 + List<ImFriendDO> getFriendList(Long userId);
34 34
35 /** 35 /**
36 - * 查询一个好友(仅限本人视角的好友关系)  
37 - * <p>  
38 - * 只返回"本人→对方"的关系记录;  
39 - * 对方不是本人好友时抛 {@link cn.iocoder.yudao.module.im.enums.ErrorCodeConstants#FRIEND_NOT_FRIEND}。 36 + * 查询一个好友关系记录(仅限本人视角)
40 * 37 *
41 * @param userId 当前登录用户编号 38 * @param userId 当前登录用户编号
42 * @param friendUserId 目标用户编号 39 * @param friendUserId 目标用户编号
43 - * @return 好友信息(含聚合后的用户信息) 40 + * @return 好友关系记录,不存在返回 null
44 */ 41 */
45 - ImFriendRespVO getFriend(Long userId, Long friendUserId); 42 + ImFriendDO getFriend(Long userId, Long friendUserId);
46 43
47 /** 44 /**
48 * 添加好友(双向绑定) 45 * 添加好友(双向绑定)
@@ -77,13 +74,5 @@ public interface ImFriendService { @@ -77,13 +74,5 @@ public interface ImFriendService {
77 */ 74 */
78 void updateFriend(Long userId, ImFriendUpdateReqVO reqVO); 75 void updateFriend(Long userId, ImFriendUpdateReqVO reqVO);
79 76
80 - /**  
81 - * 查询某个用户视角下的好友关系记录(仅单边)  
82 - *  
83 - * @param userId 用户编号  
84 - * @param friendUserId 好友用户编号  
85 - * @return 关系记录,不存在返回 null  
86 - */  
87 - ImFriendDO getFriendDO(Long userId, Long friendUserId);  
88 77
89 } 78 }
yudao-module-im/yudao-module-im-biz/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.iocoder.yudao.framework.common.enums.CommonStatusEnum; 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;  
5 -import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendRespVO;  
6 import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; 4 import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO;
7 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; 5 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
8 import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; 6 import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper;
9 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 7 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
10 -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;  
11 import jakarta.annotation.Resource; 8 import jakarta.annotation.Resource;
  9 +import org.springframework.cache.annotation.CacheEvict;
  10 +import org.springframework.cache.annotation.Cacheable;
12 import org.springframework.stereotype.Service; 11 import org.springframework.stereotype.Service;
13 import org.springframework.transaction.annotation.Transactional; 12 import org.springframework.transaction.annotation.Transactional;
14 import org.springframework.validation.annotation.Validated; 13 import org.springframework.validation.annotation.Validated;
15 14
16 import java.time.LocalDateTime; 15 import java.time.LocalDateTime;
17 -import java.util.Collections;  
18 import java.util.List; 16 import java.util.List;
19 -import java.util.Map;  
20 import java.util.Objects; 17 import java.util.Objects;
21 18
22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 19 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
23 -import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_ADD_SELF;  
24 -import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND; 20 +import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.FRIEND;
  21 +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
25 22
26 /** 23 /**
27 * IM 好友关系 Service 实现类 24 * IM 好友关系 Service 实现类
@@ -39,40 +36,19 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -39,40 +36,19 @@ public class ImFriendServiceImpl implements ImFriendService {
39 private AdminUserApi adminUserApi; 36 private AdminUserApi adminUserApi;
40 37
41 @Override 38 @Override
42 - public void validateFriendExists(Long userId, Long friendUserId) { 39 + @Cacheable(cacheNames = FRIEND, key = "#userId + '_' + #friendUserId", unless = "#result == null")
  40 + public boolean isFriend(Long userId, Long friendUserId) {
43 ImFriendDO friend = imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); 41 ImFriendDO friend = imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId);
44 - if (friend == null || CommonStatusEnum.isDisable(friend.getStatus())) {  
45 - throw exception(FRIEND_NOT_FRIEND);  
46 - }  
47 - }  
48 -  
49 - // TODO @AI:这里只负责返回 List<ImFriendDO>;VO 组装在 controller 搞;  
50 - // TODO @AI:方法改成 getEnabledFriendList 之类的,明确只返回未删除的好友;如果需要展示已删除的好友,提供一个 getAllFriendList 之类的方法。  
51 - @Override  
52 - public List<ImFriendRespVO> getMyFriendList(Long userId) {  
53 - List<ImFriendDO> friends = imFriendMapper.selectListByUserId(userId);  
54 - if (friends.isEmpty()) {  
55 - return Collections.emptyList();  
56 - }  
57 - // 批量聚合 AdminUser 信息(昵称 / 头像),避免 N+1  
58 - List<Long> friendUserIds = CollectionUtils.convertList(friends, ImFriendDO::getFriendUserId);  
59 - Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(friendUserIds);  
60 - return CollectionUtils.convertList(friends, f -> buildRespVO(f, userMap.get(f.getFriendUserId()))); 42 + return friend != null && CommonStatusEnum.isEnable(friend.getStatus());
61 } 43 }
62 44
63 - // TODO @AI:这里只负责返回 ImFriendDO;  
64 @Override 45 @Override
65 - public ImFriendRespVO getFriend(Long userId, Long friendUserId) {  
66 - ImFriendDO friend = imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId);  
67 - if (friend == null) {  
68 - throw exception(FRIEND_NOT_FRIEND);  
69 - }  
70 - AdminUserRespDTO user = adminUserApi.getUser(friendUserId);  
71 - return buildRespVO(friend, user); 46 + public List<ImFriendDO> getFriendList(Long userId) {
  47 + return imFriendMapper.selectListByUserId(userId);
72 } 48 }
73 49
74 @Override 50 @Override
75 - public ImFriendDO getFriendDO(Long userId, Long friendUserId) { 51 + public ImFriendDO getFriend(Long userId, Long friendUserId) {
76 return imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); 52 return imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId);
77 } 53 }
78 54
@@ -83,6 +59,7 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -83,6 +59,7 @@ public class ImFriendServiceImpl implements ImFriendService {
83 if (friend == null) { 59 if (friend == null) {
84 throw exception(FRIEND_NOT_FRIEND); 60 throw exception(FRIEND_NOT_FRIEND);
85 } 61 }
  62 +
86 // 2. 更新好友属性(目前仅免打扰) 63 // 2. 更新好友属性(目前仅免打扰)
87 ImFriendDO updateObj = new ImFriendDO(); 64 ImFriendDO updateObj = new ImFriendDO();
88 updateObj.setId(friend.getId()); 65 updateObj.setId(friend.getId());
@@ -131,8 +108,8 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -131,8 +108,8 @@ public class ImFriendServiceImpl implements ImFriendService {
131 * - 已存在且 ENABLE:无需重复操作 108 * - 已存在且 ENABLE:无需重复操作
132 * - 已存在但 DISABLE:恢复关系(status=ENABLE,刷新 addTime,清空 deleteTime) 109 * - 已存在但 DISABLE:恢复关系(status=ENABLE,刷新 addTime,清空 deleteTime)
133 */ 110 */
134 - // TODO @AI:spring cache;参考别的模块的写法;  
135 - private void bindFriend(Long userId, Long friendUserId) { 111 + @CacheEvict(cacheNames = FRIEND, key = "#userId + '_' + #friendUserId")
  112 + public void bindFriend(Long userId, Long friendUserId) {
136 ImFriendDO exists = imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); 113 ImFriendDO exists = imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId);
137 if (exists != null) { 114 if (exists != null) {
138 if (CommonStatusEnum.isEnable(exists.getStatus())) { 115 if (CommonStatusEnum.isEnable(exists.getStatus())) {
@@ -161,7 +138,8 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -161,7 +138,8 @@ public class ImFriendServiceImpl implements ImFriendService {
161 /** 138 /**
162 * 单向解除好友关系(status 设为 DISABLE,记录 deleteTime) 139 * 单向解除好友关系(status 设为 DISABLE,记录 deleteTime)
163 */ 140 */
164 - private void unbindFriend(Long userId, Long friendUserId) { 141 + @CacheEvict(cacheNames = FRIEND, key = "#userId + '_' + #friendUserId")
  142 + public void unbindFriend(Long userId, Long friendUserId) {
165 ImFriendDO exists = imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); 143 ImFriendDO exists = imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId);
166 if (exists == null || CommonStatusEnum.isDisable(exists.getStatus())) { 144 if (exists == null || CommonStatusEnum.isDisable(exists.getStatus())) {
167 return; 145 return;
@@ -173,22 +151,4 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -173,22 +151,4 @@ public class ImFriendServiceImpl implements ImFriendService {
173 imFriendMapper.updateById(updateObj); 151 imFriendMapper.updateById(updateObj);
174 } 152 }
175 153
176 - /**  
177 - * 聚合 ImFriendDO + AdminUser 构造响应 VO  
178 - */  
179 - private ImFriendRespVO buildRespVO(ImFriendDO friend, AdminUserRespDTO user) {  
180 - ImFriendRespVO vo = new ImFriendRespVO();  
181 - vo.setId(friend.getId());  
182 - vo.setFriendUserId(friend.getFriendUserId());  
183 - vo.setMuted(friend.getMuted());  
184 - vo.setStatus(friend.getStatus());  
185 - vo.setAddTime(friend.getAddTime());  
186 - vo.setDeleteTime(friend.getDeleteTime());  
187 - if (user != null) {  
188 - vo.setNickname(user.getNickname());  
189 - vo.setAvatar(user.getAvatar());  
190 - }  
191 - return vo;  
192 - }  
193 -  
194 } 154 }
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
@@ -67,7 +67,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @@ -67,7 +67,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
67 return existing; 67 return existing;
68 } 68 }
69 // 1.2 好友校验 69 // 1.2 好友校验
70 - friendService.validateFriendExists(senderId, reqVO.getReceiverId()); 70 + if (!friendService.isFriend(senderId, reqVO.getReceiverId())) {
  71 + throw exception(FRIEND_NOT_FRIEND);
  72 + }
71 // 1.3 文本消息敏感词过滤 73 // 1.3 文本消息敏感词过滤
72 if (ImMessageTypeEnum.TEXT.getType().equals(reqVO.getType())) { 74 if (ImMessageTypeEnum.TEXT.getType().equals(reqVO.getType())) {
73 sensitiveWordService.validateText(reqVO.getContent()); 75 sensitiveWordService.validateText(reqVO.getContent());
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java
@@ -81,7 +81,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -81,7 +81,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
81 assertNotNull(result.getSendTime()); 81 assertNotNull(result.getSendTime());
82 82
83 // 验证调用 83 // 验证调用
84 - verify(imFriendService).validateFriendExists(1L, 2L); 84 + verify(imFriendService).isFriend(1L, 2L);
85 verify(imSensitiveWordService).validateText(reqVO.getContent()); 85 verify(imSensitiveWordService).validateText(reqVO.getContent());
86 verify(imPrivateMessageMapper).insert(any(ImPrivateMessageDO.class)); 86 verify(imPrivateMessageMapper).insert(any(ImPrivateMessageDO.class));
87 verify(webSocketMessageSender, times(2)).sendObject(anyInt(), anyLong(), anyString(), any()); 87 verify(webSocketMessageSender, times(2)).sendObject(anyInt(), anyLong(), anyString(), any());
@@ -114,8 +114,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -114,8 +114,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
114 ImPrivateMessageSendReqVO reqVO = buildSendReqVO(); 114 ImPrivateMessageSendReqVO reqVO = buildSendReqVO();
115 when(imPrivateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001")) 115 when(imPrivateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001"))
116 .thenReturn(null); 116 .thenReturn(null);
117 - doThrow(new ServiceException(FRIEND_NOT_FRIEND))  
118 - .when(imFriendService).validateFriendExists(1L, 2L); 117 + when(imFriendService.isFriend(1L, 2L)).thenReturn(false);
119 118
120 // 调用并断言 119 // 调用并断言
121 ServiceException exception = assertThrows(ServiceException.class, 120 ServiceException exception = assertThrows(ServiceException.class,