Pārlūkot izejas kodu

新增:消息发送和代码优化

安浩浩 1 gadu atpakaļ
vecāks
revīzija
4015b2f213
76 mainītis faili ar 1107 papildinājumiem un 1947 dzēšanām
  1. 1 1
      yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/api/package-info.java
  2. 1 0
      yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java
  3. 46 0
      yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/conversation/ConversationTypeEnum.java
  4. 0 29
      yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/conversation/ImConversationTypeEnum.java
  5. 0 47
      yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java
  6. 77 0
      yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/MessageContentTypeEnum.java
  7. 36 0
      yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/MessageSourceEnum.java
  8. 16 10
      yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/MessageStatusEnum.java
  9. 15 15
      yudao-module-im/yudao-module-im-biz/pom.xml
  10. 8 8
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/ConversationController.java
  11. 1 1
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/vo/ImConversationPageReqVO.java
  12. 1 1
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/vo/ImConversationSaveReqVO.java
  13. 4 4
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java
  14. 1 1
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/groupmember/ImGroupMemberController.java
  15. 7 7
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/inbox/ImInboxController.java
  16. 42 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/inbox/vo/InboxSaveMessageReqVO.java
  17. 2 2
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/inbox/vo/InboxSaveReqVO.java
  18. 11 8
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/inbox/vo/InboxSendMessageReqVO.java
  19. 0 68
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImMessageController.java
  20. 57 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/MessageController.java
  21. 0 62
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/ImMessagePageReqVO.java
  22. 36 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/ImMessageReqVO.java
  23. 0 77
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/ImMessageRespVO.java
  24. 25 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/MessagePageReqVO.java
  25. 36 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/SendMessageReqVO.java
  26. 26 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/SendMessageRespVO.java
  27. 8 10
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/conversation/ConversationDO.java
  28. 5 3
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/GroupDO.java
  29. 1 5
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/GroupMemberDO.java
  30. 1 1
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/inbox/InboxDO.java
  31. 19 18
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/MessageDO.java
  32. 0 31
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/body/ImAudioMessageBody.java
  33. 0 31
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/body/ImFileMessageBody.java
  34. 0 30
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/body/ImImageMessageBody.java
  35. 0 12
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/body/ImMessageBody.java
  36. 0 22
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/body/ImTextMessageBody.java
  37. 0 27
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/body/ImVideoMessageBody.java
  38. 48 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/AudioMessage.java
  39. 38 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/CardMessage.java
  40. 27 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FileMessage.java
  41. 23 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/ImageMessage.java
  42. 5 4
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/LocationMessage.java
  43. 23 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/TextMessage.java
  44. 19 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/VideoMessage.java
  45. 34 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ConversationMapper.java
  46. 0 36
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationMapper.java
  47. 12 12
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java
  48. 1 1
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/groupmember/ImGroupMemberMapper.java
  49. 9 9
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/inbox/ImInboxMapper.java
  50. 0 62
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImMessageMapper.java
  51. 51 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/MessageMapper.java
  52. 7 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java
  53. 39 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/inbox/InboxLockRedisDAO.java
  54. 9 1
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/inbox/SequenceRedisDao.java
  55. 0 38
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/jackson/ImMessageBodyDeserializer.java
  56. 5 5
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ConversationService.java
  57. 22 22
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ConversationServiceImpl.java
  58. 3 3
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
  59. 5 5
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
  60. 1 1
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/groupmember/ImGroupMemberService.java
  61. 1 1
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/groupmember/ImGroupMemberServiceImpl.java
  62. 12 6
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/inbox/ImInboxService.java
  63. 63 8
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/inbox/ImInboxServiceImpl.java
  64. 0 103
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImMessageService.java
  65. 0 143
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImMessageServiceImpl.java
  66. 43 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/MessageService.java
  67. 122 0
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/MessageServiceImpl.java
  68. 0 184
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/ImWebSocketMessageListener.java
  69. 0 26
      yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/message/ImSendMessage.java
  70. 1 1
      yudao-module-im/yudao-module-im-biz/src/main/resources/mapper/conversation/ConversationMapper.xml
  71. 1 1
      yudao-module-im/yudao-module-im-biz/src/main/resources/mapper/message/MessageMapper.xml
  72. 0 148
      yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationServiceImplTest.java
  73. 0 147
      yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java
  74. 0 147
      yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/groupmember/ImGroupMemberServiceImplTest.java
  75. 0 135
      yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/inbox/ImInboxServiceImplTest.java
  76. 0 167
      yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImMessageServiceImplTest.java

+ 1 - 1
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/api/package-info.java

@@ -1,5 +1,5 @@
 /**
  * @author anhaohao
- * @date 2024/3/9 下午8:59 TODO @hao:没有 @date 这个,应该是 @since 哈
+ * @since 2024/3/9 下午8:59
  */
 package cn.iocoder.yudao.module.im.api;

+ 1 - 0
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java

@@ -17,6 +17,7 @@ public interface ErrorCodeConstants {
 
     // ========== 消息 (1-040-300-000) ==========
     ErrorCode MESSAGE_NOT_EXISTS = new ErrorCode(1_040_300_000, "消息不存在");
+    ErrorCode MESSAGE_RECEIVER_NOT_EXISTS = new ErrorCode(1_040_300_001, "接收人不存在");
 
     // ========== 群 (1-040-400-000) ==========
     ErrorCode GROUP_NOT_EXISTS = new ErrorCode(1_040_400_000, "群不存在");

+ 46 - 0
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/conversation/ConversationTypeEnum.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.im.enums.conversation;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * IM 会话类型枚举
+ * 参考 <a href="https://doc.rentsoft.cn/zh-Hans/sdks/enum/conversationType">“会话类型”</a> 文档
+ *
+ * @author anhaohao
+ */
+@Getter
+@AllArgsConstructor
+public enum ConversationTypeEnum {
+
+    SINGLE(1, "单聊"),
+    GROUP(3, "群聊"),
+    NOTIFICATION(4, "通知会话");
+
+    /**
+     * 类型
+     */
+    private final Integer type;
+
+    /**
+     * 名字
+     */
+    private final String name;
+
+    /**
+     * 生成会话编号
+     * @param fromUserId 发送者编号
+     * @param receiverId 接收者编号
+     * @param conversationType 会话类型
+     * @return 会话编号
+     */
+    public static String generateConversationNo(Long fromUserId, Long receiverId, Integer conversationType) {
+        if (conversationType.equals(ConversationTypeEnum.SINGLE.getType())) {
+            return "s_" + fromUserId + "_" + receiverId;
+        } else if (conversationType.equals(ConversationTypeEnum.GROUP.getType())) {
+            return "g_" + receiverId;
+        }
+        return null;
+    }
+
+}

+ 0 - 29
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/conversation/ImConversationTypeEnum.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.im.enums.conversation;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-/**
- * IM 会话的类型枚举
- *
- * @author 芋道源码
- */
-@Getter
-@AllArgsConstructor
-public enum ImConversationTypeEnum {
-
-    // TODO @hao:单聊,我们使用 SINGLE,主要 private 这个单词在 java 里太特殊了;
-    PRIVATE(1, "单聊"),
-    GROUP(2, "群聊"),
-    NOTICE(4, "通知会话");
-
-    /**
-     * 类型
-     */
-    private final Integer type;
-    /**
-     * 名字
-     */
-    private final String name;
-
-}

+ 0 - 47
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java

@@ -1,47 +0,0 @@
-package cn.iocoder.yudao.module.im.enums.message;
-
-import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-import java.util.Arrays;
-
-// TODO @hao:类型保持和 openim 一致
-/**
- * IM 消息的类型枚举
- * <p>
- * 参考 <a href="https://doc.yunxin.163.com/messaging/docs/zg3NzA3NTA?platform=web#消息类型">“消息类型”</a> 文档
- *
- * @author 芋道源码
- */
-@Getter
-@AllArgsConstructor
-public enum ImMessageTypeEnum implements IntArrayValuable {
-
-    TEXT(1, "文本"), // 消息内容为普通文本
-    IMAGE(2, "图片"), // 消息内容为图片 URL 地址、尺寸、图片大小等信息
-    AUDIO(3, "语音"), // 消息内容为语音文件的 URL 地址、时长、大小、格式等信息
-    VIDEO(4, "视频"), // 消息内容为视频文件的 URL 地址、时长、大小、格式等信息
-    FILE(5, "文件"), // 消息内容为文件的 URL 地址、大小、格式等信息
-    LOCATION(6, "地理位置"), // 消息内容为地理位置标题、经度、纬度信息
-    // TODO @芋艿:下面两种,貌似企业微信设计的更好:https://developer.work.weixin.qq.com/document/path/90240
-    TIP(7, "提示"), // 又叫做 Tip 消息,没有推送和通知栏提醒,主要用于会话内的通知提醒,例如进入会话时出现的欢迎消息,或是会话过程中命中敏感词后的提示消息等场景
-    NOTIFICATION(8, "通知"), // 主要用于群组、聊天室和超大群的事件通知,由服务端下发,客户端无法发送事件通知消息。通知类消息有在线、离线、漫游机制;没有通知栏提醒
-    ;
-
-    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ImMessageTypeEnum::getType).toArray();
-    /**
-     * 类型
-     */
-    private final Integer type;
-    /**
-     * 名字
-     */
-    private final String name;
-
-    @Override
-    public int[] array() {
-        return ARRAYS;
-    }
-
-}

+ 77 - 0
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/MessageContentTypeEnum.java

@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.im.enums.message;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+
+/**
+ * IM 消息的类型枚举
+ * <p>
+ * 参考 <a href="https://doc.rentsoft.cn/zh-Hans/sdks/enum/messageContentType">“消息类型”</a> 文档
+ *
+ * @author anhaohao
+ */
+@Getter
+@AllArgsConstructor
+public enum MessageContentTypeEnum implements IntArrayValuable {
+
+    TEXT(101, "文本消息"),
+    PICTURE(102, "图片消息"),
+    VOICE(103, "语音消息"),
+    VIDEO(104, "视频消息"),
+    FILE(105, "文件消息"),
+    AT_TEXT(106, "@消息"),
+    MERGE(107, "合并消息"),
+    CARD(108, "名片消息"),
+    LOCATION(109, "位置消息"),
+    CUSTOM(110, "自定义消息"),
+    REVOKE_RECEIPT(111, "撤回消息回执"),
+    C2C_RECEIPT(112, "单聊消息回执"),
+    TYPING(113, "输入状态"),
+    QUOTE(114, "引用消息"),
+    FACE(115, "表情消息"),
+    ADVANCED_REVOKE(118, "高级撤回消息"),
+    //好友通知 1200-1299
+    FRIEND_ADDED(1201, "双方成为好友通知"),
+    //系统通知 1400
+    OA_NOTIFICATION(1400, "系统通知"),
+    //群相关 1500-1599
+    GROUP_CREATED(1501, "群创建通知"),
+    GROUP_INFO_CHANGED(1502, "群信息改变通知"),
+    MEMBER_QUIT(1504, "群成员退出通知"),
+    GROUP_OWNER_CHANGED(1507, "群主更换通知"),
+    MEMBER_KICKED(1508, "群成员被踢通知"),
+    MEMBER_INVITED(1509, "邀请群成员通知"),
+    MEMBER_ENTER(1510, "群成员进群通知"),
+    GROUP_DISMISSED(1511, "解散群通知"),
+    GROUP_MEMBER_MUTED(1512, "群成员禁言通知"),
+    GROUP_MEMBER_CANCEL_MUTED(1513, "取消群成员禁言通知"),
+    GROUP_MUTED(1514, "群禁言通知"),
+    GROUP_CANCEL_MUTED(1515, "取消群禁言通知"),
+    GROUP_ANNOUNCEMENT_UPDATED(1519, "群公告改变通知"),
+    GROUP_NAME_UPDATED(1520, "群名称改变通知"),
+
+    BURN_CHANGE(1701, "阅后即焚开启或关闭通知"),
+    REVOKE(2101, "撤回消息通知");;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MessageContentTypeEnum::getType).toArray();
+
+    /**
+     * 类型
+     */
+    private final Integer type;
+
+    /**
+     * 名字
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 36 - 0
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/MessageSourceEnum.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.im.enums.message;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IM 消息的消息来源 100-用户发送;200-系统发送(一般是通知);不能为空
+ */
+@RequiredArgsConstructor
+@Getter
+public enum MessageSourceEnum implements IntArrayValuable {
+
+    USER_SEND(100, "用户发送"),
+    SYSTEM_SEND(200, "系统发送");
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MessageSourceEnum::getStatus).toArray();
+
+    /**
+     * 状态
+     */
+    private final Integer status;
+
+    /**
+     * 名字
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 16 - 10
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageStatusEnum.java → yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/MessageStatusEnum.java

@@ -6,21 +6,27 @@ import lombok.RequiredArgsConstructor;
 
 import java.util.Arrays;
 
-// TODO @hao:注释哈;
+/**
+ * IM 消息的状态枚举
+ */
 @RequiredArgsConstructor
 @Getter
-public enum ImMessageStatusEnum implements IntArrayValuable {
+public enum MessageStatusEnum implements IntArrayValuable {
 
     SENDING(1, "发送中"),
-    SUCCESS(2, "发送成功"),
-    FAILURE(3, "发送失败"),
-    DELETED(4, "已删除"),
-    RECALL(5, "已撤回");
-
-    // TODO @hao:静态变量,和普通变量,最好空一行;
-    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ImMessageStatusEnum::getStatus).toArray();
-    // TODO @hao:注释哈;
+    SUCCEED(2, "发送成功"),
+    FAILED(3, "发送失败");
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MessageStatusEnum::getStatus).toArray();
+
+    /**
+     * 状态
+     */
     private final Integer status;
+
+    /**
+     * 名字
+     */
     private final String name;
 
     @Override

+ 15 - 15
yudao-module-im/yudao-module-im-biz/pom.xml

@@ -5,19 +5,19 @@
     <parent>
         <artifactId>yudao-module-im</artifactId>
         <groupId>cn.iocoder.boot</groupId>
-        <version>${revision}</version> <!-- 1. 修改 version 为 ${revision} TODO @hao:这种都去掉哈; -->
+        <version>${revision}</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
-    <packaging>jar</packaging> <!-- 2. 新增 packaging 为 jar -->
+    <packaging>jar</packaging>
 
     <artifactId>yudao-module-im-biz</artifactId>
 
-    <name>${project.artifactId}</name> <!-- 3. 新增 name 为 ${project.artifactId} -->
-    <description> <!-- 4. 新增 description 为该模块的描述 -->
+    <name>${project.artifactId}</name>
+    <description>
         im 模块,主要实现 im 模块的业务逻辑。
     </description>
 
-    <dependencies>  <!-- 5. 新增依赖,这里引入的都是比较常用的业务组件、技术组件 -->
+    <dependencies>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-module-im-api</artifactId>
@@ -41,34 +41,34 @@
             <artifactId>yudao-spring-boot-starter-security</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-websocket</artifactId>
+        </dependency>
+
         <!-- DB 相关 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
         </dependency>
 
-        <!-- Test 测试相关 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-spring-boot-starter-test</artifactId>
+            <artifactId>yudao-spring-boot-starter-redis</artifactId>
         </dependency>
 
-        <!-- TODO @hao:下面几个依赖的位置不太对。主要是它不属于 test,可以参考别的模块,顺序调整下哈 -->
-
+        <!-- 工具类相关 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-excel</artifactId>
         </dependency>
+
+        <!-- Test 测试相关 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-spring-boot-starter-websocket</artifactId>
+            <artifactId>yudao-spring-boot-starter-test</artifactId>
         </dependency>
 
-        <!-- TODO @hao:引入我们自己的 redis starter 哈 -->
-        <dependency>
-            <groupId>org.redisson</groupId>
-            <artifactId>redisson-spring-boot-starter</artifactId>
-        </dependency>
     </dependencies>
 
 </project>

+ 8 - 8
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/ImConversationController.java → yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/ConversationController.java

@@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationRespVO;
 import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationSaveReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationDO;
-import cn.iocoder.yudao.module.im.service.conversation.ImConversationService;
+import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ConversationDO;
+import cn.iocoder.yudao.module.im.service.conversation.ConversationService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
@@ -19,22 +19,22 @@ import java.util.List;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
 // TODO @hao: 管理后台 - IM 会话
-@Tag(name = "管理后台 - 会话")
+@Tag(name = "管理后台 - IM 会话")
 @RestController
 @RequestMapping("/im/conversation")
 @Validated
