每周热议功能

  • 大概思路
    • Redis 命令实践一下看看
    • 代码来进行实现
    • 前端展示出来
  • 总结

大概思路

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

# 利用Redsi 添加 数据命令
# day:1 指的是在1号的时候 post:1 第一篇文章添加了 10 条评论。
#后面 6 post:2 指的是 在1号第二篇添加了6条评论
zadd day:1 10 post:1 6 post:2
zadd day:2 10 post:1 6 post:2
zadd 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> ping
PONG
127.0.0.1:6379> zadd day:1 10 post:1
(integer) 1
127.0.0.1:6379> zadd day:2 10 post:1
(integer) 1
127.0.0.1:6379> zadd day:3 10 post:1
(integer) 1
127.0.0.1:6379> zadd day:1 5 post:2
(integer) 1
127.0.0.1:6379> zadd day:2 5 post:2
(integer) 1
127.0.0.1:6379> zadd day:3 5 post:2
(integer) 1
127.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 withscores
1) "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 withscores
1) "post:1"
2) "30"
3) "post:2"
4) "15"
127.0.0.1:6379>

上面的记录是没有错误的。上述的命令可以帮我们简单的实现了我们的想法。下面用代码来实现。上面还有一个小的 bug 就是当day:1这一天可能会出现就是不可能直接就过完了。可能会一条一条的增加,这个时候应该使用的是自增这个命令来解决这个问题。

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

代码来进行实现

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

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

服务类serviceimpl类
大概的思路

1,// 获取7天内发表的文章
2,// 初始化文章的总阅读量
// 缓存文章的基本信息(id,标题,评论数,作者 ID )
// 这样就可以避免的查库。可以直接用我们的缓存。
3,// 做并集

这里需要一个Redis 的工具类,我在网上找到的,不是我写的。网上一大堆。直接拿来用就可以了

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;@Component
public class RedisUtil {@Autowiredprivate 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) {// 设置 keyString 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);// 设置并集后的新的 keyString 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) {// 设置 keyString 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 withscores
1) "3"
2) "1"
3) "2"
4) "1"
5) "4"
6) "0"
127.0.0.1:6379>

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


确实只有一条评论的

前端展示出来

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

/*** 本周热议*/
@Component
public class HotsTemplate extends TemplateDirective {@AutowiredRedisUtil redisUtil;@Overridepublic String getName() {return "hots";}@Overridepublic void execute(DirectiveHandler handler) throws Exception {// 设置 keyString key ="week:rank";Set<ZSetOperations.TypedTuple> zSetRank = redisUtil.getZSetRank(key, 0, 6);ArrayList<Map> maps = new ArrayList<>();// 便利for (ZSetOperations.TypedTuple typedTuple : zSetRank) {// 创建 MapHashMap<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把我们写的便签注入就可以使用我们自定义的标签了

@Configuration
public class FreemarkerConfig {@Autowiredprivate freemarker.template.Configuration configuration;@AutowiredPostsTemplate postsTemplate;@AutowiredHotsTemplate hotsTemplate;@PostConstructpublic void setUp() {configuration.setSharedVariable("timeAgo", new TimeAgoMethod());configuration.setSharedVariable("posts", postsTemplate);configuration.setSharedVariable("hosts", hotsTemplate);}}

前端的页面获取

效果

总结

在做这个功能的时候。不够全面。虽然写完了,但是应该是获取7天内的评论。我获取了7天的文章。虽然是个 bug 但是我不想修复。就这样吧。现在能用就行。道理是一样的。到时候有时间出问题了在改吧。累了,无情的代码机器记录代码生活中。。。。。。

学习记录,利用Redis的(sorted set)做每周热评的功能相关推荐

  1. 利用 Redis 的 sorted set 做每周热评的功能

    大概思路 思路分析 做每周热议,应该用缓存来做,如果直接查库的话,会对数据库造成压力.用缓存做的话,用Redis 来做缓存的话比较合适一点. 上面的命令可以帮我们记录一下7天的所有的评论数.但是还没有 ...

  2. 小样本学习记录————利用所有数据的元学习Few-shot Text Classification with Distributional Signatures

    小样本学习记录----利用所有数据的元学习Few-shot Text Classification with Distributional Signatures 在计算机视觉中,低水平的模式是可以跨学 ...

