# Part06-侧边栏本周热议

blog
│  pom.xml
│
├─src
│  └─main
│      ├─java
│      │  └─org
│      │      └─myslayers
│      │          ├─config
│      │          │      RedisConfig.java
│      │          │      ContextStartup.java
│      │          │      FreemarkerConfig.java
│      │          │
│      │          ├─service
│      │          │  │  PostService.java
│      │          │  │
│      │          │  └─impl
│      │          │         PostServiceImpl.java
│      │          │
│      │          ├─template
│      │          │      HotsTemplate.java
│      │          │
│      │          ├─utils
│      │          │      RedisUtil.java
│      │
│      └─resources
│          ├─templates
│          │  └─inc
│          │        right.ftl

# 6.1 Redis环境搭建

  • pom.xml :项目依赖,【添加 redis 依赖,添加 hutool 依赖】
<dependencies>
    <!--Redis-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!--hutool:工具包,例如DateUtils工具类...-->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>4.1.17</version>
    </dependency>
</dependencies>
  • RedisConfig.java :配置类,【考虑到 redis 序列化后出现乱码问题,使用 RedisConfig 配置类进行编码的处理】
/**
 * 指定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;
    }

}
  • RedisUtil.java :工具类
/**
 * RedisUtil 工具类
 */
@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    //============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     *
     * @param key 键
     * @param delta  要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     *
     * @param key 键
     * @param delta  要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    //================================Map=================================

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    //============================set=============================

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    //===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束  0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引  index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    //================有序集合 sort set===================
    /**
     * 有序set添加元素
     *
     * @param key
     * @param value
     * @param score
     * @return
     */
    public boolean zSet(String key, Object value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    public long batchZSet(String key, Set<ZSetOperations.TypedTuple> typles) {
        return redisTemplate.opsForZSet().add(key, typles);
    }

    public void zIncrementScore(String key, Object value, long delta) {
        redisTemplate.opsForZSet().incrementScore(key, value, delta);
    }

    /**
     * ZUNIONSTORE destination numkeys key [key ...]中,无numkeys,因为numkeys = key + otherKeys
     *
     * @param key 【第7天的key,即今天的key】
     * @param otherKeys 【前6天的keys,用Collection集合来保存】
     * @param destKey 描述key
     */
    public void zUnionAndStore(String key, Collection otherKeys, String destKey) {
        redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey);
    }

    /**
     * 获取zset数量
     * @param key
     * @param value
     * @return
     */
    public long getZsetScore(String key, Object value) {
        Double score = redisTemplate.opsForZSet().score(key, value);
        if(score==null){
            return 0;
        }else{
            return score.longValue();
        }
    }

    /**
     * 获取有序集 key 中成员 member 的排名 。
     * 其中有序集成员按 score 值递减 (从大到小) 排序。
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<ZSetOperations.TypedTuple> getZSetRank(String key, long start, long end) {
        return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
    }

}

# 6.2 本周热议的【基本原理】

  • 缓存热评文章——哈希表 Hash
  • 评论数量排行——有序列表 sortedSet:ZADD(添加)、ZREVRANGE(展示)、ZUNIONSTORE(并集)
  • ZADD key score member [[score member] [score member] ...]
127.0.0.1:6379> ZADD day:18 10 post:1 6 post:2 4 post:3
(integer) 3
127.0.0.1:6379> ZADD day:19 10 post:1 6 post:2 4 post:3
(integer) 3
127.0.0.1:6379> ZADD day:20 10 post:1 6 post:2 4 post:3
(integer) 3
127.0.0.1:6379> ZADD day:21 10 post:1 6 post:2 4 post:3
(integer) 3
127.0.0.1:6379> ZADD day:22 10 post:1 6 post:2 4 post:3
(integer) 3
127.0.0.1:6379> ZADD day:23 10 post:1 6 post:2 4 post:3
(integer) 3
127.0.0.1:6379> ZADD day:24 10 post:1 6 post:2 4 post:3
(integer) 3
  • ZREVRANGE key start stop [WITHSCORES]
127.0.0.1:6379> ZREVRANGE day:18 0 -1 withscores
1) "post:1"
2) "10"
3) "post:2"
4) "6"
5) "post:3"
6) "4"
  • ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
127.0.0.1:6379> ZUNIONSTORE week:rank 7 day:18 day:19 day:20 day:21 day:22 day:23 day:24
1) "post:1"
2) "post:2"
  • 查看排行榜
127.0.0.1:6379> ZREVRANGE week:rank 0 -1 withscores
1) "post:1"
2) "70"
3) "post:2"
4) "42"
5) "post:3"
6) "28"
  • 添加/删除评论
127.0.0.1:6379> ZINCRBY day:18 10 post:1
"20"
127.0.0.1:6379> ZREVRANGE  day:18 0 -1 withscores
1) "post:1"
2) "20"
3) "post:2"
4) "6"
5) "post:3"
6) "4"
127.0.0.1:6379> ZINCRBY day:18 -10 post:1
"10"

# 6.3 本周热议的【初始化操作】

  • 实现逻辑:
    • 项目启动前,获取【近 7 天文章】
    • 初始化【近 7 天文章】的总评论量(先使用 SortedSet 集合对【排行榜 7 天内全部文章】进行 zadd 操作,并设置它们 expire 为 7 天;再使用 Hash 哈希表对【排行榜 7 天内全部文章】进行 hexists 判断,再 hset 缓存操作)
      • 添加 add——将【近 7 天文章】创建日期时间作为 key 值,每篇文章对应的 id 作为它的 value 值,每篇文章对应的评论 comment 作为它的 score 值,并使用 redis 的工具类(RedisUtil),对文章的具体属性进行 zSet()缓存操作
      • 过期 expire——让【近 7 天文章】的 key 过期: 7-(当前时间-创建时间)= 过期时间
      • 缓存——缓存【近 7 天文章】的一些基本信息,例如文章 id,标题 title,评论数量,作者信息...方便访问【近 7 天文章】时,直接 redis,而非 MySQL
        • 先对文章进行 EXISTS 判断其缓存是否存在
        • 如果 false 不存在,则再 hset 缓存操作
    • 对【近 7 天文章】做并集运算(zUnionAndStore), 并使用根据评论量的数量从大到小进行展示(zrevrange)
  • ContextStartup.java :配置类
/**
 * Context配置类
 */
