代码解析:基于时间轴(Timeline)的博客分页查询功能

public Result queryBlogOfFollow(Long max, Integer offset) {
    //获取当前登录用户
        UserDTO user = UserHolder.getUser();
        String key = "feed:" + user.getId();
        //1.根据用户查询
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(key,0, max, offset, 10);
        if(typedTuples == null || typedTuples.isEmpty()){
            return Result.ok();
        }
        //解析
        List<Long> ids = new ArrayList<>(typedTuples.size());
        Long min = 0L;
        int os = 1 ;
        for (ZSetOperations.TypedTuple<String> tuple : typedTuples) {
            ids.add(Long.valueOf(tuple.getValue()));
            //获取时间戳
            long time = tuple.getScore().longValue();
            if(min == time){
                os++;
            }else{
                min = time;
                os = 1;
            }
        }
        os = min == max ? os : os+offset;
        //根据id查询blog
        String idsStr = StrUtil.join(",", ids);
        List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idsStr + ")").list();
        blogs.forEach(blog -> {
            queryBlogUser(blog);
            isBlogLiked(blog);
        });
        //封装返回
        ScrollResult r = new ScrollResult();
        r.setList(blogs);
        r.setOffset(os);
        r.setMinTime(min);
        return Result.ok(r);
    }
这段代码实现了一个基于时间轴(Timeline)的博客分页查询功能,常用于社交平台的"关注动态"展示。核心思路是使用 Redis 的有序集合实现按时间排序的分页查询。下面逐部分详细解析:

1. 方法签名与基础准备

public Result queryBlogOfFollow(Long max, Integer offset) {
// 获取当前登录用户
UserDTO user = UserHolder.getUser();
String key = "feed:" + user.getId(); // Redis键:feed:用户ID
  • 参数说明
  • max:上一页的最小时间戳(分页起点)
  • offset:上一页中相同时间戳的偏移量(处理同分页问题)
  • Redis Keyfeed:{userId} 存储用户关注的所有博主最新动态(Sorted Set 结构)
  • Value:博客ID
  • Score:发布时间戳(毫秒)

2. Redis 分页查询核心

Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
.reverseRangeByScoreWithScores(key, 0, max, offset, 10); // 关键查询
  • 功能:从 Redis 有序集合中获取分页数据
  • 参数解析
  • keyfeed:用户ID
  • min=0:分数最小值(时间戳起点)
  • max:当前分页截止时间戳
  • offset:跳过相同分数的元素数
  • count=10:每页数量
  • 返回值TypedTuple<String> 的 Set 集合
  • getValue():博客ID(String)
  • getScore():时间戳(Double)

📌 查询逻辑:获取时间戳 ≤ max 的最新10条动态(按时间倒序)


3. 处理空结果

if(typedTuples == null || typedTuples.isEmpty()){
return Result.ok(); // 无数据直接返回
}

4. 结果解析与分页参数计算

List<Long> ids = new ArrayList<>(typedTuples.size());
Long min = 0L; // 当前页最小时间戳
int os = 1;// 相同时间戳计数

for (ZSetOperations.TypedTuple<String> tuple : typedTuples) {
// 1. 收集博客ID
ids.add(Long.valueOf(tuple.getValue()));

// 2. 提取时间戳(毫秒)
long time = tuple.getScore().longValue();

// 3. 计算相同时间戳的数量
if(min == time){
os++; // 相同时间戳计数+1
}else{
min = time; // 更新最小时间戳
os = 1;// 重置计数器
}
}

// 4. 计算下一页偏移量
os = min == max ? os : os + offset; // 关键逻辑
  • ids:存储本页所有博客ID(用于后续查询)
  • min:记录本页中最小的(最旧的)时间戳
  • os 计算逻辑
  • 如果当前页的 min 等于传入的 max(时间戳连续),偏移量 = 当前页末尾相同时间戳数量 os
  • 如果 min < max(时间戳不连续),偏移量 = 当前页末尾相同时间戳数量 os + 传入的 offset

⚠️ 解决分页边界问题:处理相同时间戳的分页跳跃(如多个动态在同一毫秒发布)


5. 查询博客详情

// 1. 拼接ID列表(保持Redis返回顺序)
String idsStr = StrUtil.join(",", ids);

// 2. 按ID顺序查询博客(使用MySQL FIELD排序)
List<Blog> blogs = query().in("id", ids)
.last("ORDER BY FIELD(id," + idsStr + ")")
.list();

// 3. 补充博客附加信息
blogs.forEach(blog -> {
queryBlogUser(blog);// 查询作者信息
isBlogLiked(blog);// 检查当前用户是否点赞
});
  • 关键技巧ORDER BY FIELD(id, ...) 保持结果顺序与 Redis 返回一致
  • 附加处理
  • queryBlogUser():填充博主信息(头像、昵称等)
  • isBlogLiked():标记当前用户是否点赞过

6. 封装分页结果

ScrollResult r = new ScrollResult();
r.setList(blogs);// 博客列表
r.setOffset(os);// 下一页的偏移量
r.setMinTime(min);// 下一页的起始时间戳

return Result.ok(r);
  • ScrollResult:自定义分页响应对象
  • list:当前页数据
  • offset:下次请求的偏移量
  • minTime:下次请求的最大时间戳(分页起点)

###🌟 核心流程图解

graph TD
A[请求参数: max_time, offset] --> B[Redis查询: ZREVRANGEBYSCORE feed:userId 0 max_time]
B --> C{结果为空?}
C -->|是| D[返回空结果]
C -->|否| E[解析ID列表]
E --> F[计算最小时间戳 min_time]
F --> G[计算相同时间戳数量 os]
G --> H[计算下次偏移量: <br>min==max ? os : os+offset]
H --> I[按ID顺序查询博客详情]
I --> J[补充作者/点赞信息]
J --> K[返回: blogs + offset + min_time]

###💡 技术亮点解析

  1. 时间轴分页模型
  • 使用时间戳作为分页锚点
  • 通过 min_time + offset 解决同分页问题
  1. 高效缓存查询
  • 利用 Redis Sorted Set 的 O(logN) 范围查询性能
  • 避免直接查询数据库的性能瓶颈
  1. 顺序一致性保证
  • ORDER BY FIELD(id,...) 保持 Redis 原始顺序
  • 确保新动态插入不影响分页连续性
  1. 无状态分页
  • 客户端只需存储 minTime + offset 即可获取下一页
  • 服务端无需维护分页状态

典型应用场景

# 第一页请求:
max = 当前时间戳 (Long.MAX_VALUE)
offset = 0

# 后续页请求:
max = 上一页返回的 minTime
offset = 上一页返回的 offset

这种模式广泛应用于:

  • 微博/朋友圈的关注动态流
  • 新闻资讯的时间线
  • 实时监控数据展示
  • 游戏排行榜翻页

通过 Redis 有序集合 + 分页参数计算,实现了高效的时间轴分页功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值