思路剖析

做每周热议,应该用缓存来做,如果间接查库的话,会对数据库造成压力。用缓存做的话,用Redis 来做缓存的话比拟适合一点。

# 利用Redsi 增加 数据命令# day:1 指的是在1号的时候 post:1 第一篇文章增加了 10 条评论。#前面 6 post:2 指的是 在1号第二篇增加了6条评论zadd day:1 10 post:1 6 post:2zadd day:2 10 post:1 6 post:2zadd day:3 10 post:1 6 post:2....zadd day:8 10 post:1 6 post:2#这样就实现了7天的记录

下面的命令能够帮咱们记录一下7天的所有的评论数。然而还没有帮咱们计算出来谁是评论最高的。看Redis 的sorted set有序汇合有个命令就能够帮咱们实现这个性能。

这个命令能够帮忙咱们实现并集,咱们只须要把7天的评论给做个并集就能够求进去。

# Redis 命令#意思是并集把这7天的 放到一个新的汇合外面 新的汇合是 week:rank 这样这个新的汇合外面就有了咱们的#7天的记录了union week:rank 7 day:1...day:8

Redis 命令实际一下看看

本地命令行测试

127.0.0.1:6379> pingPONG127.0.0.1:6379> zadd day:1 10 post:1(integer) 1127.0.0.1:6379> zadd day:2 10 post:1(integer) 1127.0.0.1:6379> zadd day:3 10 post:1(integer) 1127.0.0.1:6379> zadd day:1 5 post:2(integer) 1127.0.0.1:6379> zadd day:2 5 post:2(integer) 1127.0.0.1:6379> zadd day:3 5 post:2(integer) 1127.0.0.1:6379> keys *1) "day:1"2) "day:2"3) "day:3"

查看当天的排行榜命令 ZRevrange

127.0.0.1:6379> zrevrange day:1 0 -1 withscores1) "post:1"2) "10"3) "post:2"4) "5"127.0.0.1:6379>

每周的评论排行榜记录。因为我只有三天的,所以只写了3天的

127.0.0.1:6379> zrevrange week:rank 0 -1 withscores1) "post:1"2) "30"3) "post:2"4) "15"127.0.0.1:6379>

下面的记录是没有谬误的。上述的命令能够帮咱们简略的实现了咱们的想法,上面用代码来实现。下面还有一个小的 bug 就是当day:1这一天可能会呈现就是不可能间接就过完了。可能会一条一条的减少,这个时候应该应用的是自增这个命令来解决这个问题。

#什么时候+1 什么时候-1 就是当你 增加一条评论的时候就增加1 删除的的时候就减1ZINCRBY day:1 10 post:1

代码来进行实现

目前前端的款式,这样的话咱们就须要在我的项目一开始的时候就启动这个性能

@Component// 实现 启动类 ,还有 上下文的servlectpublic class ContextStartup implements ApplicationRunner, ServletContextAware {    // 注入 categoryService    @Autowired    IMCategoryService categoryService;    ServletContext servletContext;    // 注入post 的服务类    @Autowired    IMPostService postService;    @Override    public void run(ApplicationArguments args) throws Exception {        // 调用全查的办法        List<MCategory> list = categoryService.list(new QueryWrapper<MCategory>().eq("status", 0));        servletContext.setAttribute("List", list);        // 调用每周热评的办法        postService.initweek();    }    @Override    public void setServletContext(ServletContext servletContext) {        this.servletContext = servletContext;    }}
服务类serviceimpl类

大略的思路

  • 获取7天内发表的文章
  • 初始化文章的总浏览量
    • 缓存文章的根本信息(id,题目,评论数,作者 ID )
    • 这样就能够防止的查库。能够间接用咱们的缓存。
  • 做并集

这里须要一个Redis 的工具类,我在网上找到的,不是我写的。网上一大堆。间接拿来用就能够了。

举荐一个开源收费的 Spring Boot 最全教程:

https://github.com/javastacks/spring-boot-best-practice
package com.example.springbootblog.util;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ZSetOperations;import org.springframework.stereotype.Component;import org.springframework.util.CollectionUtils;import java.util.Collection;import java.util.List;import java.util.Map;import java.util.Set;import java.util.concurrent.TimeUnit;@Componentpublic 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);    }    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);    }}

