@@ -15,7 +16,7 @@ import java.util.Arrays;
*/
@Getter
@AllArgsConstructor
-public enum ImMessageContentTypeEnum implements IntArrayValuable {
+public enum ImMessageContentTypeEnum implements ArrayValuable
+ * 私聊:UNREAD / READ / RECALL
+ * 群聊:NORMAL / RECALL
+ *
+ * @author 芋道源码
*/
@RequiredArgsConstructor
@Getter
-public enum ImMessageStatusEnum implements IntArrayValuable {
+public enum ImMessageStatusEnum implements ArrayValuable
+ * 删除好友采用逻辑删除,沿用 BaseDO 的 deleted。
+ * 重新添加好友时优先恢复原关系记录,不新插重复行。
+ *
+ * @author 芋道源码
+ */
+@TableName("im_friend")
+@KeySequence("im_friend_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ImFriendDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 用户编号
+ *
+ * 关联 AdminUserDO 的 id 字段
+ */
+ private Long userId;
+ /**
+ * 好友用户编号
+ *
+ * 关联 AdminUserDO 的 id 字段
+ */
+ private Long friendUserId;
+ /**
+ * 是否免打扰
+ */
+ private Boolean muted;
+
+}
diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java
index 727f1aa..1bbc793 100644
--- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java
+++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java
@@ -26,32 +26,37 @@ public class ImGroupDO extends BaseDO {
*/
@TableId
private Long id;
- // TODO @hao:name,如果这个表已经是 group 了,不用在带额外的
/**
- * 群名字
+ * 群名称
*/
- private String groupName;
- // TODO @hao:关联字段;
+ private String name;
/**
- * 群主编号
+ * 群主用户编号
+ *
+ * 关联 AdminUserDO 的 id 字段
*/
- private Long ownerId;
- // TODO @hao:头像使用 avatar 好了,整个项目统一;然后 Thumb 是不是不用存,这个更多是文件服务做裁剪
+ private Long ownerUserId;
/**
* 群头像
*/
- private String headImage;
- /**
- * 群头像缩略图
- */
- private String headImageThumb;
+ private String avatar;
/**
* 群公告
*/
private String notice;
/**
- * 群备注
+ * 是否封禁
+ */
+ private Boolean banned;
+ /**
+ * 封禁原因
+ */
+ private String bannedReason;
+ // TODO @AI:封禁时间;
+ /**
+ * 是否解散
*/
- private String remark;
+ private Boolean dissolved;
+ // TODO @AI:解散时间
}
\ No newline at end of file
diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupMemberDO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupMemberDO.java
index 6d7ada1..140b5ac 100644
--- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupMemberDO.java
+++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupMemberDO.java
@@ -1,9 +1,15 @@
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 cn.iocoder.yudao.module.im.enums.group.ImGroupMemberStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+import java.time.LocalDateTime;
+
+// TODO @AI:好友关系是逻辑删除,群成员是 status 标识,感觉是不是要统一下?
/**
* IM 群成员 DO
*
@@ -24,31 +30,43 @@ public class ImGroupMemberDO extends BaseDO {
*/
@TableId
private Long id;
- // TODO @hao:groupId 和 userId 都写下关联字段哈
/**
* 群编号
+ *
+ * 关联 {@link ImGroupDO#getId()}
*/
private Long groupId;
/**
* 用户编号
+ *
+ * 关联 AdminUserDO 的 id 字段
*/
private Long userId;
- // TODO @hao:nickname 和 avatar 是不是不用存储哈;
/**
- * 昵称
+ * 组内显示名
+ */
+ private String displayUserName;
+ /**
+ * 群显示备注
+ */
+ private String displayGroupName;
+ /**
+ * 是否免打扰
*/
- private String nickname;
+ private Boolean muted;
/**
- * 头像
+ * 成员状态
+ *
+ * 枚举 {@link ImGroupMemberStatusEnum}
*/
- private String avatar;
+ private Integer status;
/**
- * 组内显示名称
+ * 入群时间
*/
- private String aliasName;
+ private LocalDateTime joinTime;
/**
- * 备注
+ * 退群时间
*/
- private String remark;
+ private LocalDateTime quitTime;
}
\ No newline at end of file
diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java
new file mode 100644
index 0000000..9f50a64
--- /dev/null
+++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java
@@ -0,0 +1,98 @@
+package cn.iocoder.yudao.module.im.dal.dataobject.message;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+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.ImMessageStatusEnum;
+import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
+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_group_message")
+@KeySequence("im_group_message_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ImGroupMessageDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 客户端消息编号,用于发送幂等
+ */
+ private String clientMessageId;
+ /**
+ * 发送人编号
+ *
+ * 关联 AdminUserDO 的 id 字段
+ */
+ private Long senderId;
+ /**
+ * 群编号
+ *
+ * 关联 {@link ImGroupDO#getId()}
+ */
+ private Long groupId;
+ /**
+ * 消息类型
+ *
+ * 枚举 {@link ImMessageTypeEnum}
+ */
+ private Integer type;
+ /**
+ * 消息内容,JSON 格式
+ *
+ * TODO @AI:/Users/yunai/Java/yudao-all-in-im/ruoyi-vue-pro/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content
+ */
+ private String content;
+ /**
+ * 消息状态
+ *
+ * 枚举 {@link ImMessageStatusEnum}
+ * 群聊状态:NORMAL(0) / RECALL(2)
+ */
+ // TODO @AI:会不会存在 READ 的情况?如果不存在,最好说明下,和单聊的差异;
+ private Integer status;
+ /**
+ * 发送时间
+ */
+ private LocalDateTime sendTime;
+ /**
+ * 定向接收用户编号列表,逗号分隔
+ *
+ * 为空表示全员可见
+ *
+ * 关联 AdminUserDO 的 id 字段
+ */
+ // TODO @AI:使用 List
+ * 枚举 {@link ImGroupMessageReceiptStatusEnum}
+ */
+ private Integer receiptStatus;
+
+}
diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java
new file mode 100644
index 0000000..084b8f2
--- /dev/null
+++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java
@@ -0,0 +1,73 @@
+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.ImMessageStatusEnum;
+import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
+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_private_message")
+@KeySequence("im_private_message_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ImPrivateMessageDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 客户端消息编号,用于发送幂等
+ */
+ private String clientMessageId;
+ /**
+ * 发送人编号
+ *
+ * 关联 AdminUserDO 的 id 字段
+ */
+ private Long senderId;
+ /**
+ * 接收人编号
+ *
+ * 关联 AdminUserDO 的 id 字段
+ */
+ private Long receiverId;
+ /**
+ * 消息类型
+ *
+ * 枚举 {@link ImMessageTypeEnum}
+ */
+ private Integer type;
+ /**
+ * 消息内容,JSON 格式
+ *
+ * TODO @AI:/Users/yunai/Java/yudao-all-in-im/ruoyi-vue-pro/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content
+ */
+ private String content;
+ /**
+ * 消息状态
+ *
+ * 枚举 {@link ImMessageStatusEnum}
+ * 私聊状态:UNREAD(0) / READ(1) / RECALL(2)
+ */
+ private Integer status;
+ /**
+ * 发送时间
+ */
+ private LocalDateTime sendTime;
+
+}
diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/sensitiveword/ImSensitiveWordDO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/sensitiveword/ImSensitiveWordDO.java
new file mode 100644
index 0000000..4f6d1fc
--- /dev/null
+++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/sensitiveword/ImSensitiveWordDO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+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
+ *
+ * @author 芋道源码
+ */
+@TableName("im_sensitive_word")
+@KeySequence("im_sensitive_word_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ImSensitiveWordDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 敏感词
+ */
+ private String word;
+ /**
+ * 状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+
+}
diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java
new file mode 100644
index 0000000..e317be2
--- /dev/null
+++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.im.dal.mysql.friend;
+
+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.friend.ImFriendDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * IM 好友关系 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ImFriendMapper extends BaseMapperX
+ * 采用 Redis Hash 存储:key=im:group:read:{groupId}, field=userId, value=maxReadMessageId
+ *
+ * @author 芋道源码
+ */
+@Repository
+public class GroupReadPositionRedisDAO {
+
+ @Resource
+ private StringRedisTemplate stringRedisTemplate;
+
+ /**
+ * 更新用户在某群的已读位置
+ *
+ * @param groupId 群编号
+ * @param userId 用户编号
+ * @param maxMessageId 最大已读消息编号
+ */
+ public void updateReadPosition(Long groupId, Long userId, Long maxMessageId) {
+ String key = String.format(GROUP_READ_POSITION, groupId);
+ // 只在新值大于旧值时更新
+ Object currentVal = stringRedisTemplate.opsForHash().get(key, userId.toString());
+ if (currentVal == null || Long.parseLong(currentVal.toString()) < maxMessageId) {
+ stringRedisTemplate.opsForHash().put(key, userId.toString(), maxMessageId.toString());
+ }
+ }
+
+ /**
+ * 获取用户在某群的已读位置
+ */
+ public Long getReadPosition(Long groupId, Long userId) {
+ String key = String.format(GROUP_READ_POSITION, groupId);
+ Object val = stringRedisTemplate.opsForHash().get(key, userId.toString());
+ return val == null ? 0L : Long.parseLong(val.toString());
+ }
+
+ /**
+ * 获取某群所有用户的已读位置
+ */
+ public Map> getReadUsers(
+ @RequestParam("groupId") Long groupId,
+ @RequestParam("messageId") Long messageId) {
+ List