-public class ImConversationController {
+public class ConversationController {
 
     // TODO @hao: conversationService 即可
     @Resource
-    private ImConversationService imConversationService;
+    private ConversationService conversationService;
 
     // TODO @hao:/list 会话列表;一般 get 是给单个对象,或者 get-count 这种;然后 conversation 已经是模块名了,所以可以简化
     @GetMapping("/get-conversation")
     @Operation(summary = "获得用户的会话列表")
     @PreAuthorize("hasAuthority('im:conversation:query')") // TODO @hao:不用权限哈
     public CommonResult<List<ImConversationRespVO>> getConversationList() {
-        List<ImConversationDO> conversationList = imConversationService.getConversationList();
+        List<ConversationDO> conversationList = conversationService.getConversationList();
         return success(BeanUtils.toBean(conversationList, ImConversationRespVO.class));
     }
 
@@ -44,7 +44,7 @@ public class ImConversationController {
     @Operation(summary = "置顶会话")
     @PreAuthorize("hasAuthority('im:conversation:update')") // TODO @hao:不用权限哈;因为肯定会判断是不是自己的
     public CommonResult<Boolean> updateTop(@Valid @RequestBody ImConversationSaveReqVO updateReqVO) {
-        imConversationService.updateTop(updateReqVO);
+        conversationService.updateTop(updateReqVO);
         return success(true);
     }
 
@@ -54,7 +54,7 @@ public class ImConversationController {
     @Operation(summary = "更新最后已读时间")
     @PreAuthorize("hasAuthority('im:conversation:update')")  // TODO @hao:不用权限哈;因为肯定会判断是不是自己的
     public CommonResult<Boolean> updateLastReadTime(@Valid @RequestBody ImConversationSaveReqVO updateReqVO) {
-        imConversationService.updateLastReadTime(updateReqVO);
+        conversationService.updateLastReadTime(updateReqVO);
         return success(true);
     }
 

+ 1 - 1
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/vo/ImConversationPageReqVO.java

@@ -22,7 +22,7 @@ public class ImConversationPageReqVO extends PageParam {
     private Long userId;
 
     @Schema(description = "类型:1 单聊;2 群聊;4 通知会话(预留)", example = "1")
-    private Integer conversationType;
+    private Integer type;
 
     @Schema(description = "单聊时,用户编号;群聊时,群编号", example = "21454")
     private Long targetId;

+ 1 - 1
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/vo/ImConversationSaveReqVO.java

@@ -21,7 +21,7 @@ public class ImConversationSaveReqVO {
 
     @Schema(description = "类型:1 单聊;2 群聊;4 通知会话(预留)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "类型:1 单聊;2 群聊;4 通知会话(预留)不能为空")
-    private Integer conversationType;
+    private Integer type;
 
     @Schema(description = "单聊时,用户编号;群聊时,群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21454")
     @NotEmpty(message = "单聊时,用户编号;群聊时,群编号不能为空")

+ 4 - 4
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java

@@ -25,7 +25,7 @@ import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
 
 import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
-import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.group.GroupDO;
 import cn.iocoder.yudao.module.im.service.group.ImGroupService;
 
 @Tag(name = "管理后台 - 群")
@@ -66,7 +66,7 @@ public class ImGroupController {
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('im:group:query')")
     public CommonResult<ImGroupRespVO> getGroup(@RequestParam("id") Long id) {
-        ImGroupDO group = imGroupService.getGroup(id);
+        GroupDO group = imGroupService.getGroup(id);
         return success(BeanUtils.toBean(group, ImGroupRespVO.class));
     }
 
@@ -74,7 +74,7 @@ public class ImGroupController {
     @Operation(summary = "获得群分页")
     @PreAuthorize("@ss.hasPermission('im:group:query')")
     public CommonResult<PageResult<ImGroupRespVO>> getGroupPage(@Valid ImGroupPageReqVO pageReqVO) {
-        PageResult<ImGroupDO> pageResult = imGroupService.getGroupPage(pageReqVO);
+        PageResult<GroupDO> pageResult = imGroupService.getGroupPage(pageReqVO);
         return success(BeanUtils.toBean(pageResult, ImGroupRespVO.class));
     }
 
@@ -85,7 +85,7 @@ public class ImGroupController {
     public void exportGroupExcel(@Valid ImGroupPageReqVO pageReqVO,
               HttpServletResponse response) throws IOException {
         pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
-        List<ImGroupDO> list = imGroupService.getGroupPage(pageReqVO).getList();
+        List<GroupDO> list = imGroupService.getGroupPage(pageReqVO).getList();
         // 导出 Excel
         ExcelUtils.write(response, "群.xls", "数据", ImGroupRespVO.class,
                         BeanUtils.toBean(list, ImGroupRespVO.class));

+ 1 - 1
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/groupmember/ImGroupMemberController.java

@@ -9,7 +9,7 @@ import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.ImGroupMemberPageReqVO;
 import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.ImGroupMemberRespVO;
 import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.ImGroupMemberSaveReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.groupmember.GroupMemberDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.group.GroupMemberDO;
 import cn.iocoder.yudao.module.im.service.groupmember.ImGroupMemberService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;

+ 7 - 7
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/inbox/ImInboxController.java

@@ -8,8 +8,8 @@ import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.ImInboxPageReqVO;
 import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.ImInboxRespVO;
-import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.ImInboxSaveReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.inbox.ImInboxDO;
+import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxSaveReqVO;
+import cn.iocoder.yudao.module.im.dal.dataobject.inbox.InboxDO;
 import cn.iocoder.yudao.module.im.service.inbox.ImInboxService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -39,14 +39,14 @@ public class ImInboxController {
     @PostMapping("/create")
     @Operation(summary = "创建收件箱")
     @PreAuthorize("@ss.hasPermission('im:inbox:create')")
-    public CommonResult<Long> createInbox(@Valid @RequestBody ImInboxSaveReqVO createReqVO) {
+    public CommonResult<Long> createInbox(@Valid @RequestBody InboxSaveReqVO createReqVO) {
         return success(imInboxService.createInbox(createReqVO));
     }
 
     @PutMapping("/update")
     @Operation(summary = "更新收件箱")
     @PreAuthorize("@ss.hasPermission('im:inbox:update')")
-    public CommonResult<Boolean> updateInbox(@Valid @RequestBody ImInboxSaveReqVO updateReqVO) {
+    public CommonResult<Boolean> updateInbox(@Valid @RequestBody InboxSaveReqVO updateReqVO) {
         imInboxService.updateInbox(updateReqVO);
         return success(true);
     }
@@ -65,7 +65,7 @@ public class ImInboxController {
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('im:inbox:query')")
     public CommonResult<ImInboxRespVO> getInbox(@RequestParam("id") Long id) {
-        ImInboxDO inbox = imInboxService.getInbox(id);
+        InboxDO inbox = imInboxService.getInbox(id);
         return success(BeanUtils.toBean(inbox, ImInboxRespVO.class));
     }
 
@@ -73,7 +73,7 @@ public class ImInboxController {
     @Operation(summary = "获得收件箱分页")
     @PreAuthorize("@ss.hasPermission('im:inbox:query')")
     public CommonResult<PageResult<ImInboxRespVO>> getInboxPage(@Valid ImInboxPageReqVO pageReqVO) {
-        PageResult<ImInboxDO> pageResult = imInboxService.getInboxPage(pageReqVO);
+        PageResult<InboxDO> pageResult = imInboxService.getInboxPage(pageReqVO);
         return success(BeanUtils.toBean(pageResult, ImInboxRespVO.class));
     }
 
@@ -84,7 +84,7 @@ public class ImInboxController {
     public void exportInboxExcel(@Valid ImInboxPageReqVO pageReqVO,
                                  HttpServletResponse response) throws IOException {
         pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
-        List<ImInboxDO> list = imInboxService.getInboxPage(pageReqVO).getList();
+        List<InboxDO> list = imInboxService.getInboxPage(pageReqVO).getList();
         // 导出 Excel
         ExcelUtils.write(response, "收件箱.xls", "数据", ImInboxRespVO.class,
                 BeanUtils.toBean(list, ImInboxRespVO.class));

+ 42 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/inbox/vo/InboxSaveMessageReqVO.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.im.controller.admin.inbox.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 收件箱保存消息 Request VO")
+@Data
+public class InboxSaveMessageReqVO {
+
+    @Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer conversationType; // 对应 ImConversationTypeEnum 枚举
+
+    @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long fromId;  // 根据 conversationType 区分
+
+    @Schema(description = "发送人昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    private String senderNickname;
+
+    @Schema(description = "发送人头像", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String senderAvatar;
+    
+    @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32494")
+    private Long receiverId;
+
+    @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12454")
+    private Long messageId;
+
+    @Schema(description = "内容类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer contentType; // 参见 ImMessageTypeEnum 枚举
+
+    @Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String content;
+
+    @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime sendTime;
+
+//    @Schema(description = "序号", requiredMode = Schema.RequiredMode.REQUIRED)
+//    private Long sequence;
+
+}

+ 2 - 2
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/inbox/vo/ImInboxSaveReqVO.java → yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/inbox/vo/InboxSaveReqVO.java

@@ -11,7 +11,7 @@ import lombok.NoArgsConstructor;
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
-public class ImInboxSaveReqVO {
+public class InboxSaveReqVO {
 
     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18389")
     private Long id;
@@ -28,7 +28,7 @@ public class ImInboxSaveReqVO {
     @NotNull(message = "序号,按照 user 递增不能为空")
     private Long sequence;
 
-    public ImInboxSaveReqVO(Long userId, Long messageId, Long sequence) {
+    public InboxSaveReqVO(Long userId, Long messageId, Long sequence) {
         this.userId = userId;
         this.messageId = messageId;
         this.sequence = sequence;

+ 11 - 8
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/message/ImReceiveMessage.java → yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/inbox/vo/InboxSendMessageReqVO.java

@@ -1,13 +1,13 @@
-package cn.iocoder.yudao.module.im.websocket.message;
+package cn.iocoder.yudao.module.im.controller.admin.inbox.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 import java.time.LocalDateTime;
 
-@Schema(description = "管理后台 - 消息发送 receive")
+@Schema(description = "管理后台 - 收件箱保存消息 Request VO")
 @Data
-public class ImReceiveMessage {
+public class InboxSendMessageReqVO {
 
     @Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Integer conversationType; // 对应 ImConversationTypeEnum 枚举
@@ -15,7 +15,14 @@ public class ImReceiveMessage {
     @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long fromId;  // 根据 conversationType 区分
 
-    // TODO @hao:昵称和头像,也直接发给接收人好了;
+    @Schema(description = "发送人昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    private String senderNickname;
+
+    @Schema(description = "发送人头像", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String senderAvatar;
+    
+    @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32494")
+    private Long receiverId;
 
     @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12454")
     private Long messageId;
@@ -29,10 +36,6 @@ public class ImReceiveMessage {
     @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime sendTime;
 
-    // TODO @hao:inboxId 不需要哈;
-    @Schema(description = "收件箱编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18389")
-    private Long inboxId;
-
     @Schema(description = "序号", requiredMode = Schema.RequiredMode.REQUIRED)
     private Long sequence;
 

+ 0 - 68
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImMessageController.java

@@ -1,68 +0,0 @@
-package cn.iocoder.yudao.module.im.controller.admin.message;
-
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessagePageReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessageRespVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
-import cn.iocoder.yudao.module.im.service.message.ImMessageService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.annotation.Resource;
-import jakarta.validation.Valid;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.List;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
-
-@Tag(name = "管理后台 - IM 消息")
-@RestController
-@RequestMapping("/im/message")
-@Validated
-public class ImMessageController {
-
-    @Resource
-    private ImMessageService imMessageService;
-
-    @GetMapping("/get-message-by-sequence") // TODO @hao:list-by-sequence
-    @Operation(summary = "拉取消息-增量拉取大于 seq 的消息") // TODO @hao:可以改成 拉取大于 sequence 的消息列表
-    @Parameter(name = "sequence", description = "序号", required = true, example = "1")
-    @Parameter(name = "size", description = "条数", required = true, example = "10")
-    @PreAuthorize("@ss.hasPermission('im:message:query')") // TODO @hao:权限可以删除哈;
-    public CommonResult<List<ImMessageRespVO>> loadMessage(@RequestParam("sequence") Long sequence,
-                                                           @RequestParam("size") Integer size) {
-        // TODO @hao:方法名,可以改成 getMessageListBySequence
-        // TODO @hao:如果是返回列表,变量要用 messages, 或者 messageList,体现出是复数
-        List<ImMessageDO> message = imMessageService.loadMessage(getLoginUserId(), sequence, size);
-        return success(BeanUtils.toBean(message, ImMessageRespVO.class));
-    }
-
-    // TODO @hao:这个接口的使用场景是哪个哈?
-    @GetMapping("/get-all-message")
-    @Operation(summary = "拉取全部消息")
-    @Parameter(name = "size", description = "条数", required = true, example = "10")
-    @PreAuthorize("@ss.hasPermission('im:message:query')")
-    public CommonResult<List<ImMessageRespVO>> loadAllMessage(@RequestParam("size") Integer size) {
-        List<ImMessageDO> message = imMessageService.loadAllMessage(getLoginUserId(), size);
-        return success(BeanUtils.toBean(message, ImMessageRespVO.class));
-    }
-
-    // TODO @hao:是不是分页参数不太对哈?应该只有 conversationNo,sendTime 字段;然后,做链式分页的查询,不太适合传统的 pageSize + number 查询;
-    @GetMapping("/page")
-    @Operation(summary = "查询聊天记录-分页")
-    @PreAuthorize("@ss.hasPermission('im:message:query')") // TODO @hao:权限可以删除哈;
-    public CommonResult<PageResult<ImMessageRespVO>> getMessagePage(@Valid ImMessagePageReqVO pageReqVO) {
-        PageResult<ImMessageDO> messagePage = imMessageService.getMessagePage(pageReqVO);
-        return success(BeanUtils.toBean(messagePage, ImMessageRespVO.class));
-    }
-
-}

+ 57 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/MessageController.java

@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.im.controller.admin.message;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.im.controller.admin.message.vo.MessagePageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.message.vo.SendMessageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.message.vo.SendMessageRespVO;
+import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
+import cn.iocoder.yudao.module.im.service.message.MessageService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - IM 消息")
+@RestController
+@RequestMapping("/im/message")
+@Validated
+public class MessageController {
+
+    @Resource
+    private MessageService messageService;
+
+    @PostMapping("/send")
+    @Operation(summary = "发送消息")
+    public CommonResult<SendMessageRespVO> sendMessage(@Valid @RequestBody SendMessageReqVO message) {
+        return success(messageService.sendMessage(getLoginUserId(), message));
+    }
+
+    @GetMapping("/list-by-sequence")
+    @Operation(summary = "拉取大于 sequence 的消息列表")
+    @Parameter(name = "sequence", description = "序号", required = true, example = "1")
+    @Parameter(name = "size", description = "条数", required = true, example = "10")
+    public CommonResult<List<ImMessageReqVO>> loadMessage(@RequestParam("sequence") Long sequence,
+                                                          @RequestParam("size") Integer size) {
+        List<MessageDO> messages = messageService.getMessageListBySequence(getLoginUserId(), sequence, size);
+        return success(BeanUtils.toBean(messages, ImMessageReqVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "查询聊天记录-分页")
+    public CommonResult<List<ImMessageReqVO>> getMessagePage(@Valid MessagePageReqVO pageReqVO) {
+        //根据会话标志和发送时间进行分页查询
+        List<MessageDO> messagePage = messageService.getMessagePage(pageReqVO);
+        return success(BeanUtils.toBean(messagePage, ImMessageReqVO.class));
+    }
+
+}

+ 0 - 62
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/ImMessagePageReqVO.java

@@ -1,62 +0,0 @@
-package cn.iocoder.yudao.module.im.controller.admin.message.vo;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import com.alibaba.excel.annotation.ExcelProperty;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import java.time.LocalDateTime;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-
-@Schema(description = "管理后台 - 消息分页 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class ImMessagePageReqVO extends PageParam {
-
-    @Schema(description = "客户端消息编号 uuid,用于排重", example = "3331")
-    private String clientMessageId;
-
-    @Schema(description = "发送人编号", example = "23239")
-    private Long senderId;
-
-    @Schema(description = "接收人编号", example = "32494")
-    private Long receiverId;
-
-    @Schema(description = "发送人昵称", example = "李四")
-    private String senderNickname;
-
-    @Schema(description = "发送人头像")
-    private String senderAvatar;
-
-    @Schema(description = "会话类型", example = "2")
-    private Integer conversationType;
-
-    @Schema(description = "会话标志")
-    private String conversationNo;
-
-    @Schema(description = "消息类型", example = "1")
-    private Integer contentType;
-
-    @Schema(description = "消息内容")
-    private String content;
-
-    @Schema(description = "发送时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] sendTime;
-
-    @Schema(description = "消息来源 100-用户发送;200-系统发送(一般是通知);")
-    private Integer sendFrom;
-
-    @Schema(description = "创建时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] createTime;
-
-    @Schema(description = "消息状态 1 发送中、2 发送成功、3 发送失败、4 已删除、5 已撤回", requiredMode = Schema.RequiredMode.REQUIRED)
-    private Integer messageStatus;
-
-}

+ 36 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/ImMessageReqVO.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.im.controller.admin.message.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.im.enums.message.MessageContentTypeEnum;
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 消息 Request VO")
+@Data
+@ExcelIgnoreUnannotated
+public class ImMessageReqVO {
+
+    @Schema(description = "客户端消息编号 uuid,用于排重", requiredMode = Schema.RequiredMode.REQUIRED, example = "3331")
+    @ExcelProperty("客户端消息编号 uuid,用于排重")
+    private String clientMessageId;
+
+    @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32494")
+    @ExcelProperty("接收人编号")
+    private Long receiverId;
+
+    @Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @ExcelProperty("会话类型")
+    private Integer conversationType;
+
+    @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty("消息类型")
+    @InEnum(MessageContentTypeEnum.class)
+    private Integer contentType;
+
+    @Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("消息内容")
+    private String content;
+
+}

+ 0 - 77
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/ImMessageRespVO.java

@@ -1,77 +0,0 @@
-package cn.iocoder.yudao.module.im.controller.admin.message.vo;
-
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
-import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
-import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
-import com.alibaba.excel.annotation.ExcelProperty;
-import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotNull;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-@Schema(description = "管理后台 - 消息 Response VO")
-@Data
-@ExcelIgnoreUnannotated
-public class ImMessageRespVO {
-
-    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30713")
-    @ExcelProperty("编号")
-    private Long id;
-
-    @Schema(description = "客户端消息编号 uuid,用于排重", requiredMode = Schema.RequiredMode.REQUIRED, example = "3331")
-    @ExcelProperty("客户端消息编号 uuid,用于排重")
-    private String clientMessageId;
-
-    @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23239")
-    @ExcelProperty("发送人编号")
-    private Long senderId;
-
-    @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32494")
-    @ExcelProperty("接收人编号")
-    private Long receiverId;
-
-    @Schema(description = "发送人昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
-    @ExcelProperty("发送人昵称")
-    private String senderNickname;
-
-    @Schema(description = "发送人头像", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("发送人头像")
-    private String senderAvatar;
-
-    @Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
-    @ExcelProperty("会话类型")
-    private Integer conversationType;
-
-    @Schema(description = "会话标志", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("会话标志")
-    private String conversationNo;
-
-    @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @ExcelProperty("消息类型")
-    @InEnum(ImMessageTypeEnum.class)
-    private Integer contentType;
-
-    @Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("消息内容")
-    private String content;
-
-    @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("发送时间")
-    private LocalDateTime sendTime;
-
-    @Schema(description = "消息来源 100-用户发送;200-系统发送(一般是通知);", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("消息来源 100-用户发送;200-系统发送(一般是通知);")
-    private Integer sendFrom;
-
-    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("创建时间")
-    private LocalDateTime createTime;
-
-    @Schema(description = "消息状态 1 发送中、2 发送成功、3 发送失败、4 已删除、5 已撤回", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("消息状态")
-    @InEnum(ImMessageStatusEnum.class)
-    private Integer messageStatus;
-
-}

+ 25 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/MessagePageReqVO.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.im.controller.admin.message.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 消息分页 Request VO")
+@Data
+@ToString(callSuper = true)
+public class MessagePageReqVO {
+
+    @Schema(description = "会话标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "g_1000")
+    private String conversationNo;
+
+    @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-03-27")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] sendTime;
+
+}

+ 36 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/SendMessageReqVO.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.im.controller.admin.message.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.im.enums.message.MessageContentTypeEnum;
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 发送消息 Request VO")
+@Data
+@ExcelIgnoreUnannotated
+public class SendMessageReqVO {
+
+    @Schema(description = "客户端消息编号 uuid,用于排重", requiredMode = Schema.RequiredMode.REQUIRED, example = "3331")
+    @ExcelProperty("客户端消息编号 uuid,用于排重")
+    private String clientMessageId;
+
+    @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32494")
+    @ExcelProperty("接收人编号")
+    private Long receiverId;
+
+    @Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @ExcelProperty("会话类型")
+    private Integer conversationType;
+
+    @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty("消息类型")
+    @InEnum(MessageContentTypeEnum.class)
+    private Integer contentType;
+
+    @Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("消息内容")
+    private String content;
+
+}

+ 26 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/SendMessageRespVO.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.im.controller.admin.message.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 发送消息 Response VO")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class SendMessageRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12")
+    private Long id;
+
+    @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-03-27 12:00:00")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime sendTime;
+
+}

+ 8 - 10
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/dal/dataobject/conversation/ConversationDO.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.im.dal.dataobject.conversation;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.im.enums.conversation.ConversationTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -21,31 +22,28 @@ import java.time.LocalDateTime;
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class ImConversationDO extends BaseDO {
+public class ConversationDO extends BaseDO {
 
     /**
      * 编号
      */
     @TableId
     private Long id;
+
     /**
      * 所属用户
      */
     private Long userId;
-    // TODO @hao:注释可以改成,如下
+
     /**
      * 会话类型
-     *
-     * 枚举 {@link cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum}
-     */
-    /**
-     * 类型:1 单聊;2 群聊;4 通知会话(预留)
+     * <p>
+     * 枚举 {@link ConversationTypeEnum}
      */
-    // TODO @hao:conversationType 改成 type
-    private Integer conversationType;
+    private Integer type;
     /**
      * 聊天对象编号
-     *
+     * <p>
      * 1. 单聊时,用户编号;群聊时,群编号
      */
     private Long targetId;

+ 5 - 3
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java → yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/GroupDO.java

@@ -1,8 +1,10 @@
 package cn.iocoder.yudao.module.im.dal.dataobject.group;
 
-import lombok.*;
-import com.baomidou.mybatisplus.annotation.*;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
 
 /**
  * IM 群信息 DO
@@ -17,7 +19,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class ImGroupDO extends BaseDO {
+public class GroupDO extends BaseDO {
 
     /**
      * 编号

+ 1 - 5
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/groupmember/GroupMemberDO.java → yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/GroupMemberDO.java

@@ -1,13 +1,9 @@
-package cn.iocoder.yudao.module.im.dal.dataobject.groupmember;
+package cn.iocoder.yudao.module.im.dal.dataobject.group;
 
 import lombok.*;
-import java.util.*;
-import java.time.LocalDateTime;
-import java.time.LocalDateTime;
 import com.baomidou.mybatisplus.annotation.*;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 
-// TODO @hao:这个挪到 group 包下;
 /**
  * IM 群成员 DO
  *

+ 1 - 1
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/inbox/ImInboxDO.java → yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/inbox/InboxDO.java

@@ -20,7 +20,7 @@ import lombok.*;
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class ImInboxDO extends BaseDO {
+public class InboxDO extends BaseDO {
 
     /**
      * 编号

+ 19 - 18
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImMessageDO.java → yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/MessageDO.java

@@ -1,6 +1,10 @@
 package cn.iocoder.yudao.module.im.dal.dataobject.message;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.im.enums.conversation.ConversationTypeEnum;
+import cn.iocoder.yudao.module.im.enums.message.MessageContentTypeEnum;
+import cn.iocoder.yudao.module.im.enums.message.MessageSourceEnum;
+import cn.iocoder.yudao.module.im.enums.message.MessageStatusEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -21,7 +25,7 @@ import java.time.LocalDateTime;
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class ImMessageDO extends BaseDO {
+public class MessageDO extends BaseDO {
 
     /**
      * 编号
@@ -32,57 +36,54 @@ public class ImMessageDO extends BaseDO {
      * 客户端消息编号 uuid,用于排重
      */
     private String clientMessageId;
-    // TODO @hao:senderId、receiverId 存储的具体值写下,对应的群之类的
     /**
-     * 发送人编号
+     * 发送人编号 存储的是用户编号
      */
     private Long senderId;
     /**
      * 接收人编号
+     * <p>
+     * 1. 单聊时,用户编号;群聊时,群编号
      */
     private Long receiverId;
-    // TODO @hao:冗余字段,要说明下的。例如说
     /**
-     * 发送人昵称
-     *
+     * 消息发送者昵称
+     * <p>
      * 冗余 AdminUserDO 的 nickname 字段
      */
     private String senderNickname;
     /**
-     * 发送人头像
+     * 消息发送者头像
+     * <p>
+     * 冗余 AdminUserDO 的 avatar 字段
      */
     private String senderAvatar;
-    // TODO @hao:关联枚举
     /**
-     * 会话类型
+     * 会话类型 枚举 {@link ConversationTypeEnum}
      */
     private Integer conversationType;
-    // TODO @hao:关联字段
     /**
-     * 会话标志
+     * 会话标志 {@link ConversationTypeEnum} 的generateConversationNo() 方法生成
      */
     private String conversationNo;
     /**
-     * 消息类型
+     * 消息类型 枚举 {@link MessageContentTypeEnum}
      */
     private Integer contentType;
-    // TODO @hao:说明下是 json 格式,在哪个包看具体的格式
     /**
-     * 消息内容
+     * 消息内容 JSON 格式 对应 dal/dataobject/message/content 包
      */
     private String content;
     /**
      * 发送时间
      */
     private LocalDateTime sendTime;
-    // TODO @hao:搞个枚举,然后代码里注释说明下
     /**
-     * 消息来源 100-用户发送;200-系统发送(一般是通知);
+     * 消息来源 枚举 {@link MessageSourceEnum}
      */
     private Integer sendFrom;
-    // TODO @hao:搞个枚举,然后代码里注释说明下
     /**
-     * 消息状态 1 发送中、2 发送成功、3 发送失败、4 已删除、5 已撤回
+     * 消息状态 枚举 {@link MessageStatusEnum}
      */
     private Integer messageStatus;
 

+ 0 - 31
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/body/ImAudioMessageBody.java

@@ -1,31 +0,0 @@
-package cn.iocoder.yudao.module.im.dal.dataobject.message.body;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * 语音消息的 {@link ImMessageBody}
- *
- * @author 芋道源码
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-public class ImAudioMessageBody implements ImMessageBody {
-
-    /**
-     * 语音 URL
-     */
-    private String url;
-    /**
-     * 语音格式,例如说 arm、mp3、speex 等
-     */
-    private String format;
-
-    // TODO 芋艿:要不要以下字段?待定;云信有、企业微信没有
-//"dur":4551,		//语音持续时长ms
-//        "md5":"87b94a090dec5c58f242b7132a530a01",	//语音文件的md5值,按照字节流加密
-//        "size":16420		//语音文件大小,单位为字节(Byte)
-
-}

+ 0 - 31
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/body/ImFileMessageBody.java

@@ -1,31 +0,0 @@
-package cn.iocoder.yudao.module.im.dal.dataobject.message.body;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * 文件消息的 {@link ImMessageBody}
- *
- * @author 芋道源码
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-public class ImFileMessageBody implements ImMessageBody {
-
-    /**
-     * 文件名
-     */
-    private String name;
-    /**
-     * 文件 URL
-     */
-    private String url;
-
-    // TODO 芋艿:要不要以下字段?待定;云信有、企业微信没有
-//  "md5":"79d62a35fa3d34c367b20c66afc2a500", //文件MD5,按照字节流加密
-//  "ext":"ttf",	//文件后缀类型
-//  "size":91680	//大小,单位为字节(Byte)
-
-}

+ 0 - 30
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/body/ImImageMessageBody.java

@@ -1,30 +0,0 @@
-package cn.iocoder.yudao.module.im.dal.dataobject.message.body;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * 图片消息的 {@link ImMessageBody}
- *
- * @author 芋道源码
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-public class ImImageMessageBody implements ImMessageBody {
-
-    /**
-     * 图片地址
-     */
-    private String url;
-
-    // TODO 芋艿:要不要以下字段?待定;云信有、企业微信没有
-//    "name":"图片发送于2015-05-07 13:59",   //图片name
-//            "md5":"9894907e4ad9de4678091277509361f7",	//图片文件md5,按照字节流加密
-//            "ext":"jpg",	//图片后缀
-//            "w":6814,	//宽,单位为像素
-//            "h":2332,	//高,单位为像素
-//            "size":388245	//图片文件大小,单位为字节(Byte)
-
-}

+ 0 - 12
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/body/ImMessageBody.java

@@ -1,12 +0,0 @@
-package cn.iocoder.yudao.module.im.dal.dataobject.message.body;
-
-
-import cn.iocoder.yudao.module.im.jackson.ImMessageBodyDeserializer;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-
-/**
- * IM 消息的 body 内容
- */
-@JsonDeserialize(using = ImMessageBodyDeserializer.class)
-public interface ImMessageBody {
-}

+ 0 - 22
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/body/ImTextMessageBody.java

@@ -1,22 +0,0 @@
-package cn.iocoder.yudao.module.im.dal.dataobject.message.body;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * 文本消息的 {@link ImMessageBody}
- *
- * @author 芋道源码
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-public class ImTextMessageBody implements ImMessageBody {
-
-    /**
-     * 文本消息内容
-     */
-    private String content;
-
-}

+ 0 - 27
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/body/ImVideoMessageBody.java

@@ -1,27 +0,0 @@
-package cn.iocoder.yudao.module.im.dal.dataobject.message.body;
-
-import lombok.Data;
-
-/**
- * 视频消息的 {@link ImMessageBody}
- *
- * @author 芋道源码
- */
-@Data
-public class ImVideoMessageBody implements ImMessageBody {
-
-    /**
-     * 视频地址
-     */
-    private String url;
-
-    // TODO 芋艿:要不要以下字段?待定;云信有、企业微信没有
-//  "dur":8003,		//视频持续时长ms
-//          "md5":"da2cef3e5663ee9c3547ef5d127f7e3e",	//视频文件的md5值,按照字节流加密
-//          "w":360,	//宽,单位为像素
-//          "h":480,	//高,单位为像素
-//          "size":16420	//视频文件大小,单位为字节(Byte)】
-//      "ext":"mp4",	//视频格式
-
-
-}

+ 48 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/AudioMessage.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
+
+import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 语音消息的 {@link MessageDO 字段 content} 的内容
+ *
+ * @author 芋道源码
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class AudioMessage {
+
+    /**
+     * 语音文件唯一 ID
+     */
+    private String uuid;
+
+    /**
+     * 语音文件的本地路径
+     */
+    private String soundPath;
+
+    /**
+     * 语音文件下载地址
+     */
+    private String sourceUrl;
+
+    /**
+     * 语音文件大小
+     */
+    private int dataSize;
+
+    /**
+     * 语音时长
+     */
+    private int duration;
+
+    /**
+     * 语音文件类型
+     */
+    private String soundType;
+
+}

+ 38 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/CardMessage.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
+
+import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 名片消息的 {@link MessageDO 字段 content} 的内容
+ *
+ * @author anhaohao
+ * @since 2024/3/23 下午5:53
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class CardMessage {
+
+    /**
+     * 用户 ID
+     */
+    private String userID;
+
+    /**
+     * 用户名
+     */
+    private String nickname;
+
+    /**
+     * 用户头像
+     */
+    private int faceURL;
+
+    /**
+     * 扩展字段
+     */
+    private String ex;
+}

+ 27 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FileMessage.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
+
+import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 文件消息的 {@link MessageDO 字段 content} 的内容
+ *
+ * @author 芋道源码
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class FileMessage {
+
+    /**
+     * 文件名
+     */
+    private String name;
+    /**
+     * 文件 URL
+     */
+    private String url;
+
+}

+ 23 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/ImageMessage.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
+
+import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 图片消息的 {@link MessageDO 字段 content} 的内容
+ *
+ * @author 芋道源码
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ImageMessage {
+
+    /**
+     * 图片地址
+     */
+    private String url;
+
+}

+ 5 - 4
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/body/ImLocationMessageBody.java → yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/LocationMessage.java

@@ -1,22 +1,23 @@
-package cn.iocoder.yudao.module.im.dal.dataobject.message.body;
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
 
+import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
 /**
- * 地址位置消息的 {@link ImMessageBody}
+ * 地址位置消息的 {@link MessageDO 字段 content} 的内容
  *
  * @author 芋道源码
  */
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
-public class ImLocationMessageBody implements ImMessageBody {
+public class LocationMessage {
 
     /**
      * 地理位置
-     *
+     * <p>
      * 例如说:中国 浙江省 杭州市 网商路 599号
      */
     private String address;

+ 23 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/TextMessage.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
+
+import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 文本消息的 {@link MessageDO 字段 content} 的内容
+ *
+ * @author 芋道源码
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class TextMessage {
+
+    /**
+     * 文本消息的具体内容
+     */
+    private String content;
+
+}

+ 19 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/VideoMessage.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
+
+import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
+import lombok.Data;
+
+/**
+ * 视频消息的 {@link MessageDO 字段 content} 的内容
+ *
+ * @author 芋道源码
+ */
+@Data
+public class VideoMessage {
+
+    /**
+     * 视频地址
+     */
+    private String url;
+
+}

+ 34 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ConversationMapper.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.im.dal.mysql.conversation;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationPageReqVO;
+import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ConversationDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * IM 会话 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ConversationMapper extends BaseMapperX<ConversationDO> {
+
+    default PageResult<ConversationDO> selectPage(ImConversationPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<ConversationDO>()
+                .eqIfPresent(ConversationDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(ConversationDO::getType, reqVO.getType())
+                .eqIfPresent(ConversationDO::getTargetId, reqVO.getTargetId())
+                .eqIfPresent(ConversationDO::getNo, reqVO.getNo())
+                .eqIfPresent(ConversationDO::getPinned, reqVO.getPinned())
+                .betweenIfPresent(ConversationDO::getLastReadTime, reqVO.getLastReadTime())
+                .betweenIfPresent(ConversationDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(ConversationDO::getId));
+    }
+
+    // TODO @hao:1)no) {,要有空格哈;2)可以直接 selectOne(ImConversationDO::getNo, no) 父类做了封装
+    default ConversationDO selectByNo(String no){
+        return selectOne(new LambdaQueryWrapperX<ConversationDO>().eq(ConversationDO::getNo, no));
+    }
+}

+ 0 - 36
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationMapper.java

@@ -1,36 +0,0 @@
-package cn.iocoder.yudao.module.im.dal.mysql.conversation;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationPageReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationDO;
-import org.apache.ibatis.annotations.Mapper;
-
-import java.util.Optional;
-
-/**
- * IM 会话 Mapper
- *
- * @author 芋道源码
- */
-@Mapper
-public interface ImConversationMapper extends BaseMapperX<ImConversationDO> {
-
-    default PageResult<ImConversationDO> selectPage(ImConversationPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<ImConversationDO>()
-                .eqIfPresent(ImConversationDO::getUserId, reqVO.getUserId())
-                .eqIfPresent(ImConversationDO::getConversationType, reqVO.getConversationType())
-                .eqIfPresent(ImConversationDO::getTargetId, reqVO.getTargetId())
-                .eqIfPresent(ImConversationDO::getNo, reqVO.getNo())
-                .eqIfPresent(ImConversationDO::getPinned, reqVO.getPinned())
-                .betweenIfPresent(ImConversationDO::getLastReadTime, reqVO.getLastReadTime())
-                .betweenIfPresent(ImConversationDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(ImConversationDO::getId));
-    }
-
-    // TODO @hao:1)no) {,要有空格哈;2)可以直接 selectOne(ImConversationDO::getNo, no) 父类做了封装
-    default ImConversationDO selectByNo(String no){
-        return selectOne(new LambdaQueryWrapperX<ImConversationDO>().eq(ImConversationDO::getNo, no));
-    }
-}

+ 12 - 12
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.im.dal.mysql.group;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.group.GroupDO;
 import org.apache.ibatis.annotations.Mapper;
 import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
 
@@ -13,18 +13,18 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
  * @author 芋道源码
  */
 @Mapper
-public interface ImGroupMapper extends BaseMapperX<ImGroupDO> {
+public interface ImGroupMapper extends BaseMapperX<GroupDO> {
 
-    default PageResult<ImGroupDO> selectPage(ImGroupPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<ImGroupDO>()
-                .likeIfPresent(ImGroupDO::getGroupName, reqVO.getGroupName())
-                .eqIfPresent(ImGroupDO::getOwnerId, reqVO.getOwnerId())
-                .eqIfPresent(ImGroupDO::getHeadImage, reqVO.getHeadImage())
-                .eqIfPresent(ImGroupDO::getHeadImageThumb, reqVO.getHeadImageThumb())
-                .eqIfPresent(ImGroupDO::getNotice, reqVO.getNotice())
-                .eqIfPresent(ImGroupDO::getRemark, reqVO.getRemark())
-                .betweenIfPresent(ImGroupDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(ImGroupDO::getId));
+    default PageResult<GroupDO> selectPage(ImGroupPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<GroupDO>()
+                .likeIfPresent(GroupDO::getGroupName, reqVO.getGroupName())
+                .eqIfPresent(GroupDO::getOwnerId, reqVO.getOwnerId())
+                .eqIfPresent(GroupDO::getHeadImage, reqVO.getHeadImage())
+                .eqIfPresent(GroupDO::getHeadImageThumb, reqVO.getHeadImageThumb())
+                .eqIfPresent(GroupDO::getNotice, reqVO.getNotice())
+                .eqIfPresent(GroupDO::getRemark, reqVO.getRemark())
+                .betweenIfPresent(GroupDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(GroupDO::getId));
     }
 
 }

+ 1 - 1
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/groupmember/ImGroupMemberMapper.java

@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.ImGroupMemberPageReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.groupmember.GroupMemberDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.group.GroupMemberDO;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.List;

+ 9 - 9
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/inbox/ImInboxMapper.java

@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.ImInboxPageReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.inbox.ImInboxDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.inbox.InboxDO;
 import org.apache.ibatis.annotations.Mapper;
 
 /**
@@ -13,15 +13,15 @@ import org.apache.ibatis.annotations.Mapper;
  * @author 芋道源码
  */
 @Mapper
-public interface ImInboxMapper extends BaseMapperX<ImInboxDO> {
+public interface ImInboxMapper extends BaseMapperX<InboxDO> {
 
-    default PageResult<ImInboxDO> selectPage(ImInboxPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<ImInboxDO>()
-                .eqIfPresent(ImInboxDO::getUserId, reqVO.getUserId())
-                .eqIfPresent(ImInboxDO::getMessageId, reqVO.getMessageId())
-                .eqIfPresent(ImInboxDO::getSequence, reqVO.getSequence())
-                .betweenIfPresent(ImInboxDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(ImInboxDO::getId));
+    default PageResult<InboxDO> selectPage(ImInboxPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<InboxDO>()
+                .eqIfPresent(InboxDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(InboxDO::getMessageId, reqVO.getMessageId())
+                .eqIfPresent(InboxDO::getSequence, reqVO.getSequence())
+                .betweenIfPresent(InboxDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(InboxDO::getId));
     }
 
 }

+ 0 - 62
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImMessageMapper.java

@@ -1,62 +0,0 @@
-package cn.iocoder.yudao.module.im.dal.mysql.message;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessagePageReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.inbox.ImInboxDO;
-import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
-import com.github.yulichang.wrapper.MPJLambdaWrapper;
-import org.apache.ibatis.annotations.Mapper;
-
-import java.util.List;
-
-/**
- * 消息 Mapper
- *
- * @author 芋道源码
- */
-@Mapper
-public interface ImMessageMapper extends BaseMapperX<ImMessageDO> {
-
-    default PageResult<ImMessageDO> selectPage(ImMessagePageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<ImMessageDO>()
-                .eqIfPresent(ImMessageDO::getClientMessageId, reqVO.getClientMessageId())
-                .eqIfPresent(ImMessageDO::getSenderId, reqVO.getSenderId())
-                .eqIfPresent(ImMessageDO::getReceiverId, reqVO.getReceiverId())
-                .likeIfPresent(ImMessageDO::getSenderNickname, reqVO.getSenderNickname())
-                .eqIfPresent(ImMessageDO::getSenderAvatar, reqVO.getSenderAvatar())
-                .eqIfPresent(ImMessageDO::getConversationType, reqVO.getConversationType())
-                .eqIfPresent(ImMessageDO::getConversationNo, reqVO.getConversationNo())
-                .eqIfPresent(ImMessageDO::getContentType, reqVO.getContentType())
-                .eqIfPresent(ImMessageDO::getContent, reqVO.getContent())
-                .betweenIfPresent(ImMessageDO::getSendTime, reqVO.getSendTime())
-                .eqIfPresent(ImMessageDO::getSendFrom, reqVO.getSendFrom())
-                .betweenIfPresent(ImMessageDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(ImMessageDO::getId));
-    }
-
-    // TODO @hao:不链表哈;先从 ImInboxDO 查询出 messageId,然后再到 ImMessageDO 里 IN
-    default List<ImMessageDO> getGreaterThanSequenceMessage(Long userId, Long sequence, Integer size) {
-        //查询 inbox 表中,大于 sequence 的消息,关联 message 表,按照 inbox 表 sequence 升序
-        return selectJoinList(ImMessageDO.class, new MPJLambdaWrapper<ImMessageDO>()
-                .selectAll(ImMessageDO.class)
-                .innerJoin(ImInboxDO.class, ImInboxDO::getMessageId, ImMessageDO::getId)
-                .eq(ImInboxDO::getUserId, userId)
-                .gt(ImInboxDO::getSequence, sequence)
-                .orderByAsc(ImInboxDO::getSequence)
-                .last("limit 0," + size));
-    }
-
-    // TODO @hao:在 dao 里,使用 selectListByUserId,查询用 select,条件用 by,这个算是 spring data 的 method dsl
-    default List<ImMessageDO> getAllMessage(Long userId, Integer size) {
-        //查询 inbox 表中,100条消息,关联 message 表,按照 inbox 表 sequence 降序
-        return selectJoinList(ImMessageDO.class, new MPJLambdaWrapper<ImMessageDO>()
-                .selectAll(ImMessageDO.class)
-                .innerJoin(ImInboxDO.class, ImInboxDO::getMessageId, ImMessageDO::getId)
-                .eq(ImInboxDO::getUserId, userId)
-                .orderByDesc(ImInboxDO::getSequence)
-                .last("limit 0," + size));
-    }
-
-}

+ 51 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/MessageMapper.java

@@ -0,0 +1,51 @@
+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.module.im.controller.admin.message.vo.MessagePageReqVO;
+import cn.iocoder.yudao.module.im.dal.dataobject.inbox.InboxDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 消息 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface MessageMapper extends BaseMapperX<MessageDO> {
+
+    // TODO @hao:不链表哈;先从 ImInboxDO 查询出 messageId,然后再到 ImMessageDO 里 IN
+    default List<MessageDO> getGreaterThanSequenceMessage(Long userId, Long sequence, Integer size) {
+        //查询 inbox 表中,大于 sequence 的消息,关联 message 表,按照 inbox 表 sequence 升序
+        return selectJoinList(MessageDO.class, new MPJLambdaWrapper<MessageDO>()
+                .selectAll(MessageDO.class)
+                .innerJoin(InboxDO.class, InboxDO::getMessageId, MessageDO::getId)
+                .eq(InboxDO::getUserId, userId)
+                .gt(InboxDO::getSequence, sequence)
+                .orderByAsc(InboxDO::getSequence)
+                .last("limit 0," + size));
+    }
+
+    // TODO @hao:在 dao 里,使用 selectListByUserId,查询用 select,条件用 by,这个算是 spring data 的 method dsl
+    default List<MessageDO> getAllMessage(Long userId, Integer size) {
+        //查询 inbox 表中,100条消息,关联 message 表,按照 inbox 表 sequence 降序
+        return selectJoinList(MessageDO.class, new MPJLambdaWrapper<MessageDO>()
+                .selectAll(MessageDO.class)
+                .innerJoin(InboxDO.class, InboxDO::getMessageId, MessageDO::getId)
+                .eq(InboxDO::getUserId, userId)
+                .orderByDesc(InboxDO::getSequence)
+                .last("limit 0," + size));
+    }
+
+    default List<MessageDO> getMessagePage(MessagePageReqVO pageReqVO) {
+        return selectList(new LambdaQueryWrapperX<MessageDO>()
+                .eqIfPresent(MessageDO::getConversationNo, pageReqVO.getConversationNo())
+                .betweenIfPresent(MessageDO::getSendTime, pageReqVO.getSendTime())
+                .orderByAsc(MessageDO::getSendTime));
+    }
+
+}

+ 7 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java

@@ -14,4 +14,11 @@ public interface RedisKeyConstants {
      * VALUE 数据类型: String
      */
     String INBOX_SEQUENCE = "im_inbox_sequence:%s";
+
+    /**
+     * 收件箱的分布式锁
+     * KEY 格式:  im:inbox:lock:{userId}
+     * VALUE 数据类型: String
+     */
+    String INBOX_LOCK = "im_inbox_lock:%s";
 }

+ 39 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/inbox/InboxLockRedisDAO.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.im.dal.redis.inbox;
+
+import jakarta.annotation.Resource;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.stereotype.Repository;
+
+import java.util.concurrent.TimeUnit;
+
+import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.INBOX_LOCK;
+
+/**
+ * 收件箱的锁 Redis DAO
+ *
+ * @author 芋道源码
+ */
+@Repository
+public class InboxLockRedisDAO {
+
+    @Resource
+    private RedissonClient redissonClient;
+
+    private static String formatKey(Long id) {
+        return String.format(INBOX_LOCK, id);
+    }
+
+    public void lock(Long id, Long timeoutMillis, Runnable runnable) {
+        String lockKey = formatKey(id);
+        RLock lock = redissonClient.getLock(lockKey);
+        try {
+            lock.lock(timeoutMillis, TimeUnit.MILLISECONDS);
+            // 执行逻辑
+            runnable.run();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+}

+ 9 - 1
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/inbox/SequenceGeneratorRedisDao.java → yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/inbox/SequenceRedisDao.java

@@ -1,19 +1,21 @@
 package cn.iocoder.yudao.module.im.dal.redis.inbox;
 
 import jakarta.annotation.Resource;
+import org.redisson.api.RedissonClient;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Repository;
 
 import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.INBOX_SEQUENCE;
 
 // TODO @芋艿:这个名字,需要在考虑下;
+
 /**
  * 序号生成器 Redis DAO
  *
  * @author anhaohao
  */
 @Repository
-public class SequenceGeneratorRedisDao {
+public class SequenceRedisDao {
 
     @Resource
     private RedisTemplate<String, Long> redisTemplate;
@@ -22,6 +24,12 @@ public class SequenceGeneratorRedisDao {
         return String.format(INBOX_SEQUENCE, userId);
     }
 
+    /**
+     * 生成序号
+     *
+     * @param userId 用户编号
+     * @return 序号
+     */
     public Long generateSequence(Long userId) {
         return redisTemplate.opsForValue().increment(formatKey(userId), 1);
     }

+ 0 - 38
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/jackson/ImMessageBodyDeserializer.java

@@ -1,38 +0,0 @@
-package cn.iocoder.yudao.module.im.jackson;
-
-import cn.iocoder.yudao.module.im.dal.dataobject.message.body.*;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonNode;
-
-import java.io.IOException;
-
-// TODO @hao:可以讨论下:是不是前端传递 type + content,后端根据 type 判断后,直接 jsonutil 解析到对应的 body 就 ok 了
-public class ImMessageBodyDeserializer extends JsonDeserializer<ImMessageBody> {
-
-    @Override
-    public ImMessageBody deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
-        JsonNode node = p.getCodec().readTree(p);
-        // 根据 node 中的内容来判断应该实例化哪个子类
-        if (node.has("content")) {
-            return new ImTextMessageBody(node.get("content").asText());
-        }
-        if (node.has("url")) {
-            String url = node.get("url").asText();
-            if (node.has("format")) {
-                return new ImAudioMessageBody(url, node.get("format").asText());
-            }
-            return new ImImageMessageBody(url);
-        }
-        if (node.has("name")) {
-            return new ImFileMessageBody(node.get("name").asText(), node.get("url").asText());
-        }
-        if (node.has("address")) {
-            return new ImLocationMessageBody(node.get("address").asText(), node.get("longitude").asDouble(), node.get("latitude").asDouble());
-        }
-        // 如果没有匹配的属性,抛出异常
-        throw ctxt.mappingException("Cannot deserialize to an instance of ImMessageBody");
-    }
-
-}

+ 5 - 5
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationService.java → yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ConversationService.java

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.im.service.conversation;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationPageReqVO;
 import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationSaveReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ConversationDO;
 import jakarta.validation.Valid;
 
 import java.util.List;
@@ -13,7 +13,7 @@ import java.util.List;
  *
  * @author 芋道源码
  */
-public interface ImConversationService {
+public interface ConversationService {
 
     /**
      * 创建会话
@@ -43,7 +43,7 @@ public interface ImConversationService {
      * @param id 编号
      * @return 会话
      */
-    ImConversationDO getConversation(Long id);
+    ConversationDO getConversation(Long id);
 
     /**
      * 获得会话分页
@@ -51,7 +51,7 @@ public interface ImConversationService {
      * @param pageReqVO 分页查询
      * @return 会话分页
      */
-    PageResult<ImConversationDO> getConversationPage(ImConversationPageReqVO pageReqVO);
+    PageResult<ConversationDO> getConversationPage(ImConversationPageReqVO pageReqVO);
 
 
     /**
@@ -59,7 +59,7 @@ public interface ImConversationService {
      *
      * @return 会话列表
      */
-    List<ImConversationDO> getConversationList();
+    List<ConversationDO> getConversationList();
 
     /**
      * 置顶会话

+ 22 - 22
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationServiceImpl.java → yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ConversationServiceImpl.java

@@ -5,8 +5,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationPageReqVO;
 import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationSaveReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationDO;
-import cn.iocoder.yudao.module.im.dal.mysql.conversation.ImConversationMapper;
+import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ConversationDO;
+import cn.iocoder.yudao.module.im.dal.mysql.conversation.ConversationMapper;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
@@ -24,17 +24,17 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.CONVERSATION_N
  */
 @Service
 @Validated
-public class ImConversationServiceImpl implements ImConversationService {
+public class ConversationServiceImpl implements ConversationService {
 
     // TODO @hao: 自己模块的注入,不用带 im 前缀哈;
     @Resource
-    private ImConversationMapper imConversationMapper;
+    private ConversationMapper conversationMapper;
 
     // TODO @hao: 这个方法,是不是不需要哈
     @Override
     public Long createConversation(ImConversationSaveReqVO createReqVO) {
-        ImConversationDO conversation = BeanUtils.toBean(createReqVO, ImConversationDO.class);
-        imConversationMapper.insert(conversation);
+        ConversationDO conversation = BeanUtils.toBean(createReqVO, ConversationDO.class);
+        conversationMapper.insert(conversation);
         return conversation.getId();
     }
 
@@ -44,8 +44,8 @@ public class ImConversationServiceImpl implements ImConversationService {
         // 校验存在
         validateConversationExists(updateReqVO.getId());
         // 更新
-        ImConversationDO updateObj = BeanUtils.toBean(updateReqVO, ImConversationDO.class);
-        imConversationMapper.updateById(updateObj);
+        ConversationDO updateObj = BeanUtils.toBean(updateReqVO, ConversationDO.class);
+        conversationMapper.updateById(updateObj);
     }
 
     // TODO @hao: 考虑到可能和端上不同步,可以不校验是不是存储。另外,不基于 id 删除。要基于 no + userId 删除哈。说白了,对端上要屏蔽 id 字段
@@ -54,28 +54,28 @@ public class ImConversationServiceImpl implements ImConversationService {
         // 校验存在
         validateConversationExists(id);
         // 删除
-        imConversationMapper.deleteById(id);
+        conversationMapper.deleteById(id);
     }
 
     private void validateConversationExists(Long id) {
-        if (imConversationMapper.selectById(id) == null) {
+        if (conversationMapper.selectById(id) == null) {
             throw exception(CONVERSATION_NOT_EXISTS);
         }
     }
 
     @Override
-    public ImConversationDO getConversation(Long id) {
-        return imConversationMapper.selectById(id);
+    public ConversationDO getConversation(Long id) {
+        return conversationMapper.selectById(id);
     }
 
     @Override
-    public PageResult<ImConversationDO> getConversationPage(ImConversationPageReqVO pageReqVO) {
-        return imConversationMapper.selectPage(pageReqVO);
+    public PageResult<ConversationDO> getConversationPage(ImConversationPageReqVO pageReqVO) {
+        return conversationMapper.selectPage(pageReqVO);
     }
 
     @Override
-    public List<ImConversationDO> getConversationList() {
-        return imConversationMapper.selectList();
+    public List<ConversationDO> getConversationList() {
+        return conversationMapper.selectList();
     }
 
     @Override
@@ -95,20 +95,20 @@ public class ImConversationServiceImpl implements ImConversationService {
     // 3. 做对应更新的 notify 推送
     private void createOrUpdateConversation(ImConversationSaveReqVO updateReqVO) {
         // 操作会话(已读、置顶)时,才会延迟创建,要先判断是否存在,根据 no 查询是否存在,不存在则新增
-        ImConversationDO conversation = imConversationMapper.selectByNo(updateReqVO.getNo());
+        ConversationDO conversation = conversationMapper.selectByNo(updateReqVO.getNo());
         if (conversation == null) {
-            ImConversationDO conversationDO = new ImConversationDO();
+            ConversationDO conversationDO = new ConversationDO();
             conversationDO.setNo(updateReqVO.getNo());
             conversationDO.setUserId(updateReqVO.getUserId());
             conversationDO.setTargetId(updateReqVO.getTargetId());
-            conversationDO.setConversationType(updateReqVO.getConversationType());
+            conversationDO.setType(updateReqVO.getType());
             conversationDO.setPinned(updateReqVO.getPinned());
             conversationDO.setLastReadTime(DateUtil.toLocalDateTime(new Date()));
-            imConversationMapper.insert(conversationDO);
+            conversationMapper.insert(conversationDO);
         } else {
             // 更新
-            ImConversationDO updateObj = BeanUtils.toBean(updateReqVO, ImConversationDO.class);
-            imConversationMapper.updateById(updateObj);
+            ConversationDO updateObj = BeanUtils.toBean(updateReqVO, ConversationDO.class);
+            conversationMapper.updateById(updateObj);
         }
     }
 

+ 3 - 3
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java

@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.im.service.group;
 
 import jakarta.validation.*;
 import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
-import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.group.GroupDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 
 /**
@@ -40,7 +40,7 @@ public interface ImGroupService {
      * @param id 编号
      * @return 群
      */
-    ImGroupDO getGroup(Long id);
+    GroupDO getGroup(Long id);
 
     /**
      * 获得群分页
@@ -48,6 +48,6 @@ public interface ImGroupService {
      * @param pageReqVO 分页查询
      * @return 群分页
      */
-    PageResult<ImGroupDO> getGroupPage(ImGroupPageReqVO pageReqVO);
+    PageResult<GroupDO> getGroupPage(ImGroupPageReqVO pageReqVO);
 
 }

+ 5 - 5
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java

@@ -5,7 +5,7 @@ import jakarta.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
 
 import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
-import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.group.GroupDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 
@@ -29,7 +29,7 @@ public class ImGroupServiceImpl implements ImGroupService {
     @Override
     public Long createGroup(ImGroupSaveReqVO createReqVO) {
         // 插入
-        ImGroupDO group = BeanUtils.toBean(createReqVO, ImGroupDO.class);
+        GroupDO group = BeanUtils.toBean(createReqVO, GroupDO.class);
         imGroupMapper.insert(group);
         // 返回
         return group.getId();
@@ -40,7 +40,7 @@ public class ImGroupServiceImpl implements ImGroupService {
         // 校验存在
         validateGroupExists(updateReqVO.getId());
         // 更新
-        ImGroupDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupDO.class);
+        GroupDO updateObj = BeanUtils.toBean(updateReqVO, GroupDO.class);
         imGroupMapper.updateById(updateObj);
     }
 
@@ -59,12 +59,12 @@ public class ImGroupServiceImpl implements ImGroupService {
     }
 
     @Override
-    public ImGroupDO getGroup(Long id) {
+    public GroupDO getGroup(Long id) {
         return imGroupMapper.selectById(id);
     }
 
     @Override
-    public PageResult<ImGroupDO> getGroupPage(ImGroupPageReqVO pageReqVO) {
+    public PageResult<GroupDO> getGroupPage(ImGroupPageReqVO pageReqVO) {
         return imGroupMapper.selectPage(pageReqVO);
     }
 

+ 1 - 1
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/groupmember/ImGroupMemberService.java

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.im.service.groupmember;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.ImGroupMemberPageReqVO;
 import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.ImGroupMemberSaveReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.groupmember.GroupMemberDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.group.GroupMemberDO;
 import jakarta.validation.Valid;
 
 import java.util.List;

+ 1 - 1
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/groupmember/ImGroupMemberServiceImpl.java

@@ -5,7 +5,7 @@ import jakarta.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
 
 import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.*;
-import cn.iocoder.yudao.module.im.dal.dataobject.groupmember.GroupMemberDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.group.GroupMemberDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 

+ 12 - 6
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/inbox/ImInboxService.java

@@ -2,8 +2,9 @@ package cn.iocoder.yudao.module.im.service.inbox;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.ImInboxPageReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.ImInboxSaveReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.inbox.ImInboxDO;
+import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxSaveReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxSaveMessageReqVO;
+import cn.iocoder.yudao.module.im.dal.dataobject.inbox.InboxDO;
 import jakarta.validation.Valid;
 
 // TODO @hao:不用的方法,删除下哈;
@@ -20,14 +21,14 @@ public interface ImInboxService {
      * @param createReqVO 创建信息
      * @return 编号
      */
-    Long createInbox(@Valid ImInboxSaveReqVO createReqVO);
+    Long createInbox(@Valid InboxSaveReqVO createReqVO);
 
     /**
      * 更新收件箱
      *
      * @param updateReqVO 更新信息
      */
-    void updateInbox(@Valid ImInboxSaveReqVO updateReqVO);
+    void updateInbox(@Valid InboxSaveReqVO updateReqVO);
 
     /**
      * 删除收件箱
@@ -42,7 +43,7 @@ public interface ImInboxService {
      * @param id 编号
      * @return 收件箱
      */
-    ImInboxDO getInbox(Long id);
+    InboxDO getInbox(Long id);
 
     /**
      * 获得收件箱分页
@@ -50,6 +51,11 @@ public interface ImInboxService {
      * @param pageReqVO 分页查询
      * @return 收件箱分页
      */
-    PageResult<ImInboxDO> getInboxPage(ImInboxPageReqVO pageReqVO);
+    PageResult<InboxDO> getInboxPage(ImInboxPageReqVO pageReqVO);
 
+    /**
+     * 保存收件箱和发送消息
+     * @param inboxSaveMessageReqVO 收件箱保存消息 Request VO
+     */
+    void saveInboxAndSendMessage(InboxSaveMessageReqVO inboxSaveMessageReqVO);
 }

+ 63 - 8
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/inbox/ImInboxServiceImpl.java

@@ -1,15 +1,27 @@
 package cn.iocoder.yudao.module.im.service.inbox;
 
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
 import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.ImInboxPageReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.ImInboxSaveReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.inbox.ImInboxDO;
+import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxSaveMessageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxSaveReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxSendMessageReqVO;
+import cn.iocoder.yudao.module.im.dal.dataobject.group.GroupMemberDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.inbox.InboxDO;
 import cn.iocoder.yudao.module.im.dal.mysql.inbox.ImInboxMapper;
+import cn.iocoder.yudao.module.im.dal.redis.inbox.InboxLockRedisDAO;
+import cn.iocoder.yudao.module.im.dal.redis.inbox.SequenceRedisDao;
+import cn.iocoder.yudao.module.im.enums.conversation.ConversationTypeEnum;
+import cn.iocoder.yudao.module.im.service.groupmember.ImGroupMemberService;
 import jakarta.annotation.Resource;
+import org.dromara.hutool.core.date.DateUnit;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import java.util.List;
+
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.INBOX_NOT_EXISTS;
 
@@ -22,22 +34,33 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.INBOX_NOT_EXIS
 @Validated
 public class ImInboxServiceImpl implements ImInboxService {
 
+    private static final Long INBOX_LOCK_TIMEOUT = 120 * DateUnit.SECOND.getMillis();
+    private static final String IM_MESSAGE_RECEIVE = "im-message-receive";
+
     @Resource
     private ImInboxMapper imInboxMapper;
+    @Resource
+    private SequenceRedisDao sequenceRedisDao; // 序列生成器Redis DAO
+    @Resource
+    private InboxLockRedisDAO inboxLockRedisDAO; // 收件箱的锁 Redis DAO
+    @Resource
+    private WebSocketMessageSender webSocketMessageSender; // WebSocket消息发送器
+    @Resource
+    private ImGroupMemberService imGroupMemberService;
 
     @Override
-    public Long createInbox(ImInboxSaveReqVO createReqVO) {
-        ImInboxDO inbox = BeanUtils.toBean(createReqVO, ImInboxDO.class);
+    public Long createInbox(InboxSaveReqVO createReqVO) {
+        InboxDO inbox = BeanUtils.toBean(createReqVO, InboxDO.class);
         imInboxMapper.insert(inbox);
         return inbox.getId();
     }
 
     @Override
-    public void updateInbox(ImInboxSaveReqVO updateReqVO) {
+    public void updateInbox(InboxSaveReqVO updateReqVO) {
         // 校验存在
         validateInboxExists(updateReqVO.getId());
         // 更新
-        ImInboxDO updateObj = BeanUtils.toBean(updateReqVO, ImInboxDO.class);
+        InboxDO updateObj = BeanUtils.toBean(updateReqVO, InboxDO.class);
         imInboxMapper.updateById(updateObj);
     }
 
@@ -56,13 +79,45 @@ public class ImInboxServiceImpl implements ImInboxService {
     }
 
     @Override
-    public ImInboxDO getInbox(Long id) {
+    public InboxDO getInbox(Long id) {
         return imInboxMapper.selectById(id);
     }
 
     @Override
-    public PageResult<ImInboxDO> getInboxPage(ImInboxPageReqVO pageReqVO) {
+    public PageResult<InboxDO> getInboxPage(ImInboxPageReqVO pageReqVO) {
         return imInboxMapper.selectPage(pageReqVO);
     }
 
+    @Override
+    public void saveInboxAndSendMessage(InboxSaveMessageReqVO inboxSaveMessage) {
+        // 保存收件箱 + 发送消息给用户
+        saveInboxAndSendMessageForUser(inboxSaveMessage.getFromId(), inboxSaveMessage);
+
+        if (inboxSaveMessage.getConversationType().equals(ConversationTypeEnum.SINGLE.getType())) {
+            saveInboxAndSendMessageForUser(inboxSaveMessage.getReceiverId(), inboxSaveMessage);
+        } else if (inboxSaveMessage.getConversationType().equals(ConversationTypeEnum.GROUP.getType())) {
+            List<GroupMemberDO> groupMembers = imGroupMemberService.selectByGroupId(inboxSaveMessage.getReceiverId());
+            groupMembers.forEach(groupMemberDO -> saveInboxAndSendMessageForUser(groupMemberDO.getUserId(), inboxSaveMessage));
+        }
+    }
+
+    private void saveInboxAndSendMessageForUser(Long userId, InboxSaveMessageReqVO inboxSaveMessage) {
+        inboxLockRedisDAO.lock(userId, INBOX_LOCK_TIMEOUT, () -> {
+            Long userSequence = sequenceRedisDao.generateSequence(userId);
+            InboxDO inbox = new InboxDO();
+            inbox.setUserId(userId);
+            inbox.setMessageId(inboxSaveMessage.getMessageId());
+            inbox.setSequence(userSequence);
+            imInboxMapper.insert(inbox);
+
+            //是发送人不发送
+            if (userId.equals(inboxSaveMessage.getFromId())) {
+                return;
+            }
+            InboxSendMessageReqVO message = BeanUtils.toBean(inboxSaveMessage, InboxSendMessageReqVO.class);
+            message.setSequence(userSequence);
+            webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), userId, IM_MESSAGE_RECEIVE, message);
+        });
+    }
+
 }

+ 0 - 103
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImMessageService.java

@@ -1,103 +0,0 @@
-package cn.iocoder.yudao.module.im.service.message;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessagePageReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessageSaveReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
-import cn.iocoder.yudao.module.im.websocket.message.ImSendMessage;
-import jakarta.validation.Valid;
-
-import java.util.List;
-
-/**
- * 消息 Service 接口
- *
- * @author 芋道源码
- */
-public interface ImMessageService {
-
-    /**
-     * 创建消息
-     *
-     * @param createReqVO 创建信息
-     * @return 编号
-     */
-    Long createMessage(@Valid ImMessageSaveReqVO createReqVO);
-
-    /**
-     * 更新消息
-     *
-     * @param updateReqVO 更新信息
-     */
-    void updateMessage(@Valid ImMessageSaveReqVO updateReqVO);
-
-    /**
-     * 删除消息
-     *
-     * @param id 编号
-     */
-    void deleteMessage(Long id);
-
-    /**
-     * 获得消息
-     *
-     * @param id 编号
-     * @return 消息
-     */
-    ImMessageDO getMessage(Long id);
-
-    /**
-     * 获得消息分页
-     *
-     * @param pageReqVO 分页查询
-     * @return 消息分页
-     */
-    PageResult<ImMessageDO> getMessagePage(ImMessagePageReqVO pageReqVO);
-
-
-    /**
-     * 保存私聊消息
-     *
-     * @param imSendMessage 消息信息
-     * @param fromUserId    发送人编号
-     * @return id
-     */
-    ImMessageDO savePrivateMessage(ImSendMessage imSendMessage, Long fromUserId);
-
-    /**
-     * 更新消息状态
-     *
-     * @param messageId     消息id
-     * @param messageStatus 消息状态
-     */
-    void updateMessageStatus(Long messageId, Integer messageStatus);
-
-    /**
-     * 保存群聊消息
-     *
-     * @param message    消息
-     * @param fromUserId 发送者用户ID
-     * @return id
-     */
-    ImMessageDO saveGroupMessage(ImSendMessage message, Long fromUserId);
-
-    /**
-     * 拉取消息-大于 seq 的消息
-     *
-     * @param userId   用户id
-     * @param sequence 序列号
-     * @param size 数量
-     * @return 消息列表
-     */
-    List<ImMessageDO> loadMessage(Long userId, Long sequence, Integer size);
-
-    // TODO @hao;这种接口,项目里叫 getMessageListByUserId,尽量保持 getList 获取列表哈;
-    /**
-     * 拉取全部消息
-     *
-     * @param userId   登录用户编号
-     * @param size 数量
-     * @return 消息列表
-     */
-    List<ImMessageDO> loadAllMessage(Long userId, Integer size);
-}

+ 0 - 143
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImMessageServiceImpl.java

@@ -1,143 +0,0 @@
-package cn.iocoder.yudao.module.im.service.message;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessagePageReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessageSaveReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
-import cn.iocoder.yudao.module.im.dal.mysql.message.ImMessageMapper;
-import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum;
-import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
-import cn.iocoder.yudao.module.im.websocket.message.ImSendMessage;
-import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
-import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
-import jakarta.annotation.Resource;
-import org.dromara.hutool.core.date.TimeUtil;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import java.util.List;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_NOT_EXISTS;
-
-/**
- * 消息 Service 实现类
- *
- * @author 芋道源码
- */
-@Service
-@Validated
-public class ImMessageServiceImpl implements ImMessageService {
-
-    @Resource
-    private ImMessageMapper imMessageMapper;
-    @Resource
-    private AdminUserApi adminUserApi;
-
-    @Override
-    public Long createMessage(ImMessageSaveReqVO createReqVO) {
-        // 插入
-        ImMessageDO message = BeanUtils.toBean(createReqVO, ImMessageDO.class);
-        imMessageMapper.insert(message);
-        // 返回
-        return message.getId();
-    }
-
-    @Override
-    public void updateMessage(ImMessageSaveReqVO updateReqVO) {
-        // 校验存在
-        validateMessageExists(updateReqVO.getId());
-        // 更新
-        ImMessageDO updateObj = BeanUtils.toBean(updateReqVO, ImMessageDO.class);
-        imMessageMapper.updateById(updateObj);
-    }
-
-    @Override
-    public void deleteMessage(Long id) {
-        // 校验存在
-        validateMessageExists(id);
-        // 删除
-        imMessageMapper.deleteById(id);
-    }
-
-    private void validateMessageExists(Long id) {
-        if (imMessageMapper.selectById(id) == null) {
-            throw exception(MESSAGE_NOT_EXISTS);
-        }
-    }
-
-    @Override
-    public ImMessageDO getMessage(Long id) {
-        return imMessageMapper.selectById(id);
-    }
-
-    @Override
-    public PageResult<ImMessageDO> getMessagePage(ImMessagePageReqVO pageReqVO) {
-        return imMessageMapper.selectPage(pageReqVO);
-    }
-
-
-    @Override
-    public ImMessageDO savePrivateMessage(ImSendMessage message, Long fromUserId) {
-        return saveImMessageDO(message, fromUserId);
-    }
-
-    private ImMessageDO saveImMessageDO(ImSendMessage message, Long fromUserId) {
-        // TODO @hao:可以搞成 messageDO
-        // TODO @hao:链式调用
-        ImMessageDO imMessageDO = new ImMessageDO();
-        imMessageDO.setClientMessageId(message.getClientMessageId());
-        imMessageDO.setSenderId(fromUserId);
-        imMessageDO.setReceiverId(message.getReceiverId());
-        // 查询发送人昵称和发送人头像
-        AdminUserRespDTO user = adminUserApi.getUser(fromUserId);
-        imMessageDO.setSenderNickname(user.getNickname());
-        imMessageDO.setSenderAvatar(user.getAvatar());
-        imMessageDO.setConversationType(message.getConversationType());
-        imMessageDO.setContentType(message.getContentType());
-        // 单聊:s_{userId}_{targetId} 群聊:群聊:g_{groupId}
-        // TODO @hao:这里的 conversationNo 的生成,可以写到工具类里,或者干脆放到 ImConversationTypeEnum 里;
-        if (message.getConversationType().equals(ImConversationTypeEnum.PRIVATE.getType())) {
-            imMessageDO.setConversationNo("s_" + fromUserId + "_" + message.getReceiverId());
-        } else if (message.getConversationType().equals(ImConversationTypeEnum.GROUP.getType())) {
-            imMessageDO.setConversationNo("g_" + message.getReceiverId());
-        }
-        imMessageDO.setContent(message.getContent());
-        // TODO @hao:枚举哈;
-        // 消息来源 100-用户发送;200-系统发送(一般是通知);不能为空
-        imMessageDO.setSendFrom(100);
-        imMessageDO.setSendTime(TimeUtil.now());
-        // TODO @hao:服务端的消息,就是默认生成哈;
-        imMessageDO.setMessageStatus(ImMessageStatusEnum.SENDING.getStatus());
-        imMessageMapper.insert(imMessageDO);
-        return imMessageDO;
-    }
-
-    @Override
-    public void updateMessageStatus(Long messageId, Integer messageStatus) {
-        // 校验 id 是否存在
-        validateMessageExists(messageId);
-        //更新消息状态
-        ImMessageDO imMessageDO = new ImMessageDO();
-        imMessageDO.setId(messageId);
-        imMessageDO.setMessageStatus(messageStatus);
-        imMessageMapper.updateById(imMessageDO);
-    }
-
-    @Override
-    public ImMessageDO saveGroupMessage(ImSendMessage message, Long fromUserId) {
-        return saveImMessageDO(message, fromUserId);
-    }
-
-    @Override
-    public List<ImMessageDO> loadMessage(Long userId, Long sequence, Integer size) {
-        return imMessageMapper.getGreaterThanSequenceMessage(userId, sequence, size);
-    }
-
-    @Override
-    public List<ImMessageDO> loadAllMessage(Long userId, Integer size) {
-        return imMessageMapper.getAllMessage(userId, size);
-    }
-
-}

+ 43 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/MessageService.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.im.service.message;
+
+import cn.iocoder.yudao.module.im.controller.admin.message.vo.MessagePageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.message.vo.SendMessageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.message.vo.SendMessageRespVO;
+import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
+
+import java.util.List;
+
+/**
+ * 消息 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface MessageService {
+
+    /**
+     * 获得消息分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 消息分页
+     */
+    List<MessageDO> getMessagePage(MessagePageReqVO pageReqVO);
+
+
+    /**
+     * 拉取消息-大于 seq 的消息
+     *
+     * @param userId   用户id
+     * @param sequence 序列号
+     * @param size 数量
+     * @return 消息列表
+     */
+    List<MessageDO> getMessageListBySequence(Long userId, Long sequence, Integer size);
+
+    /**
+     * 发送消息
+     * @param loginUserId 登录用户编号
+     * @param message 消息
+     * @return 消息编号
+     */
+    SendMessageRespVO sendMessage(Long loginUserId, SendMessageReqVO message);
+}

+ 122 - 0
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/MessageServiceImpl.java

@@ -0,0 +1,122 @@
+package cn.iocoder.yudao.module.im.service.message;
+
+import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxSaveMessageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.message.vo.MessagePageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.message.vo.SendMessageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.message.vo.SendMessageRespVO;
+import cn.iocoder.yudao.module.im.dal.dataobject.group.GroupMemberDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
+import cn.iocoder.yudao.module.im.dal.mysql.message.MessageMapper;
+import cn.iocoder.yudao.module.im.enums.conversation.ConversationTypeEnum;
+import cn.iocoder.yudao.module.im.enums.message.MessageSourceEnum;
+import cn.iocoder.yudao.module.im.enums.message.MessageStatusEnum;
+import cn.iocoder.yudao.module.im.service.groupmember.ImGroupMemberService;
+import cn.iocoder.yudao.module.im.service.inbox.ImInboxService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.hutool.core.date.TimeUtil;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_RECEIVER_NOT_EXISTS;
+
+/**
+ * 消息 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+@Slf4j
+public class MessageServiceImpl implements MessageService {
+
+    @Resource
+    private MessageMapper messageMapper;
+    @Resource
+    private ImInboxService imInboxService; // IM收件箱服务
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private ImGroupMemberService imGroupMemberService;
+
+    @Override
+    public List<MessageDO> getMessagePage(MessagePageReqVO pageReqVO) {
+        return messageMapper.getMessagePage(pageReqVO);
+    }
+
+    @Override
+    public List<MessageDO> getMessageListBySequence(Long userId, Long sequence, Integer size) {
+        return messageMapper.getGreaterThanSequenceMessage(userId, sequence, size);
+    }
+
+    @Override
+    public SendMessageRespVO sendMessage(Long fromUserId, SendMessageReqVO message) {
+        // 保存消息
+        InboxSaveMessageReqVO inboxSaveMessageReqVO = new InboxSaveMessageReqVO();
+        SendMessageRespVO sendMessageRespVO = saveMessage(fromUserId, message, inboxSaveMessageReqVO);
+
+        // 保存收件箱 + 发送消息给用户
+        imInboxService.saveInboxAndSendMessage(inboxSaveMessageReqVO);
+        return sendMessageRespVO;
+    }
+
+    public SendMessageRespVO saveMessage(Long fromUserId, SendMessageReqVO message, InboxSaveMessageReqVO inboxSaveMessageReqVO) {
+        //需要校验 receiverId 存在
+        validateReceiverIdExists(message);
+        // 查询发送人昵称和发送人头像
+        AdminUserRespDTO fromUser = adminUserApi.getUser(fromUserId);
+        // 使用链式调用创建 MessageDO 对象
+        MessageDO messageDO = new MessageDO()
+                .setClientMessageId(message.getClientMessageId())
+                .setSenderId(fromUserId)
+                .setReceiverId(message.getReceiverId())
+                .setSenderNickname(fromUser.getNickname())
+                .setSenderAvatar(fromUser.getAvatar())
+                .setConversationType(message.getConversationType())
+                .setContentType(message.getContentType())
+                .setConversationNo(ConversationTypeEnum.generateConversationNo(fromUserId, message.getReceiverId(), message.getConversationType()))
+                .setContent(message.getContent())
+                .setSendFrom(MessageSourceEnum.USER_SEND.getStatus())
+                .setSendTime(TimeUtil.now())
+                .setMessageStatus(MessageStatusEnum.SENDING.getStatus());
+        messageMapper.insert(messageDO);
+
+        // 设置 InboxSaveMessageReqVO 对象
+        inboxSaveMessageReqVO.setConversationType(message.getConversationType());
+        inboxSaveMessageReqVO.setFromId(fromUserId);
+        inboxSaveMessageReqVO.setReceiverId(message.getReceiverId());
+        inboxSaveMessageReqVO.setMessageId(messageDO.getId());
+        inboxSaveMessageReqVO.setContentType(message.getContentType());
+        inboxSaveMessageReqVO.setContent(message.getContent());
+        inboxSaveMessageReqVO.setSendTime(messageDO.getSendTime());
+
+        // 返回 SendMessageRespVO 对象
+        return new SendMessageRespVO(messageDO.getId(), messageDO.getSendTime());
+    }
+
+    private void validateReceiverIdExists(SendMessageReqVO message) {
+        if (message.getReceiverId() == null) {
+            throw exception(MESSAGE_RECEIVER_NOT_EXISTS);
+        }
+        if (message.getConversationType().equals(ConversationTypeEnum.SINGLE.getType())) {
+            //校验用户是否存在;
+            AdminUserRespDTO receiverUser = adminUserApi.getUser(message.getReceiverId());
+            if (receiverUser == null) {
+                throw exception(MESSAGE_RECEIVER_NOT_EXISTS);
+            }
+
+        } else if (message.getConversationType().equals(ConversationTypeEnum.GROUP.getType())) {
+            //校验群聊是否存在;
+            List<GroupMemberDO> groupMemberDOS = imGroupMemberService.selectByGroupId(message.getReceiverId());
+            if (groupMemberDOS.isEmpty()) {
+                throw exception(MESSAGE_RECEIVER_NOT_EXISTS);
+            }
+        }
+    }
+
+}

+ 0 - 184
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/ImWebSocketMessageListener.java

@@ -1,184 +0,0 @@
-package cn.iocoder.yudao.module.im.websocket;
-
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.websocket.core.listener.WebSocketMessageListener;
-import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
-import cn.iocoder.yudao.framework.websocket.core.util.WebSocketFrameworkUtils;
-import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.ImInboxSaveReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.groupmember.GroupMemberDO;
-import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
-import cn.iocoder.yudao.module.im.dal.redis.inbox.SequenceGeneratorRedisDao;
-import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum;
-import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
-import cn.iocoder.yudao.module.im.service.conversation.ImConversationService;
-import cn.iocoder.yudao.module.im.service.groupmember.ImGroupMemberService;
-import cn.iocoder.yudao.module.im.service.inbox.ImInboxService;
-import cn.iocoder.yudao.module.im.service.message.ImMessageService;
-import cn.iocoder.yudao.module.im.websocket.message.ImReceiveMessage;
-import cn.iocoder.yudao.module.im.websocket.message.ImSendMessage;
-import jakarta.annotation.Resource;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-import org.springframework.web.socket.WebSocketSession;
-
-import java.util.List;
-
-// TODO @hao:消息发送,使用 http 上行。因为在 cloud 框架下,我们比较难去 Listener。因为 im-server 不会自己启动 websocket 路径
-/**
- * WebSocket im
- *
- * @author 芋道源码
- */
-@Component
-@Slf4j
-public class ImWebSocketMessageListener implements WebSocketMessageListener<ImSendMessage> {
-
-    public static final String IM_MESSAGE_RECEIVE = "im-message-receive";
-    @Resource
-    private WebSocketMessageSender webSocketMessageSender; // WebSocket消息发送器
-    @Resource
-    private ImMessageService imMessageService; // IM消息服务
-    @Resource
-    private ImConversationService imConversationService; // IM会话服务
-    @Resource
-    private ImInboxService imInboxService; // IM收件箱服务
-    @Resource
-    private ImGroupMemberService imGroupMemberService; // 群成员服务
-    @Resource
-    private SequenceGeneratorRedisDao sequenceGeneratorRedisDao; // 序列生成器Redis DAO
-
-    /**
-     * 处理WebSocket消息
-     *
-     * @param session WebSocket会话
-     * @param message 发送的IM消息
-     */
-    @Override
-    public void onMessage(WebSocketSession session, ImSendMessage message) {
-        Long fromUserId = WebSocketFrameworkUtils.getLoginUserId(session); // 获取登录用户ID
-
-        // 如果是私人消息,处理私人消息
-        if (message.getConversationType().equals(ImConversationTypeEnum.PRIVATE.getType())) {
-            handlePrivateMessage(fromUserId, message);
-        } else if (message.getConversationType().equals(ImConversationTypeEnum.GROUP.getType())) {
-            // 处理群聊消息
-            handleGroupMessage(fromUserId, message);
-        } else {
-            // TODO @hao:是不是应该会话类型哈?然后打印这种错误日志,最好把 message 整个打进去,这样更好定位完整的消息;
-            log.error("[onMessage][消息类型({}) 未支持]", message.getConversationType());
-        }
-    }
-
-    /**
-     * 处理群聊消息
-     *
-     * @param fromUserId 发送者用户ID
-     * @param message    发送的IM消息
-     */
-    private void handleGroupMessage(Long fromUserId, ImSendMessage message) {
-        // TODO @hao:群存在;
-        // TODO @hao:发送人在群里;
-        ImMessageDO imMessageDO = imMessageService.saveGroupMessage(message, fromUserId); // 保存群聊消息
-        Long groupId = message.getReceiverId();
-
-        // 发送消息给群聊成员
-        // TODO @hao:变量名可以叫 groupMembers;一般不用 DO 后缀,除非为了区分;
-        List<GroupMemberDO> groupMemberDOList = imGroupMemberService.selectByGroupId(groupId);
-        groupMemberDOList.forEach(groupMemberDO -> {
-            // 过滤掉自己
-            // TODO @hao:98 到 99 的代码,是不是可以融合到这里来;
-            if (groupMemberDO.getUserId().equals(fromUserId)) {
-                return;
-            }
-            Long receiverSequence = sequenceGeneratorRedisDao.generateSequence(groupMemberDO.getUserId()); // 生成接收者序列
-            Long receiverInboxId = createAndSaveInbox(groupMemberDO.getUserId(), imMessageDO.getId(), receiverSequence); // 创建并保存接收者收件箱
-            sendMessage(groupMemberDO.getUserId(), receiverInboxId, imMessageDO, message, receiverSequence);
-        });
-
-        Long fromUserSequence = sequenceGeneratorRedisDao.generateSequence(fromUserId); // 生成发送者序列
-        Long fromUserInboxId = createAndSaveInbox(fromUserId, imMessageDO.getId(), fromUserSequence); // 创建并保存发送者收件箱
-
-        // 发送消息给发送者
-        sendMessage(fromUserId, fromUserInboxId, imMessageDO, message, fromUserSequence);
-
-        // 更新消息状态为成功
-        imMessageService.updateMessageStatus(imMessageDO.getId(), ImMessageStatusEnum.SUCCESS.getStatus());
-    }
-
-    /**
-     * 处理私人消息
-     *
-     * @param fromUserId 发送者用户ID
-     * @param message    发送的IM消息
-     */
-    private void handlePrivateMessage(Long fromUserId, ImSendMessage message) {
-        // TODO @hao:需要校验 receiverId 存在;
-        ImMessageDO imMessageDO = imMessageService.savePrivateMessage(message, fromUserId); // 保存私人消息
-        Long receiverId = message.getReceiverId();
-
-        // TODO @hao:sequenceGeneratorRedisDao.generateSequence 和 createAndSaveInbox 步骤,要加锁;不然会有并发问题;如果有疑问,可以微信招我沟通哈;另外,创建 seq、保存收件箱、发送,可以统一封装到 imInboxService 里哈;这样,后续要发某个消息,就是丢个 message 对象进去;
-        Long fromUserSequence = sequenceGeneratorRedisDao.generateSequence(fromUserId); // 生成发送者序列
-        Long fromUserInboxId = createAndSaveInbox(fromUserId, imMessageDO.getId(), fromUserSequence); // 创建并保存发送者收件箱
-
-        Long receiverSequence = sequenceGeneratorRedisDao.generateSequence(message.getReceiverId()); // 生成接收者序列
-        Long receiverInboxId = createAndSaveInbox(message.getReceiverId(), imMessageDO.getId(), receiverSequence); // 创建并保存接收者收件箱
-
-        // 发送消息给接收者和发送者
-        sendMessage(fromUserId, fromUserInboxId, imMessageDO, message, fromUserSequence);
-        sendMessage(receiverId, receiverInboxId, imMessageDO, message, receiverSequence);
-
-        // 更新消息状态为成功
-        // TODO @hao:后端消息的发送,默认就是 success 哈,不要区分 status;status 更多是前端使用的;
-        imMessageService.updateMessageStatus(imMessageDO.getId(), ImMessageStatusEnum.SUCCESS.getStatus());
-        // 保存私人会话,只有在 client 操作会话(已读、置顶)时,才会延迟创建
-        //imConversationService.savePrivateConversation(fromUserId, receiverId);
-    }
-
-    /**
-     * 创建并保存收件箱
-     *
-     * @param userId    用户ID
-     * @param messageId 消息ID
-     * @param sequence  序列
-     * @return 收件箱ID
-     */
-    private Long createAndSaveInbox(Long userId, Long messageId, Long sequence) {
-        ImInboxSaveReqVO inboxSaveReqVO = new ImInboxSaveReqVO(userId, messageId, sequence); // 创建收件箱保存请求VO
-        return imInboxService.createInbox(inboxSaveReqVO); // 创建收件箱
-    }
-
-    // TODO @hao:这个使用 WebSocketSenderApi 发送哈;
-    /**
-     * 发送消息
-     *
-     * @param fromUserId  发送者用户ID
-     * @param inboxId     收件箱ID
-     * @param imMessageDO IM消息数据对象
-     * @param message     发送的IM消息
-     * @param sequence    序列
-     */
-    private void sendMessage(Long fromUserId, Long inboxId, ImMessageDO imMessageDO, ImSendMessage message, Long sequence) {
-        // TODO @hao:不要每个字段 set 占一行;想同类型的字段用一行,然后链式 set,会更简洁干净。
-        ImReceiveMessage receiveMessage = new ImReceiveMessage(); // 创建接收消息
-        receiveMessage.setFromId(fromUserId); // 设置发送者ID
-        receiveMessage.setConversationType(message.getConversationType()); // 设置会话类型
-        receiveMessage.setContentType(message.getContentType()); // 设置内容类型
-        receiveMessage.setContent(message.getContent()); // 设置内容
-        receiveMessage.setMessageId(imMessageDO.getId()); // 设置消息ID
-        receiveMessage.setInboxId(inboxId); // 设置收件箱ID
-        receiveMessage.setSendTime(imMessageDO.getSendTime()); // 设置发送时间
-        receiveMessage.setSequence(sequence); // 设置序列
-        webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), fromUserId, ImWebSocketMessageListener.IM_MESSAGE_RECEIVE, receiveMessage); // 发送消息
-    }
-
-    /**
-     * 获取类型
-     *
-     * @return 类型
-     */
-    @Override
-    public String getType() {
-        return "im-message-send";
-    }
-
-}

+ 0 - 26
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/message/ImSendMessage.java

@@ -1,26 +0,0 @@
-package cn.iocoder.yudao.module.im.websocket.message;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Schema(description = "管理后台 - IM 消息发送 send")
-@Data
-public class ImSendMessage {
-
-    @Schema(description = "客户端消息编号 uuid,用于排重", requiredMode = Schema.RequiredMode.REQUIRED, example = "3331")
-    private String clientMessageId;
-
-    @Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    // TODO @hao:用下 InEnum 校验。这样可以少写 15 的注释哈;类似 contentType 也是;
-    private Integer conversationType; // 对应 ImConversationTypeEnum 枚举
-
-    @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
-    private Long receiverId;  // 根据 conversationType 区分
-
-    @Schema(description = "内容类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer contentType; // 参见 ImMessageTypeEnum 枚举
-
-    @Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED)
-    private String content;
-
-}

+ 1 - 1
yudao-module-im/yudao-module-im-biz/src/main/resources/mapper/conversation/ImConversationMapper.xml → yudao-module-im/yudao-module-im-biz/src/main/resources/mapper/conversation/ConversationMapper.xml

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="cn.iocoder.yudao.module.im.dal.mysql.conversation.ImConversationMapper">
+<mapper namespace="cn.iocoder.yudao.module.im.dal.mysql.conversation.ConversationMapper">
 
     <!-- TODO @hao:xml 如果用不到,可以先删除哈 -->
     <!--

+ 1 - 1
yudao-module-im/yudao-module-im-biz/src/main/resources/mapper/message/ImMessageMapper.xml → yudao-module-im/yudao-module-im-biz/src/main/resources/mapper/message/MessageMapper.xml

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="cn.iocoder.yudao.module.im.dal.mysql.message.ImMessageMapper">
+<mapper namespace="cn.iocoder.yudao.module.im.dal.mysql.message.MessageMapper">
 
     <!--
         一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。

+ 0 - 148
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationServiceImplTest.java

@@ -1,148 +0,0 @@
-package cn.iocoder.yudao.module.im.service.conversation;
-
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import jakarta.annotation.Resource;
-
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-
-import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.*;
-import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationDO;
-import cn.iocoder.yudao.module.im.dal.mysql.conversation.ImConversationMapper;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-
-import org.springframework.context.annotation.Import;
-
-import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
-import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
-import static org.junit.jupiter.api.Assertions.*;
-
-// TODO @hao:单测可以先都删除,等后面搞了在弄
-/**
- * {@link ImConversationServiceImpl} 的单元测试类
- *
- * @author 芋道源码
- */
-@Import(ImConversationServiceImpl.class)
-public class ImConversationServiceImplTest extends BaseDbUnitTest {
-
-    @Resource
-    private ImConversationServiceImpl conversationService;
-
-    @Resource
-    private ImConversationMapper imConversationMapper;
-
-    @Test
-    public void testCreateConversation_success() {
-        // 准备参数
-        ImConversationSaveReqVO createReqVO = randomPojo(ImConversationSaveReqVO.class).setId(null);
-
-        // 调用
-        Long conversationId = conversationService.createConversation(createReqVO);
-        // 断言
-        assertNotNull(conversationId);
-        // 校验记录的属性是否正确
-        ImConversationDO conversation = imConversationMapper.selectById(conversationId);
-        assertPojoEquals(createReqVO, conversation, "id");
-    }
-
-    @Test
-    public void testUpdateConversation_success() {
-        // mock 数据
-        ImConversationDO dbConversation = randomPojo(ImConversationDO.class);
-        imConversationMapper.insert(dbConversation);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        ImConversationSaveReqVO updateReqVO = randomPojo(ImConversationSaveReqVO.class, o -> {
-            o.setId(dbConversation.getId()); // 设置更新的 ID
-        });
-
-        // 调用
-        conversationService.updateConversation(updateReqVO);
-        // 校验是否更新正确
-        ImConversationDO conversation = imConversationMapper.selectById(updateReqVO.getId()); // 获取最新的
-        assertPojoEquals(updateReqVO, conversation);
-    }
-
-    @Test
-    public void testUpdateConversation_notExists() {
-        // 准备参数
-        ImConversationSaveReqVO updateReqVO = randomPojo(ImConversationSaveReqVO.class);
-
-        // 调用, 并断言异常
-        assertServiceException(() -> conversationService.updateConversation(updateReqVO), CONVERSATION_NOT_EXISTS);
-    }
-
-    @Test
-    public void testDeleteConversation_success() {
-        // mock 数据
-        ImConversationDO dbConversation = randomPojo(ImConversationDO.class);
-        imConversationMapper.insert(dbConversation);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        Long id = dbConversation.getId();
-
-        // 调用
-        conversationService.deleteConversation(id);
-       // 校验数据不存在了
-       assertNull(imConversationMapper.selectById(id));
-    }
-
-    @Test
-    public void testDeleteConversation_notExists() {
-        // 准备参数
-        Long id = randomLongId();
-
-        // 调用, 并断言异常
-        assertServiceException(() -> conversationService.deleteConversation(id), CONVERSATION_NOT_EXISTS);
-    }
-
-    @Test
-    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
-    public void testGetConversationPage() {
-       // mock 数据
-       ImConversationDO dbConversation = randomPojo(ImConversationDO.class, o -> { // 等会查询到
-           o.setUserId(null);
-           o.setConversationType(null);
-           o.setTargetId(null);
-           o.setNo(null);
-           o.setPinned(null);
-           o.setLastReadTime(null);
-           o.setCreateTime(null);
-       });
-       imConversationMapper.insert(dbConversation);
-       // 测试 userId 不匹配
-       imConversationMapper.insert(cloneIgnoreId(dbConversation, o -> o.setUserId(null)));
-       // 测试 conversationType 不匹配
-       imConversationMapper.insert(cloneIgnoreId(dbConversation, o -> o.setConversationType(null)));
-       // 测试 targetId 不匹配
-       imConversationMapper.insert(cloneIgnoreId(dbConversation, o -> o.setTargetId(null)));
-       // 测试 no 不匹配
-       imConversationMapper.insert(cloneIgnoreId(dbConversation, o -> o.setNo(null)));
-       // 测试 pinned 不匹配
-       imConversationMapper.insert(cloneIgnoreId(dbConversation, o -> o.setPinned(null)));
-       // 测试 lastReadTime 不匹配
-       imConversationMapper.insert(cloneIgnoreId(dbConversation, o -> o.setLastReadTime(null)));
-       // 测试 createTime 不匹配
-       imConversationMapper.insert(cloneIgnoreId(dbConversation, o -> o.setCreateTime(null)));
-       // 准备参数
-       ImConversationPageReqVO reqVO = new ImConversationPageReqVO();
-       reqVO.setUserId(null);
-       reqVO.setConversationType(null);
-       reqVO.setTargetId(null);
-       reqVO.setNo(null);
-       reqVO.setPinned(null);
-       reqVO.setLastReadTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
-       reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
-
-       // 调用
-       PageResult<ImConversationDO> pageResult = conversationService.getConversationPage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbConversation, pageResult.getList().get(0));
-    }
-
-}

+ 0 - 147
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java

@@ -1,147 +0,0 @@
-package cn.iocoder.yudao.module.im.service.group;
-
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import jakarta.annotation.Resource;
-
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-
-import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
-import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
-import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-
-import org.springframework.context.annotation.Import;
-
-import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
-import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * {@link ImGroupServiceImpl} 的单元测试类
- *
- * @author 芋道源码
- */
-@Import(ImGroupServiceImpl.class)
-public class ImGroupServiceImplTest extends BaseDbUnitTest {
-
-    @Resource
-    private ImGroupServiceImpl groupService;
-
-    @Resource
-    private ImGroupMapper imGroupMapper;
-
-    @Test
-    public void testCreateGroup_success() {
-        // 准备参数
-        ImGroupSaveReqVO createReqVO = randomPojo(ImGroupSaveReqVO.class).setId(null);
-
-        // 调用
-        Long groupId = groupService.createGroup(createReqVO);
-        // 断言
-        assertNotNull(groupId);
-        // 校验记录的属性是否正确
-        ImGroupDO group = imGroupMapper.selectById(groupId);
-        assertPojoEquals(createReqVO, group, "id");
-    }
-
-    @Test
-    public void testUpdateGroup_success() {
-        // mock 数据
-        ImGroupDO dbGroup = randomPojo(ImGroupDO.class);
-        imGroupMapper.insert(dbGroup);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        ImGroupSaveReqVO updateReqVO = randomPojo(ImGroupSaveReqVO.class, o -> {
-            o.setId(dbGroup.getId()); // 设置更新的 ID
-        });
-
-        // 调用
-        groupService.updateGroup(updateReqVO);
-        // 校验是否更新正确
-        ImGroupDO group = imGroupMapper.selectById(updateReqVO.getId()); // 获取最新的
-        assertPojoEquals(updateReqVO, group);
-    }
-
-    @Test
-    public void testUpdateGroup_notExists() {
-        // 准备参数
-        ImGroupSaveReqVO updateReqVO = randomPojo(ImGroupSaveReqVO.class);
-
-        // 调用, 并断言异常
-        assertServiceException(() -> groupService.updateGroup(updateReqVO), GROUP_NOT_EXISTS);
-    }
-
-    @Test
-    public void testDeleteGroup_success() {
-        // mock 数据
-        ImGroupDO dbGroup = randomPojo(ImGroupDO.class);
-        imGroupMapper.insert(dbGroup);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        Long id = dbGroup.getId();
-
-        // 调用
-        groupService.deleteGroup(id);
-       // 校验数据不存在了
-       assertNull(imGroupMapper.selectById(id));
-    }
-
-    @Test
-    public void testDeleteGroup_notExists() {
-        // 准备参数
-        Long id = randomLongId();
-
-        // 调用, 并断言异常
-        assertServiceException(() -> groupService.deleteGroup(id), GROUP_NOT_EXISTS);
-    }
-
-    @Test
-    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
-    public void testGetGroupPage() {
-       // mock 数据
-       ImGroupDO dbGroup = randomPojo(ImGroupDO.class, o -> { // 等会查询到
-           o.setGroupName(null);
-           o.setOwnerId(null);
-           o.setHeadImage(null);
-           o.setHeadImageThumb(null);
-           o.setNotice(null);
-           o.setRemark(null);
-           o.setCreateTime(null);
-       });
-       imGroupMapper.insert(dbGroup);
-       // 测试 groupName 不匹配
-       imGroupMapper.insert(cloneIgnoreId(dbGroup, o -> o.setGroupName(null)));
-       // 测试 ownerId 不匹配
-       imGroupMapper.insert(cloneIgnoreId(dbGroup, o -> o.setOwnerId(null)));
-       // 测试 headImage 不匹配
-       imGroupMapper.insert(cloneIgnoreId(dbGroup, o -> o.setHeadImage(null)));
-       // 测试 headImageThumb 不匹配
-       imGroupMapper.insert(cloneIgnoreId(dbGroup, o -> o.setHeadImageThumb(null)));
-       // 测试 notice 不匹配
-       imGroupMapper.insert(cloneIgnoreId(dbGroup, o -> o.setNotice(null)));
-       // 测试 remark 不匹配
-       imGroupMapper.insert(cloneIgnoreId(dbGroup, o -> o.setRemark(null)));
-       // 测试 createTime 不匹配
-       imGroupMapper.insert(cloneIgnoreId(dbGroup, o -> o.setCreateTime(null)));
-       // 准备参数
-       ImGroupPageReqVO reqVO = new ImGroupPageReqVO();
-       reqVO.setGroupName(null);
-       reqVO.setOwnerId(null);
-       reqVO.setHeadImage(null);
-       reqVO.setHeadImageThumb(null);
-       reqVO.setNotice(null);
-       reqVO.setRemark(null);
-       reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
-
-       // 调用
-       PageResult<ImGroupDO> pageResult = groupService.getGroupPage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbGroup, pageResult.getList().get(0));
-    }
-
-}

+ 0 - 147
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/groupmember/ImGroupMemberServiceImplTest.java

@@ -1,147 +0,0 @@
-package cn.iocoder.yudao.module.im.service.groupmember;
-
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import jakarta.annotation.Resource;
-
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-
-import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.*;
-import cn.iocoder.yudao.module.im.dal.dataobject.groupmember.GroupMemberDO;
-import cn.iocoder.yudao.module.im.dal.mysql.groupmember.ImGroupMemberMapper;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-
-import org.springframework.context.annotation.Import;
-
-import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
-import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * {@link ImGroupMemberServiceImpl} 的单元测试类
- *
- * @author 芋道源码
- */
-@Import(ImGroupMemberServiceImpl.class)
-public class ImGroupMemberServiceImplTest extends BaseDbUnitTest {
-
-    @Resource
-    private ImGroupMemberServiceImpl groupMemberService;
-
-    @Resource
-    private ImGroupMemberMapper imGroupMemberMapper;
-
-    @Test
-    public void testCreateGroupMember_success() {
-        // 准备参数
-        ImGroupMemberSaveReqVO createReqVO = randomPojo(ImGroupMemberSaveReqVO.class).setId(null);
-
-        // 调用
-        Long groupMemberId = groupMemberService.createGroupMember(createReqVO);
-        // 断言
-        assertNotNull(groupMemberId);
-        // 校验记录的属性是否正确
-        GroupMemberDO groupMember = imGroupMemberMapper.selectById(groupMemberId);
-        assertPojoEquals(createReqVO, groupMember, "id");
-    }
-
-    @Test
-    public void testUpdateGroupMember_success() {
-        // mock 数据
-        GroupMemberDO dbGroupMember = randomPojo(GroupMemberDO.class);
-        imGroupMemberMapper.insert(dbGroupMember);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        ImGroupMemberSaveReqVO updateReqVO = randomPojo(ImGroupMemberSaveReqVO.class, o -> {
-            o.setId(dbGroupMember.getId()); // 设置更新的 ID
-        });
-
-        // 调用
-        groupMemberService.updateGroupMember(updateReqVO);
-        // 校验是否更新正确
-        GroupMemberDO groupMember = imGroupMemberMapper.selectById(updateReqVO.getId()); // 获取最新的
-        assertPojoEquals(updateReqVO, groupMember);
-    }
-
-    @Test
-    public void testUpdateGroupMember_notExists() {
-        // 准备参数
-        ImGroupMemberSaveReqVO updateReqVO = randomPojo(ImGroupMemberSaveReqVO.class);
-
-        // 调用, 并断言异常
-        assertServiceException(() -> groupMemberService.updateGroupMember(updateReqVO), GROUP_MEMBER_NOT_EXISTS);
-    }
-
-    @Test
-    public void testDeleteGroupMember_success() {
-        // mock 数据
-        GroupMemberDO dbGroupMember = randomPojo(GroupMemberDO.class);
-        imGroupMemberMapper.insert(dbGroupMember);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        Long id = dbGroupMember.getId();
-
-        // 调用
-        groupMemberService.deleteGroupMember(id);
-       // 校验数据不存在了
-       assertNull(imGroupMemberMapper.selectById(id));
-    }
-
-    @Test
-    public void testDeleteGroupMember_notExists() {
-        // 准备参数
-        Long id = randomLongId();
-
-        // 调用, 并断言异常
-        assertServiceException(() -> groupMemberService.deleteGroupMember(id), GROUP_MEMBER_NOT_EXISTS);
-    }
-
-    @Test
-    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
-    public void testGetGroupMemberPage() {
-       // mock 数据
-       GroupMemberDO dbGroupMember = randomPojo(GroupMemberDO.class, o -> { // 等会查询到
-           o.setGroupId(null);
-           o.setUserId(null);
-           o.setNickname(null);
-           o.setAvatar(null);
-           o.setAliasName(null);
-           o.setRemark(null);
-           o.setCreateTime(null);
-       });
-       imGroupMemberMapper.insert(dbGroupMember);
-       // 测试 groupId 不匹配
-       imGroupMemberMapper.insert(cloneIgnoreId(dbGroupMember, o -> o.setGroupId(null)));
-       // 测试 userId 不匹配
-       imGroupMemberMapper.insert(cloneIgnoreId(dbGroupMember, o -> o.setUserId(null)));
-       // 测试 nickname 不匹配
-       imGroupMemberMapper.insert(cloneIgnoreId(dbGroupMember, o -> o.setNickname(null)));
-       // 测试 avatar 不匹配
-       imGroupMemberMapper.insert(cloneIgnoreId(dbGroupMember, o -> o.setAvatar(null)));
-       // 测试 aliasName 不匹配
-       imGroupMemberMapper.insert(cloneIgnoreId(dbGroupMember, o -> o.setAliasName(null)));
-       // 测试 remark 不匹配
-       imGroupMemberMapper.insert(cloneIgnoreId(dbGroupMember, o -> o.setRemark(null)));
-       // 测试 createTime 不匹配
-       imGroupMemberMapper.insert(cloneIgnoreId(dbGroupMember, o -> o.setCreateTime(null)));
-       // 准备参数
-       ImGroupMemberPageReqVO reqVO = new ImGroupMemberPageReqVO();
-       reqVO.setGroupId(null);
-       reqVO.setUserId(null);
-       reqVO.setNickname(null);
-       reqVO.setAvatar(null);
-       reqVO.setAliasName(null);
-       reqVO.setRemark(null);
-       reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
-
-       // 调用
-       PageResult<GroupMemberDO> pageResult = groupMemberService.getGroupMemberPage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbGroupMember, pageResult.getList().get(0));
-    }
-
-}

+ 0 - 135
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/inbox/ImInboxServiceImplTest.java

@@ -1,135 +0,0 @@
-package cn.iocoder.yudao.module.im.service.inbox;
-
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import jakarta.annotation.Resource;
-
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-
-import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.*;
-import cn.iocoder.yudao.module.im.dal.dataobject.inbox.ImInboxDO;
-import cn.iocoder.yudao.module.im.dal.mysql.inbox.ImInboxMapper;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-
-import org.springframework.context.annotation.Import;
-
-import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
-import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * {@link ImInboxServiceImpl} 的单元测试类
- *
- * @author 芋道源码
- */
-@Import(ImInboxServiceImpl.class)
-public class ImInboxServiceImplTest extends BaseDbUnitTest {
-
-    @Resource
-    private ImInboxServiceImpl inboxService;
-
-    @Resource
-    private ImInboxMapper imInboxMapper;
-
-    @Test
-    public void testCreateInbox_success() {
-        // 准备参数
-        ImInboxSaveReqVO createReqVO = randomPojo(ImInboxSaveReqVO.class).setId(null);
-
-        // 调用
-        Long inboxId = inboxService.createInbox(createReqVO);
-        // 断言
-        assertNotNull(inboxId);
-        // 校验记录的属性是否正确
-        ImInboxDO inbox = imInboxMapper.selectById(inboxId);
-        assertPojoEquals(createReqVO, inbox, "id");
-    }
-
-    @Test
-    public void testUpdateInbox_success() {
-        // mock 数据
-        ImInboxDO dbInbox = randomPojo(ImInboxDO.class);
-        imInboxMapper.insert(dbInbox);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        ImInboxSaveReqVO updateReqVO = randomPojo(ImInboxSaveReqVO.class, o -> {
-            o.setId(dbInbox.getId()); // 设置更新的 ID
-        });
-
-        // 调用
-        inboxService.updateInbox(updateReqVO);
-        // 校验是否更新正确
-        ImInboxDO inbox = imInboxMapper.selectById(updateReqVO.getId()); // 获取最新的
-        assertPojoEquals(updateReqVO, inbox);
-    }
-
-    @Test
-    public void testUpdateInbox_notExists() {
-        // 准备参数
-        ImInboxSaveReqVO updateReqVO = randomPojo(ImInboxSaveReqVO.class);
-
-        // 调用, 并断言异常
-        assertServiceException(() -> inboxService.updateInbox(updateReqVO), INBOX_NOT_EXISTS);
-    }
-
-    @Test
-    public void testDeleteInbox_success() {
-        // mock 数据
-        ImInboxDO dbInbox = randomPojo(ImInboxDO.class);
-        imInboxMapper.insert(dbInbox);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        Long id = dbInbox.getId();
-
-        // 调用
-        inboxService.deleteInbox(id);
-       // 校验数据不存在了
-       assertNull(imInboxMapper.selectById(id));
-    }
-
-    @Test
-    public void testDeleteInbox_notExists() {
-        // 准备参数
-        Long id = randomLongId();
-
-        // 调用, 并断言异常
-        assertServiceException(() -> inboxService.deleteInbox(id), INBOX_NOT_EXISTS);
-    }
-
-    @Test
-    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
-    public void testGetInboxPage() {
-       // mock 数据
-       ImInboxDO dbInbox = randomPojo(ImInboxDO.class, o -> { // 等会查询到
-           o.setUserId(null);
-           o.setMessageId(null);
-           o.setSequence(null);
-           o.setCreateTime(null);
-       });
-       imInboxMapper.insert(dbInbox);
-       // 测试 userId 不匹配
-       imInboxMapper.insert(cloneIgnoreId(dbInbox, o -> o.setUserId(null)));
-       // 测试 messageId 不匹配
-       imInboxMapper.insert(cloneIgnoreId(dbInbox, o -> o.setMessageId(null)));
-       // 测试 sequence 不匹配
-       imInboxMapper.insert(cloneIgnoreId(dbInbox, o -> o.setSequence(null)));
-       // 测试 createTime 不匹配
-       imInboxMapper.insert(cloneIgnoreId(dbInbox, o -> o.setCreateTime(null)));
-       // 准备参数
-       ImInboxPageReqVO reqVO = new ImInboxPageReqVO();
-       reqVO.setUserId(null);
-       reqVO.setMessageId(null);
-       reqVO.setSequence(null);
-       reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
-
-       // 调用
-       PageResult<ImInboxDO> pageResult = inboxService.getInboxPage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbInbox, pageResult.getList().get(0));
-    }
-
-}

+ 0 - 167
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImMessageServiceImplTest.java

@@ -1,167 +0,0 @@
-package cn.iocoder.yudao.module.im.service.message;
-
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import jakarta.annotation.Resource;
-
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-
-import cn.iocoder.yudao.module.im.controller.admin.message.vo.*;
-import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
-import cn.iocoder.yudao.module.im.dal.mysql.message.ImMessageMapper;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-
-import org.springframework.context.annotation.Import;
-
-import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
-import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * {@link ImMessageServiceImpl} 的单元测试类
- *
- * @author 芋道源码
- */
-@Import(ImMessageServiceImpl.class)
-public class ImMessageServiceImplTest extends BaseDbUnitTest {
-
-    @Resource
-    private ImMessageServiceImpl messageService;
-
-    @Resource
-    private ImMessageMapper imMessageMapper;
-
-    @Test
-    public void testCreateMessage_success() {
-        // 准备参数
-        ImMessageSaveReqVO createReqVO = randomPojo(ImMessageSaveReqVO.class).setId(null);
-
-        // 调用
-        Long messageId = messageService.createMessage(createReqVO);
-        // 断言
-        assertNotNull(messageId);
-        // 校验记录的属性是否正确
-        ImMessageDO message = imMessageMapper.selectById(messageId);
-        assertPojoEquals(createReqVO, message, "id");
-    }
-
-    @Test
-    public void testUpdateMessage_success() {
-        // mock 数据
-        ImMessageDO dbMessage = randomPojo(ImMessageDO.class);
-        imMessageMapper.insert(dbMessage);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        ImMessageSaveReqVO updateReqVO = randomPojo(ImMessageSaveReqVO.class, o -> {
-            o.setId(dbMessage.getId()); // 设置更新的 ID
-        });
-
-        // 调用
-        messageService.updateMessage(updateReqVO);
-        // 校验是否更新正确
-        ImMessageDO message = imMessageMapper.selectById(updateReqVO.getId()); // 获取最新的
-        assertPojoEquals(updateReqVO, message);
-    }
-
-    @Test
-    public void testUpdateMessage_notExists() {
-        // 准备参数
-        ImMessageSaveReqVO updateReqVO = randomPojo(ImMessageSaveReqVO.class);
-
-        // 调用, 并断言异常
-        assertServiceException(() -> messageService.updateMessage(updateReqVO), MESSAGE_NOT_EXISTS);
-    }
-
-    @Test
-    public void testDeleteMessage_success() {
-        // mock 数据
-        ImMessageDO dbMessage = randomPojo(ImMessageDO.class);
-        imMessageMapper.insert(dbMessage);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        Long id = dbMessage.getId();
-
-        // 调用
-        messageService.deleteMessage(id);
-       // 校验数据不存在了
-       assertNull(imMessageMapper.selectById(id));
-    }
-
-    @Test
-    public void testDeleteMessage_notExists() {
-        // 准备参数
-        Long id = randomLongId();
-
-        // 调用, 并断言异常
-        assertServiceException(() -> messageService.deleteMessage(id), MESSAGE_NOT_EXISTS);
-    }
-
-    @Test
-    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
-    public void testGetMessagePage() {
-       // mock 数据
-       ImMessageDO dbMessage = randomPojo(ImMessageDO.class, o -> { // 等会查询到
-           o.setClientMessageId(null);
-           o.setSenderId(null);
-           o.setReceiverId(null);
-           o.setSenderNickname(null);
-           o.setSenderAvatar(null);
-           o.setConversationType(null);
-           o.setConversationNo(null);
-           o.setContentType(null);
-           o.setContent(null);
-           o.setSendTime(null);
-           o.setSendFrom(null);
-           o.setCreateTime(null);
-       });
-       imMessageMapper.insert(dbMessage);
-       // 测试 clientMessageId 不匹配
-       imMessageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setClientMessageId(null)));
-       // 测试 senderId 不匹配
-       imMessageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setSenderId(null)));
-       // 测试 receiverId 不匹配
-       imMessageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setReceiverId(null)));
-       // 测试 senderNickname 不匹配
-       imMessageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setSenderNickname(null)));
-       // 测试 senderAvatar 不匹配
-       imMessageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setSenderAvatar(null)));
-       // 测试 conversationType 不匹配
-       imMessageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setConversationType(null)));
-       // 测试 conversationNo 不匹配
-       imMessageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setConversationNo(null)));
-       // 测试 contentType 不匹配
-       imMessageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setContentType(null)));
-       // 测试 content 不匹配
-       imMessageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setContent(null)));
-       // 测试 sendTime 不匹配
-       imMessageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setSendTime(null)));
-       // 测试 sendFrom 不匹配
-       imMessageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setSendFrom(null)));
-       // 测试 createTime 不匹配
-       imMessageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setCreateTime(null)));
-       // 准备参数
-       ImMessagePageReqVO reqVO = new ImMessagePageReqVO();
-       reqVO.setClientMessageId(null);
-       reqVO.setSenderId(null);
-       reqVO.setReceiverId(null);
-       reqVO.setSenderNickname(null);
-       reqVO.setSenderAvatar(null);
-       reqVO.setConversationType(null);
-       reqVO.setConversationNo(null);
-       reqVO.setContentType(null);
-       reqVO.setContent(null);
-       reqVO.setSendTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
-       reqVO.setSendFrom(null);
-       reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
-
-       // 调用
-       PageResult<ImMessageDO> pageResult = messageService.getMessagePage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbMessage, pageResult.getList().get(0));
-    }
-
-}