实现类的代码

// 每周热评的办法@Overridepublic void initweek() {    //获取 7天的文章    List<MPost> posts = this.list(new QueryWrapper<MPost>().ge("created", DateUtil.lastWeek())              .select("id", "title", "user_id", "comment_count", "view_count", "created")      );// 获取到7天前的以及依照这几个查问,不须要全副查问      // 初始化文章的总评论      for (MPost post : posts) {          // 设置 key          String key = "day:rank:" + DateUtil.format(post.getCreated(), DatePattern.PURE_DATE_FORMAT);          // 缓存进去的评论数量          redisUtil.zSet(key, post.getId(), post.getCommentCount());          //设置主动过期 7天过期          long between = DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY);          long expireTime = (7 - between) * 24 * 60 * 60; // 无效 工夫          redisUtil.expire(key, expireTime);          // 缓存文章的一些根本信息          this.hashCachePost(post, expireTime);      }      // 做并集      this.zunionAndStore();} /**  * 文章每周评论数量并集操作  **/ private void zunionAndStore() {     String destkey = "day:rank:" + DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT);     // 设置并集后的新的 key     String newkey = "week:rank";     ArrayList<String> otherKeys = new ArrayList<>();     // 计算7天的     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(destkey, otherKeys, newkey); } /**  * 文章作者缓存  **/ private void hashCachePost(MPost post, long expireTime) {     // 设置 key     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);     } }}

这样就能够把咱们的命令行转换成代码的模式。就能够把咱们的数据库的数据先存到缓存中去了。

成果

127.0.0.1:6379> keys *1) "rank:post:4"2) "week:rank"3) "day:rank:20210724"4) "rank:post:3"5) "rank:post:2"6) "day:rank:20210726"#查看咱们并集完后的数据 id 为 3 的有 1条评论。127.0.0.1:6379> zrevrange week:rank 0 -1 withscores1) "3"2) "1"3) "2"4) "1"5) "4"6) "0"127.0.0.1:6379>

数据库中id 为 3 的有 1条评论

的确只有一条评论的

前端展现进去

这里的思路就比较简单了,把咱们的数据从缓存中取出来就能够。用的freemarker能够自定义标签。我自定义了标签。

Hosttemplate

/** * 本周热议 */@Componentpublic class HotsTemplate extends TemplateDirective {    @Autowired    RedisUtil redisUtil;    @Override    public String getName() {        return "hots";    }    @Override    public void execute(DirectiveHandler handler) throws Exception {// 设置 key        String key ="week:rank";        Set<ZSetOperations.TypedTuple> zSetRank = redisUtil.getZSetRank(key, 0, 6);        ArrayList<Map> maps = new ArrayList<>();        // 便当        for (ZSetOperations.TypedTuple typedTuple : zSetRank) {            // 创立 Map            HashMap<String, Object> map = new HashMap<>();            Object post_id = typedTuple.getValue();            String PostHashKey = "rank:post:" +post_id;            map.put("id",post_id);            map.put("title",redisUtil.hget(PostHashKey,"post:title"));            map.put("commentCount",typedTuple.getScore());            maps.add(map);        }        handler.put(RESULTS,maps).render();    }}

FreemarkerConfig把咱们写的便签注入就能够应用咱们自定义的标签了

@Configurationpublic class FreemarkerConfig {    @Autowired    private freemarker.template.Configuration configuration;    @Autowired    PostsTemplate postsTemplate;    @Autowired    HotsTemplate hotsTemplate;    @PostConstruct    public void setUp() {        configuration.setSharedVariable("timeAgo", new TimeAgoMethod());        configuration.setSharedVariable("posts", postsTemplate);        configuration.setSharedVariable("hosts", hotsTemplate);    }}

前端的页面获取

成果

总结

在做这个性能的时候。不够全面。尽管写完了,然而应该是获取7天内的评论。我获取了7天的文章。尽管是个 bug 然而我不想修复。就这样吧。当初能用就行。情理是一样的。到时候有工夫出问题了在改吧。累了,有情的代码机器记录代码生存中。。。。。

起源:blog.csdn.net/m0_46937429/article/details/119172118

近期热文举荐:

1.1,000+ 道 Java面试题及答案整顿(2022最新版)

2.劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!

5.《Java开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞+转发哦!