From ca0aadac49e43d3047667347642c86dfb2886171 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 20 Apr 2026 19:08:52 +0800 Subject: [PATCH] ✨ feat(im): 添加私聊历史消息列表请求对象及相关接口调整 --- yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java | 4 ++-- yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageListReqVO.java | 27 +++++++++++++++++++++++++++ yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java | 34 ++++++++++++++-------------------- yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java | 2 +- yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java | 81 ++++++++++++++++++++++++++++++++++++++++----------------------------------------- yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java | 8 ++++---- 6 files changed, 88 insertions(+), 68 deletions(-) create mode 100644 yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageListReqVO.java diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java index 3b00ebf..fe0d4a2 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java @@ -42,10 +42,10 @@ public class ImGroupMessageController { @Operation(summary = "拉取群聊消息(增量)") @Parameter(name = "minId", description = "最小消息 id", required = true, example = "0") @Parameter(name = "size", description = "拉取数量", required = true, example = "100") - public CommonResult> pullGroupMessages( + public CommonResult> pullGroupMessageList( @RequestParam("minId") Long minId, @RequestParam("size") Integer size) { - List messages = imGroupMessageService.pullGroupMessages(getLoginUserId(), minId, size); + List messages = imGroupMessageService.pullGroupMessageList(getLoginUserId(), minId, size); return success(messages.stream().map(this::toRespVO).collect(Collectors.toList())); } diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageListReqVO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageListReqVO.java new file mode 100644 index 0000000..c9259d9 --- /dev/null +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageListReqVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.im.controller.admin.message.vo.privates; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 私聊历史消息列表 Request VO") +@Data +public class ImPrivateMessageListReqVO { + + @Schema(description = "接收人编号(对方)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "接收人编号不能为空") + private Long receiverId; + + @Schema(description = "起始消息编号,从该 id 往前拉取(不含)。为空则从最新消息开始", example = "1024") + private Long maxId; + + @Schema(description = "拉取数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + @NotNull(message = "拉取数量不能为空") + @Min(value = 1, message = "拉取数量最小值为 1") + @Max(value = 200, message = "拉取数量最大值为 200") + private Integer limit; + +} diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java index 2058b55..3ab747f 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.im.dal.mysql.message; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; import org.apache.ibatis.annotations.Mapper; @@ -15,42 +16,35 @@ import java.util.List; @Mapper public interface ImGroupMessageMapper extends BaseMapperX { - /** - * 根据群编号和 minId 增量拉取群聊消息 - * - * @param groupIds 用户所在的群编号列表 - * @param minId 最小消息 id(不含) - * @param size 拉取数量 - * @return 消息列表 - */ default List selectListByMinId(List groupIds, Long minId, Integer size) { - return selectList(new LambdaQueryWrapperX() - .in(ImGroupMessageDO::getGroupId, groupIds) - .gt(ImGroupMessageDO::getId, minId) - .orderByAsc(ImGroupMessageDO::getId) - .last("LIMIT " + size)); + QueryWrapperX wrapper = new QueryWrapperX<>(); + wrapper.in("group_id", groupIds) + .gt("id", minId) + .orderByAsc("id"); + wrapper.limitN(size); + return selectList(wrapper); } - /** - * 根据 senderId 和 clientMessageId 查询(幂等校验) - */ default ImGroupMessageDO selectBySenderIdAndClientMessageId(Long senderId, String clientMessageId) { return selectOne(new LambdaQueryWrapperX() .eq(ImGroupMessageDO::getSenderId, senderId) .eq(ImGroupMessageDO::getClientMessageId, clientMessageId)); } + // TODO @芋艿:暂时没 review /** * 获取群内最新消息 id */ default Long selectMaxIdByGroupId(Long groupId) { - ImGroupMessageDO msg = selectOne(new LambdaQueryWrapperX() - .eq(ImGroupMessageDO::getGroupId, groupId) - .orderByDesc(ImGroupMessageDO::getId) - .last("LIMIT 1")); + QueryWrapperX wrapper = new QueryWrapperX<>(); + wrapper.eq("group_id", groupId) + .orderByDesc("id"); + wrapper.limitN(1); + ImGroupMessageDO msg = selectOne(wrapper); return msg == null ? null : msg.getId(); } + // TODO @芋艿:暂时没 review /** * 获取群内需要回执的消息(状态为 PENDING) */ diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java index 8dbc512..d5e4e42 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java @@ -29,7 +29,7 @@ public interface ImGroupMessageService { * @param size 拉取数量 * @return 消息列表 */ - List pullGroupMessages(Long userId, Long minId, Integer size); + List pullGroupMessageList(Long userId, Long minId, Integer size); /** * 标记群聊消息已读 diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java index 20b3dbc..1fbccc9 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java @@ -11,8 +11,12 @@ import cn.iocoder.yudao.module.im.dal.redis.group.GroupReadPositionRedisDAO; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageSceneEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; -import cn.iocoder.yudao.module.im.enums.message.ImWebSocketTypeConstants; +import cn.iocoder.yudao.module.im.websocket.ImGroupMessageSendMessage; +import cn.iocoder.yudao.module.im.websocket.ImMessageReadMessage; +import cn.iocoder.yudao.module.im.websocket.ImMessageReceiptMessage; +import cn.iocoder.yudao.module.im.websocket.ImMessageRecallMessage; import cn.iocoder.yudao.module.im.service.group.ImGroupService; import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; @@ -28,6 +32,7 @@ import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*; /** * IM 群聊消息 Service 实现类 @@ -39,8 +44,6 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; @Slf4j public class ImGroupMessageServiceImpl implements ImGroupMessageService { - private static final int MAX_PULL_SIZE = 1000; - @Resource private ImGroupMessageMapper imGroupMessageMapper; @Resource @@ -100,8 +103,8 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { } @Override - public List pullGroupMessages(Long userId, Long minId, Integer size) { - if (size > MAX_PULL_SIZE) { + public List pullGroupMessageList(Long userId, Long minId, Integer size) { + if (size > MESSAGE_MAX_PULL_SIZE) { throw exception(MESSAGE_PULL_SIZE_EXCEEDED); } // 获取用户所在的所有群 @@ -162,11 +165,11 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { groupReadPositionRedisDAO.updateReadPosition(groupId, userId, maxMessageId); // 4. 发送 READ 事件给自己的其他终端 - Map readEvent = new HashMap<>(); - readEvent.put("groupId", groupId.toString()); - readEvent.put("messageScene", "group"); + ImMessageReadMessage readMessage = new ImMessageReadMessage() + .setGroupId(groupId) + .setScene(ImMessageSceneEnum.GROUP.getScene()); webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), userId, - ImWebSocketTypeConstants.READ, readEvent); + ImMessageReadMessage.TYPE, readMessage); // 5. 刷新群回执 refreshGroupReceipts(groupId); @@ -195,17 +198,17 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { // 5. 发送 RECALL 事件给所有群成员 List members = imGroupMemberService.selectByGroupId(message.getGroupId()); - Map recallEvent = new HashMap<>(); - recallEvent.put("messageId", messageId.toString()); - recallEvent.put("groupId", message.getGroupId().toString()); - recallEvent.put("messageScene", "group"); - recallEvent.put("senderId", userId.toString()); + ImMessageRecallMessage recallMessage = new ImMessageRecallMessage() + .setMessageId(messageId) + .setGroupId(message.getGroupId()) + .setSenderId(userId) + .setScene(ImMessageSceneEnum.GROUP.getScene()); for (ImGroupMemberDO m : members) { if (CommonStatusEnum.DISABLE.getStatus().equals(m.getStatus())) { continue; } webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), m.getUserId(), - ImWebSocketTypeConstants.RECALL, recallEvent); + ImMessageRecallMessage.TYPE, recallMessage); } } @@ -285,20 +288,21 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { } // 6. 广播 RECEIPT 事件给活跃成员 - Map receiptEvent = new HashMap<>(); - receiptEvent.put("messageId", msg.getId().toString()); - receiptEvent.put("groupId", groupId.toString()); - receiptEvent.put("readCount", readCount); - receiptEvent.put("receiptStatus", allRead - ? ImGroupMessageReceiptStatusEnum.DONE.getStatus() - : ImGroupMessageReceiptStatusEnum.PENDING.getStatus()); + ImMessageReceiptMessage receiptMessage = new ImMessageReceiptMessage() + .setMessageId(msg.getId()) + .setGroupId(groupId) + .setReadCount(readCount) + .setReceiptStatus(allRead + ? ImGroupMessageReceiptStatusEnum.DONE.getStatus() + : ImGroupMessageReceiptStatusEnum.PENDING.getStatus()) + .setScene(ImMessageSceneEnum.GROUP.getScene()); for (ImGroupMemberDO m : allMembers) { if (CommonStatusEnum.DISABLE.getStatus().equals(m.getStatus())) { continue; } webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), m.getUserId(), - ImWebSocketTypeConstants.RECEIPT, receiptEvent); + ImMessageReceiptMessage.TYPE, receiptMessage); } } } @@ -341,23 +345,18 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { */ private void sendGroupMessageEvent(ImGroupMessageDO message, Long groupId) { List members = imGroupMemberService.selectByGroupId(groupId); - Map event = new HashMap<>(); - event.put("id", message.getId().toString()); - event.put("clientMessageId", message.getClientMessageId()); - event.put("senderId", message.getSenderId().toString()); - event.put("groupId", message.getGroupId().toString()); - event.put("type", message.getType()); - event.put("content", message.getContent()); - event.put("status", message.getStatus()); - event.put("sendTime", message.getSendTime().toString()); - event.put("messageScene", "group"); - if (CollUtil.isNotEmpty(message.getAtUserIds())) { - event.put("atUserIds", message.getAtUserIds()); - } - if (CollUtil.isNotEmpty(message.getReceiverUserIds())) { - event.put("receiverUserIds", message.getReceiverUserIds()); - } - event.put("receiptStatus", message.getReceiptStatus()); + ImGroupMessageSendMessage sendMessage = new ImGroupMessageSendMessage() + .setId(message.getId()) + .setClientMessageId(message.getClientMessageId()) + .setSenderId(message.getSenderId()) + .setGroupId(message.getGroupId()) + .setType(message.getType()) + .setContent(message.getContent()) + .setStatus(message.getStatus()) + .setSendTime(message.getSendTime()) + .setAtUserIds(message.getAtUserIds()) + .setReceiverUserIds(message.getReceiverUserIds()) + .setReceiptStatus(message.getReceiptStatus()); // 确定推送目标 List receiverList = message.getReceiverUserIds(); @@ -372,7 +371,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { continue; } webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), member.getUserId(), - ImWebSocketTypeConstants.GROUP_MESSAGE, event); + ImGroupMessageSendMessage.TYPE, sendMessage); } } diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java index 78b9623..b9c5754 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java @@ -186,13 +186,13 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { ImMessageReceiptMessage.TYPE, receiptMessage); } - private ImPrivateMessageServiceImpl getSelf() { - return SpringUtil.getBean(getClass()); - } - @Override public List getPrivateMessageList(Long userId, ImPrivateMessageListReqVO reqVO) { return privateMessageMapper.selectHistoryList(userId, reqVO.getReceiverId(), reqVO.getMaxId(), reqVO.getLimit()); } + private ImPrivateMessageServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + } -- libgit2 0.21.4