Commit ff88d53b3b28f55d17fdee5753791f3d1006f41c
1 parent
77f3131e
新增:私聊消息发送成功后保存会话列表
Showing
11 changed files
with
151 additions
and
58 deletions
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/conversation/ImConversationTypeEnum.java
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/vo/ImConversationPageReqVO.java
| ... | ... | @@ -24,7 +24,7 @@ public class ImConversationPageReqVO extends PageParam { |
| 24 | 24 | private Integer conversationType; |
| 25 | 25 | |
| 26 | 26 | @Schema(description = "单聊时,用户编号;群聊时,群编号", example = "21454") |
| 27 | - private String targetId; | |
| 27 | + private Long targetId; | |
| 28 | 28 | |
| 29 | 29 | @Schema(description = "会话标志 单聊:s_{userId}_{targetId},需要排序 userId 和 targetId 群聊:g_groupId") |
| 30 | 30 | private String no; | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/vo/ImConversationRespVO.java
| ... | ... | @@ -26,7 +26,7 @@ public class ImConversationRespVO { |
| 26 | 26 | |
| 27 | 27 | @Schema(description = "单聊时,用户编号;群聊时,群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21454") |
| 28 | 28 | @ExcelProperty("单聊时,用户编号;群聊时,群编号") |
| 29 | - private String targetId; | |
| 29 | + private Long targetId; | |
| 30 | 30 | |
| 31 | 31 | @Schema(description = "会话标志 单聊:s_{userId}_{targetId},需要排序 userId 和 targetId 群聊:g_groupId", requiredMode = Schema.RequiredMode.REQUIRED) |
| 32 | 32 | @ExcelProperty("会话标志 单聊:s_{userId}_{targetId},需要排序 userId 和 targetId 群聊:g_groupId") | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/vo/ImConversationSaveReqVO.java
| ... | ... | @@ -24,7 +24,7 @@ public class ImConversationSaveReqVO { |
| 24 | 24 | |
| 25 | 25 | @Schema(description = "单聊时,用户编号;群聊时,群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21454") |
| 26 | 26 | @NotEmpty(message = "单聊时,用户编号;群聊时,群编号不能为空") |
| 27 | - private String targetId; | |
| 27 | + private Long targetId; | |
| 28 | 28 | |
| 29 | 29 | @Schema(description = "会话标志 单聊:s_{userId}_{targetId},需要排序 userId 和 targetId 群聊:g_groupId", requiredMode = Schema.RequiredMode.REQUIRED) |
| 30 | 30 | @NotEmpty(message = "会话标志 单聊:s_{userId}_{targetId},需要排序 userId 和 targetId 群聊:g_groupId不能为空") | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/conversation/ImConversationDO.java
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationService.java
| ... | ... | @@ -52,4 +52,11 @@ public interface ImConversationService { |
| 52 | 52 | PageResult<ImConversationDO> getConversationPage(ImConversationPageReqVO pageReqVO); |
| 53 | 53 | |
| 54 | 54 | |
| 55 | + /** | |
| 56 | + * 保存私聊会话 | |
| 57 | + * | |
| 58 | + * @param fromUserId 发送者 | |
| 59 | + * @param receiverId 接收者 | |
| 60 | + */ | |
| 61 | + void savePrivateConversation(Long fromUserId, Long receiverId); | |
| 55 | 62 | } |
| 56 | 63 | \ No newline at end of file | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationServiceImpl.java
| ... | ... | @@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversatio |
| 6 | 6 | import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationSaveReqVO; |
| 7 | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationDO; |
| 8 | 8 | import cn.iocoder.yudao.module.im.dal.mysql.conversation.ConversationMapper; |
| 9 | +import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum; | |
| 10 | +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | |
| 9 | 11 | import jakarta.annotation.Resource; |
| 10 | 12 | import org.springframework.stereotype.Service; |
| 11 | 13 | import org.springframework.validation.annotation.Validated; |
| ... | ... | @@ -67,4 +69,28 @@ public class ImConversationServiceImpl implements ImConversationService { |
| 67 | 69 | return conversationMapper.selectPage(pageReqVO); |
| 68 | 70 | } |
| 69 | 71 | |
| 72 | + @Override | |
| 73 | + public void savePrivateConversation(Long fromUserId, Long receiverId) { | |
| 74 | + // 创建并保存会话 | |
| 75 | + createAndSaveConversation(fromUserId, receiverId); | |
| 76 | + createAndSaveConversation(receiverId, fromUserId); | |
| 77 | + } | |
| 78 | + | |
| 79 | + private void createAndSaveConversation(Long userId, Long targetId) { | |
| 80 | + // 创建会话 | |
| 81 | + ImConversationDO conversation = new ImConversationDO(); | |
| 82 | + conversation.setUserId(userId); | |
| 83 | + conversation.setConversationType(ImConversationTypeEnum.PRIVATE.getType()); | |
| 84 | + conversation.setTargetId(targetId); | |
| 85 | + conversation.setNo("s_" + userId + "_" + targetId); | |
| 86 | + conversation.setPinned(false); | |
| 87 | + | |
| 88 | + // 根据 no 查询是否存在,不存在则新增 | |
| 89 | + QueryWrapper<ImConversationDO> queryWrapper = new QueryWrapper<>(); | |
| 90 | + queryWrapper.eq("no", conversation.getNo()); | |
| 91 | + if (conversationMapper.selectOne(queryWrapper) == null) { | |
| 92 | + conversationMapper.insert(conversation); | |
| 93 | + } | |
| 94 | + } | |
| 95 | + | |
| 70 | 96 | } |
| 71 | 97 | \ No newline at end of file | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImMessageService.java
| ... | ... | @@ -60,7 +60,7 @@ public interface ImMessageService { |
| 60 | 60 | * @param fromUserId 发送人编号 |
| 61 | 61 | * @return id |
| 62 | 62 | */ |
| 63 | - Long savePrivateMessage(ImSendMessage imSendMessage, Long fromUserId); | |
| 63 | + ImMessageDO savePrivateMessage(ImSendMessage imSendMessage, Long fromUserId); | |
| 64 | 64 | |
| 65 | 65 | /** |
| 66 | 66 | * 更新消息状态 | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImMessageServiceImpl.java
| ... | ... | @@ -76,24 +76,25 @@ public class ImMessageServiceImpl implements ImMessageService { |
| 76 | 76 | |
| 77 | 77 | |
| 78 | 78 | @Override |
| 79 | - public Long savePrivateMessage(ImSendMessage message, Long senderId) { | |
| 80 | - ImMessageSaveReqVO imMessageSaveReqVO = new ImMessageSaveReqVO(); | |
| 81 | - imMessageSaveReqVO.setClientMessageId(message.getClientMessageId()); | |
| 82 | - imMessageSaveReqVO.setSenderId(senderId); | |
| 83 | - imMessageSaveReqVO.setReceiverId(message.getReceiverId()); | |
| 79 | + public ImMessageDO savePrivateMessage(ImSendMessage message, Long senderId) { | |
| 80 | + ImMessageDO imMessageDO = new ImMessageDO(); | |
| 81 | + imMessageDO.setClientMessageId(message.getClientMessageId()); | |
| 82 | + imMessageDO.setSenderId(senderId); | |
| 83 | + imMessageDO.setReceiverId(message.getReceiverId()); | |
| 84 | 84 | //查询发送人昵称和发送人头像 |
| 85 | 85 | AdminUserRespDTO user = adminUserApi.getUser(senderId); |
| 86 | - imMessageSaveReqVO.setSenderNickname(user.getNickname()); | |
| 87 | - imMessageSaveReqVO.setSenderAvatar(user.getAvatar()); | |
| 88 | - imMessageSaveReqVO.setConversationType(message.getConversationType()); | |
| 89 | - imMessageSaveReqVO.setContentType(message.getContentType()); | |
| 90 | - imMessageSaveReqVO.setConversationNo(senderId + "_" + message.getReceiverId()); | |
| 91 | - imMessageSaveReqVO.setContent(message.getContent()); | |
| 86 | + imMessageDO.setSenderNickname(user.getNickname()); | |
| 87 | + imMessageDO.setSenderAvatar(user.getAvatar()); | |
| 88 | + imMessageDO.setConversationType(message.getConversationType()); | |
| 89 | + imMessageDO.setContentType(message.getContentType()); | |
| 90 | + imMessageDO.setConversationNo(senderId + "_" + message.getReceiverId()); | |
| 91 | + imMessageDO.setContent(message.getContent()); | |
| 92 | 92 | //消息来源 100-用户发送;200-系统发送(一般是通知);不能为空 |
| 93 | - imMessageSaveReqVO.setSendFrom(100); | |
| 94 | - imMessageSaveReqVO.setSendTime(TimeUtil.now()); | |
| 95 | - imMessageSaveReqVO.setMessageStatus(ImMessageStatusEnum.SENDING.getStatus()); | |
| 96 | - return createMessage(imMessageSaveReqVO); | |
| 93 | + imMessageDO.setSendFrom(100); | |
| 94 | + imMessageDO.setSendTime(TimeUtil.now()); | |
| 95 | + imMessageDO.setMessageStatus(ImMessageStatusEnum.SENDING.getStatus()); | |
| 96 | + imMessageMapper.insert(imMessageDO); | |
| 97 | + return imMessageDO; | |
| 97 | 98 | } |
| 98 | 99 | |
| 99 | 100 | @Override | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/ImWebSocketMessageListener.java
| ... | ... | @@ -3,9 +3,9 @@ package cn.iocoder.yudao.module.im.websocket; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; |
| 4 | 4 | import cn.iocoder.yudao.framework.websocket.core.listener.WebSocketMessageListener; |
| 5 | 5 | import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; |
| 6 | -import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager; | |
| 7 | 6 | import cn.iocoder.yudao.framework.websocket.core.util.WebSocketFrameworkUtils; |
| 8 | 7 | import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.ImInboxSaveReqVO; |
| 8 | +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO; | |
| 9 | 9 | import cn.iocoder.yudao.module.im.dal.redis.inbox.SequenceGeneratorRedisDao; |
| 10 | 10 | import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum; |
| 11 | 11 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; |
| ... | ... | @@ -19,8 +19,6 @@ import lombok.extern.slf4j.Slf4j; |
| 19 | 19 | import org.springframework.stereotype.Component; |
| 20 | 20 | import org.springframework.web.socket.WebSocketSession; |
| 21 | 21 | |
| 22 | -import java.util.List; | |
| 23 | - | |
| 24 | 22 | /** |
| 25 | 23 | * WebSocket im |
| 26 | 24 | * |
| ... | ... | @@ -31,55 +29,100 @@ import java.util.List; |
| 31 | 29 | public class ImWebSocketMessageListener implements WebSocketMessageListener<ImSendMessage> { |
| 32 | 30 | |
| 33 | 31 | @Resource |
| 34 | - private WebSocketMessageSender webSocketMessageSender; | |
| 35 | - @Resource | |
| 36 | - private ImMessageService imMessageService; | |
| 32 | + private WebSocketMessageSender webSocketMessageSender; // WebSocket消息发送器 | |
| 37 | 33 | @Resource |
| 38 | - private ImConversationService imConversationService; | |
| 34 | + private ImMessageService imMessageService; // IM消息服务 | |
| 39 | 35 | @Resource |
| 40 | - private ImInboxService imInboxService; | |
| 36 | + private ImConversationService imConversationService; // IM会话服务 | |
| 41 | 37 | @Resource |
| 42 | - private SequenceGeneratorRedisDao sequenceGeneratorRedisDao; | |
| 38 | + private ImInboxService imInboxService; // IM收件箱服务 | |
| 43 | 39 | @Resource |
| 44 | - private WebSocketSessionManager webSocketSessionManager; | |
| 40 | + private SequenceGeneratorRedisDao sequenceGeneratorRedisDao; // 序列生成器Redis DAO | |
| 45 | 41 | |
| 42 | + /** | |
| 43 | + * 处理WebSocket消息 | |
| 44 | + * | |
| 45 | + * @param session WebSocket会话 | |
| 46 | + * @param message 发送的IM消息 | |
| 47 | + */ | |
| 46 | 48 | @Override |
| 47 | 49 | public void onMessage(WebSocketSession session, ImSendMessage message) { |
| 48 | - Long fromUserId = WebSocketFrameworkUtils.getLoginUserId(session); | |
| 49 | - //1、插入消息表 | |
| 50 | - Long messageId = imMessageService.savePrivateMessage(message, fromUserId); | |
| 50 | + Long fromUserId = WebSocketFrameworkUtils.getLoginUserId(session); // 获取登录用户ID | |
| 51 | 51 | |
| 52 | - // 私聊 | |
| 52 | + // 如果是私人消息,处理私人消息 | |
| 53 | 53 | if (message.getConversationType().equals(ImConversationTypeEnum.PRIVATE.getType())) { |
| 54 | - //2、插入收件箱表(私聊:两条,群聊:每个群有一条) | |
| 55 | - imInboxService.createInbox(new ImInboxSaveReqVO(message.getReceiverId(), messageId, sequenceGeneratorRedisDao.generateSequence(message.getReceiverId()))); | |
| 56 | - imInboxService.createInbox(new ImInboxSaveReqVO(fromUserId, messageId, sequenceGeneratorRedisDao.generateSequence(fromUserId))); | |
| 54 | + ImMessageDO imMessageDO = imMessageService.savePrivateMessage(message, fromUserId); // 保存私人消息 | |
| 55 | + handlePrivateMessage(fromUserId, imMessageDO, message); | |
| 56 | + } | |
| 57 | + } | |
| 57 | 58 | |
| 58 | - //3、推送消息 | |
| 59 | - // 3.1判断是否在线 | |
| 60 | - List<WebSocketSession> sessions = (List<WebSocketSession>) webSocketSessionManager.getSessionList(UserTypeEnum.ADMIN.getValue(), message.getReceiverId()); | |
| 61 | - if (sessions.isEmpty()) { | |
| 62 | - //更新消息状态,为发送失败 | |
| 63 | - imMessageService.updateMessageStatus(messageId, ImMessageStatusEnum.FAILURE.getStatus()); | |
| 64 | - return; | |
| 65 | - } | |
| 66 | - //3.2发送 | |
| 67 | - ImReceiveMessage toMessage = new ImReceiveMessage(); | |
| 68 | - toMessage.setFromId(fromUserId); | |
| 69 | - toMessage.setConversationType(ImConversationTypeEnum.PRIVATE.getType()); | |
| 70 | - toMessage.setContentType(message.getContentType()); | |
| 71 | - toMessage.setContent(message.getContent()); | |
| 72 | - webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), message.getReceiverId(), // 给指定用户 | |
| 73 | - "im-message-receive", toMessage); | |
| 74 | - //4、更新消息状态,为发送成功 | |
| 75 | - imMessageService.updateMessageStatus(messageId, ImMessageStatusEnum.SUCCESS.getStatus()); | |
| 59 | + /** | |
| 60 | + * 处理私人消息 | |
| 61 | + * | |
| 62 | + * @param fromUserId 发送者用户ID | |
| 63 | + * @param imMessageDO IM消息数据对象 | |
| 64 | + * @param message 发送的IM消息 | |
| 65 | + */ | |
| 66 | + private void handlePrivateMessage(Long fromUserId, ImMessageDO imMessageDO, ImSendMessage message) { | |
| 67 | + Long fromUserSequence = sequenceGeneratorRedisDao.generateSequence(fromUserId); // 生成发送者序列 | |
| 68 | + Long fromUserInboxId = createAndSaveInbox(fromUserId, imMessageDO.getId(), fromUserSequence); // 创建并保存发送者收件箱 | |
| 69 | + Long receiverSequence = sequenceGeneratorRedisDao.generateSequence(fromUserId); // 生成接收者序列 | |
| 70 | + Long receiverInboxId = createAndSaveInbox(message.getReceiverId(), imMessageDO.getId(), receiverSequence); // 创建并保存接收者收件箱 | |
| 76 | 71 | |
| 77 | - } | |
| 72 | + // 发送消息给接收者和发送者 | |
| 73 | + sendMessage(fromUserId, receiverInboxId, imMessageDO, message, "im-message-receive", fromUserSequence); | |
| 74 | + sendMessage(fromUserId, fromUserInboxId, imMessageDO, message, "im-message-receive", receiverSequence); | |
| 75 | + | |
| 76 | + // 更新消息状态为成功 | |
| 77 | + imMessageService.updateMessageStatus(imMessageDO.getId(), ImMessageStatusEnum.SUCCESS.getStatus()); | |
| 78 | + // 保存私人会话 | |
| 79 | + imConversationService.savePrivateConversation(fromUserId, message.getReceiverId()); | |
| 80 | + } | |
| 81 | + | |
| 82 | + /** | |
| 83 | + * 创建并保存收件箱 | |
| 84 | + * | |
| 85 | + * @param userId 用户ID | |
| 86 | + * @param messageId 消息ID | |
| 87 | + * @param sequence 序列 | |
| 88 | + * @return 收件箱ID | |
| 89 | + */ | |
| 90 | + private Long createAndSaveInbox(Long userId, Long messageId, Long sequence) { | |
| 91 | + ImInboxSaveReqVO inboxSaveReqVO = new ImInboxSaveReqVO(userId, messageId, sequence); // 创建收件箱保存请求VO | |
| 92 | + return imInboxService.createInbox(inboxSaveReqVO); // 创建收件箱 | |
| 93 | + } | |
| 94 | + | |
| 95 | + /** | |
| 96 | + * 发送消息 | |
| 97 | + * | |
| 98 | + * @param fromUserId 发送者用户ID | |
| 99 | + * @param inboxId 收件箱ID | |
| 100 | + * @param imMessageDO IM消息数据对象 | |
| 101 | + * @param message 发送的IM消息 | |
| 102 | + * @param messageType 消息类型 | |
| 103 | + * @param sequence 序列 | |
| 104 | + */ | |
| 105 | + private void sendMessage(Long fromUserId, Long inboxId, ImMessageDO imMessageDO, ImSendMessage message, String messageType, Long sequence) { | |
| 106 | + ImReceiveMessage receiveMessage = new ImReceiveMessage(); // 创建接收消息 | |
| 107 | + receiveMessage.setFromId(fromUserId); // 设置发送者ID | |
| 108 | + receiveMessage.setConversationType(ImConversationTypeEnum.PRIVATE.getType()); // 设置会话类型为私人 | |
| 109 | + receiveMessage.setContentType(message.getContentType()); // 设置内容类型 | |
| 110 | + receiveMessage.setContent(message.getContent()); // 设置内容 | |
| 111 | + receiveMessage.setMessageId(imMessageDO.getId()); // 设置消息ID | |
| 112 | + receiveMessage.setInboxId(inboxId); // 设置收件箱ID | |
| 113 | + receiveMessage.setSendTime(imMessageDO.getSendTime()); // 设置发送时间 | |
| 114 | + receiveMessage.setSequence(sequence); // 设置序列 | |
| 115 | + webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), fromUserId, messageType, receiveMessage); // 发送消息 | |
| 78 | 116 | } |
| 79 | 117 | |
| 118 | + /** | |
| 119 | + * 获取类型 | |
| 120 | + * | |
| 121 | + * @return 类型 | |
| 122 | + */ | |
| 80 | 123 | @Override |
| 81 | 124 | public String getType() { |
| 82 | 125 | return "im-message-send"; |
| 83 | 126 | } |
| 84 | 127 | |
| 85 | 128 | -} |
| 129 | +} | |
| 86 | 130 | \ No newline at end of file | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/message/ImReceiveMessage.java
| ... | ... | @@ -2,8 +2,11 @@ package cn.iocoder.yudao.module.im.websocket.message; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.module.im.dal.dataobject.message.body.ImMessageBody; |
| 4 | 4 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | +import jakarta.validation.constraints.NotNull; | |
| 5 | 6 | import lombok.Data; |
| 6 | 7 | |
| 8 | +import java.time.LocalDateTime; | |
| 9 | + | |
| 7 | 10 | @Schema(description = "管理后台 - 消息发送 receive") |
| 8 | 11 | @Data |
| 9 | 12 | public class ImReceiveMessage { |
| ... | ... | @@ -20,4 +23,16 @@ public class ImReceiveMessage { |
| 20 | 23 | @Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED) |
| 21 | 24 | private String content; |
| 22 | 25 | |
| 26 | + @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12454") | |
| 27 | + private Long messageId; | |
| 28 | + | |
| 29 | + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 30 | + private LocalDateTime sendTime; | |
| 31 | + | |
| 32 | + @Schema(description = "收件箱编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18389") | |
| 33 | + private Long inboxId; | |
| 34 | + | |
| 35 | + @Schema(description = "序号,按照 user 递增", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 36 | + private Long sequence; | |
| 37 | + | |
| 23 | 38 | } |
| 24 | 39 | \ No newline at end of file | ... | ... |