getMaxReadMessageId(@RequestParam("peerId") Long peerId) {
return success(privateMessageService.getMaxReadMessageId(getLoginUserId(), peerId));
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/channel/ImChannelMessagePullRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/channel/ImChannelMessagePullRespVO.java
index 41bbbf9..8a30fcb 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/channel/ImChannelMessagePullRespVO.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/channel/ImChannelMessagePullRespVO.java
@@ -24,8 +24,8 @@ public class ImChannelMessagePullRespVO {
@Schema(description = "消息内容;payload JSON 快照", requiredMode = Schema.RequiredMode.REQUIRED)
private String content;
- @Schema(description = "当前用户的已读态;按 Redis 游标计算填充", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
- private Integer status; // 参见 ImMessageStatusEnum 枚举类
+ @Schema(description = "当前用户回执 / 已读态;按 Redis 读位置计算(已读 DONE,未读 PENDING)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Integer receiptStatus; // 参见 ImMessageReceiptStatusEnum 枚举类
@Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime sendTime;
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageSendReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageSendReqVO.java
index f02d919..813e35a 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageSendReqVO.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageSendReqVO.java
@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.im.controller.admin.message.vo.group;
import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
+import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
@@ -28,7 +28,7 @@ public class ImGroupMessageSendReqVO {
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "消息类型不能为空")
- @InEnum(ImMessageTypeEnum.class)
+ @InEnum(ImContentTypeEnum.class)
private Integer type;
@Schema(description = "消息内容,JSON 格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "{\"content\":\"你好\"}")
@@ -47,7 +47,7 @@ public class ImGroupMessageSendReqVO {
@AssertTrue(message = "消息类型不允许")
@JsonIgnore
public boolean isTypeNormal() {
- return type == null || ImMessageTypeEnum.validate(type).isNormal();
+ return type == null || ImContentTypeEnum.validate(type).isNormal();
}
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageRespVO.java
index 3484b12..9b62689 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageRespVO.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageRespVO.java
@@ -31,7 +31,10 @@ public class ImPrivateMessageRespVO {
private String content;
@Schema(description = "消息状态", example = "0")
- private Integer status;
+ private Integer status; // 参见 ImMessageStatusEnum 枚举类
+
+ @Schema(description = "回执状态", example = "0")
+ private Integer receiptStatus; // 参见 ImMessageReceiptStatusEnum 枚举类
@Schema(description = "发送时间")
private LocalDateTime sendTime;
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageSendReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageSendReqVO.java
index d6c703f..6552234 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageSendReqVO.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageSendReqVO.java
@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.im.controller.admin.message.vo.privates;
import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
+import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
@@ -26,20 +26,23 @@ public class ImPrivateMessageSendReqVO {
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "消息类型不能为空")
- @InEnum(ImMessageTypeEnum.class)
+ @InEnum(ImContentTypeEnum.class)
private Integer type;
@Schema(description = "消息内容,JSON 格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "{\"content\":\"你好\"}")
@NotEmpty(message = "消息内容不能为空")
private String content;
+ @Schema(description = "是否需要回执", example = "false")
+ private Boolean receipt;
+
/**
* 仅允许用户消息(normal)类型
*/
@AssertTrue(message = "消息类型不允许")
@JsonIgnore
public boolean isTypeNormal() {
- return type == null || ImMessageTypeEnum.validate(type).isNormal();
+ return type == null || ImContentTypeEnum.validate(type).isNormal();
}
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/conversation/ImConversationReadDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/conversation/ImConversationReadDO.java
new file mode 100644
index 0000000..bdd9fa0
--- /dev/null
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/conversation/ImConversationReadDO.java
@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.im.dal.dataobject.conversation;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
+import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
+import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * IM 会话读位置 DO
+ *
+ * 只表达「用户在某个会话的最大已读位置」,私聊 / 群聊 / 频道统一落这张表,是读位置的唯一权威。
+ *
+ * @author 芋道源码
+ */
+@TableName("im_conversation_read")
+@KeySequence("im_conversation_read_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ImConversationReadDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 用户编号
+ *
+ * 关联 AdminUserDO 的 id 字段
+ */
+ private Long userId;
+ /**
+ * 会话类型
+ *
+ * 枚举 {@link ImConversationTypeEnum}
+ */
+ private Integer conversationType;
+ /**
+ * 目标编号
+ *
+ * 私聊关联 AdminUserDO 的 id;群聊关联 {@link ImGroupDO#getId()};频道关联 {@link ImChannelDO#getId()}
+ */
+ private Long targetId;
+ /**
+ * 最大已读消息编号
+ *
+ * 关联 {@link ImPrivateMessageDO#getId()}、{@link ImGroupMessageDO#getId()}、{@link ImChannelMessageDO#getId()}
+ */
+ private Long messageId;
+ /**
+ * 最近已读时间
+ */
+ private LocalDateTime readTime;
+
+}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImChannelMessageDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImChannelMessageDO.java
index 2572828..d71ee7c 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImChannelMessageDO.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImChannelMessageDO.java
@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO;
import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO;
-import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
+import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -54,7 +54,7 @@ public class ImChannelMessageDO extends BaseDO {
/**
* 消息类型
*
- * 枚举 {@link ImMessageTypeEnum}
+ * 枚举 {@link ImContentTypeEnum}
*/
private Integer type;
/**
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java
index f6c3d60..bdab8a8 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java
@@ -3,9 +3,9 @@ package cn.iocoder.yudao.module.im.dal.dataobject.message;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
-import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum;
+import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum;
import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
-import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
+import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -54,7 +54,7 @@ public class ImGroupMessageDO extends BaseDO {
/**
* 消息类型
*
- * 枚举 {@link ImMessageTypeEnum}
+ * 枚举 {@link ImContentTypeEnum}
*/
private Integer type;
/**
@@ -67,8 +67,6 @@ public class ImGroupMessageDO extends BaseDO {
* 消息状态
*
* 枚举 {@link ImMessageStatusEnum}
- *
- * 为什么没有 READ 状态?与单聊的差异:单聊用 UNREAD/READ 跟踪每条消息状态,群聊用 Redis 存储每个成员的已读位置(游标模型)
*/
private Integer status;
/**
@@ -94,7 +92,7 @@ public class ImGroupMessageDO extends BaseDO {
/**
* 回执状态
*
- * 枚举 {@link ImGroupMessageReceiptStatusEnum}
+ * 枚举 {@link ImMessageReceiptStatusEnum}
*/
private Integer receiptStatus;
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java
index bb87939..808c1ee 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java
@@ -1,8 +1,9 @@
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.message.ImMessageReceiptStatusEnum;
import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
-import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
+import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -49,7 +50,7 @@ public class ImPrivateMessageDO extends BaseDO {
/**
* 消息类型
*
- * 枚举 {@link ImMessageTypeEnum}
+ * 枚举 {@link ImContentTypeEnum}
*/
private Integer type;
/**
@@ -65,6 +66,12 @@ public class ImPrivateMessageDO extends BaseDO {
*/
private Integer status;
/**
+ * 回执状态
+ *
+ * 枚举 {@link ImMessageReceiptStatusEnum}
+ */
+ private Integer receiptStatus;
+ /**
* 发送时间
*/
private LocalDateTime sendTime;
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/AudioMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/AudioMessage.java
index dd37cc0..2422467 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/AudioMessage.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/AudioMessage.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.im.service.websocket.dto.message;
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
import lombok.Data;
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/CardMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/CardMessage.java
index 39bef39..d99ed12 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/CardMessage.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/CardMessage.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.im.service.websocket.dto.message;
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
import lombok.Data;
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FaceMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FaceMessage.java
index f5c6b6c..6d6a642 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FaceMessage.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FaceMessage.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.im.service.websocket.dto.message;
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
import lombok.Data;
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FileMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FileMessage.java
index 7feb9ff..bd4fcc3 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FileMessage.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FileMessage.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.im.service.websocket.dto.message;
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
import lombok.Data;
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/ImageMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/ImageMessage.java
index bc8e41e..052342a 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/ImageMessage.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/ImageMessage.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.im.service.websocket.dto.message;
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
import lombok.Data;
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/LocationMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/LocationMessage.java
index 8c36d1a..fdbb7c7 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/LocationMessage.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/LocationMessage.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.im.service.websocket.dto.message;
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
import lombok.Data;
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/MaterialMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MaterialMessage.java
index 9ef4308..41fbdce 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/MaterialMessage.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MaterialMessage.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.im.service.websocket.dto.message;
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -6,7 +6,7 @@ import lombok.experimental.Accessors;
/**
* 频道素材消息 payload
*
- * 对应 {@link cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum#MATERIAL}(type=125)。
+ * 对应 {@link cn.iocoder.yudao.module.im.enums.ImContentTypeEnum#MATERIAL}(type=125)。
* 客户端按本字段集渲染图文卡片;
* 富文本正文不在本 payload 中传递,点击详情时另调 /im/channel/material/get-content?id= 按需拉取。
*/
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/MergeMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MergeMessage.java
index 62a051d..9bc675b 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/MergeMessage.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MergeMessage.java
@@ -1,6 +1,6 @@
-package cn.iocoder.yudao.module.im.service.websocket.dto.message;
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
-import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
+import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
import lombok.Data;
import java.util.List;
@@ -47,7 +47,7 @@ public class MergeMessage {
/**
* 消息类型
*
- * 枚举 {@link ImMessageTypeEnum}
+ * 枚举 {@link ImContentTypeEnum}
*/
private Integer type;
/**
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/QuoteMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/QuoteMessage.java
index 20fa9f3..a06d417 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/QuoteMessage.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/QuoteMessage.java
@@ -1,6 +1,6 @@
-package cn.iocoder.yudao.module.im.service.websocket.dto.message;
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
-import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
+import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -39,7 +39,7 @@ public class QuoteMessage {
/**
* 被引用消息类型
*
- * 枚举 {@link ImMessageTypeEnum}
+ * 枚举 {@link ImContentTypeEnum}
*/
private Integer type;
/**
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/RecallMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/RecallMessage.java
index 5df78f2..a9ba575 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/RecallMessage.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/RecallMessage.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.im.service.websocket.dto.message;
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
import lombok.Data;
import lombok.experimental.Accessors;
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/TextMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/TextMessage.java
index 9d54b7d..4d8a66c 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/TextMessage.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/TextMessage.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.im.service.websocket.dto.message;
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
import lombok.Data;
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/VideoMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/VideoMessage.java
index a5ba53a..5bceef1 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/VideoMessage.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/VideoMessage.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.im.service.websocket.dto.message;
+package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
import lombok.Data;
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapper.java
new file mode 100644
index 0000000..45a702f
--- /dev/null
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapper.java
@@ -0,0 +1,83 @@
+package cn.iocoder.yudao.module.im.dal.mysql.conversation;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * IM 会话读位置 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ImConversationReadMapper extends BaseMapperX {
+
+ default ImConversationReadDO selectByUserIdAndConversation(Long userId, Integer conversationType, Long conversationId) {
+ return selectOne(new LambdaQueryWrapperX()
+ .eq(ImConversationReadDO::getUserId, userId)
+ .eq(ImConversationReadDO::getConversationType, conversationType)
+ .eq(ImConversationReadDO::getTargetId, conversationId));
+ }
+
+ default List selectListByConversation(Integer conversationType, Long conversationId) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(ImConversationReadDO::getConversationType, conversationType)
+ .eq(ImConversationReadDO::getTargetId, conversationId));
+ }
+
+ default List selectListByUserIdAndConversations(Long userId, Integer conversationType,
+ Collection conversationIds) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(ImConversationReadDO::getUserId, userId)
+ .eq(ImConversationReadDO::getConversationType, conversationType)
+ .in(ImConversationReadDO::getTargetId, conversationIds));
+ }
+
+ /**
+ * 增量拉取当前用户的会话读位置(按 update_time + id 正向游标)
+ *
+ * @param userId 当前用户编号
+ * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null
+ * @param lastId 上次拉取到的记录编号;首次拉取传 null
+ * @param limit 拉取数量
+ * @return 会话读位置列表
+ */
+ default List selectPullListByUserId(Long userId, Long lastUpdateTime, Long lastId, Integer limit) {
+ LambdaQueryWrapperX query = new LambdaQueryWrapperX()
+ .eq(ImConversationReadDO::getUserId, userId);
+ if (lastUpdateTime != null && lastId != null) {
+ LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime);
+ query.and(w -> w.gt(ImConversationReadDO::getUpdateTime, lastTime)
+ .or(n -> n.eq(ImConversationReadDO::getUpdateTime, lastTime).gt(ImConversationReadDO::getId, lastId)));
+ }
+ return selectList(query.orderByAsc(ImConversationReadDO::getUpdateTime).orderByAsc(ImConversationReadDO::getId)
+ .last("LIMIT " + limit));
+ }
+
+ /**
+ * 单调递增更新读位置:仅当新位置更大时才更新
+ *
+ * 通过 {@code read_message_id < messageId} 的 CAS 条件,保证乱序 / 并发上报时读位置不会回退。
+ *
+ * @param id 记录编号
+ * @param messageId 新的最大已读消息编号
+ * @param readTime 已读时间
+ * @return 影响行数;0 表示新位置不大于已有位置,未更新
+ */
+ default int updateReadMessageIdToLarger(Long id, Long messageId, LocalDateTime readTime) {
+ return update(null, Wrappers.lambdaUpdate()
+ .set(ImConversationReadDO::getMessageId, messageId)
+ .set(ImConversationReadDO::getReadTime, readTime)
+ .set(ImConversationReadDO::getUpdateTime, readTime)
+ .eq(ImConversationReadDO::getId, id)
+ .lt(ImConversationReadDO::getMessageId, messageId));
+ }
+
+}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java
index 83752a9..8673100 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.im.dal.mysql.friend;
+import cn.hutool.core.date.LocalDateTimeUtil;
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;
@@ -31,6 +32,28 @@ public interface ImFriendMapper extends BaseMapperX {
.eq(ImFriendDO::getUserId, userId)
.orderByDesc(ImFriendDO::getId));
}
+
+ /**
+ * 增量拉取当前用户的好友关系(含已删除,按 update_time + id 正向游标)
+ *
+ * @param userId 当前用户编号
+ * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null
+ * @param lastId 上次拉取到的记录编号;首次拉取传 null
+ * @param limit 拉取数量
+ * @return 好友关系列表
+ */
+ default List selectPullListByUserId(Long userId, Long lastUpdateTime, Long lastId, Integer limit) {
+ LambdaQueryWrapperX query = new LambdaQueryWrapperX()
+ .eq(ImFriendDO::getUserId, userId);
+ if (lastUpdateTime != null && lastId != null) {
+ LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime);
+ query.and(w -> w.gt(ImFriendDO::getUpdateTime, lastTime)
+ .or(n -> n.eq(ImFriendDO::getUpdateTime, lastTime).gt(ImFriendDO::getId, lastId)));
+ }
+ return selectList(query.orderByAsc(ImFriendDO::getUpdateTime).orderByAsc(ImFriendDO::getId)
+ .last("LIMIT " + limit));
+ }
+
default List selectListByUserIdAndStatus(Long userId, Integer status) {
return selectList(new LambdaQueryWrapperX()
.eq(ImFriendDO::getUserId, userId)
@@ -68,12 +91,14 @@ public interface ImFriendMapper extends BaseMapperX {
@SuppressWarnings("UnusedReturnValue")
default int updateReAddFields(Long id, Integer status, LocalDateTime addTime,
+ LocalDateTime updateTime,
Boolean silent, Boolean pinned, Boolean blocked,
String displayName, Integer addSource) {
return update(null, Wrappers.lambdaUpdate()
.eq(ImFriendDO::getId, id)
.set(ImFriendDO::getStatus, status)
.set(ImFriendDO::getAddTime, addTime)
+ .set(ImFriendDO::getUpdateTime, updateTime)
.set(ImFriendDO::getSilent, silent)
.set(ImFriendDO::getPinned, pinned)
.set(ImFriendDO::getBlocked, blocked)
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java
index e81def0..6dffaa5 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.im.dal.mysql.friend;
+import cn.hutool.core.date.LocalDateTimeUtil;
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;
@@ -28,6 +29,12 @@ public interface ImFriendRequestMapper extends BaseMapperX {
/**
* 拉取「我相关」的好友申请列表
+ *
+ * @param userId 当前用户编号
+ * @param maxRequestUpdateTime 起始更新时间;首次拉取传 null
+ * @param maxId 起始记录编号;首次拉取传 null
+ * @param limit 拉取数量
+ * @return 好友申请列表
*/
default List selectMyList(Long userId, LocalDateTime maxRequestUpdateTime,
Long maxId, int limit) {
@@ -45,6 +52,28 @@ public interface ImFriendRequestMapper extends BaseMapperX {
return selectList(wrapper);
}
+ /**
+ * 增量拉取「我相关」的好友申请(双向 OR,按 update_time + id 正向游标)
+ *
+ * @param userId 当前用户编号
+ * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null
+ * @param lastId 上次拉取到的记录编号;首次拉取传 null
+ * @param limit 拉取数量
+ * @return 好友申请列表
+ */
+ default List selectPullListByUserId(Long userId, Long lastUpdateTime, Long lastId, Integer limit) {
+ LambdaQueryWrapperX query = new LambdaQueryWrapperX<>();
+ query.and(w -> w.eq(ImFriendRequestDO::getFromUserId, userId)
+ .or().eq(ImFriendRequestDO::getToUserId, userId));
+ if (lastUpdateTime != null && lastId != null) {
+ LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime);
+ query.and(w -> w.gt(ImFriendRequestDO::getUpdateTime, lastTime)
+ .or(n -> n.eq(ImFriendRequestDO::getUpdateTime, lastTime).gt(ImFriendRequestDO::getId, lastId)));
+ }
+ return selectList(query.orderByAsc(ImFriendRequestDO::getUpdateTime).orderByAsc(ImFriendRequestDO::getId)
+ .last("LIMIT " + limit));
+ }
+
default int updateByIdAndHandleResult(Long id, Integer handleResult, ImFriendRequestDO updateObj) {
return update(updateObj, new LambdaUpdateWrapper()
.eq(ImFriendRequestDO::getId, id).eq(ImFriendRequestDO::getHandleResult, handleResult));
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java
index 2ef54da..2afa559 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java
@@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.im.dal.mysql.group;
import cn.hutool.core.collection.CollUtil;
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
@@ -59,20 +58,8 @@ public interface ImGroupMemberMapper extends BaseMapperX {
.eq(ImGroupMemberDO::getStatus, status));
}
- /**
- * 查询用户已退群的成员记录
- *
- * 当 {@code minQuitTime} 非空时额外按 {@code quitTime ≥ minQuitTime} 过滤。
- *
- * @param userId 用户编号
- * @param minQuitTime 最早退群时间(含),可空
- * @return 已退群成员记录列表
- */
- default List selectQuitListByUserId(Long userId, LocalDateTime minQuitTime) {
- return selectList(new LambdaQueryWrapperX()
- .eq(ImGroupMemberDO::getUserId, userId)
- .eq(ImGroupMemberDO::getStatus, CommonStatusEnum.DISABLE.getStatus())
- .geIfPresent(ImGroupMemberDO::getQuitTime, minQuitTime));
+ default List selectListByUserId(Long userId) {
+ return selectList(ImGroupMemberDO::getUserId, userId);
}
@SuppressWarnings("UnusedReturnValue")
@@ -143,7 +130,8 @@ public interface ImGroupMemberMapper extends BaseMapperX {
.set(ImGroupMemberDO::getAddSource, addSource)
.set(ImGroupMemberDO::getInviterUserId, inviterUserId)
.set(ImGroupMemberDO::getQuitTime, null)
- .set(ImGroupMemberDO::getMuteEndTime, null));
+ .set(ImGroupMemberDO::getMuteEndTime, null)
+ .set(ImGroupMemberDO::getUpdateTime, joinTime));
}
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java
index 72fe756..c344493 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.im.dal.mysql.group;
+import cn.hutool.core.date.LocalDateTimeUtil;
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;
@@ -43,6 +44,28 @@ public interface ImGroupRequestMapper extends BaseMapperX {
.orderByDesc(ImGroupRequestDO::getId));
}
+ /**
+ * 增量拉取「我管理的群」下的加群申请(含已处理,按 update_time + id 正向游标)
+ *
+ * @param groupIds 群编号集合
+ * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null
+ * @param lastId 上次拉取到的记录编号;首次拉取传 null
+ * @param limit 拉取数量
+ * @return 加群申请列表
+ */
+ default List selectPullListByGroupIds(Collection groupIds, Long lastUpdateTime,
+ Long lastId, Integer limit) {
+ LambdaQueryWrapperX query = new LambdaQueryWrapperX()
+ .in(ImGroupRequestDO::getGroupId, groupIds);
+ if (lastUpdateTime != null && lastId != null) {
+ LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime);
+ query.and(w -> w.gt(ImGroupRequestDO::getUpdateTime, lastTime)
+ .or(n -> n.eq(ImGroupRequestDO::getUpdateTime, lastTime).gt(ImGroupRequestDO::getId, lastId)));
+ }
+ return selectList(query.orderByAsc(ImGroupRequestDO::getUpdateTime).orderByAsc(ImGroupRequestDO::getId)
+ .last("LIMIT " + limit));
+ }
+
default int updateByIdAndHandleResult(Long id, Integer expectedHandleResult, ImGroupRequestDO updateObj) {
return update(updateObj, new LambdaUpdateWrapper()
.eq(ImGroupRequestDO::getId, id).eq(ImGroupRequestDO::getHandleResult, expectedHandleResult));
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java
index 84736fd..d0d5584 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java
@@ -4,13 +4,15 @@ 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.framework.mybatis.core.query.QueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO;
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
-import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum;
+import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum;
import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
+import java.util.Collection;
import java.util.List;
/**
@@ -22,48 +24,22 @@ import java.util.List;
public interface ImGroupMessageMapper extends BaseMapperX {
/**
- * 根据 minId + 时间窗口增量拉取群聊消息(在群成员使用)
+ * 根据 minId + 时间窗口增量拉取群聊消息
*
- * @param groupIds 用户当前仍在群内的群编号列表
+ * @param userId 当前用户编号
+ * @param groupIds 候选群编号集合(当前在群 ∪ 窗口内退群),不能为空
* @param minId 最小消息 id(不含)
* @param minSendTime 最早发送时间(不含),限制离线消息时间窗口
* @param size 拉取数量
* @return 消息列表(按 id 升序)
*/
- default List selectListByMinId(List groupIds, Long minId,
+ default List selectListByMinId(Long userId, Collection groupIds, Long minId,
LocalDateTime minSendTime, Integer size) {
QueryWrapperX wrapper = new QueryWrapperX<>();
wrapper.in("group_id", groupIds)
.gt("id", minId)
.gt("send_time", minSendTime)
- .orderByAsc("id");
- wrapper.limitN(size);
- return selectList(wrapper);
- }
-
- /**
- * 查询"退群前"的离线消息(退群成员使用)
- *
- * 语义:用户已退出某群,但仍需把 {@code minId} 之后、{@code minSendTime} 之后、不晚于退群时间的消息补齐到本地,便于前端看到完整上下文。
- *
- * 撤回消息(status=RECALL)保留返回,与在群成员的 {@link #selectListByMinId} 行为一致,由前端按撤回信号渲染「消息已撤回」气泡。
- *
- * @param groupId 群编号
- * @param minId 最小消息 id(不含)
- * @param minSendTime 最早发送时间(不含)
- * @param quitTime 退群时间(含),仅返回退群当时已存在的消息
- * @param size 拉取数量
- * @return 消息列表(按 id 升序)
- */
- default List selectListByGroupIdAndMinIdAndQuitTimeBefore(Long groupId, Long minId,
- LocalDateTime minSendTime,
- LocalDateTime quitTime,
- Integer size) {
- QueryWrapperX wrapper = new QueryWrapperX<>();
- wrapper.eq("group_id", groupId)
- .gt("id", minId)
- .gt("send_time", minSendTime)
- .le("send_time", quitTime)
+ .apply(MyBatisUtils.findInSet("receiver_user_ids"), userId)
.orderByAsc("id");
wrapper.limitN(size);
return selectList(wrapper);
@@ -72,17 +48,17 @@ public interface ImGroupMessageMapper extends BaseMapperX {
/**
* 查询群聊历史消息(游标拉取)
*
- * @param groupId 群编号
- * @param maxId 起始消息 id(不含),为空则从最新开始
- * @param limit 拉取数量
- * @param joinTime 入群时间,仅返回入群之后的消息
+ * @param userId 当前用户编号
+ * @param groupId 群编号
+ * @param maxId 起始消息 id(不含),为空则从最新开始
+ * @param limit 拉取数量
* @return 消息列表(按 id 倒序)
*/
- default List selectHistoryList(Long groupId, Long maxId, Integer limit, LocalDateTime joinTime) {
+ default List selectHistoryListByUser(Long userId, Long groupId, Long maxId, Integer limit) {
QueryWrapperX wrapper = new QueryWrapperX<>();
wrapper.eq("group_id", groupId)
.lt(maxId != null, "id", maxId)
- .ge(joinTime != null, "send_time", joinTime)
+ .apply(MyBatisUtils.findInSet("receiver_user_ids"), userId)
.orderByDesc("id");
wrapper.limitN(limit);
return selectList(wrapper);
@@ -108,7 +84,7 @@ public interface ImGroupMessageMapper extends BaseMapperX {
default List selectListByGroupIdAndPendingReceipt(Long groupId, Long minId, Long maxId) {
return selectList(new LambdaQueryWrapperX()
.eq(ImGroupMessageDO::getGroupId, groupId)
- .eq(ImGroupMessageDO::getReceiptStatus, ImGroupMessageReceiptStatusEnum.PENDING.getStatus())
+ .eq(ImGroupMessageDO::getReceiptStatus, ImMessageReceiptStatusEnum.PENDING.getStatus())
.gt(minId != null, ImGroupMessageDO::getId, minId)
.le(ImGroupMessageDO::getId, maxId)
.ne(ImGroupMessageDO::getStatus, ImMessageStatusEnum.RECALL.getStatus()));
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java
index a33f29f..b666dfb 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java
@@ -67,23 +67,13 @@ public interface ImPrivateMessageMapper extends BaseMapperX
.eq(ImPrivateMessageDO::getClientMessageId, clientMessageId));
}
- default Long selectMaxIdBySenderIdAndReceiverIdAndStatus(Long senderId, Long receiverId, Integer status) {
- ImPrivateMessageDO message = selectOne(new LambdaQueryWrapperX()
- .eq(ImPrivateMessageDO::getSenderId, senderId)
- .eq(ImPrivateMessageDO::getReceiverId, receiverId)
- .eq(ImPrivateMessageDO::getStatus, status)
- .orderByDesc(ImPrivateMessageDO::getId)
- .last("LIMIT 1"));
- return message != null ? message.getId() : null;
- }
-
- default int updateBySenderIdAndReceiverIdAndIdLeAndStatus(Long senderId, Long receiverId, Long maxMessageId,
- Integer whereStatus, ImPrivateMessageDO updateObj) {
+ default int updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus(Long senderId, Long receiverId, Long maxMessageId,
+ Integer whereReceiptStatus, ImPrivateMessageDO updateObj) {
return update(updateObj, new LambdaQueryWrapperX()
.eq(ImPrivateMessageDO::getSenderId, senderId)
.eq(ImPrivateMessageDO::getReceiverId, receiverId)
.le(ImPrivateMessageDO::getId, maxMessageId)
- .eq(ImPrivateMessageDO::getStatus, whereStatus));
+ .eq(ImPrivateMessageDO::getReceiptStatus, whereReceiptStatus));
}
default PageResult selectPage(ImPrivateMessageManagerPageReqVO reqVO) {
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java
index 141f6b8..26e3d98 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java
@@ -154,7 +154,7 @@ public interface ImStatisticsManagerMapper {
@Param("endTime") LocalDateTime endTime);
/**
- * 区间内消息类型分布(私聊+群聊合并)
+ * 区间内内容类型分布(私聊+群聊合并)
*
* @return [{type: 0, count: 123}, ...]
*/
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java
index 247ad33..92d3890 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java
@@ -9,20 +9,6 @@ package cn.iocoder.yudao.module.im.dal.redis;
public interface RedisKeyConstants {
/**
- * 群消息已读位置
- * KEY 格式: im:group:message:read:{groupId}
- * VALUE 数据类型: Hash (field: userId, value: maxReadMessageId)
- */
- String GROUP_MESSAGE_READ = "im:group:message:read:%s";
-
- /**
- * 频道消息已读位置
- * KEY 格式: im:channel:message:read:{channelId}
- * VALUE 数据类型: Hash (field: userId, value: maxReadMessageId)
- */
- String CHANNEL_MESSAGE_READ = "im:channel:message:read:%s";
-
- /**
* 好友关系状态缓存(合并「是否好友」+「是否拉黑」两态)
*
* KEY 格式:friend_state:{userId}_{friendUserId}
@@ -44,7 +30,7 @@ public interface RedisKeyConstants {
* KEY 格式:group_member_ids:{groupId}
* VALUE 数据类型:List
*
- * 说明:只缓存轻量的 userId 列表,适合"群消息推送目标"这类只关心 userId 的场景。
+ * 说明:只缓存轻量的 userId 列表,适合「群消息推送目标」这类只关心 userId 的场景
*/
String GROUP_MEMBER_IDS = "group_member_ids";
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImChannelMessageReadRedisDAO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImChannelMessageReadRedisDAO.java
deleted file mode 100644
index bda4b33..0000000
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImChannelMessageReadRedisDAO.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package cn.iocoder.yudao.module.im.dal.redis.message;
-
-import cn.hutool.core.convert.Convert;
-import jakarta.annotation.Resource;
-import org.springframework.data.redis.core.StringRedisTemplate;
-import org.springframework.stereotype.Repository;
-
-import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.CHANNEL_MESSAGE_READ;
-
-/**
- * IM 频道消息已读位置 Redis DAO
- *
- * @author 芋道源码
- */
-@Repository
-public class ImChannelMessageReadRedisDAO {
-
- @Resource
- private StringRedisTemplate stringRedisTemplate;
-
- /**
- * 更新用户在某频道的最大已读消息编号
- *
- * @param channelId 频道编号
- * @param userId 用户编号
- * @param maxMessageId 最大已读消息编号
- */
- public void updateReadMaxMessageId(Long channelId, Long userId, Long maxMessageId) {
- String key = formatKey(channelId);
- stringRedisTemplate.opsForHash().put(key, userId.toString(), maxMessageId.toString());
- }
-
- /**
- * 获取用户在某频道的最大已读消息编号
- *
- * @param channelId 频道编号
- * @param userId 用户编号
- * @return 最大已读消息编号;不存在则返回 null
- */
- public Long getReadMaxMessageId(Long channelId, Long userId) {
- String key = formatKey(channelId);
- Object val = stringRedisTemplate.opsForHash().get(key, userId.toString());
- return Convert.toLong(val);
- }
-
- private static String formatKey(Long channelId) {
- return String.format(CHANNEL_MESSAGE_READ, channelId);
- }
-
-}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImGroupMessageReadRedisDAO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImGroupMessageReadRedisDAO.java
deleted file mode 100644
index 5c76f79..0000000
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImGroupMessageReadRedisDAO.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package cn.iocoder.yudao.module.im.dal.redis.message;
-
-import cn.hutool.core.convert.Convert;
-import jakarta.annotation.Resource;
-import org.springframework.data.redis.core.StringRedisTemplate;
-import org.springframework.stereotype.Repository;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP_MESSAGE_READ;
-
-/**
- * IM 群消息已读位置 Redis DAO
- *
- * @author 芋道源码
- */
-@Repository
-public class ImGroupMessageReadRedisDAO {
-
- @Resource
- private StringRedisTemplate stringRedisTemplate;
-
- /**
- * 更新用户在某群的最大已读消息编号
- *
- * @param groupId 群编号
- * @param userId 用户编号
- * @param maxMessageId 最大已读消息编号
- */
- public void updateReadMaxMessageId(Long groupId, Long userId, Long maxMessageId) {
- String key = formatKey(groupId);
- stringRedisTemplate.opsForHash().put(key, userId.toString(), maxMessageId.toString());
- }
-
- /**
- * 获取用户在某群的最大已读消息编号
- *
- * @param groupId 群编号
- * @param userId 用户编号
- * @return 最大已读消息编号,不存在则返回 null
- */
- public Long getReadMaxMessageId(Long groupId, Long userId) {
- String key = formatKey(groupId);
- Object val = stringRedisTemplate.opsForHash().get(key, userId.toString());
- return Convert.toLong(val);
- }
-
- /**
- * 获取某群所有用户的已读位置
- *
- * @param groupId 群编号
- * @return userId → maxReadMessageId 映射
- */
- public Map getReadMaxMessageIdMap(Long groupId) {
- String key = formatKey(groupId);
- Map