当前位置: 首页 > news >正文

使用redis缓存文章浏览量

效果展示

views202482165211

好处

首先初始化所有浏览量

访问文章后增加的浏览量**不直接修改数据库,先存到redis然后访问也是获取redis的浏览量,做个定时任务,后续自定义时间同步数据库**,好像也就是一个集中处理罢了

CommandLineRunner实现项目启动时预处理

新建包

新建runner初始化包和新建viewsRunner初始化浏览类

image-20240802120124237

package com.example.vueelementson.runner;import com.example.vueelementson.dao.ArticleDao;
import com.example.vueelementson.entity.Article;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/*** @BelongsProject: blog-springboot* @BelongsPackage: com.example.vueelementson.runner* @Author: Zww* @CreateTime: 2024-08-02  11:53* @Description: TODO* @Version: 1.0*/
@Component
public class ViewsRunner implements CommandLineRunner {@Autowiredprivate ArticleDao articleDao;@Overridepublic void run(String... args) throws Exception {// 查询每一篇博客 id对应浏览量,封装map-> articleId,viewsList<Article> articles = articleDao.selectList(null);Map<Integer, Integer> viewsMap =articles.stream().collect(Collectors.toMap(Article::getId, article -> article.getViews()));}
}

RedisTemplate工具类

package com.example.vueelementson.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;import java.util.*;
import java.util.concurrent.TimeUnit;@Component
public class RedisCache {@Autowiredpublic RedisTemplate redisTemplate;// 设置map属性值的自增(浏览量递增)public void incrementMapValueCache(String key, String hkey, int step) {redisTemplate.opsForHash().increment(key, hkey, step);}/*** 缓存基本的对象,Integer、String、实体类等** @param key   缓存的键值* @param value 缓存的值*/public <T> void setCacheObject(final String key, final T value) {redisTemplate.opsForValue().set(key, value);}/*** 缓存基本的对象,Integer、String、实体类等** @param key      缓存的键值* @param value    缓存的值* @param timeout  时间* @param timeUnit 时间颗粒度*/public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {redisTemplate.opsForValue().set(key, value, timeout, timeUnit);}/*** 设置有效时间** @param key     Redis键* @param timeout 超时时间* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout) {return expire(key, timeout, TimeUnit.SECONDS);}/*** 设置有效时间** @param key     Redis键* @param timeout 超时时间* @param unit    时间单位* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout, final TimeUnit unit) {return redisTemplate.expire(key, timeout, unit);}/*** 获取有效时间** @param key Redis键* @return 有效时间*/public long getExpire(final String key) {return redisTemplate.getExpire(key);}/*** 判断 key是否存在** @param key 键* @return true 存在 false不存在*/public Boolean hasKey(String key) {return redisTemplate.hasKey(key);}/*** 获得缓存的基本对象。** @param key 缓存键值* @return 缓存键值对应的数据*/public <T> T getCacheObject(final String key) {ValueOperations<String, T> operation = redisTemplate.opsForValue();return operation.get(key);}/*** 删除单个对象** @param key*/public boolean deleteObject(final String key) {return redisTemplate.delete(key);}/*** 删除集合对象** @param collection 多个对象* @return*/public boolean deleteObject(final Collection collection) {return redisTemplate.delete(collection) > 0;}/*** 缓存List数据** @param key      缓存的键值* @param dataList 待缓存的List数据* @return 缓存的对象*/public <T> long setCacheList(final String key, final List<T> dataList) {Long count = redisTemplate.opsForList().rightPushAll(key, dataList);return count == null ? 0 : count;}/*** 获得缓存的list对象** @param key 缓存的键值* @return 缓存键值对应的数据*/public <T> List<T> getCacheList(final String key) {return redisTemplate.opsForList().range(key, 0, -1);}/*** 缓存Set** @param key     缓存键值* @param dataSet 缓存的数据* @return 缓存数据的对象*/public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);Iterator<T> it = dataSet.iterator();while (it.hasNext()) {setOperation.add(it.next());}return setOperation;}/*** 获得缓存的set** @param key* @return*/public <T> Set<T> getCacheSet(final String key) {return redisTemplate.opsForSet().members(key);}/*** 缓存Map** @param key* @param dataMap*/public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {if (dataMap != null) {redisTemplate.opsForHash().putAll(key, dataMap);}}/*** 获得缓存的Map** @param key* @return*/public <T> Map<String, T> getCacheMap(final String key) {return redisTemplate.opsForHash().entries(key);}/*** 往Hash中存入数据** @param key   Redis键* @param hKey  Hash键* @param value 值*/public <T> void setCacheMapValue(final String key, final String hKey, final T value) {redisTemplate.opsForHash().put(key, hKey, value);}/*** 获取Hash中的数据** @param key  Redis键* @param hKey Hash键* @return Hash中的对象*/public <T> T getCacheMapValue(final String key, final String hKey) {HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();return opsForHash.get(key, hKey);}/*** 获取多个Hash中的数据** @param key   Redis键* @param hKeys Hash键集合* @return Hash对象集合*/public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {return redisTemplate.opsForHash().multiGet(key, hKeys);}/*** 删除Hash中的某条数据** @param key  Redis键* @param hKey Hash键* @return 是否成功*/public boolean deleteCacheMapValue(final String key, final String hKey) {return redisTemplate.opsForHash().delete(key, hKey) > 0;}/*** 获得缓存的基本对象列表** @param pattern 字符串前缀* @return 对象列表*/public Collection<String> keys(final String pattern) {return redisTemplate.keys(pattern);}
}

注入工具类并使用

