前言(技术选型):
Feed流
推模式
Redis ZSet
第一次查:查当前时间之前的 10 条。
第二次查:查上一页最后一条动态的时间戳之前的 10 条。
推送笔记
流程:
代码实现:
@Override
public Result saveBlog(Blog blog) {
// 获取登录用户
UserDTO user = UserHolder.getUser();
blog.setUserId(user.getId());
// 保存探店博文
blogService.save(blog);
//查询作者所有粉丝
List<Follow> fans = followService.query().eq("follow_user_id", user.getId()).list();
if (fans == null || fans.isEmpty()){
return Result.ok();
}
//推送笔记id给所有粉丝
for (Follow fan : fans) {
String key = FEED_KEY + fan.getUserId();
stringRedisTemplate
.opsForZSet()
.add(key, blog.getId().toString(), System.currentTimeMillis());
}
//返回id
return Result.ok(blog.getId());
}
注意事项:
将笔记推送到粉丝邮箱时,以当前时间戳作为分数。
读取推送
流程:
- key为要查询的key
- 0, maxScore为分数查询范围
- safeOffset为偏移量
- 2为每页展示数据
代码实现:
@Override
public Result queryBlogOfFollow(Long lastId, Integer offset) {
// 获取当前用户
Long userId = UserHolder.getUser().getId();
// 防御式处理,避免请求参数缺失导致空指针
long maxScore = (lastId == null || lastId <= 0) ? Long.MAX_VALUE : lastId;
int safeOffset = (offset == null || offset < 0) ? 0 : offset;
// 查询收件箱 ZREVRANGEBYSCORE key Max Min WITHSCORES LIMIT offset count String key = FEED_KEY + userId;
Set<String> ids = stringRedisTemplate.opsForZSet()
.reverseRangeByScore(key, 0, maxScore, safeOffset, 2);
if (ids == null || ids.isEmpty()) {
return Result.ok();
}
// 解析出blogId和score
List<Long> blogIds = ids.stream().map(Long::valueOf).collect(Collectors.toList());
long minTime = 0;
int os = 1;
for (String id : ids) {
Double score = stringRedisTemplate.opsForZSet().score(key, id);
if (score == null) {
continue;
}
long time = score.longValue();
if (time == minTime) {
os++;
} else {
minTime = time;
os = 1;
}
}
// 根据id查询blog
String idStr = StrUtil.join(",", blogIds);
List<Blog> blogs = query().in("id", blogIds)
.last("ORDER BY FIELD(id," + idStr + ")")
.list();
for (Blog blog : blogs) {
queryBlogUser(blog);
isBlogLiked(blog);
}
ScrollPageResult scrollPageResult = new ScrollPageResult(blogs, minTime, os);
return Result.ok(scrollPageResult);
}
注意事项


