SpringBoot博客项目+Redis简单实现文章浏览量记录

编程入门 行业动态 更新时间:2024-10-11 17:24:18

SpringBoot博客项目+Redis简单实现文章<a href=https://www.elefans.com/category/jswz/34/1771176.html style=浏览量记录"/>

SpringBoot博客项目+Redis简单实现文章浏览量记录

首先我们先配置好springboot和Redis的环境,确保连接上Redis

首先引入需要的依赖

      

  <!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--对象池--><dependency><groupId>org.apachemons</groupId><artifactId>commons-pool2</artifactId></dependency>


1. 第一个依赖是spring boot整合redis的依赖
2.  第二个对象池的依赖。
对象池:我们在服务器开发的过程中,往往会有一些对象,它的创建和初始化需要的时间比较长,比如数据库连接,网络IO,大数据对象等。在大量使用这些对象时,如果不采用一些技术优化,就会造成一些不可忽略的性能影响。一种办法就是使用对象池,每次创建的对象并不实际销毁,而是缓存在对象池中,下次使用的时候,不用再重新创建,直接从对象池的缓存中取即可。为了避免重新造轮子,我们可以使用优秀的开源对象池化组件apache-common-pool2,它对对象池化操作进行了很好的封装,我们只需要根据自己的业务需求重写或实现部分接口即可,使用它可以快速的创建一个方便,简单,强大对象连接池管理类。

## 配置文件
spring:
  redis:
    host: xxxx #Redis服务器的Ip
    port: 6379  #端口号
    #password: 123456
    lettuce:
      pool:        
        max-active: 8 # 最大连接  
        max-idle: 8 # 最大空闲连接
        min-idle: 0 # 最小空闲连接
        max-wait: 100 # 连接等待时间

 Redis工具类:

/*** Redis配置类* @author wl**/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport{/*** 配置自定义redisTemplate* @return*/@BeanRedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);// 设置值(value)的序列化采用Jackson2JsonRedisSerializer。redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// 设置键(key)的序列化采用StringRedisSerializer。redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.afterPropertiesSet();return redisTemplate;}}

Redis工具类:通用模板 拿来即用

