Commit ca0aadac49e43d3047667347642c86dfb2886171

Authored by YunaiV
1 parent e89724e5

✨ feat(im): 添加私聊历史消息列表请求对象及相关接口调整

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 {
42 42 @Operation(summary = "拉取群聊消息(增量)")
43 43 @Parameter(name = "minId", description = "最小消息 id", required = true, example = "0")
44 44 @Parameter(name = "size", description = "拉取数量", required = true, example = "100")
45   - public CommonResult<List<ImGroupMessageRespVO>> pullGroupMessages(
  45 + public CommonResult<List<ImGroupMessageRespVO>> pullGroupMessageList(
46 46 @RequestParam("minId") Long minId,
47 47 @RequestParam("size") Integer size) {
48   - List<ImGroupMessageDO> messages = imGroupMessageService.pullGroupMessages(getLoginUserId(), minId, size);
  48 + List<ImGroupMessageDO> messages = imGroupMessageService.pullGroupMessageList(getLoginUserId(), minId, size);
49 49 return success(messages.stream().map(this::toRespVO).collect(Collectors.toList()));
50 50 }
51 51  
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageListReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.message.vo.privates;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import jakarta.validation.constraints.Max;
  7 +import jakarta.validation.constraints.Min;
  8 +import jakarta.validation.constraints.NotNull;
  9 +
  10 +@Schema(description = "管理后台 - 私聊历史消息列表 Request VO")
  11 +@Data
  12 +public class ImPrivateMessageListReqVO {
  13 +
  14 + @Schema(description = "接收人编号(对方)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  15 + @NotNull(message = "接收人编号不能为空")
  16 + private Long receiverId;
  17 +
  18 + @Schema(description = "起始消息编号,从该 id 往前拉取(不含)。为空则从最新消息开始", example = "1024")
  19 + private Long maxId;
  20 +
  21 + @Schema(description = "拉取数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
  22 + @NotNull(message = "拉取数量不能为空")
  23 + @Min(value = 1, message = "拉取数量最小值为 1")
  24 + @Max(value = 200, message = "拉取数量最大值为 200")
  25 + private Integer limit;
  26 +
  27 +}
... ...
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;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
5 6 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
6 7 import org.apache.ibatis.annotations.Mapper;
7 8  
... ... @@ -15,42 +16,35 @@ import java.util.List;
15 16 @Mapper
16 17 public interface ImGroupMessageMapper extends BaseMapperX<ImGroupMessageDO> {
17 18  
18   - /**
19   - * 根据群编号和 minId 增量拉取群聊消息
20   - *
21   - * @param groupIds 用户所在的群编号列表
22   - * @param minId 最小消息 id(不含)
23   - * @param size 拉取数量
24   - * @return 消息列表
25   - */
26 19 default List<ImGroupMessageDO> selectListByMinId(List<Long> groupIds, Long minId, Integer size) {
27   - return selectList(new LambdaQueryWrapperX<ImGroupMessageDO>()
28   - .in(ImGroupMessageDO::getGroupId, groupIds)
29   - .gt(ImGroupMessageDO::getId, minId)
30   - .orderByAsc(ImGroupMessageDO::getId)
31   - .last("LIMIT " + size));
  20 + QueryWrapperX<ImGroupMessageDO> wrapper = new QueryWrapperX<>();
  21 + wrapper.in("group_id", groupIds)
  22 + .gt("id", minId)
  23 + .orderByAsc("id");
  24 + wrapper.limitN(size);
  25 + return selectList(wrapper);
32 26 }
33 27  
34   - /**
35   - * 根据 senderId 和 clientMessageId 查询(幂等校验)
36   - */
37 28 default ImGroupMessageDO selectBySenderIdAndClientMessageId(Long senderId, String clientMessageId) {
38 29 return selectOne(new LambdaQueryWrapperX<ImGroupMessageDO>()
39 30 .eq(ImGroupMessageDO::getSenderId, senderId)
40 31 .eq(ImGroupMessageDO::getClientMessageId, clientMessageId));
41 32 }
42 33  
  34 + // TODO @芋艿:暂时没 review
43 35 /**
44 36 * 获取群内最新消息 id
45 37 */
46 38 default Long selectMaxIdByGroupId(Long groupId) {
47   - ImGroupMessageDO msg = selectOne(new LambdaQueryWrapperX<ImGroupMessageDO>()
48   - .eq(ImGroupMessageDO::getGroupId, groupId)
49   - .orderByDesc(ImGroupMessageDO::getId)
50   - .last("LIMIT 1"));
  39 + QueryWrapperX<ImGroupMessageDO> wrapper = new QueryWrapperX<>();
  40 + wrapper.eq("group_id", groupId)
  41 + .orderByDesc("id");
  42 + wrapper.limitN(1);
  43 + ImGroupMessageDO msg = selectOne(wrapper);
51 44 return msg == null ? null : msg.getId();
52 45 }
53 46  
  47 + // TODO @芋艿:暂时没 review
54 48 /**
55 49 * 获取群内需要回执的消息(状态为 PENDING)
56 50 */
... ...
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 {
29 29 * @param size 拉取数量
30 30 * @return 消息列表
31 31 */
32   - List<ImGroupMessageDO> pullGroupMessages(Long userId, Long minId, Integer size);
  32 + List<ImGroupMessageDO> pullGroupMessageList(Long userId, Long minId, Integer size);
33 33  
34 34 /**
35 35 * 标记群聊消息已读
... ...
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;
11 11 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
12 12 import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum;
13 13 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
  14 +import cn.iocoder.yudao.module.im.enums.message.ImMessageSceneEnum;
14 15 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
15   -import cn.iocoder.yudao.module.im.enums.message.ImWebSocketTypeConstants;
  16 +import cn.iocoder.yudao.module.im.websocket.ImGroupMessageSendMessage;
  17 +import cn.iocoder.yudao.module.im.websocket.ImMessageReadMessage;
  18 +import cn.iocoder.yudao.module.im.websocket.ImMessageReceiptMessage;
  19 +import cn.iocoder.yudao.module.im.websocket.ImMessageRecallMessage;
16 20 import cn.iocoder.yudao.module.im.service.group.ImGroupService;
17 21 import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService;
18 22 import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
... ... @@ -28,6 +32,7 @@ import java.util.stream.Collectors;
28 32  
29 33 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
30 34 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
  35 +import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*;
31 36  
32 37 /**
33 38 * IM 群聊消息 Service 实现类
... ... @@ -39,8 +44,6 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
39 44 @Slf4j
40 45 public class ImGroupMessageServiceImpl implements ImGroupMessageService {
41 46  
42   - private static final int MAX_PULL_SIZE = 1000;
43   -
44 47 @Resource
45 48 private ImGroupMessageMapper imGroupMessageMapper;
46 49 @Resource
... ... @@ -100,8 +103,8 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
100 103 }
101 104  
102 105 @Override
103   - public List<ImGroupMessageDO> pullGroupMessages(Long userId, Long minId, Integer size) {
104   - if (size > MAX_PULL_SIZE) {
  106 + public List<ImGroupMessageDO> pullGroupMessageList(Long userId, Long minId, Integer size) {
  107 + if (size > MESSAGE_MAX_PULL_SIZE) {
105 108 throw exception(MESSAGE_PULL_SIZE_EXCEEDED);
106 109 }
107 110 // 获取用户所在的所有群
... ... @@ -162,11 +165,11 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
162 165 groupReadPositionRedisDAO.updateReadPosition(groupId, userId, maxMessageId);
163 166  
164 167 // 4. 发送 READ 事件给自己的其他终端
165   - Map<String, Object> readEvent = new HashMap<>();
166   - readEvent.put("groupId", groupId.toString());
167   - readEvent.put("messageScene", "group");
  168 + ImMessageReadMessage readMessage = new ImMessageReadMessage()
  169 + .setGroupId(groupId)
  170 + .setScene(ImMessageSceneEnum.GROUP.getScene());
168 171 webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), userId,
169   - ImWebSocketTypeConstants.READ, readEvent);
  172 + ImMessageReadMessage.TYPE, readMessage);
170 173  
171 174 // 5. 刷新群回执
172 175 refreshGroupReceipts(groupId);
... ... @@ -195,17 +198,17 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
195 198  
196 199 // 5. 发送 RECALL 事件给所有群成员
197 200 List<ImGroupMemberDO> members = imGroupMemberService.selectByGroupId(message.getGroupId());
198   - Map<String, Object> recallEvent = new HashMap<>();
199   - recallEvent.put("messageId", messageId.toString());
200   - recallEvent.put("groupId", message.getGroupId().toString());
201   - recallEvent.put("messageScene", "group");
202   - recallEvent.put("senderId", userId.toString());
  201 + ImMessageRecallMessage recallMessage = new ImMessageRecallMessage()
  202 + .setMessageId(messageId)
  203 + .setGroupId(message.getGroupId())
  204 + .setSenderId(userId)
  205 + .setScene(ImMessageSceneEnum.GROUP.getScene());
203 206 for (ImGroupMemberDO m : members) {
204 207 if (CommonStatusEnum.DISABLE.getStatus().equals(m.getStatus())) {
205 208 continue;
206 209 }
207 210 webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), m.getUserId(),
208   - ImWebSocketTypeConstants.RECALL, recallEvent);
  211 + ImMessageRecallMessage.TYPE, recallMessage);
209 212 }
210 213 }
211 214  
... ... @@ -285,20 +288,21 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
285 288 }
286 289  
287 290 // 6. 广播 RECEIPT 事件给活跃成员
288   - Map<String, Object> receiptEvent = new HashMap<>();
289   - receiptEvent.put("messageId", msg.getId().toString());
290   - receiptEvent.put("groupId", groupId.toString());
291   - receiptEvent.put("readCount", readCount);
292   - receiptEvent.put("receiptStatus", allRead
293   - ? ImGroupMessageReceiptStatusEnum.DONE.getStatus()
294   - : ImGroupMessageReceiptStatusEnum.PENDING.getStatus());
  291 + ImMessageReceiptMessage receiptMessage = new ImMessageReceiptMessage()
  292 + .setMessageId(msg.getId())
  293 + .setGroupId(groupId)
  294 + .setReadCount(readCount)
  295 + .setReceiptStatus(allRead
  296 + ? ImGroupMessageReceiptStatusEnum.DONE.getStatus()
  297 + : ImGroupMessageReceiptStatusEnum.PENDING.getStatus())
  298 + .setScene(ImMessageSceneEnum.GROUP.getScene());
295 299  
296 300 for (ImGroupMemberDO m : allMembers) {
297 301 if (CommonStatusEnum.DISABLE.getStatus().equals(m.getStatus())) {
298 302 continue;
299 303 }
300 304 webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), m.getUserId(),
301   - ImWebSocketTypeConstants.RECEIPT, receiptEvent);
  305 + ImMessageReceiptMessage.TYPE, receiptMessage);
302 306 }
303 307 }
304 308 }
... ... @@ -341,23 +345,18 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
341 345 */
342 346 private void sendGroupMessageEvent(ImGroupMessageDO message, Long groupId) {
343 347 List<ImGroupMemberDO> members = imGroupMemberService.selectByGroupId(groupId);
344   - Map<String, Object> event = new HashMap<>();
345   - event.put("id", message.getId().toString());
346   - event.put("clientMessageId", message.getClientMessageId());
347   - event.put("senderId", message.getSenderId().toString());
348   - event.put("groupId", message.getGroupId().toString());
349   - event.put("type", message.getType());
350   - event.put("content", message.getContent());
351   - event.put("status", message.getStatus());
352   - event.put("sendTime", message.getSendTime().toString());
353   - event.put("messageScene", "group");
354   - if (CollUtil.isNotEmpty(message.getAtUserIds())) {
355   - event.put("atUserIds", message.getAtUserIds());
356   - }
357   - if (CollUtil.isNotEmpty(message.getReceiverUserIds())) {
358   - event.put("receiverUserIds", message.getReceiverUserIds());
359   - }
360   - event.put("receiptStatus", message.getReceiptStatus());
  348 + ImGroupMessageSendMessage sendMessage = new ImGroupMessageSendMessage()
  349 + .setId(message.getId())
  350 + .setClientMessageId(message.getClientMessageId())
  351 + .setSenderId(message.getSenderId())
  352 + .setGroupId(message.getGroupId())
  353 + .setType(message.getType())
  354 + .setContent(message.getContent())
  355 + .setStatus(message.getStatus())
  356 + .setSendTime(message.getSendTime())
  357 + .setAtUserIds(message.getAtUserIds())
  358 + .setReceiverUserIds(message.getReceiverUserIds())
  359 + .setReceiptStatus(message.getReceiptStatus());
361 360  
362 361 // 确定推送目标
363 362 List<Long> receiverList = message.getReceiverUserIds();
... ... @@ -372,7 +371,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
372 371 continue;
373 372 }
374 373 webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), member.getUserId(),
375   - ImWebSocketTypeConstants.GROUP_MESSAGE, event);
  374 + ImGroupMessageSendMessage.TYPE, sendMessage);
376 375 }
377 376 }
378 377  
... ...
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 {
186 186 ImMessageReceiptMessage.TYPE, receiptMessage);
187 187 }
188 188  
189   - private ImPrivateMessageServiceImpl getSelf() {
190   - return SpringUtil.getBean(getClass());
191   - }
192   -
193 189 @Override
194 190 public List<ImPrivateMessageDO> getPrivateMessageList(Long userId, ImPrivateMessageListReqVO reqVO) {
195 191 return privateMessageMapper.selectHistoryList(userId, reqVO.getReceiverId(), reqVO.getMaxId(), reqVO.getLimit());
196 192 }
197 193  
  194 + private ImPrivateMessageServiceImpl getSelf() {
  195 + return SpringUtil.getBean(getClass());
  196 + }
  197 +
198 198 }
... ...