Commit f360040a3d144733ba2d14845557eec32789a6c6
1 parent
ded12090
✨ feat(im): 初始化群申请 v0.5:第六把 review(性能 / 健壮性 / 简洁度收口)
后端
- createInviteRequestList N+1 → 3 SQL:批量 select IN + update IN + insertBatch;20 人邀请从 40 RTT 降到 3 RTT
- service 不再出现 mybatis:复用记录的 update(null, wrapper) 下沉到 Im{Group,Friend}RequestMapper.update*Reset helper
- inviteGroupMember 入参去重切 hutool:CollUtil.subtractToList(CollUtil.distinct(...), activeMemberUserIds)
- 删除 dead 字段 inviterUserId(GroupRequestApprovedNotification / GroupRequestRejectedNotification):前端不再消费
前端
- 1505 / 1506 通知改静默:同意走群事件 1509 / 1510 渲染系统提示,拒绝不再打扰
- 修竞态:addByRequestId 校验 handleResult === UNHANDLED,避免 1503 在途时被 1505 / 1506 抢先后又把已处理记录塞回未处理列表
- 修复 dialog 复用记录刷新:watch key 含 inviterUserId / applyContent,同 id 不同内容也触发 refetch;actingId 期间跳过避免本端动作多余 RTT
- 修复 willGoApproval 误报:group.ownerUserId 兜底群主;members 未到位时保守按非审批处理
- unhandledCountMap memoized getter:O(N) 扫一次缓存到 Map,ConversationItem 直读 Map 消除 O(N×M) 重复 filter
Showing
6 changed files
with
101 additions
and
68 deletions
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java
| ... | ... | @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | 6 | import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendRequestManagerPageReqVO; |
| 7 | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO; |
| 8 | +import cn.iocoder.yudao.module.im.enums.friend.ImFriendRequestHandleResultEnum; | |
| 8 | 9 | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| 9 | 10 | import org.apache.ibatis.annotations.Mapper; |
| 10 | 11 | |
| ... | ... | @@ -43,6 +44,22 @@ public interface ImFriendRequestMapper extends BaseMapperX<ImFriendRequestDO> { |
| 43 | 44 | .eq(ImFriendRequestDO::getId, id).eq(ImFriendRequestDO::getHandleResult, handleResult)); |
| 44 | 45 | } |
| 45 | 46 | |
| 47 | + /** | |
| 48 | + * 复用 (fromUserId, toUserId) 旧申请记录:覆盖申请理由 / 备注 / 来源,重置为未处理 + 清空旧处理痕迹 | |
| 49 | + * <p> | |
| 50 | + * handleContent / handleTime 走 LambdaUpdateWrapper.set 显式置 null,updateById 默认会忽略 null 字段 | |
| 51 | + */ | |
| 52 | + default int updateByIdReset(Long id, String applyContent, String displayName, Integer addSource) { | |
| 53 | + return update(null, new LambdaUpdateWrapper<ImFriendRequestDO>() | |
| 54 | + .eq(ImFriendRequestDO::getId, id) | |
| 55 | + .set(ImFriendRequestDO::getApplyContent, applyContent) | |
| 56 | + .set(ImFriendRequestDO::getDisplayName, displayName) | |
| 57 | + .set(ImFriendRequestDO::getAddSource, addSource) | |
| 58 | + .set(ImFriendRequestDO::getHandleResult, ImFriendRequestHandleResultEnum.UNHANDLED.getResult()) | |
| 59 | + .set(ImFriendRequestDO::getHandleContent, null) | |
| 60 | + .set(ImFriendRequestDO::getHandleTime, null)); | |
| 61 | + } | |
| 62 | + | |
| 46 | 63 | default PageResult<ImFriendRequestDO> selectPage(ImFriendRequestManagerPageReqVO reqVO) { |
| 47 | 64 | return selectPage(reqVO, new LambdaQueryWrapperX<ImFriendRequestDO>() |
| 48 | 65 | .eqIfPresent(ImFriendRequestDO::getFromUserId, reqVO.getFromUserId()) | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java
| ... | ... | @@ -5,9 +5,11 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | 6 | import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupRequestManagerPageReqVO; |
| 7 | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupRequestDO; |
| 8 | +import cn.iocoder.yudao.module.im.enums.group.ImGroupRequestHandleResultEnum; | |
| 8 | 9 | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| 9 | 10 | import org.apache.ibatis.annotations.Mapper; |
| 10 | 11 | |
| 12 | +import java.time.LocalDateTime; | |
| 11 | 13 | import java.util.Collection; |
| 12 | 14 | import java.util.List; |
| 13 | 15 | |
| ... | ... | @@ -52,6 +54,42 @@ public interface ImGroupRequestMapper extends BaseMapperX<ImGroupRequestDO> { |
| 52 | 54 | .eq(ImGroupRequestDO::getId, id).eq(ImGroupRequestDO::getHandleResult, expectedHandleResult)); |
| 53 | 55 | } |
| 54 | 56 | |
| 57 | + /** | |
| 58 | + * 复用主动申请的旧记录:覆盖申请理由 / 来源,重置为未处理 + 清空旧处理痕迹 + 刷 update_time | |
| 59 | + * <p> | |
| 60 | + * update_time 显式 set,因为 update(null, wrapper) 不会触发 MetaObjectHandler.updateFill; | |
| 61 | + * 列表查询按 update_time 倒序,复用记录必须刷这一列才会排到最前 | |
| 62 | + */ | |
| 63 | + default int updateApplyByIdReset(Long id, String applyContent, Integer addSource, LocalDateTime updateTime) { | |
| 64 | + return update(null, new LambdaUpdateWrapper<ImGroupRequestDO>() | |
| 65 | + .eq(ImGroupRequestDO::getId, id) | |
| 66 | + .set(ImGroupRequestDO::getApplyContent, applyContent) | |
| 67 | + .set(ImGroupRequestDO::getAddSource, addSource) | |
| 68 | + .set(ImGroupRequestDO::getHandleResult, ImGroupRequestHandleResultEnum.UNHANDLED.getResult()) | |
| 69 | + .set(ImGroupRequestDO::getInviterUserId, null) | |
| 70 | + .set(ImGroupRequestDO::getHandleUserId, null) | |
| 71 | + .set(ImGroupRequestDO::getHandleContent, null) | |
| 72 | + .set(ImGroupRequestDO::getHandleTime, null) | |
| 73 | + .set(ImGroupRequestDO::getUpdateTime, updateTime)); | |
| 74 | + } | |
| 75 | + | |
| 76 | + /** | |
| 77 | + * 复用邀请审批的旧记录(批量):盖写 inviterUserId / addSource,重置为未处理 + 清空旧处理痕迹 + 刷 update_time | |
| 78 | + */ | |
| 79 | + default int updateInviteByGroupIdAndUserIdsReset(Long groupId, Collection<Long> userIds, | |
| 80 | + Long inviterUserId, Integer addSource, LocalDateTime updateTime) { | |
| 81 | + return update(null, new LambdaUpdateWrapper<ImGroupRequestDO>() | |
| 82 | + .eq(ImGroupRequestDO::getGroupId, groupId) | |
| 83 | + .in(ImGroupRequestDO::getUserId, userIds) | |
| 84 | + .set(ImGroupRequestDO::getInviterUserId, inviterUserId) | |
| 85 | + .set(ImGroupRequestDO::getAddSource, addSource) | |
| 86 | + .set(ImGroupRequestDO::getHandleResult, ImGroupRequestHandleResultEnum.UNHANDLED.getResult()) | |
| 87 | + .set(ImGroupRequestDO::getHandleUserId, null) | |
| 88 | + .set(ImGroupRequestDO::getHandleContent, null) | |
| 89 | + .set(ImGroupRequestDO::getHandleTime, null) | |
| 90 | + .set(ImGroupRequestDO::getUpdateTime, updateTime)); | |
| 91 | + } | |
| 92 | + | |
| 55 | 93 | default PageResult<ImGroupRequestDO> selectPage(ImGroupRequestManagerPageReqVO reqVO) { |
| 56 | 94 | return selectPage(reqVO, new LambdaQueryWrapperX<ImGroupRequestDO>() |
| 57 | 95 | .eqIfPresent(ImGroupRequestDO::getGroupId, reqVO.getGroupId()) | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.friend; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.util.ObjUtil; |
| 4 | -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | 4 | import cn.hutool.extra.spring.SpringUtil; |
| 6 | 5 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 7 | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| ... | ... | @@ -88,18 +87,10 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { |
| 88 | 87 | } |
| 89 | 88 | |
| 90 | 89 | // 2. 落库:upsert 语义;同一对 (from, to) 唯一,已有记录覆盖申请理由 / 备注 / 来源 + 重置为未处理 + 清空旧处理痕迹 |
| 91 | - // null 字段(handleContent / handleTime)走 LambdaUpdateWrapper.set 显式清空,updateById 默认会忽略 null | |
| 92 | - // TODO @AI:看看是不是在 basemapperx 里,增加一个 updateXXXX;可以根据传递的 DO,深度更新的方法?应该匹配这个场景的对哇?【主要希望 service 不要出现 mapper 相关的类】 | |
| 93 | 90 | ImFriendRequestDO request = friendRequestMapper.selectByFromUserIdAndToUserId(fromUserId, toUserId); |
| 94 | 91 | if (request != null) { |
| 95 | - friendRequestMapper.update(null, new LambdaUpdateWrapper<ImFriendRequestDO>() | |
| 96 | - .eq(ImFriendRequestDO::getId, request.getId()) | |
| 97 | - .set(ImFriendRequestDO::getApplyContent, reqVO.getApplyContent()) | |
| 98 | - .set(ImFriendRequestDO::getDisplayName, reqVO.getDisplayName()) | |
| 99 | - .set(ImFriendRequestDO::getAddSource, reqVO.getAddSource()) | |
| 100 | - .set(ImFriendRequestDO::getHandleResult, ImFriendRequestHandleResultEnum.UNHANDLED.getResult()) | |
| 101 | - .set(ImFriendRequestDO::getHandleContent, null) | |
| 102 | - .set(ImFriendRequestDO::getHandleTime, null)); | |
| 92 | + friendRequestMapper.updateByIdReset(request.getId(), | |
| 93 | + reqVO.getApplyContent(), reqVO.getDisplayName(), reqVO.getAddSource()); | |
| 103 | 94 | request.setApplyContent(reqVO.getApplyContent()).setDisplayName(reqVO.getDisplayName()) |
| 104 | 95 | .setAddSource(reqVO.getAddSource()) |
| 105 | 96 | .setHandleResult(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()) | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.group; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 6 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 7 | 6 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| ... | ... | @@ -94,25 +93,12 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { |
| 94 | 93 | return null; |
| 95 | 94 | } |
| 96 | 95 | |
| 97 | - // 3. 情况二:群开启了审批;upsert 一条「主动申请」记录,inviterUserId=null | |
| 98 | - // 已有记录(无论之前是未处理 / 已同意 / 已拒绝),覆盖申请理由 + 来源 + 重置为未处理 + 清空旧处理痕迹 | |
| 99 | - // null 字段(inviterUserId / handleUserId / handleContent / handleTime)走 LambdaUpdateWrapper.set 显式清空,updateById 默认会忽略 null | |
| 100 | - // TODO @AI:看看是不是在 basemapperx 里,增加一个 updateXXXX;可以根据传递的 DO,深度更新的方法?应该匹配这个场景的对哇?【主要希望 service 不要出现 mapper 相关的类】 | |
| 96 | + // 3. 情况二:群开启了审批;upsert 一条主动申请记录(inviterUserId=null) | |
| 101 | 97 | ImGroupRequestDO request = groupRequestMapper.selectByGroupIdAndUserId(groupId, userId); |
| 102 | 98 | if (request != null) { |
| 103 | - // update_time 显式置当前时间:update(null, wrapper) 不会触发 MetaObjectHandler.updateFill; | |
| 104 | - // 列表查询按 update_time 倒序排,复用旧记录时必须刷新这一列才能让本次重新提交排到最前 | |
| 105 | 99 | LocalDateTime now = LocalDateTime.now(); |
| 106 | - groupRequestMapper.update(null, new LambdaUpdateWrapper<ImGroupRequestDO>() | |
| 107 | - .eq(ImGroupRequestDO::getId, request.getId()) | |
| 108 | - .set(ImGroupRequestDO::getApplyContent, reqVO.getApplyContent()) | |
| 109 | - .set(ImGroupRequestDO::getAddSource, reqVO.getAddSource()) | |
| 110 | - .set(ImGroupRequestDO::getHandleResult, ImGroupRequestHandleResultEnum.UNHANDLED.getResult()) | |
| 111 | - .set(ImGroupRequestDO::getInviterUserId, null) | |
| 112 | - .set(ImGroupRequestDO::getHandleUserId, null) | |
| 113 | - .set(ImGroupRequestDO::getHandleContent, null) | |
| 114 | - .set(ImGroupRequestDO::getHandleTime, null) | |
| 115 | - .set(ImGroupRequestDO::getUpdateTime, now)); | |
| 100 | + groupRequestMapper.updateApplyByIdReset(request.getId(), | |
| 101 | + reqVO.getApplyContent(), reqVO.getAddSource(), now); | |
| 116 | 102 | request.setApplyContent(reqVO.getApplyContent()).setAddSource(reqVO.getAddSource()) |
| 117 | 103 | .setHandleResult(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()) |
| 118 | 104 | .setInviterUserId(null).setHandleUserId(null) |
| ... | ... | @@ -209,45 +195,40 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { |
| 209 | 195 | return; |
| 210 | 196 | } |
| 211 | 197 | ImGroupDO group = groupService.validateGroupExists(groupId); |
| 212 | - | |
| 213 | - // 1. 每个被邀请人 upsert 一条 inviter_user_id=邀请人 的记录 | |
| 214 | - // 已有记录(同一对 group_id, user_id 唯一):覆盖 inviter_user_id / add_source + 重置 handle_result + 清空旧处理痕迹(走 LambdaUpdateWrapper.set 显式置 null) | |
| 215 | - // add_source 强制覆写为 INVITE,避免审批通过后把残留的旧来源回写进群成员留痕 | |
| 216 | 198 | Integer inviteSource = ImGroupAddSourceEnum.INVITE.getSource(); |
| 217 | - List<ImGroupRequestDO> requests = new ArrayList<>(invitedUserIds.size()); | |
| 218 | - // TODO @AI:看看是不是在 basemapperx 里,增加一个 updateXXXX;可以根据传递的 DO,深度更新的方法?应该匹配这个场景的对哇?【主要希望 service 不要出现 mapper 相关的类】 | |
| 219 | - // 同 applyJoinGroup:update(null, wrapper) 不走 MetaObjectHandler.updateFill,复用旧记录必须显式刷 update_time,否则按 update_time 倒序排不到最前 | |
| 199 | + Integer unhandled = ImGroupRequestHandleResultEnum.UNHANDLED.getResult(); | |
| 220 | 200 | LocalDateTime now = LocalDateTime.now(); |
| 221 | - for (Long invitedUserId : invitedUserIds) { | |
| 222 | - ImGroupRequestDO existing = groupRequestMapper.selectByGroupIdAndUserId(groupId, invitedUserId); | |
| 223 | - if (existing != null) { | |
| 224 | - groupRequestMapper.update(null, new LambdaUpdateWrapper<ImGroupRequestDO>() | |
| 225 | - .eq(ImGroupRequestDO::getId, existing.getId()) | |
| 226 | - .set(ImGroupRequestDO::getInviterUserId, inviterUserId) | |
| 227 | - .set(ImGroupRequestDO::getAddSource, inviteSource) | |
| 228 | - .set(ImGroupRequestDO::getHandleResult, ImGroupRequestHandleResultEnum.UNHANDLED.getResult()) | |
| 229 | - .set(ImGroupRequestDO::getHandleUserId, null) | |
| 230 | - .set(ImGroupRequestDO::getHandleContent, null) | |
| 231 | - .set(ImGroupRequestDO::getHandleTime, null) | |
| 232 | - .set(ImGroupRequestDO::getUpdateTime, now)); | |
| 233 | - existing.setInviterUserId(inviterUserId).setAddSource(inviteSource) | |
| 234 | - .setHandleResult(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()) | |
| 235 | - .setHandleUserId(null).setHandleContent(null).setHandleTime(null) | |
| 236 | - .setUpdateTime(now); | |
| 237 | - requests.add(existing); | |
| 238 | - continue; | |
| 239 | - } | |
| 240 | - ImGroupRequestDO insert = new ImGroupRequestDO() | |
| 241 | - .setGroupId(groupId).setUserId(invitedUserId).setInviterUserId(inviterUserId) | |
| 242 | - .setAddSource(inviteSource) | |
| 243 | - .setHandleResult(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()); | |
| 244 | - groupRequestMapper.insert(insert); | |
| 245 | - requests.add(insert); | |
| 201 | + | |
| 202 | + // 1. 批量查现有记录,分流为「复用旧记录」+「新增」 | |
| 203 | + List<ImGroupRequestDO> existingList = groupRequestMapper.selectListByGroupIdAndUserIds(groupId, invitedUserIds); | |
| 204 | + Set<Long> existingUserIds = convertSet(existingList, ImGroupRequestDO::getUserId); | |
| 205 | + Collection<Long> newUserIds = CollUtil.subtract(invitedUserIds, existingUserIds); | |
| 206 | + | |
| 207 | + // 2. 复用旧记录:一条 update WHERE IN 完成 | |
| 208 | + if (CollUtil.isNotEmpty(existingList)) { | |
| 209 | + groupRequestMapper.updateInviteByGroupIdAndUserIdsReset(groupId, existingUserIds, | |
| 210 | + inviterUserId, inviteSource, now); | |
| 211 | + // 同步内存对象,下面 buildRequestNotification 要读 inviterUserId / addSource | |
| 212 | + existingList.forEach(r -> r.setInviterUserId(inviterUserId).setAddSource(inviteSource) | |
| 213 | + .setHandleResult(unhandled) | |
| 214 | + .setHandleUserId(null).setHandleContent(null).setHandleTime(null) | |
| 215 | + .setUpdateTime(now)); | |
| 216 | + } | |
| 217 | + | |
| 218 | + // 3. 新增:批量 insert | |
| 219 | + List<ImGroupRequestDO> newList = convertList(newUserIds, userId -> new ImGroupRequestDO() | |
| 220 | + .setGroupId(groupId).setUserId(userId).setInviterUserId(inviterUserId) | |
| 221 | + .setAddSource(inviteSource).setHandleResult(unhandled)); | |
| 222 | + if (CollUtil.isNotEmpty(newList)) { | |
| 223 | + groupRequestMapper.insertBatch(newList); | |
| 246 | 224 | } |
| 247 | 225 | |
| 248 | - // 2. 推 1503 给群主 + 全部管理员;多端同步;每条申请单独推一帧 | |
| 226 | + // 4. 推 1503 给群主 + 全部管理员;多端同步;每条申请单独推一帧 | |
| 249 | 227 | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(invitedUserIds); |
| 250 | 228 | List<Long> ownerAndAdmins = getGroupMemberListByOwnerAndAdminUserIds(group); |
| 229 | + List<ImGroupRequestDO> requests = new ArrayList<>(existingList.size() + newList.size()); | |
| 230 | + requests.addAll(existingList); | |
| 231 | + requests.addAll(newList); | |
| 251 | 232 | for (ImGroupRequestDO request : requests) { |
| 252 | 233 | AdminUserRespDTO applyUser = userMap.get(request.getUserId()); |
| 253 | 234 | GroupRequestReceivedNotification payload = buildRequestNotification(group, request, applyUser); | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
| ... | ... | @@ -197,8 +197,8 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 197 | 197 | // 1.2 入参去重 + 排除已在群中的用户 |
| 198 | 198 | List<ImGroupMemberDO> activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId); |
| 199 | 199 | Set<Long> activeMemberUserIds = convertSet(activeMembers, ImGroupMemberDO::getUserId); |
| 200 | - List<Long> memberUserIds = new ArrayList<>(new LinkedHashSet<>(inviteReqVO.getMemberUserIds())); | |
| 201 | - memberUserIds.removeAll(activeMemberUserIds); | |
| 200 | + List<Long> memberUserIds = CollUtil.subtractToList( | |
| 201 | + CollUtil.distinct(inviteReqVO.getMemberUserIds()), activeMemberUserIds); | |
| 202 | 202 | if (CollUtil.isEmpty(memberUserIds)) { |
| 203 | 203 | return; |
| 204 | 204 | } | ... | ... |
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java
| ... | ... | @@ -23,6 +23,7 @@ import org.mockito.ArgumentCaptor; |
| 23 | 23 | import org.mockito.InjectMocks; |
| 24 | 24 | import org.mockito.Mock; |
| 25 | 25 | |
| 26 | +import java.util.Collection; | |
| 26 | 27 | import java.util.List; |
| 27 | 28 | |
| 28 | 29 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; |
| ... | ... | @@ -253,6 +254,8 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { |
| 253 | 254 | .joinApproval(true) |
| 254 | 255 | .status(CommonStatusEnum.ENABLE.getStatus()).build(); |
| 255 | 256 | when(groupService.validateGroupExists(10L)).thenReturn(group); |
| 257 | + when(groupRequestMapper.selectListByGroupIdAndUserIds(eq(10L), anyCollection())) | |
| 258 | + .thenReturn(List.of()); | |
| 256 | 259 | when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(List.of( |
| 257 | 260 | ImGroupMemberDO.builder().groupId(10L).userId(99L) |
| 258 | 261 | .role(ImGroupMemberRoleEnum.OWNER.getRole()) |
| ... | ... | @@ -261,15 +264,18 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { |
| 261 | 264 | 2L, buildUser(2L, "用户A"), |
| 262 | 265 | 3L, buildUser(3L, "用户B"))); |
| 263 | 266 | |
| 264 | - // 调用:邀请人 1L 邀请 2L、3L | |
| 267 | + // 调用:邀请人 1L 邀请 2L、3L(都没有旧记录) | |
| 265 | 268 | groupRequestService.createInviteRequestList(10L, 1L, List.of(2L, 3L)); |
| 266 | 269 | |
| 267 | - // 断言:插入 2 条申请记录 + 推 1503 给 owner(每条 1 帧)共 2 帧 | |
| 268 | - ArgumentCaptor<ImGroupRequestDO> captor = ArgumentCaptor.forClass(ImGroupRequestDO.class); | |
| 269 | - verify(groupRequestMapper, times(2)).insert(captor.capture()); | |
| 270 | + // 断言:批量插入 2 条 + 推 1503 给 owner(每条 1 帧)共 2 帧 | |
| 271 | + @SuppressWarnings("unchecked") | |
| 272 | + ArgumentCaptor<Collection<ImGroupRequestDO>> captor = ArgumentCaptor.forClass(Collection.class); | |
| 273 | + verify(groupRequestMapper).insertBatch(captor.capture()); | |
| 270 | 274 | verify(websocketService, times(2)).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); |
| 271 | 275 | // 断言:每条记录 inviterUserId=1 + addSource=INVITE,避免审批通过后回写群成员留痕的来源为空 / 脏带旧值 |
| 272 | - captor.getAllValues().forEach(insert -> { | |
| 276 | + Collection<ImGroupRequestDO> inserted = captor.getValue(); | |
| 277 | + assertEquals(2, inserted.size()); | |
| 278 | + inserted.forEach(insert -> { | |
| 273 | 279 | assertEquals(1L, insert.getInviterUserId()); |
| 274 | 280 | assertEquals(ImGroupAddSourceEnum.INVITE.getSource(), insert.getAddSource()); |
| 275 | 281 | }); | ... | ... |