@Component
public class RedisUtil {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;private StringRedisTemplate stringRedisTemplate;public RedisUtil(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) {this.redisTemplate = redisTemplate;this.stringRedisTemplate = stringRedisTemplate;}/*** 指定缓存失效时间* @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;}}/*** 模糊查询获取key值* @param pattern* @return*/public Set keys(String pattern){return redisTemplate.keys(pattern);}/*** 使用Redis的消息队列* @param channel* @param message 消息内容*/public void convertAndSend(String channel, Object message){redisTemplate.convertAndSend(channel,message);}/*** 根据起始结束序号遍历Redis中的list* @param listKey* @param start  起始序号* @param end  结束序号* @return*/public List<Object> rangeList(String listKey, long start, long end) {//绑定操作BoundListOperations<String, Object> boundValueOperations = redisTemplate.boundListOps(listKey);//查询数据return boundValueOperations.range(start, end);}/*** 弹出右边的值 --- 并且移除这个值* @param listKey*/public Object rifhtPop(String listKey){//绑定操作BoundListOperations<String, Object> boundValueOperations = redisTemplate.boundListOps(listKey);return boundValueOperations.rightPop();}/**----------------zSet相关操作------------------*//*** 添加元素,有序集合是按照元素的score值由小到大排列** @param key* @param value* @param score* @return*/public Boolean zAdd(String key, String value, double score) {return stringRedisTemplate.opsForZSet().add(key, value, score);}public Long zAdd(String key, Set<ZSetOperations.TypedTuple<String>> values) {return stringRedisTemplate.opsForZSet().add(key, values);}public Long zRemove(String key, Object... values) {return stringRedisTemplate.opsForZSet().remove(key, values);}/*** 增加元素的score值,并返回增加后的值* @return*/public Double zIncrementScore(String key, String value, double delta) {return stringRedisTemplate.opsForZSet().incrementScore(key, value, delta);}/*** 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列* @param key* @param value* @return*/public Long zRank(String key, Object value) {return stringRedisTemplate.opsForZSet().rank(key, value);}/*** 返回元素在集合的排名,按元素的score值由大到小排列* @param key* @param value* @return*/public Long zReverseRank(String key, Object value) {return stringRedisTemplate.opsForZSet().reverseRank(key ,value);}/*** 获取集合的元素, 从小到大排序* @param key* @param start* @param end* @return*/public Set<String> zRange(String key, long start, long end) {return stringRedisTemplate.opsForZSet().range(key, start, end);}/*** 获取集合元素, 并且把score值也获取*/public Set<ZSetOperations.TypedTuple<String>> zRangeWithScores(String key, long start, long end) {return stringRedisTemplate.opsForZSet().rangeWithScores(key, start, end);}/*** 根据Score值查询集合元素* @param key* @param min* @param max* @return*/public Set<String> zRangeByScore(String key, double min, double max) {return stringRedisTemplate.opsForZSet().rangeByScore(key, min, max);}/*** 根据Score值查询集合元素, 从小到大排序* @param key* @param min* @param max* @return*/public Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key, double min, double max) {return stringRedisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);}public Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key, double min, double max, long start, long end) {return stringRedisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max, start, end);}/*** 获取集合的元素, 从大到小排序* @param key* @param start* @param end* @return*/public Set<String> zReverseRange(String key, long start, long end) {return stringRedisTemplate.opsForZSet().reverseRange(key, start, end);}/*** 获取集合的元素, 从大到小排序, 并返回score值* @param key* @param start* @param end* @return*/public Set<ZSetOperations.TypedTuple<String>> zReverseRangeWithScores(String key, long start, long end) {return stringRedisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);}
}

万事具备,接下来写一个监听类来实现Redis和MySQL数据库的数据交换。项目启动时,将MySQL数据库中的文章浏览量查询出来写入到Redis中。Servlet销毁时或者每隔多少秒就将Redis中的文章浏览量写入到MySQL数据库当中,这样就保证了不会因为服务关闭而导致数据丢失和数据不一致的情况。
 

@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ListenHandler {private final BlogService blogService;private final RedisUtil redisUtil;private static final String VIEW_KEY = "views";@Autowiredpublic ListenHandler(BlogService blogService, RedisUtil redisUtil) {this.blogService = blogService;this.redisUtil = redisUtil;}@PostConstructpublic void init() throws Exception {log.info("数据初始化开始...");//将数据库中的数据写入redisList<Blog> blogList = blogService.getAllBlogList();for (Blog blog: blogList ){redisUtil.zAdd(VIEW_KEY, blog.getId().toString(), blog.getViews());}log.info("数据已写入redis...");}@Scheduled(cron = "*/30 * * * * ?")public void updateNum() {log.info("周期任务开始执行...");Set<ZSetOperations.TypedTuple<String>> viewNum = redisUtil.zReverseRangeWithScores(VIEW_KEY, 0, 100);writeNum(viewNum, VIEW_KEY);log.info("周期任务执行完毕,redis写入数据库完毕");}private void writeNum(Set<ZSetOperations.TypedTuple<String>> set, String fieldName) {set.forEach(item -> {Long id = Long.valueOf(item.getValue());Integer num = item.getScore().intValue();Blog blog = blogService.getIdBlog(id);//更新数据库if (blog!=null){blog.setViews(num);blogService.updateViews(blog);}});log.info("{} 更新完毕", fieldName);}/*** 关闭时操作*/@PreDestroypublic void afterDestroy()  {log.info("开始关闭...");//将redis中的数据写入数据库Set<ZSetOperations.TypedTuple<String>> viewNum = redisUtil.zReverseRangeWithScores(VIEW_KEY, 0, 10);writeNum(viewNum, VIEW_KEY);log.info("redis写入数据库完毕");}}
@PostConstruct
public void init() throws Exception {log.info("数据初始化开始...");//将数据库中的数据写入redisList<Blog> blogList = blogService.getAllBlogList();for (Blog blog: blogList ){redisUtil.zAdd(VIEW_KEY, blog.getId().toString(), blog.getViews());}log.info("数据已写入redis...");
}

将数据库中的数据写入redis 然后通过定时任务写入mysql

private void writeNum(Set<ZSetOperations.TypedTuple<String>> set, String fieldName) {set.forEach(item -> {Long id = Long.valueOf(item.getValue());Integer num = item.getScore().intValue();Blog blog = blogService.getIdBlog(id);//更新数据库if (blog!=null){blog.setViews(num);blogService.updateViews(blog);}});log.info("{} 更新完毕", fieldName);
}

@PostConstruct:指的是在项目启动的时候执行这个方法,也可以理解为在spring容器启动的时候执行,可作为一些数据的常规化加载。

@Scheduled注解是spring+boot提供的⽤于定时任务控制的注解,主要⽤于控制任务在某个指定时间执⾏,注意需要配合@EnableScheduling注解使用

单位允许值允许通配符
毫秒0-59,  -  *  / 
分钟0-59,  -  *  / 
小时0-23,  -  *  / 
日期1-31,  -  *  /  ?  L  W
月份1-12或JAN-DEC(大小写均可),  -  *  /  ?
星期

1-7或SUN-SAT(大小写均可)

注:星期日为每周第一天,所以1-7表示周末到周六

,  -  *  /  ?  L  #

cron表达式各占位符解释:

{秒数}{分钟}{小时}{日期}{星期} ==> 不允许为空值,若值不合法,调度器将抛出SchedulerException异常“/”代表触发步进(step),”/”前面的值代表初始值(""等同"0"),后面的值代表偏移量,比如"0/20"或者"/20"代表从0秒钟开始,每隔20秒钟触发1次,即第0秒触发1次,第20秒触发1次,第40秒触发1次;"5/20"代表第5秒触发1次,第25秒触发1次,第45秒触发1次;"10-45/20"代表在[10,45]内步进20秒命中的时间点触发,即第10秒触发1次,第30秒触发1次

cron通配符

符号含义
*所有值,在秒字段上表示每秒执行,在月字段上表示每月执行
?不指定值,不需要关心当前指定的字段的值,比如每天都执行但不需要关心周几就可以把周的字段设为?
-区间或者范围,如秒的0-2 ,表示0秒、1秒、2秒都会触发
,指定值,比如在0秒、20秒、25秒触发,可以把秒的字段设为0,20,25
/递增触发,比如秒的字段上设0/3 ,表示从第0秒开始,每隔3秒触发
L最后,只允许在日字段或周字段上,在日字段上使用L表示当月最后- -天,在周字段上使用3L表示该月最后一个周四
w只允许用在日字段上,表示距离最近的该日的工作日,工作日指的是周一至周五
#只允许在周字段上,表示每月的第几个周几,如2#3 , 每月的第3个周二

cron表达式示例

“30 * * * * ?” 每半分钟触发任务 “30 10 * * * ?” 每小时的10分30秒触发任务 “30 10 1 * * ?” 每天1点10分30秒触发任务 “30 10 1 20 * ?” 每月20号1点10分30秒触发任务 “30 10 1 20 10 ? *” 每年10月20号1点10分30秒触发任务 “30 10 1 20 10 ? 2011” 2011年10月20号1点10分30秒触发任务 “30 10 1 ? 10 * 2011” 2011年10月每天1点10分30秒触发任务 “30 10 1 ? 10 SUN 2011” 2011年10月每周日1点10分30秒触发任务 “15,30,45 * * * * ?” 每15秒,30秒,45秒时触发任务 “15-45 * * * * ?” 15到45秒内,每秒都触发任务 “15/5 * * * * ?” 每分钟的每15秒开始触发,每隔5秒触发一次 “15-30/5 * * * * ?” 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次 “0 0/3 * * * ?” 每小时的第0分0秒开始,每三分钟触发一次 “0 15 10 ? * MON-FRI” 星期一到星期五的10点15分0秒触发任务 “0 15 10 L * ?” 每个月最后一天的10点15分0秒触发任务 “0 15 10 LW * ?” 每个月最后一个工作日的10点15分0秒触发任务 “0 15 10 ? * 5L” 每个月最后一个星期四的10点15分0秒触发任务 “0 15 10 ? * 5#3” 每个月第三周的星期四的10点15分0秒触发任务

 cron表达式参数数量解疑

cron表达式格式:在此,表达式以空格分为6或7个域@Scheduled(cron = "{秒数} {分钟} {小时} {日期} {月份} {星期} {年份(可为空)}"){年份} ==> 允许值范围: 1970~2099 ,允许为空,若值不合法,调度器将抛出SchedulerException异常注意:除了{日期}和{星期}可以使用”?”来实现互斥,表达无意义的信息之外,其他占位符都要具有具体的时间含义,且依赖关系为:年->月->日期(星期)->小时->分钟->秒数

@EnableScheduling 开启对定时任务的支持

 通过@Pointcut注解标注在方法上面,用来定义切入点

 在这个查询博客详情方法定义切入点,最后利用AOP在用户请求上面这个接口后实现查看的文章浏览量加一

 在查询到博客详情后执行这个doAfter方法 更具博客id更新浏览量+1

更多推荐

SpringBoot博客项目+Redis简单实现文章浏览量记录

本文发布于:2024-03-07 22:54:24,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1719130.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:浏览量   简单   项目   文章   博客

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!