公众号:MarkerHub(关注获取更多项目资源)
eblog 代码仓库:github.com/markerhub/e…
eblog 项目视频: www.bilibili.com/video/BV1ri…
开发文档目录:
(eblog)2、整合Redis,以及项目优雅的异常处理与返回结果封装
(eblog)3、用Redis的zset有序集合实现一个本周热议功能
(eblog)4、自定义 Freemaker 标签实现博客首页数据填充
前后端分离项目vueblog请点击这里:超详细!4小时开发一个SpringBoot+vue前后端分离博客项目!!
本周热议
本周热议,本周发表并且评论最多的文章排行,如果直接查询数据库的话很快就可以实现,只需要限定一下文章创建时间,然后更加评论数量倒叙取前几篇即可搞定。
但这里我们使用 redis 来完成。之前上课时候我们说过,排行榜功能,我们可以使用 redis 的有序集合 zset 来完成。现在我们就这个数据结构来完成本周热议的功能。
在编码之前,我们需要先来回顾一下 zset 的几个基本命令。
- zrange key start stop [WITHSCORES]
withscores 代表的是否显示顺序号 start 和 stop 代表所在的位置的索引。可以这样理解:将集合元素依照顺序值升序排序再输出,start 和 stop 限制遍历的限制范围
- zincrby key increment member
为有序集 key 的成员 member 的 score 值加上增量 increment 。
- ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
计算给定的一个或多个有序集的并集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集 (结果集) 储存到 destination 。
默认情况下,结果集中某个成员的 score 值是所有给定集下该成员 score 值之 和 。
其他命令可以参考这里:doc.redisfans.com/
以下是我做的实验:

我们来分析一下我们的需求。我们想用缓存来完成这本周热议排行榜功能,不依赖数据库(除了初始化数据)。有人发表评论之后,直接使用命令加一,并重新计算并集得到排行榜。
项目启动时候我们先初始化最近文章的评论数量。基本逻辑如下:
-
查库获取最近 7 天的所有文章(或者加多一个条件:评论数量大于 0)
-
然后把文章的评论数量作为有序集合的分数,文章 id 作为 ID 存储到 zset 中。
-
本周热议上有标题和评论数量,因此,我们还需要把文章的基本信息存储到 redis 总。这样得到文章的 id 之后,我们再从缓存中得到标题等信息,这里我们可以使用 hash 的结构来存储文章的信息。
-
另外,因为是本周热议,如果文章发表超过 7 天了之后就没啥用了,所以我们可以给文章的有序集合一个有效时间。超过 7 天之后就自定删除缓存。
具体代码如下:
- com.example.service.impl.PostServiceImpl#initIndexWeekRank
/**
* 初始化首页的周评论排行榜
*/
@Override
public void initIndexWeekRank() {
//缓存最近7天的文章评论数量
List<Post> last7DayPosts = this.list(new QueryWrapper<Post>()
.ge("created", DateUtil.offsetDay(new Date(), -7).toJdkDate())
.select("id, title, user_id, comment_count, view_count, created"));
for (Post post : last7DayPosts) {
String key = "day_rank:" + DateUtil.format(post.getCreated(), DatePattern.PURE_DATE_PATTERN);
//设置有效期
long between = DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY);
long expireTime = (7 - between) * 24 * 60 * 60;
//缓存文章到set中,评论数量作为排行标准
redisUtil.zSet(key, post.getId(), post.getCommentCount());
//设置有效期
redisUtil.expire(key, expireTime);
//缓存文章基本信息(hash结构)
this.hashCachePostIdAndTitle(post);
}
//7天阅读相加。
this.zUnionAndStoreLast7DaysForLastWeekRank();
}
- 对应的缓存文章信息的方法如下:
/**
* hash结构缓存文章标题和id
* @param post
*/
private void hashCachePostIdAndTitle(Post post) {
boolean isExist = redisUtil.hasKey("rank_post_" + post.getId());
if(!isExist) {
long between = DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY);
long expireTime = (7 - between) * 24 * 60 * 60;
//缓存文章基本信息(hash结构)
redisUtil.hset("rank_post_" + post.getId(), "post:id", post.getId(), expireTime);
redisUtil.hset("rank_post_" + post.getId(), "post:title", post.getTitle(), expireTime);
//redisUtil.hset("rank_post_" + post.getId(), "post:comment_count", post.getCommentCount(), expireTime);
}
}
- 统计 7 天的文章集合交集数量:
/**
* 把最近7天的文章评论数量统计一下
* 用于首页的7天评论排行榜
*/
public void zUnionAndStoreLast7DaysForLastWeekRank() {
String prifix = "day_rank:";
List<String> keys = new ArrayList<>();
String key = prifix + DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
for(int i = -7 ; i < 0; i++) {
Date date = DateUtil.offsetDay(new Date(), i).toJdkDate();
keys.add(prifix + DateUtil.format(date, DatePattern.PURE_DATE_PATTERN));
}
redisUtil.zUnionAndStore(key, keys, "last_week_rank");
}
写好了之后,我们再我们的项目启动类中调用一下即可完成了初始化。
@Slf4j
@Order(1000)
@Component
public class ContextStartup implements ApplicationRunner, ServletContextAware {
private ServletContext servletContext;
@Autowired
PostService postService;
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public void run(ApplicationArguments args) throws Exception {
servletContext.setAttribute("base", servletContext.getContextPath());
//初始化首页的周评论排行榜
postService.initIndexWeekRank();
}
}
以上就完成了初始化。这时候我们在本周热议模块已经可以看到效果了。缓存中已经有我们想要的数据,接下我们在 controller 中获取出来,然后返回给个我们的页面,页面用异步加载的模式,所以这里定义一个异步接口:
- com.example.controller.PostController
@ResponseBody
@GetMapping("/post/hots")
public Result hotPost() {
Set<ZSetOperations.TypedTuple> lastWeekRank = redisUtil.getZSetRank("last_week_rank", 0, 6);
List<Map<String, Object>> hotPosts = new ArrayList<>();
for (ZSetOperations.TypedTuple typedTuple : lastWeekRank) {
Map<String, Object> map = new HashMap<>();
map.put("comment_count", typedTuple.getScore());
map.put("id", redisUtil.hget("rank_post_" + typedTuple.getValue(), "post:id"));
map.put("title", redisUtil.hget("rank_post_" + typedTuple.getValue(), "post:title"));
hotPosts.add(map);
}
return Result.succ(hotPosts);
}
测试结果:

致此,我们已经完成了获取本周热议的数据,但是,只是一个初始化而已,当有评论的时候还应该添加数据到我们的缓存中,还有页面的内容我们也应该写一些 ajax 加载数据,这些我们先留着,先到这里,以上是我们之前课程讲过的内容,大家先行消化一下。
(完)
MarkerHub 文章索引: