Commit f938362b04fde4296f541a58fe440ef79c4d5b2d

Authored by YunaiV
1 parent b18281fc

✨ feat(im): 增加频道消息的已读状态

yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImChannelMessageManagerController.java
@@ -72,7 +72,8 @@ public class ImChannelMessageManagerController { @@ -72,7 +72,8 @@ public class ImChannelMessageManagerController {
72 convertSet(pageResult.getList(), ImChannelMessageDO::getMaterialId)); 72 convertSet(pageResult.getList(), ImChannelMessageDO::getMaterialId));
73 return success(BeanUtils.toBean(pageResult, ImChannelMessageRespVO.class, vo -> { 73 return success(BeanUtils.toBean(pageResult, ImChannelMessageRespVO.class, vo -> {
74 MapUtils.findAndThen(channelMap, vo.getChannelId(), c -> vo.setChannelName(c.getName())); 74 MapUtils.findAndThen(channelMap, vo.getChannelId(), c -> vo.setChannelName(c.getName()));
75 - MapUtils.findAndThen(materialMap, vo.getMaterialId(), m -> vo.setMaterialTitle(m.getTitle())); 75 + MapUtils.findAndThen(materialMap, vo.getMaterialId(),
  76 + material -> vo.setMaterialTitle(material.getTitle()).setMaterialCoverUrl(material.getCoverUrl()));
76 })); 77 }));
77 } 78 }
78 79
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImChannelMessageController.java
1 package cn.iocoder.yudao.module.im.controller.admin.message; 1 package cn.iocoder.yudao.module.im.controller.admin.message;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 import cn.iocoder.yudao.module.im.controller.admin.message.vo.channel.ImChannelMessagePullRespVO; 6 import cn.iocoder.yudao.module.im.controller.admin.message.vo.channel.ImChannelMessagePullRespVO;
6 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; 7 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO;
  8 +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
7 import cn.iocoder.yudao.module.im.service.message.ImChannelMessageService; 9 import cn.iocoder.yudao.module.im.service.message.ImChannelMessageService;
8 import io.swagger.v3.oas.annotations.Operation; 10 import io.swagger.v3.oas.annotations.Operation;
  11 +import io.swagger.v3.oas.annotations.Parameter;
9 import io.swagger.v3.oas.annotations.tags.Tag; 12 import io.swagger.v3.oas.annotations.tags.Tag;
10 import jakarta.annotation.Resource; 13 import jakarta.annotation.Resource;
11 import jakarta.validation.constraints.Max; 14 import jakarta.validation.constraints.Max;
@@ -13,13 +16,17 @@ import jakarta.validation.constraints.Min; @@ -13,13 +16,17 @@ import jakarta.validation.constraints.Min;
13 import jakarta.validation.constraints.PositiveOrZero; 16 import jakarta.validation.constraints.PositiveOrZero;
14 import org.springframework.validation.annotation.Validated; 17 import org.springframework.validation.annotation.Validated;
15 import org.springframework.web.bind.annotation.GetMapping; 18 import org.springframework.web.bind.annotation.GetMapping;
  19 +import org.springframework.web.bind.annotation.PutMapping;
16 import org.springframework.web.bind.annotation.RequestMapping; 20 import org.springframework.web.bind.annotation.RequestMapping;
17 import org.springframework.web.bind.annotation.RequestParam; 21 import org.springframework.web.bind.annotation.RequestParam;
18 import org.springframework.web.bind.annotation.RestController; 22 import org.springframework.web.bind.annotation.RestController;
19 23
  24 +import java.util.Collections;
20 import java.util.List; 25 import java.util.List;
  26 +import java.util.Map;