@Component
public class ContextStartup implements ApplicationRunner, ServletContextAware {

    @Autowired
    CategoryService categoryService;

    ServletContext servletContext;

    @Autowired
    PostService postService;

    /**
     * 项目启动时,会同时调用该run方法:
     *
     * 加载导航栏中的“提问、分享、讨论、建议”,并将其list放入servletContext上下文对象
     * 加载本周热议
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        List<Category> categories = categoryService.list(new QueryWrapper<Category>()
                .eq("status", 0)
        );
        servletContext.setAttribute("categorys", categories);

        postService.initWeekRank();
    }

    /**
     * servletContext上下文对象
     */
    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }
}
  • PostServiceImpl.java :业务层实现
@Service
public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements PostService {

    @Autowired
    RedisUtil redisUtil;

    /**
     * 项目启动前,初始化本周热议(近7天全部文章评论量的排行榜)
     */
    @Override
    public void initWeekRank() {
        //1.获取【近7天文章】
        List<Post> posts = this.list(new QueryWrapper<Post>()
                .gt("created", DateUtil.offsetDay(new Date(), -6))  //根据created时间,对最近7天内的文章进行筛选
                .select("id, title, user_id, comment_count, view_count, created") //对文章的属性进行筛选,加快查询速率
        );

        //2.初始化【近7天文章】的总评论量(先使用SortedSet集合对【排行榜7天内全部文章】进行zadd操作,并设置它们expire为7天;再使用Hash哈希表对【排行榜7天内全部文章】进行hexists判断,再hset缓存操作)
        for (Post post : posts) {
            //1.添加add——|day:rank:20210202--0208|,将【近7天文章】创建日期时间作为key值,每篇文章对应的id作为它的value值,每篇文章对应的评论comment作为它的score值,并使用redis的工具类(RedisUtil),对文章的具体属性进行zSet()缓存操作
            String zKey = "day:rank:" + DateUtil.format(post.getCreated(), DatePattern.PURE_DATE_FORMAT);
            redisUtil.zSet(zKey, post.getId(), post.getCommentCount());//阅读redisUtil工具类,可知zSet等同于zadd

            //2.过期expire——|day:rank:20210202--0208|,让【近7天文章】的key过期: 7-(当前时间-创建时间)= 过期时间
            long expireTime = (7 - DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY)) * 24 * 60 * 60;
            redisUtil.expire(zKey, expireTime);

            //3.缓存——|day:rank:post:1~16|,缓存【近7天文章】的一些基本信息,例如文章id,标题title,评论数量,作者信息...方便访问【近7天文章】时,直接redis,而非MySQL
            //3.1先对文章进行EXISTS判断其缓存是否存在
            String hKey = "day:rank:post:" + post.getId();
            if (!redisUtil.hasKey(hKey)) {
                //3.2如果false不存在,则再hset缓存操作
                redisUtil.hset(hKey, "post-id", post.getId(), expireTime);
                redisUtil.hset(hKey, "post-title", post.getTitle(), expireTime);
                redisUtil.hset(hKey, "post-commentCount", post.getCommentCount(), expireTime);
                redisUtil.hset(hKey, "post-viewCount", post.getViewCount(), expireTime);
            }
        }

        //3.对【近7天文章】做并集运算(zUnionAndStore), 并使用根据评论量的数量从大到小进行展示(zrevrange)
        String currentKey = "day:rank:" + DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT);
        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);
        }
        String destKey = "week:rank";
        redisUtil.zUnionAndStore(currentKey, otherKeys, destKey);
    }
}

