2026-06-30 24681c81c09022f584a57006f2534b5f74723414
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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<ImConversationReadDO> {
 
    default ImConversationReadDO selectByUserIdAndConversation(Long userId, Integer conversationType, Long conversationId) {
        return selectOne(new LambdaQueryWrapperX<ImConversationReadDO>()
                .eq(ImConversationReadDO::getUserId, userId)
                .eq(ImConversationReadDO::getConversationType, conversationType)
                .eq(ImConversationReadDO::getTargetId, conversationId));
    }
 
    default List<ImConversationReadDO> selectListByConversation(Integer conversationType, Long conversationId) {
        return selectList(new LambdaQueryWrapperX<ImConversationReadDO>()
                .eq(ImConversationReadDO::getConversationType, conversationType)
                .eq(ImConversationReadDO::getTargetId, conversationId));
    }
 
    default List<ImConversationReadDO> selectListByUserIdAndConversations(Long userId, Integer conversationType,
                                                                          Collection<Long> conversationIds) {
        return selectList(new LambdaQueryWrapperX<ImConversationReadDO>()
                .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<ImConversationReadDO> selectPullListByUserId(Long userId, Long lastUpdateTime, Long lastId, Integer limit) {
        LambdaQueryWrapperX<ImConversationReadDO> query = new LambdaQueryWrapperX<ImConversationReadDO>()
                .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));
    }
 
    /**
     * 单调递增更新读位置:仅当新位置更大时才更新
     * <p>
     * 通过 {@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.<ImConversationReadDO>lambdaUpdate()
                .set(ImConversationReadDO::getMessageId, messageId)
                .set(ImConversationReadDO::getReadTime, readTime)
                .set(ImConversationReadDO::getUpdateTime, readTime)
                .eq(ImConversationReadDO::getId, id)
                .lt(ImConversationReadDO::getMessageId, messageId));
    }
 
}