利用 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:8Redis 命令实践一下看看本地命令行测试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查看当天的排行榜命令ZRevrange127.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 Autowired IMCategoryService categoryService; ServletContext servletContext; // 注入post 的服务类 Autowired IMPostService postService; Override public void run(ApplicationArguments args) throws Exception { // 调用全查的方法 ListMCategory list categoryService.list(new QueryWrapperMCategory().eq(status, 0)); servletContext.setAttribute(List, list); // 调用每周热评的方法 postService.initweek(); } Override public void setServletContext(ServletContext servletContext) { this.servletContext servletContext; } }服务类serviceimpl类大概的思路获取7天内发表的文章初始化文章的总阅读量缓存文章的基本信息id,标题评论数作者 ID 这样就可以避免的查库。可以直接用我们的缓存。做并集这里需要一个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 { 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 MapObject, Object hmget(String key) { return redisTemplate.opsForHash().entries(key); } /** * HashSet * * param key 键 * param map 对应多个键值 * return true 成功 false 失败 */ public boolean hmset(String key, MapString, 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, MapString, 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 SetObject 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 ListObject 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 索引 index0时 0 表头1 第二个元素依次类推index0时-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, ListObject 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, ListObject 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, SetZSetOperations.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(scorenull){ return 0; }else{ return score.longValue(); } } /** * 获取有序集 key 中成员 member 的排名 。 * 其中有序集成员按 score 值递减 (从大到小) 排序。 * param key * param start * param end * return */ public SetZSetOperations.TypedTuple getZSetRank(String key, long start, long end) { return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end); } }实现类的代码// 每周热评的方法 Override public void initweek() { //获取 7天的文章 ListMPost posts this.list(new QueryWrapperMPost().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; ArrayListString 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 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 { Autowired RedisUtil redisUtil; Override public String getName() { return hots; } Override public void execute(DirectiveHandler handler) throws Exception { // 设置 key String key week:rank; SetZSetOperations.TypedTuple zSetRank redisUtil.getZSetRank(key, 0, 6); ArrayListMap maps new ArrayList(); // 便利 for (ZSetOperations.TypedTuple typedTuple : zSetRank) { // 创建 Map HashMapString, 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 { 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); } }前端的页面获取效果

相关新闻

BDNF、EGF、IL10、IL6、IL6R、MCP1、SOD、TH 八因子 CBA 流式多因子 Panel—— 融合神经修复、氧化应激、中枢炎症多维通路,搭建神经损伤一体化生物标志物检测方案

BDNF、EGF、IL10、IL6、IL6R、MCP1、SOD、TH 八因子 CBA 流式多因子 Panel—— 融合神经修复、氧化应激、中枢炎症多维通路,搭建神经损伤一体化生物标志物检测方案

神经退行性疾病、脑缺血、创伤性脑损伤、神经炎症领域研究,同时涉及神经营养重塑、氧化应激损伤、中枢炎症浸润三大交互通路,BDNF、TH 代表神经细胞存活与多巴胺能神经元功能;SOD 反映机体抗氧化能力;EGF 调控胶质、血管组织修复&…

2026/11/1 18:46:21 阅读更多 →
Vue 3 中纯 CSS 实现动态鱼骨图:轻量级因果分析组件开发实践

Vue 3 中纯 CSS 实现动态鱼骨图:轻量级因果分析组件开发实践

1. 项目缘起:为什么要在Vue里用纯CSS画鱼骨图? 最近在做一个后台管理系统,产品经理甩过来一个需求,要在一个项目复盘报告里,展示一个“问题根因分析”的图表。他给的原型图,就是一个典型的鱼骨图&#xff0…

2026/10/31 2:06:14 阅读更多 →
揭秘肥西县重点建设局网站背后的民生答卷与工程奇迹,带你读懂城市生长的力量

揭秘肥西县重点建设局网站背后的民生答卷与工程奇迹,带你读懂城市生长的力量

在这个快节奏的时代,我们每个人的生活轨迹似乎都被巨大的钢筋水泥森林所定义。清晨,当我们从梦中醒来,推开窗户,看到的是窗外拔地而起的新地标,还是熟悉的旧街巷?傍晚下班,穿梭在刚刚拓宽的主干道上,感叹交通的便捷,还是看着家门口新修缮的公园,享受片刻的宁静?这些…

2026/11/1 13:54:27 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/2 0:25:10 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/2 10:46:11 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/2 10:47:40 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/2 1:16:51 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/2 10:46:31 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/2 10:46:26 阅读更多 →

日新闻

周新闻

月新闻