  3. 学习记录贴:上班摸鱼做一个看lol文字直播的小工具

    学习记录贴:上班摸鱼做一个看lol文字直播的小工具 前言 运行图 流程及思路 项目及代码 总结 前言 本人纯新手小白,上班练手. 致谢: 基于Python命令行的NBA文字直播小工具. 运行图 流程及 ...

  4. 面试官:Redis 如何实现每周热评功能?说说思路!

    思路分析 做每周热议,应该用缓存来做,如果直接查库的话,会对数据库造成压力.用缓存做的话,用Redis 来做缓存的话比较合适一点. # 利用Redsi 添加 数据命令 # day:1 指的是在1号的时 ...

  5. 渗透学习-学习记录-利用浏览器的开发者工具实时修改网页前端JS代码(实现绕过)

    文章目录 前言 一.JS前端的修改 前言 最近学习了一些有关于JS脚本搭建网站方面的安全知识.通常来说JS是前端的页面代码,因此我们可以直接修改前端的JS代码来实现绕过,故我试着做了一下利用浏览器的开 ...

  6. 学习记录 第01章 数据能做什么

    一.优秀数据分析师的三个特点 1.好奇,是数据分析师的动力 从数据中发现问题 解决问题 2.谨慎,数据分析师成熟的标致 没有完美的数据,也没有完美的方法 使用多维数据比较印证 从多个角度关注问题,不能 ...

  7. python学习记录—— 利用再分析数据绘制天气图

    1 方法数据 1.1数据 数据采用1°*1°的NCEP/NCAR的再分析数据,数据格式为grib2. 数据下载地址:FNL1*1 说明:需要用邮箱注册账号,之后按需求下载具体日期的数据,每日4个时次, ...

  8. Redis实战之百度首页新闻热榜

    目录 目标 功能 分析 代码实现 控制层 业务层 工具类 相关博文 目标 利用Redis实现类似百度首页新闻热榜功能. 功能 新闻排行榜以热度为指标降序排序,这里假设热度就是评论数量且统计的热度时间范 ...

  9. Redis实战案例及问题分析之好友关注功能(关注、共同好友、消息推送)

    关注和取关 需求:基于该表数据结构,实现两个接口 关注和取关接口 @Overridepublic Result follow(Long followUserId, Boolean isFollow) ...

最新文章

  1. HALC: High throughput algorithm for long read error correction
  2. arduino如何调用mysql,【 实测可用 】Arduino 直接访问 mysql
  3. html5如何掷色子,html5 canvas掷骰子(简单,学习基础canvas)
  4. javascript箭头函数和this的指向问题
  5. 命令行运行vbs脚本并传参数给vbs中的变量简单示例
  6. 什么是Session?
  7. vlan跨交换机 udp广播_【详解】VLAN和VXLAN有何区别?VXLAN运用场景有哪些?
  8. 深入理解Java中的volatile关键字
  9. django模板的使用方法
  10. 哈工大离散数学期末试题2021回忆版
  11. ONNX: export failure: Exporting the operator silu to ONNX opset version 12 is not suppo
  12. 哥伦比亚大学 Schulzrinne 教授:撰写科研论文详细教程
  13. 重启osd服务失败:Start request repeated too quickly
  14. 一些个人总结(2008)
  15. 生死看淡,不服就GAN
  16. STM32MINI板写的超声波代码
  17. Sql有条件的插入语句
  18. 【大学生活】优秀的有趣的博客
  19. 常用的四拍子,八拍子
  20. 如何设计一个通用的查询接口

热门文章

  1. golang web开发
  2. CUDA PYTHON 并行计算基础
  3. 2021-2027全球与中国触控马达市场现状及未来发展趋势
  4. html table clonenode,HTML DOM cloneNode() 方法
  5. 3006-60-8,2-Acetamido-1,3,4,6-tetra-O-acetyl-2-deoxy-b-D-galactopyranose,半乳糖 1,3,4,6-四乙酸酯
  6. b站Java康师傅小小自学5
  7. Scala 函数(二)
  8. 火焰纹章2java_火焰之纹章2手机java版有秘籍么?
  9. text-align:center居中
  10. .9图片四个边的含义