    @Autowiredprivate ArticleDao articleDao;@Autowiredprivate RedisCache redisCache;@Overridepublic void run(String... args) throws Exception {// 查询每一篇博客 id对应浏览量,封装map-> articleId,viewsList<Article> articles = articleDao.selectList(null);Map<String , Integer> viewsMap =articles.stream().collect(Collectors.toMap(article -> article.getId().toString(), article -> article.getViews()));redisCache.setCacheMap("article:views", viewsMap);}

Redis序列化乱码配置类

package com.example.vueelementson.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory connectionFactory){RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();//设置String类型的key设置序列化器,方便在可视化redis控制器里查看redisTemplate.setKeySerializer(new StringRedisSerializer());// String类型 value序列器redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());//设置Hash类型的key设置序列化器,方便在可视化redis控制器里查看redisTemplate.setHashKeySerializer(new StringRedisSerializer());// Hash类型 value序列器redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());//设置redis链接工厂redisTemplate.setConnectionFactory(connectionFactory);return redisTemplate;}
}

根据id查询文章时增加浏览量

也可以单独封装到service或方法里,或封装个接口前端多发一次请求,实现解耦

    @GetMapping(value = "/{id}")public Result selectOne(@PathVariable("id") Long id, HttpServletRequest request, HttpServletResponse response) {Article byId = articleService.getById(id);redisCache.incrementMapValueCache("article:views", id.toString(), 1);return Result.success(byId);}

自行访问并且查看redis值

定时任务更新数据库

设置定时任务,每隔5秒更新一次,或者自定义时间

启动类加注解@EnableScheduling

package com.example.vueelementson;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;@SpringBootApplication
@MapperScan("com.example.vueelementson.dao")
@EnableAsync
@EnableScheduling
public class VueElementSonApplication {public static void main(String[] args) {SpringApplication.run(VueElementSonApplication.class, args);}}

定时任务类

//从0秒开始过5秒执行一次,不写的话会变成每分钟的第五秒执行   
@Scheduled(cron = "0/5 * * * *?")public void updateViewCount() {Map<String, Integer> cacheMap = redisCache.getCacheMap("article:views");// 获取map所有的entry集合转换为article对象,有id和浏览量属性,然后通过mp的iservice接口的批量更新实体方法将所有缓存里的浏览量更新到数据库List<Article> articleList = cacheMap.entrySet().stream().map(entry -> new Article(Integer.parseInt(entry.getKey()), entry.getValue())).collect(Collectors.toList());// 调用mp方法更新articleService.updateBatchById(articleList);System.out.println("更新完毕");}

自定义时间例子参考

每天上午 9 点执行任务:
java复制
@Scheduled(cron = "0 0 9 * * ?")
public void runTaskAtNineAM() {// 在每天上午 9 点执行的任务逻辑
}
Cron 表达式 "0 0 9 * * ?" 的含义是:: 0 秒 (光写秒意思为每分钟的第几秒,前面带个0/意思为从0开始每隔五秒执行一次)
分: 0 分
时: 9 点
日: 不关心
月: 不关心
周: 不关心
每个工作日下午 5 点执行任务:
java复制
@Scheduled(cron = "0 0 17 ? * MON-FRI")
public void runTaskAtFivePM() {// 在每个工作日下午 5 点执行的任务逻辑
}
Cron 表达式 "0 0 17 ? * MON-FRI" 的含义是:: 0 秒
分: 0 分
时: 17(下午 5): 不关心
月: 不关心
周: 周一到周五 (MON-FRI)
每月 1 号和 15 号的 2330 分执行任务:
java复制
@Scheduled(cron = "0 30 23 1,15 * ?")
public void runTaskAtTheFirstAndFifteenth() {// 在每月 1 号和 15 号的 23 点 30 分执行的任务逻辑
}
Cron 表达式 "0 30 23 1,15 * ?" 的含义是:: 0 秒
分: 30 分
时: 23(11): 1 号和 15 号
月: 不关心
周: 不关心

如果你懒得计算了,直接使用cron表达式生成器,和设闹钟一个道理

Cron - 在线Cron表达式生成器 (ciding.cc)

image-20240802155245192

cron语法

六个* 秒分时日月周

?表示该时间单位不计入,只可单独用在具体几号和星期几,同时使用发生冲突

*表示任意值

1,2,3表示时间列表,表示1,2,3秒都执行

1-5 一到五秒内的时间点执行

0/5 从0秒开始每隔5秒 0/*可以写成/*

周1-7 1为星期日

0 0 0 31W * ? W表示最接近几号的工作日,31号是星期六,那么就为30,星期日则为29

L表示每个月的最后一天last

LW表示每月最后一个工作日

6L表示每月最后一个星期五

0 0 0 ? * 6#3表示第三个星期五

获取文章数据时从redis拿浏览量

此时我们还可以引入cookie判断用户是否携带已浏览的 cookie键值,如果不存在则增加redis值,然后标记冷却时间也就是给响应体赋予cookie存活时间自定义,如果存在,则不增加浏览量,等到cookie死掉才进行增加,可以防止被多线程循环发请求爆破

 @GetMapping(value = "/{id}")public Result selectOne(@PathVariable("id") Integer id, HttpServletRequest request, HttpServletResponse response) {Article byId = articleService.getById(id);if (StrUtil.isEmpty(CookieUtils.getCookieValue(request, Article.VIEW_COOKIES + id))) {redisCache.incrementMapValueCache("article:views", id.toString(), 1);CookieUtils.setCookie(request, response, Article.VIEW_COOKIES + id, "viewd", 60 * 60);System.out.println("浏览量增加冷却时间已过,开始增加");}Integer redisViews = redisCache.getCacheMapValue("article:views", id.toString());byId.setViews(redisViews);return Result.success(byId);// 如果cookie为空,说明第一次访问,访问量可以增加,隔一小时再看浏览量才能增加}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • PHP中如何处理字符串
  • thinkphp8开发的广告联盟网站系统源码
  • C#:通用方法总结—第11集
  • SSM大学生就业咨询管理系统-计算机毕业设计源码79442
  • “网络身份证”来了,淘宝、微信、小红书等已上线试点版功能
  • TCP为什么需要四次挥手?
  • 软件测试经理工作日常随记【7】-接口+UI自动化(多端集成测试)
  • 利用Qt实现调用文字大模型的API,文心一言、通义千问、豆包、GPT、Gemini、Claude。
  • Python Beautiful Soup介绍
  • js逻辑或(||)和且()
  • 在Jira中使用AI
  • 【Mind+】掌控板入门教程02 趣味相框
  • CTFHub——XSS——反射型
  • [YashanDB认证]YashanDB个人版安装
  • ECCV 2024 | 深入探索GAN先验,哈工大、清华提出模型反演新方案
  • [PHP内核探索]PHP中的哈希表
  • bootstrap创建登录注册页面
  • codis proxy处理流程
  • django开发-定时任务的使用
  • exports和module.exports
  • JavaScript函数式编程(一)
  • JavaScript类型识别
  • Python学习之路13-记分
  • React 快速上手 - 07 前端路由 react-router
  • 对JS继承的一点思考
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 面试遇到的一些题
  • 前嗅ForeSpider中数据浏览界面介绍
  • 数组的操作
  • 一道闭包题引发的思考
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • 关于Android全面屏虚拟导航栏的适配总结
  • 如何在招聘中考核.NET架构师
  • ​软考-高级-系统架构设计师教程(清华第2版)【第9章 软件可靠性基础知识(P320~344)-思维导图】​
  • # wps必须要登录激活才能使用吗?
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • (C语言)深入理解指针2之野指针与传值与传址与assert断言
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (三)Kafka离线安装 - ZooKeeper开机自启
  • (四)activit5.23.0修复跟踪高亮显示BUG
  • .NET 6 Mysql Canal (CDC 增量同步,捕获变更数据) 案例版
  • .net core使用RPC方式进行高效的HTTP服务访问
  • .net Signalr 使用笔记
  • [2008][note]腔内级联拉曼发射的,二极管泵浦多频调Q laser——
  • [2544]最短路 (两种算法)(HDU)
  • [C++] 如何使用Visual Studio 2022 + QT6创建桌面应用
  • [EFI]Acer Aspire A515-54g电脑 Hackintosh 黑苹果efi引导文件
  • [Hive] CTE 通用表达式 WITH关键字
  • [HNOI2008]玩具装箱toy
  • [JS入门到进阶] 7条关于 async await 的使用口诀,新学 async await?背10遍,以后要考!快收藏
  • [Linux]于Mac在配置Linuxserver安装Nginx+PHP
  • [Mybatis-Plus笔记] MybatisPlus-03-QueryWrapper条件构造器
  • [NBIoT]NBIoT相关知识
  • [Nginx]反向代理Node将3000端口访问转换成80端口