Commit bf8eb273ec01bacc91369d079d5f7d6d2311bae2
1 parent
5e824ccb
✨ feat(message): 优化群聊的 enrichPullResultForViewer 已读状态、人数
Showing
1 changed file
with
29 additions
and
36 deletions
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java
| @@ -35,13 +35,9 @@ import java.time.LocalDateTime; | @@ -35,13 +35,9 @@ import java.time.LocalDateTime; | ||
| 35 | import java.util.*; | 35 | import java.util.*; |
| 36 | 36 | ||
| 37 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 37 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 38 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | ||
| 39 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | ||
| 40 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | 38 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| 41 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; | 39 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; |
| 42 | -import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.MESSAGE_GROUP_PULL_MAX_DAYS; | ||
| 43 | -import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.MESSAGE_MAX_PULL_SIZE; | ||
| 44 | -import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.MESSAGE_RECALL_TIMEOUT_MINUTES; | 40 | +import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*; |
| 45 | 41 | ||
| 46 | /** | 42 | /** |
| 47 | * IM 群聊消息 Service 实现类 | 43 | * IM 群聊消息 Service 实现类 |
| @@ -145,8 +141,8 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { | @@ -145,8 +141,8 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { | ||
| 145 | .sorted(Comparator.comparing(ImGroupMessageDO::getId)) | 141 | .sorted(Comparator.comparing(ImGroupMessageDO::getId)) |
| 146 | .limit(size) | 142 | .limit(size) |
| 147 | .toList(); | 143 | .toList(); |
| 148 | - // 3.2 按当前用户补齐离线拉取结果:消息已读态(相对 Redis 已读游标)、本人发送的回执消息的已读人数 | ||
| 149 | - enrichPullResultForViewer(userId, result); | 144 | + // 3.2 按当前用户补齐:消息已读态(相对 Redis 已读游标)、本人发送的回执消息的已读人数 |
| 145 | + appendMessageStatusAndReceipt(userId, result); | ||
| 150 | 146 | ||
| 151 | log.info("[pullGroupMessageList][userId({}) minId({}) size({}) result({})]", | 147 | log.info("[pullGroupMessageList][userId({}) minId({}) size({}) result({})]", |
| 152 | userId, minId, size, result.size()); | 148 | userId, minId, size, result.size()); |
| @@ -154,26 +150,29 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { | @@ -154,26 +150,29 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { | ||
| 154 | } | 150 | } |
| 155 | 151 | ||
| 156 | /** | 152 | /** |
| 157 | - * 离线拉取结果按当前用户补齐:消息已读态(相对 Redis 已读游标)、本人发送的回执消息的已读人数 | 153 | + * 补全消息已读态和回执已读人数 |
| 154 | + * | ||
| 155 | + * 1. 消息已读态(status):根据 Redis 已读游标判断 READ / UNREAD | ||
| 156 | + * 2. 回执已读人数(readCount):仅对本人发送的回执消息,计算可见成员中的已读人数 | ||
| 158 | */ | 157 | */ |
| 159 | - @SuppressWarnings("StatementWithEmptyBody") | ||
| 160 | - private void enrichPullResultForViewer(Long userId, List<ImGroupMessageDO> messages) { | 158 | + @SuppressWarnings({"StatementWithEmptyBody", "DataFlowIssue"}) |
| 159 | + private void appendMessageStatusAndReceipt(Long userId, List<ImGroupMessageDO> messages) { | ||
| 161 | if (CollUtil.isEmpty(messages)) { | 160 | if (CollUtil.isEmpty(messages)) { |
| 162 | return; | 161 | return; |
| 163 | } | 162 | } |
| 164 | - Map<Long, Long> viewerReadCursorByGroup = new HashMap<>(); // 群 → 已读位置 | 163 | + Map<Long, Long> readMaxMessageIdsByGroup = new HashMap<>(); // 群 → 已读位置 |
| 165 | Map<Long, Map<Long, Long>> readPositionsByGroup = new HashMap<>(); // 群 → (用户 → 已读位置) | 164 | Map<Long, Map<Long, Long>> readPositionsByGroup = new HashMap<>(); // 群 → (用户 → 已读位置) |
| 166 | Map<Long, List<ImGroupMemberDO>> membersByGroup = new HashMap<>(); // 群 → 全部成员列表 | 165 | Map<Long, List<ImGroupMemberDO>> membersByGroup = new HashMap<>(); // 群 → 全部成员列表 |
| 167 | for (ImGroupMessageDO message : messages) { | 166 | for (ImGroupMessageDO message : messages) { |
| 168 | // 消息已读态(status):撤回 > 已读 > 未读 | 167 | // 消息已读态(status):撤回 > 已读 > 未读 |
| 169 | Long groupId = message.getGroupId(); | 168 | Long groupId = message.getGroupId(); |
| 170 | - long readCursor = viewerReadCursorByGroup.computeIfAbsent(groupId, gid -> { | ||
| 171 | - Long readMax = groupMessageReadRedisDAO.getReadMaxMessageId(gid, userId); | ||
| 172 | - return readMax != null ? readMax : -1L; | 169 | + long readMaxMessageId = readMaxMessageIdsByGroup.computeIfAbsent(groupId, gid -> { |
| 170 | + Long readMaxMsgId = groupMessageReadRedisDAO.getReadMaxMessageId(gid, userId); | ||
| 171 | + return readMaxMsgId != null ? readMaxMsgId : -1L; | ||
| 173 | }); | 172 | }); |
| 174 | if (ImMessageStatusEnum.RECALL.getStatus().equals(message.getStatus())) { | 173 | if (ImMessageStatusEnum.RECALL.getStatus().equals(message.getStatus())) { |
| 175 | // 保持撤回态 | 174 | // 保持撤回态 |
| 176 | - } else if (readCursor >= message.getId()) { | 175 | + } else if (readMaxMessageId >= message.getId()) { |
| 177 | message.setStatus(ImMessageStatusEnum.READ.getStatus()); | 176 | message.setStatus(ImMessageStatusEnum.READ.getStatus()); |
| 178 | } else { | 177 | } else { |
| 179 | message.setStatus(ImMessageStatusEnum.UNREAD.getStatus()); | 178 | message.setStatus(ImMessageStatusEnum.UNREAD.getStatus()); |
| @@ -190,15 +189,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { | @@ -190,15 +189,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { | ||
| 190 | groupMemberService::getGroupMemberListByGroupId); | 189 | groupMemberService::getGroupMemberListByGroupId); |
| 191 | Set<Long> visibleUserIds = getVisibleUserIds(message, allMembers); | 190 | Set<Long> visibleUserIds = getVisibleUserIds(message, allMembers); |
| 192 | visibleUserIds.remove(message.getSenderId()); | 191 | visibleUserIds.remove(message.getSenderId()); |
| 193 | - int readCount = 0; | ||
| 194 | - if (CollUtil.isNotEmpty(visibleUserIds)) { | ||
| 195 | - for (Long uid : visibleUserIds) { | ||
| 196 | - Long pos = positions.get(uid); | ||
| 197 | - if (pos != null && pos >= message.getId()) { | ||
| 198 | - readCount++; | ||
| 199 | - } | ||
| 200 | - } | ||
| 201 | - } | 192 | + int readCount = getSumValue(visibleUserIds, |
| 193 | + uid -> positions.getOrDefault(uid, -1L) >= message.getId() ? 1 : null, | ||
| 194 | + Integer::sum, 0); | ||
| 202 | message.setReadCount(readCount); | 195 | message.setReadCount(readCount); |
| 203 | } | 196 | } |
| 204 | } | 197 | } |
| @@ -394,29 +387,29 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { | @@ -394,29 +387,29 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { | ||
| 394 | 387 | ||
| 395 | /** | 388 | /** |
| 396 | * 判断一条群消息对某个群成员是否可见 | 389 | * 判断一条群消息对某个群成员是否可见 |
| 397 | - * <p> | ||
| 398 | - * 规则: | ||
| 399 | - * 1. 入群时间晚于消息发送时间 → 不可见 | ||
| 400 | - * 2. 已退群且退群时间早于消息发送时间 → 不可见 | ||
| 401 | - * 3. 存在定向接收列表且当前用户既不在列表中也不是发送者 → 不可见 | 390 | + * |
| 391 | + * @param msg 消息 | ||
| 392 | + * @param member 群成员 | ||
| 393 | + * @param userId 当前用户编号(用于定向消息过滤) | ||
| 394 | + * @return 是否可见 | ||
| 402 | */ | 395 | */ |
| 403 | private boolean isMessageVisible(ImGroupMessageDO msg, ImGroupMemberDO member, Long userId) { | 396 | private boolean isMessageVisible(ImGroupMessageDO msg, ImGroupMemberDO member, Long userId) { |
| 404 | if (member == null) { | 397 | if (member == null) { |
| 405 | return false; | 398 | return false; |
| 406 | } | 399 | } |
| 400 | + // 1. 入群时间晚于消息发送时间 → 不可见 | ||
| 407 | if (member.getJoinTime() != null && msg.getSendTime().isBefore(member.getJoinTime())) { | 401 | if (member.getJoinTime() != null && msg.getSendTime().isBefore(member.getJoinTime())) { |
| 408 | return false; | 402 | return false; |
| 409 | } | 403 | } |
| 404 | + // 2. 已退群且退群时间早于消息发送时间 → 不可见 | ||
| 410 | if (CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus()) | 405 | if (CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus()) |
| 411 | && member.getQuitTime() != null && msg.getSendTime().isAfter(member.getQuitTime())) { | 406 | && member.getQuitTime() != null && msg.getSendTime().isAfter(member.getQuitTime())) { |
| 412 | return false; | 407 | return false; |
| 413 | } | 408 | } |
| 414 | - if (CollUtil.isNotEmpty(msg.getReceiverUserIds()) | ||
| 415 | - && !msg.getReceiverUserIds().contains(userId) | ||
| 416 | - && ObjUtil.notEqual(msg.getSenderId(), userId)) { | ||
| 417 | - return false; | ||
| 418 | - } | ||
| 419 | - return true; | 409 | + // 3. 存在定向接收列表且当前用户既不在列表中也不是发送者 → 不可见 |
| 410 | + return !CollUtil.isNotEmpty(msg.getReceiverUserIds()) | ||
| 411 | + || msg.getReceiverUserIds().contains(userId) | ||
| 412 | + || !ObjUtil.notEqual(msg.getSenderId(), userId); | ||
| 420 | } | 413 | } |
| 421 | 414 | ||
| 422 | /** | 415 | /** |