21 27
22 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 28 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
23 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; 30 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
24 31
25 @Tag(name = "用户 APP - IM 频道消息") 32 @Tag(name = "用户 APP - IM 频道消息")
@@ -38,8 +45,31 @@ public class ImChannelMessageController { @@ -38,8 +45,31 @@ public class ImChannelMessageController {
38 @RequestParam(value = "size", defaultValue = "100") 45 @RequestParam(value = "size", defaultValue = "100")
39 @Min(value = 1, message = "size 必须大于 0") 46 @Min(value = 1, message = "size 必须大于 0")
40 @Max(value = 200, message = "size 一次最多 200 条") Integer size) { 47 @Max(value = 200, message = "size 一次最多 200 条") Integer size) {
41 - List<ImChannelMessageDO> list = channelMessageService.getMessageListForPull(getLoginUserId(), minId, size);  
42 - return success(BeanUtils.toBean(list, ImChannelMessagePullRespVO.class)); 48 + // 1. 拉取消息列表
  49 + Long userId = getLoginUserId();
  50 + List<ImChannelMessageDO> list = channelMessageService.getMessageListForPull(userId, minId, size);
  51 + if (CollUtil.isEmpty(list)) {
  52 + return success(Collections.emptyList());
  53 + }
  54 + // 2. 按 Redis 已读游标补 status;device A 已读后 device B 拉到这条不再算入未读
  55 + Map<Long, Long> readMaxByChannel = channelMessageService.getChannelReadMaxMessageIdMap(
  56 + userId, convertSet(list, ImChannelMessageDO::getChannelId));
  57 + return success(BeanUtils.toBean(list, ImChannelMessagePullRespVO.class, vo -> {
  58 + Long readMax = readMaxByChannel.get(vo.getChannelId());
  59 + vo.setStatus(readMax != null && readMax >= vo.getId()
  60 + ? ImMessageStatusEnum.READ.getStatus()
  61 + : ImMessageStatusEnum.UNREAD.getStatus());
  62 + }));
  63 + }
  64 +
  65 + @PutMapping("/read")
  66 + @Operation(summary = "标记频道消息已读")
  67 + @Parameter(name = "channelId", description = "频道编号", required = true, example = "1")
  68 + @Parameter(name = "messageId", description = "已读到的消息编号", required = true, example = "100")
  69 + public CommonResult<Boolean> readChannelMessages(@RequestParam("channelId") Long channelId,
  70 + @RequestParam("messageId") Long messageId) {
  71 + channelMessageService.readChannelMessages(getLoginUserId(), channelId, messageId);
  72 + return success(true);
43 } 73 }
44 74
45 } 75 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/channel/ImChannelMessagePullRespVO.java
@@ -24,6 +24,9 @@ public class ImChannelMessagePullRespVO { @@ -24,6 +24,9 @@ public class ImChannelMessagePullRespVO {
24 @Schema(description = "消息内容;payload JSON 快照", requiredMode = Schema.RequiredMode.REQUIRED) 24 @Schema(description = "消息内容;payload JSON 快照", requiredMode = Schema.RequiredMode.REQUIRED)
25 private String content; 25 private String content;
26 26
  27 + @Schema(description = "当前用户的已读态;按 Redis 游标计算填充", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  28 + private Integer status; // 参见 ImMessageStatusEnum 枚举类
  29 +
27 @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) 30 @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
28 private LocalDateTime sendTime; 31 private LocalDateTime sendTime;
29 32
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java
@@ -16,6 +16,13 @@ public interface RedisKeyConstants { @@ -16,6 +16,13 @@ public interface RedisKeyConstants {
16 String GROUP_MESSAGE_READ = "im:group:message:read:%s"; 16 String GROUP_MESSAGE_READ = "im:group:message:read:%s";
17 17
18 /** 18 /**
  19 + * 频道消息已读位置
  20 + * KEY 格式: im:channel:message:read:{channelId}
  21 + * VALUE 数据类型: Hash (field: userId, value: maxReadMessageId)
  22 + */
  23 + String CHANNEL_MESSAGE_READ = "im:channel:message:read:%s";
  24 +
  25 + /**
19 * 好友关系状态缓存(合并「是否好友」+「是否拉黑」两态) 26 * 好友关系状态缓存(合并「是否好友」+「是否拉黑」两态)
20 * <p> 27 * <p>
21 * KEY 格式:friend_state:{userId}_{friendUserId} 28 * KEY 格式:friend_state:{userId}_{friendUserId}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImChannelMessageReadRedisDAO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.dal.redis.message;
  2 +
  3 +import cn.hutool.core.convert.Convert;
  4 +import jakarta.annotation.Resource;
  5 +import org.springframework.data.redis.core.StringRedisTemplate;
  6 +import org.springframework.stereotype.Repository;
  7 +
  8 +import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.CHANNEL_MESSAGE_READ;
  9 +
  10 +/**
  11 + * IM 频道消息已读位置 Redis DAO
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Repository
  16 +public class ImChannelMessageReadRedisDAO {
  17 +
  18 + @Resource
  19 + private StringRedisTemplate stringRedisTemplate;
  20 +
  21 + /**
  22 + * 更新用户在某频道的最大已读消息编号
  23 + *
  24 + * @param channelId 频道编号
  25 + * @param userId 用户编号
  26 + * @param maxMessageId 最大已读消息编号
  27 + */
  28 + public void updateReadMaxMessageId(Long channelId, Long userId, Long maxMessageId) {
  29 + String key = formatKey(channelId);
  30 + stringRedisTemplate.opsForHash().put(key, userId.toString(), maxMessageId.toString());
  31 + }
  32 +
  33 + /**
  34 + * 获取用户在某频道的最大已读消息编号
  35 + *
  36 + * @param channelId 频道编号
  37 + * @param userId 用户编号
  38 + * @return 最大已读消息编号;不存在则返回 null
  39 + */
  40 + public Long getReadMaxMessageId(Long channelId, Long userId) {
  41 + String key = formatKey(channelId);
  42 + Object val = stringRedisTemplate.opsForHash().get(key, userId.toString());
  43 + return Convert.toLong(val);
  44 + }
  45 +
  46 + private static String formatKey(Long channelId) {
  47 + return String.format(CHANNEL_MESSAGE_READ, channelId);
  48 + }
  49 +
  50 +}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/GroupMessageReadRedisDAO.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImGroupMessageReadRedisDAO.java
@@ -12,12 +12,12 @@ import java.util.Map; @@ -12,12 +12,12 @@ import java.util.Map;
12 import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP_MESSAGE_READ; 12 import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP_MESSAGE_READ;
13 13
14 /** 14 /**
15 - * 群消息已读位置 Redis DAO 15 + * IM 群消息已读位置 Redis DAO
16 * 16 *
17 * @author 芋道源码 17 * @author 芋道源码
18 */ 18 */
19 @Repository 19 @Repository
20 -public class GroupMessageReadRedisDAO { 20 +public class ImGroupMessageReadRedisDAO {
21 21
22 @Resource 22 @Resource
23 private StringRedisTemplate stringRedisTemplate; 23 private StringRedisTemplate stringRedisTemplate;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageService.java
@@ -6,7 +6,9 @@ import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.Im @@ -6,7 +6,9 @@ import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.Im
6 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; 6 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO;
7 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
8 8
  9 +import java.util.Collection;
9 import java.util.List; 10 import java.util.List;
  11 +import java.util.Map;
10 12
11 /** 13 /**
12 * IM 频道消息 Service 接口 14 * IM 频道消息 Service 接口
@@ -27,6 +29,23 @@ public interface ImChannelMessageService { @@ -27,6 +29,23 @@ public interface ImChannelMessageService {
27 */ 29 */
28 List<ImChannelMessageDO> getMessageListForPull(Long userId, Long minId, Integer size); 30 List<ImChannelMessageDO> getMessageListForPull(Long userId, Long minId, Integer size);
29 31
  32 + /**
  33 + * 上报频道消息已读位置;同步推 READ 事件给自己多端
  34 + *
  35 + * @param userId 当前用户编号
  36 + * @param channelId 频道编号
  37 + * @param messageId 已读到的最大消息编号
  38 + */
  39 + void readChannelMessages(Long userId, Long channelId, Long messageId);
  40 +
  41 + /**
  42 + * 批量查询用户在多个频道下的已读游标
  43 + *
  44 + * @param userId 当前用户编号
  45 + * @param channelIds 频道编号集合
  46 + * @return channelId → 已读到的最大消息编号(未读到的频道不出现在返回 map 中)
  47 + */
  48 + Map<Long, Long> getChannelReadMaxMessageIdMap(Long userId, Collection<Long> channelIds);
30 49
31 // ==================== 管理后台 ==================== 50 // ==================== 管理后台 ====================
32 51
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImpl.java
1 package cn.iocoder.yudao.module.im.service.message; 1 package cn.iocoder.yudao.module.im.service.message;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.extra.spring.SpringUtil;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -9,18 +11,23 @@ import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.Im @@ -9,18 +11,23 @@ import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.Im
9 import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO; 11 import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO;
10 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; 12 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO;
11 import cn.iocoder.yudao.module.im.dal.mysql.message.ImChannelMessageMapper; 13 import cn.iocoder.yudao.module.im.dal.mysql.message.ImChannelMessageMapper;
  14 +import cn.iocoder.yudao.module.im.dal.redis.message.ImChannelMessageReadRedisDAO;
12 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 15 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
13 import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService; 16 import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService;
14 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 17 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
15 import cn.iocoder.yudao.module.im.service.websocket.dto.ImChannelMessageDTO; 18 import cn.iocoder.yudao.module.im.service.websocket.dto.ImChannelMessageDTO;
16 import cn.iocoder.yudao.module.im.service.websocket.dto.message.MaterialMessage; 19 import cn.iocoder.yudao.module.im.service.websocket.dto.message.MaterialMessage;
17 import jakarta.annotation.Resource; 20 import jakarta.annotation.Resource;
  21 +import org.springframework.scheduling.annotation.Async;
18 import org.springframework.stereotype.Service; 22 import org.springframework.stereotype.Service;
19 import org.springframework.transaction.annotation.Transactional; 23 import org.springframework.transaction.annotation.Transactional;
20 import org.springframework.validation.annotation.Validated; 24 import org.springframework.validation.annotation.Validated;
21 25
22 import java.time.LocalDateTime; 26 import java.time.LocalDateTime;
  27 +import java.util.Collection;
  28 +import java.util.HashMap;
23 import java.util.List; 29 import java.util.List;
  30 +import java.util.Map;
24 31
25 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 32 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
26 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.IM_CHANNEL_MESSAGE_NOT_EXISTS; 33 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.IM_CHANNEL_MESSAGE_NOT_EXISTS;
@@ -36,11 +43,15 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService { @@ -36,11 +43,15 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService {
36 43
37 @Resource 44 @Resource
38 private ImChannelMessageMapper channelMessageMapper; 45 private ImChannelMessageMapper channelMessageMapper;
  46 +
39 @Resource 47 @Resource
40 private ImChannelMaterialService channelMaterialService; 48 private ImChannelMaterialService channelMaterialService;
41 @Resource 49 @Resource
42 private ImWebSocketService webSocketService; 50 private ImWebSocketService webSocketService;
43 51
  52 + @Resource
  53 + private ImChannelMessageReadRedisDAO channelMessageReadRedisDAO;
  54 +
44 // ==================== 用户端 ==================== 55 // ==================== 用户端 ====================
45 56
46 @Override 57 @Override
@@ -48,6 +59,47 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService { @@ -48,6 +59,47 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService {
48 return channelMessageMapper.selectListByUserAndMinId(userId, minId, size); 59 return channelMessageMapper.selectListByUserAndMinId(userId, minId, size);
49 } 60 }
50 61
  62 + @Override
  63 + public Map<Long, Long> getChannelReadMaxMessageIdMap(Long userId, Collection<Long> channelIds) {
  64 + Map<Long, Long> result = new HashMap<>(channelIds.size());
  65 + for (Long channelId : channelIds) {
  66 + Long max = channelMessageReadRedisDAO.getReadMaxMessageId(channelId, userId);
  67 + if (max != null) {
  68 + result.put(channelId, max);
  69 + }
  70 + }
  71 + return result;
  72 + }
  73 +
  74 + @Override
  75 + public void readChannelMessages(Long userId, Long channelId, Long messageId) {
  76 + Assert.notNull(channelId, "频道编号不能为空");
  77 + Assert.notNull(messageId, "已读消息编号不能为空");
  78 + // 1. 已读位置未前进,直接返回
  79 + Long prevMaxMessageId = channelMessageReadRedisDAO.getReadMaxMessageId(channelId, userId);
  80 + if (prevMaxMessageId != null && prevMaxMessageId >= messageId) {
  81 + return;
  82 + }
  83 +
  84 + // 2. 更新 Redis 频道已读位置
  85 + channelMessageReadRedisDAO.updateReadMaxMessageId(channelId, userId, messageId);
  86 +
  87 + // 3. 异步推 READ 事件给自己多端同步
  88 + getSelf().readChannelMessageEvent(userId, channelId, messageId);
  89 + }
  90 +
  91 + /**
  92 + * 发送频道已读 READ 事件给自己其他终端;频道无「给发送方刷回执」概念,不广播
  93 + */
  94 + @Async
  95 + public void readChannelMessageEvent(Long userId, Long channelId, Long readId) {
  96 + webSocketService.sendChannelMessageAsync(userId, ImChannelMessageDTO.ofRead(channelId, readId));
  97 + }
  98 +
  99 + private ImChannelMessageServiceImpl getSelf() {
  100 + return SpringUtil.getBean(getClass());
  101 + }
  102 +
51 // ==================== 管理后台 ==================== 103 // ==================== 管理后台 ====================
52 104
53 @Override 105 @Override
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java
@@ -17,7 +17,7 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; @@ -17,7 +17,7 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
17 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 17 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
18 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; 18 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
19 import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; 19 import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper;
20 -import cn.iocoder.yudao.module.im.dal.redis.message.GroupMessageReadRedisDAO; 20 +import cn.iocoder.yudao.module.im.dal.redis.message.ImGroupMessageReadRedisDAO;
21 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; 21 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum;
22 import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; 22 import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum;
23 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 23 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
@@ -66,7 +66,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -66,7 +66,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
66 @Resource 66 @Resource
67 private ImGroupMessageMapper groupMessageMapper; 67 private ImGroupMessageMapper groupMessageMapper;
68 @Resource 68 @Resource
69 - private GroupMessageReadRedisDAO groupMessageReadRedisDAO; 69 + private ImGroupMessageReadRedisDAO groupMessageReadRedisDAO;
70 70
71 @Resource 71 @Resource
72 private ImGroupService groupService; 72 private ImGroupService groupService;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImChannelMessageDTO.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.im.service.websocket.dto; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.im.service.websocket.dto;
2 2
3 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 3 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; 4 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO;
  5 +import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
5 import lombok.Data; 6 import lombok.Data;
6 import lombok.experimental.Accessors; 7 import lombok.experimental.Accessors;
7 8
@@ -53,4 +54,17 @@ public class ImChannelMessageDTO { @@ -53,4 +54,17 @@ public class ImChannelMessageDTO {
53 return BeanUtils.toBean(message, ImChannelMessageDTO.class); 54 return BeanUtils.toBean(message, ImChannelMessageDTO.class);
54 } 55 }
55 56
  57 + /**
  58 + * 构建已读同步 DTO;多端同步:通知自己的其他终端「我已经读了某频道」
  59 + *
  60 + * @param channelId 频道编号
  61 + * @param readId 已读位置(最大已读消息编号)
  62 + * @return 频道 DTO
  63 + */
  64 + public static ImChannelMessageDTO ofRead(Long channelId, Long readId) {
  65 + return new ImChannelMessageDTO()
  66 + .setId(readId).setType(ImMessageTypeEnum.READ.getType())
  67 + .setChannelId(channelId);
  68 + }
  69 +
56 } 70 }
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java
@@ -10,7 +10,7 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; @@ -10,7 +10,7 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
10 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 10 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
11 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; 11 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
12 import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; 12 import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper;
13 -import cn.iocoder.yudao.module.im.dal.redis.message.GroupMessageReadRedisDAO; 13 +import cn.iocoder.yudao.module.im.dal.redis.message.ImGroupMessageReadRedisDAO;
14 import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; 14 import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum;
15 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 15 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
16 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 16 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
@@ -56,7 +56,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -56,7 +56,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
56 @Mock 56 @Mock
57 private ImSensitiveWordService sensitiveWordService; 57 private ImSensitiveWordService sensitiveWordService;
58 @Mock 58 @Mock
59 - private GroupMessageReadRedisDAO groupMessageReadRedisDAO; 59 + private ImGroupMessageReadRedisDAO groupMessageReadRedisDAO;
60 @Mock 60 @Mock
61 private ImWebSocketService imWebSocketService; 61 private ImWebSocketService imWebSocketService;
62 62