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 Key :
feed:{userId}存储用户关注的所有博主最新动态(Sorted Set 结构) - Value:博客ID
- Score:发布时间戳(毫秒)
2. Redis 分页查询核心
java
Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
.reverseRangeByScoreWithScores(key, 0, max, offset, 10); // 关键查询
- 功能:从 Redis 有序集合中获取分页数据
- 参数解析:
key:feed:用户IDmin=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]
min==max ? os : os+offset] H --> I[按ID顺序查询博客详情] I --> J[补充作者/点赞信息] J --> K[返回: blogs + offset + min_time]
###💡 技术亮点解析
- 时间轴分页模型:
- 使用时间戳作为分页锚点
- 通过
min_time + offset解决同分页问题
- 高效缓存查询:
- 利用 Redis Sorted Set 的 O(logN) 范围查询性能
- 避免直接查询数据库的性能瓶颈
- 顺序一致性保证:
ORDER BY FIELD(id,...)保持 Redis 原始顺序- 确保新动态插入不影响分页连续性
- 无状态分页:
- 客户端只需存储
minTime + offset即可获取下一页 - 服务端无需维护分页状态
典型应用场景
bash
# 第一页请求:
max = 当前时间戳 (Long.MAX_VALUE)
offset = 0
# 后续页请求:
max = 上一页返回的 minTime
offset = 上一页返回的 offset
这种模式广泛应用于:
- 微博/朋友圈的关注动态流
- 新闻资讯的时间线
- 实时监控数据展示
- 游戏排行榜翻页
通过 Redis 有序集合 + 分页参数计算,实现了高效的时间轴分页功能。