博客项目目录: 请戳这里
准备
原来的侧边栏:
需求:将实际的本周热议文章填充到模板里
分析:可以通过redis的有序集合来做,redis的SortedSet有三个方法ZADD、ZUNIONSTORE和ZREVRANGE,我们将每一天看作一个key,每篇文章看作集合成员,文章的评论数看作分数,那么可以先将近7天的文章以及评论数添加(ZADD)到每一天的key,然后再将这7天的key通过ZUNIONSTORE方法进行合并,合并后的key命名为周排行,最后对周排行的所有文章进行降序排列(ZREVRANGE)
1.SortedSet方法分析
- 方法1(ZADD)
ZADD key score member [[score member] [score member] ...]
将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值,并通过重新插入这个 member 元素,来保证该
member 在正确的位置上。score 值可以是整数值或双精度浮点数。
如果 key 不存在,则创建一个空的有序集并执行 ZADD 操作。
当 key 存在但不是有序集类型时,返回一个错误。
这里day:18,day:19和day:20是key,post:1和post:2是member,中间的数量是score,以下操作,表示将文章1和文章2这三天的评论数分别加到了这三天的集合里。最后对18号从第一个到倒数第一个(0到-1)进行逆序排列,得到这一天的本日热议
test:0>zadd day:18 20 post:1
"1"
test:0>zadd day:19 20 post:1
"1"
test:0>zadd day:20 20 post:1
"1"
test:0>zadd day:18 10 post:2
"1"
test:0>zadd day:19 10 post:2
"1"
test:0>zadd day:20 10 post:2
"1"
test:0>keys *
1) "day:20"
2) "day:18"
3) "day:19"
test:0>zrevrange day:18 0 -1 withscores
1) "post:1"
2) "20"
3) "post:2"
4) "10"
- 方法2(ZUNIONSTORE)
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight
...]] [AGGREGATE SUM|MIN|MAX]计算给定的一个或多个有序集的并集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集(结果集)储存到 destination 。
默认情况下,结果集中某个成员的 score 值是所有给定集下该成员 score 值之和 。
WEIGHTS
使用 WEIGHTS 选项,你可以为 每个 给定有序集 分别 指定一个乘法因子(multiplication
factor),每个给定有序集的所有成员的 score 值在传递给聚合函数(aggregation
function)之前都要先乘以该有序集的因子。如果没有指定 WEIGHTS 选项,乘法因子默认设置为 1 。
AGGREGATE
使用 AGGREGATE 选项,你可以指定并集的结果集的聚合方式。
默认使用的参数 SUM ,可以将所有集合中某个成员的 score 值之 和 作为结果集中该成员的 score 值;使用参数 MIN,可以将所有集合中某个成员的 最小 score 值作为结果集中该成员的 score 值;而参数 MAX 则是将所有集合中某个成员的最大 score 值作为结果集中该成员的 score 值。
下面的第一条命名是合并这三天的记录,第二条命令展示所有keys,发现多了个周排行的key,
test:0>zunionstore week:rank 3 day:20 day:18 day:19
"2"
test:0>keys *
1) "day:20"
2) "week:rank"
3) "day:18"
4) "day:19"
- 方法3(ZREVRANGE)
ZREVRANGE key start stop [WITHSCORES]
返回有序集 key 中,指定区间内的成员。
其中成员的位置按 score 值递减(从大到小)来排列。 具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列。 除了成员按 score 值递减的次序排列这一点外, ZREVRANGE 命令的其他方面和ZRANGE 命令一样。
下面的命令是将周排行所有成员降序排列,得到最终文章的排行
test:0>zrevrange week:rank 0 -1 withscores
1) "post:1"
2) "60"
3) "post:2"
4) "30"
2.添加hutool工具依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.1.17</version>
</dependency>
3.在项目启动配置类里新增周排行方法
@Component
public class ContextStartup implements ApplicationRunner, ServletContextAware {
@Autowired
PostService postService;
@Override
public void run(ApplicationArguments args) throws Exception {
postService.initWeekRank();
}
}
4.在PostService定义对应接口
public interface PostService extends IService<Post> {
void initWeekRank();
}
5.在impl层进行实现
实现分为三步:
- 初始化文章的总评论数,相当于之前我们分析的ZADD操作,先获取7天内发表的文章,然后将7天的键值,文章id,评论数一起添加到有序集合。最后给文章设置过期时间
- 将文章的id、标题、评论数、浏览量添加到缓存
- 将7天内的所有有序集合做并集,得到周排行
注意:在这个过程中引入RedisUtil工具类
/**
* 本周热议初始化
*/
@Override
public void initWeekRank() {
// 获取7天的发表的文章
List<Post> posts = this.list(new QueryWrapper<Post>()
.ge("created", DateUtil.offsetDay(new Date(), -7)) // 11号
.select("id, title, user_id, comment_count, view_count, created")
);
// 1.初始化文章的总评论量
for (Post post : posts) {
//定义7天内集合的键
String key = "day:rank:" + DateUtil.format(post.getCreated(), DatePattern.PURE_DATE_FORMAT);
redisUtil.zSet(key, post.getId(), post.getCommentCount());
// 7天后自动过期(15号发表,7-(18-15)=4)
long between = DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY);
long expireTime = (7 - between) * 24 * 60 * 60; // 有效时间
redisUtil.expire(key, expireTime);
// 2.缓存文章的一些基本信息(id,标题,评论数量,浏览量)
this.hashCachePostIdAndTitle(post, expireTime);
}
// 3.做并集
this.zunionAndStoreLast7DayForWeekRank();
}
6.hashCachePostIdAndTitle方法实现
缓存文章的id、标题、评论数、浏览量
/**
* 缓存文章的基本信息
* @param post
* @param expireTime
*/
private void hashCachePostIdAndTitle(Post post, long expireTime) {
String key = "rank:post:" + post.getId();
boolean hasKey = redisUtil.hasKey(key);
if(!hasKey) {
redisUtil.hset(key, "post:id", post.getId(), expireTime);
redisUtil.hset(key, "post:title", post.getTitle(), expireTime);
redisUtil.hset(key, "post:commentCount", post.getCommentCount(), expireTime);
redisUtil.hset(key, "post:viewCount", post.getViewCount(), expireTime);
}
}
7.zunionAndStoreLast7DayForWeekRank方法实现
首先定义周排行的key,将当天的排行与之前6天的排行(存到一个List容器里)做并集,合并到周排行。
/**
* 合并本周每日评论数量
*/
private void zunionAndStoreLast7DayForWeekRank() {
String currentKey = "day:rank:" + DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT);
String destKey = "week:rank";
List<String> otherKeys = new ArrayList<>();
for(int i=-6; i < 0; i++) {
String temp = "day:rank:" +
DateUtil.format(DateUtil.offsetDay(new Date(), i), DatePattern.PURE_DATE_FORMAT);
otherKeys.add(temp);
}
redisUtil.zUnionAndStore(currentKey, otherKeys, destKey);
}
8.对redis缓存进行配置
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
jackson2JsonRedisSerializer.setObjectMapper(new ObjectMapper());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
}
9.运行程序,查看redis缓存
test:0>flushdb
"OK"
test:0>keys *
1) "rank:post:5"
2) "rank:post:1"
3) "week:rank"
4) "day:rank:20210422"
5) "day:rank:20210423"
test:0>zrevrange week:rank 0 -1 withscores
1) "1"
2) "10"
3) "5"
4) "2"
10.自定义本周热议模板
模板里定义一个hots标签,用于将后端查询到的内容传到前端,然后execute方法里通过缓存获取本周热议信息(文章id,标题,评论数),然后存到一个Map类型的list,命名为hotPosts,作为结果与hots标签绑在一起。
/**
* 本周热议
*/
@Component
public class HotsTemplate extends TemplateDirective {
@Autowired
RedisUtil redisUtil;
@Override
public String getName() {
return "hots";
}
@Override
public void execute(DirectiveHandler handler) throws Exception {
String weekRankKey = "week:rank";
Set<ZSetOperations.TypedTuple> typedTuples = redisUtil.getZSetRank(weekRankKey, 0, 6);
List<Map> hotPosts = new ArrayList<>();
for (ZSetOperations.TypedTuple typedTuple : typedTuples) {
Map<String, Object> map = new HashMap<>();
Object value = typedTuple.getValue(); // post的id
String postKey = "rank:post:" + value;
map.put("id", value);
map.put("title", redisUtil.hget(postKey, "post:title"));
map.put("commentCount", typedTuple.getScore());
hotPosts.add(map);
}
handler.put(RESULTS, hotPosts).render();
}
}
11.在配置类里将标签注入到前端
@Configuration
public class FreemarkerConfig {
@Autowired
HotsTemplate hotsTemplate;
@PostConstruct
public void setUp() {
configuration.setSharedVariable("hots", hotsTemplate);
}
}
12.修改right.ftl
<dl class="fly-panel fly-list-one">
<dt class="fly-panel-title">本周热议</dt>
<@hots>
<#list results as post>
<dd>
<a href="/post/${post.id}">${post.title}</a>
<span><i class="iconfont icon-pinglun1"></i> ${post.commentCount}</span>
</dd>
</#list>
</@hots>
</dl>
13.测试结果
查看数据库:
发现标题和评论数与缓存、数据库是一致的,并且创建时间也是最近7天内
14.新增或删除评论后跟新本周热议
- 获取文章id,如果新增评论,则设置缓存评论数加1,如果删除,则设置缓存评论数减1。
- 缓存文章基本信息,并设置过期时间
- 重新对近7天的文章做并集
public interface PostService extends IService<Post> {
void incrCommentCountAndUnionForWeekRank(long postId, boolean isIncr);
}
@Override
public void incrCommentCountAndUnionForWeekRank(long postId, boolean isIncr) {
String currentKey = "day:rank:" + DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT);
redisUtil.zIncrementScore(currentKey, postId, isIncr? 1: -1);
Post post = this.getById(postId);
// 7天后自动过期(15号发表,7-(18-15)=4)
long between = DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY);
long expireTime = (7 - between) * 24 * 60 * 60; // 有效时间
// 缓存这篇文章的基本信息
this.hashCachePostIdAndTitle(post, expireTime);
// 重新做并集
this.zunionAndStoreLast7DayForWeekRank();
}
参考资料:
https://github.com/MarkerHub/eblog
本文暂时没有评论,来添加一个吧(●'◡'●)