# 6.4 本周热议的【更新操作】

  • 实现逻辑:
    • 自增/自减评论数
    • 更新这篇文章的缓存时间,并更新这篇文章的基本信息
    • 对【近 7 天文章】重新做并集运算(zUnionAndStore), 并使用根据评论量的数量从大到小进行展示(zrevrange)
  • PostServiceImpl.java :业务层实现
@Service
public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements PostService {

    @Autowired
    RedisUtil redisUtil;

    /**
     * 本周热议:增加评论后,通过自增/自减评论数、再对排行榜做并集运算
     */
    @Override
    public void incrCommentCountAndUnionForWeekRank(Post post, boolean isIncr) {
        //1.自增/自减评论数
        String currentKey = "day:rank:" + DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT);
        redisUtil.zIncrementScore(currentKey, post.getId(), isIncr ? 1 : -1);

        //2.更新这篇文章的缓存时间,并更新这篇文章的基本信息
        String zKey = "day:rank:" + DateUtil.format(post.getCreated(), DatePattern.PURE_DATE_FORMAT);
        long expireTime = (7 - DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY)) * 24 * 60 * 60;
        redisUtil.expire(zKey, expireTime);
        String hKey = "day:rank:post:" + post.getId();
        if (!redisUtil.hasKey(hKey)) {
            //3.2如果false不存在,则再hset缓存操作
            redisUtil.hset(hKey, "post-id", post.getId(), expireTime);
            redisUtil.hset(hKey, "post-title", post.getTitle(), expireTime);
            redisUtil.hset(hKey, "post-commentCount", post.getCommentCount(), expireTime);
            redisUtil.hset(hKey, "post-viewCount", post.getViewCount(), expireTime);
        }

        //3.对【近7天文章】重新做并集运算(zUnionAndStore)
        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);
        }
        String destKey = "week:rank";
        redisUtil.zUnionAndStore(currentKey, otherKeys, destKey);
    }
}

