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

java 复制代码
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. 方法签名与基础准备

java 复制代码
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 分页查询核心

java 复制代码
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. 处理空结果

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

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

java 复制代码
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. 查询博客详情

java 复制代码
// 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. 封装分页结果

java 复制代码
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[计算下次偏移量:
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 即可获取下一页
  • 服务端无需维护分页状态

典型应用场景

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

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

这种模式广泛应用于:

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

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

相关推荐
大飞哥~BigFei4 小时前
RabbitMq消费延迟衰减重试实现思路
java·分布式·rabbitmq
有趣的野鸭4 小时前
JAVA课程十一次实验课程主要知识点示例
java·前端·数据库
q***07146 小时前
Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘(上)
java·spring boot·后端
q***49866 小时前
Spring Boot 3.4 正式发布,结构化日志!
java·spring boot·后端
沐浴露z8 小时前
【微服务】基本概念介绍
java·微服务
Z3r4y9 小时前
【代码审计】RuoYi-4.7.3&4.7.8 定时任务RCE 漏洞分析
java·web安全·ruoyi·代码审计
Kuo-Teng10 小时前
LeetCode 160: Intersection of Two Linked Lists
java·算法·leetcode·职场和发展
Jooou10 小时前
Spring事务实现原理深度解析:从源码到架构全面剖析
java·spring·架构·事务
盖世英雄酱5813611 小时前
commit 成功为什么数据只更新了部分?
java·数据库·后端