程序员的资源宝库

网站首页 > gitee 正文

博客项目学习笔记九:侧边栏本周热议

sanyeah 2024-04-03 18:36:36 gitee 7 ℃ 0 评论

博客项目目录: 请戳这里

准备

原来的侧边栏:

需求:将实际的本周热议文章填充到模板里
分析:可以通过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层进行实现

实现分为三步:

  1. 初始化文章的总评论数,相当于之前我们分析的ZADD操作,先获取7天内发表的文章,然后将7天的键值,文章id,评论数一起添加到有序集合。最后给文章设置过期时间
  2. 将文章的id、标题、评论数、浏览量添加到缓存
  3. 将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.新增或删除评论后跟新本周热议

  1. 获取文章id,如果新增评论,则设置缓存评论数加1,如果删除,则设置缓存评论数减1。
  2. 缓存文章基本信息,并设置过期时间
  3. 重新对近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

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表