# 6.5 本周热议的【标签】

  • HotsTemplate.java :标签类,【开发标签】
/**
 * 本周热议文章【标签】
 */
@Component
public class HotsTemplate extends TemplateDirective {

    @Autowired
    RedisUtil redisUtil;

    @Override
    public String getName() {
        return "hots";
    }

    @Override
    public void execute(DirectiveHandler handler) throws Exception {
        List<Map> hostPost = new ArrayList<>();

        // 获取有序集 key 中成员 member 的排名,其中有序集成员按 score 值递减 (从大到小) 排序
        Set<ZSetOperations.TypedTuple> typedTuples = redisUtil.getZSetRank("week:rank", 0, 6);
        for (ZSetOperations.TypedTuple typedTuple : typedTuples) {
            Map<String, Object> map = new HashMap<>();

            //zSet(key, value, score)  -> zSet(文章日期, 文章id, 文章评论数commentCount),此处取出zSet中的value,即文章id
            String postHashKey = "day:rank:post:" + typedTuple.getValue();

            map.put("id", redisUtil.hget(postHashKey, "post-id"));
            map.put("title", redisUtil.hget(postHashKey, "post-title"));
            map.put("commentCount", redisUtil.hget(postHashKey, "post-commentCount"));
            map.put("viewCount", redisUtil.hget(postHashKey, "post-viewCount"));

            hostPost.add(map);
        }

        handler.put(RESULTS, hostPost).render();
    }
}
  • FreemarkerConfig.java :配置类,【注册标签】
/**
 * Freemarker配置类
 */
@Configuration
public class FreemarkerConfig {

    @Autowired
    private freemarker.template.Configuration configuration;

    @Autowired
    TimeAgoMethod timeAgoMethod;

    @Autowired
    PostsTemplate postsTemplate;

    @Autowired
    HotsTemplate hotsTemplate;

    /**
     * 注册为“timeAgo”函数:快速实现日期转换 ;注册为“posts”函数:快速实现分页
     */
    @PostConstruct
    public void setUp() {
        configuration.setSharedVariable("timeAgo", timeAgoMethod);
        configuration.setSharedVariable("details", postsTemplate);
        configuration.setSharedVariable("hots", hotsTemplate);
    }
}
  • right.ftl :模板引擎
<#--【三(2)、右侧md4】-->
<div class="layui-col-md4">

  <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>

  <div class="fly-panel">
    <div class="fly-panel-title">
      站点信息
    </div>
    <div class="fly-panel-main">
      <a href="https://github.com/" target="_blank" class="fly-zanzhu"
         time-limit="2017.09.25-2099.01.01" style="background-color: #5FB878;">Don't let joy take
        you down !</a>
    </div>
  </div>

  <div class="fly-panel fly-link">
    <h3 class="fly-panel-title">友情链接</h3>
    <dl class="fly-panel-main">
      <dd>
        <a href="https://www.youtube.com/" target="_blank">YouTube</a>
      <dd>
      <dd>
        <a href="https://www.facebook.com/" target="_blank">Facebook</a>
      <dd>
      <dd>
        <a href="https://www.twitter.com/" target="_blank">Twitter</a>
      <dd>
      <dd>
        <a href="https://www.instagram.com/" target="_blank">Instagram</a>
      <dd>
          <#--
          <dd>
              <a href="mailto:[email protected]?subject=%E7%94%B3%E8%AF%B7Fly%E7%A4%BE%E5%8C%BA%E5%8F%8B%E9%93%BE" class="fly-link">申请友链</a>
          <dd>
          -->
    </dl>
  </div>

</div>
Last Updated: 6/6/2024, 7:53:07 AM