使用redis缓存文章浏览量

news2024/11/15 2:12:40

效果展示

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 {
    @Autowired
    private ArticleDao articleDao;

    @Override
    public void run(String... args) throws Exception {
        // 查询每一篇博客 id对应浏览量,封装map-> articleId,views
        List<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 {
    @Autowired
    public 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);
    }
}

注入工具类并使用

    @Autowired
    private ArticleDao articleDao;

    @Autowired
    private RedisCache redisCache;
    @Override
    public void run(String... args) throws Exception {
        // 查询每一篇博客 id对应浏览量,封装map-> articleId,views
        List<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 {
    @Bean
    public 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为空,说明第一次访问,访问量可以增加,隔一小时再看浏览量才能增加

    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1974216.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鸿蒙系统学习指南

&#x1f41f;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢编程&#x1fab4; &#x1f421;&#x1f419;个人主页&#x1f947;&#xff1a;Aic山鱼 &#x1f420;WeChat&#xff1a;z7010cyy &#x1f988;系列专栏&#xff1a;&#x1f3de;️ 前端-JS基础专栏✨前…

8月2日SpringBoot学习笔记

今日内容: AOP 面向切面 代理模式 springmvc 运行原理 拦截器 springmvc异常处理机制 代理模式 为其他对象提供一种代理以控制对这个对象的访问。想在访问一个类时做一些控制是使用。 静态代理 优点&#xff1a;便于理解 缺点&#xff1a; 1.代码冗余&…

Linux 内核源码分析---I/O 体系结构与访问设备

I/O 体系结构 与外设的通信通常称之为输入输出&#xff0c;一般都缩写为I/O。 在实现外设的I/O时&#xff0c;内核必须处理3个可能出现的问题&#xff1a; &#xff08;1&#xff09;必须根据具体的设备类型和模型&#xff0c;使用各种方法对硬件寻址&#xff1b; &#xff08…

java面试题Spring部分(四)

一、什么事spring的三级缓存 典型回答 在Spring的BeanFactory体系中&#xff0c;BeanFactory是Spring IOC容器的基础接口&#xff0c;其DefaultSingletonBeanRegistry类实现了BeanFactory接口&#xff0c;并维护了三级缓存&#xff1a; public class DefaultSingletonBeanRe…

如何在OpenHarmony 4.1R上设置系统默认不锁屏(修改系统锁屏应用)

本文介绍如何修改系统锁屏应用&#xff0c;从而实现在OpenHarmony 4.1R上设置系统默认不锁屏。 环境配置 1.DevEco Studio 4.1 Release&#xff0c;下载链接地址 API10 Full SDK,安装教程 步骤 1.首先下载4.1r分支的系统锁屏应用applications_screenlock 2.修改系统锁屏应…

【Plotly-驯化】一文画出漂亮的流量漏斗图:plotly.funnel函数使用技巧

【Plotly-驯化】一文画出漂亮的流量漏斗图&#xff1a;plotly.funnel函数使用技巧 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &#x1f387; 免费获…

HTML+CSS練習---空隙產生記錄

1.第一層和第二層之間的間隙&#xff1a;以為導航欄超過高度朝下擠下來了 2.第2層兩個div中的空隙 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Document</title><style>font-face {f…

ATTCK实战系列-红队评估 (一)Vulnstack三层网络域渗透

目录 一、搭建环境 1.靶场下载地址&#xff1a; 2、网络拓扑 3、环境配置 Win7&#xff08;外网服务器 &#xff09; Win2008&#xff08;域控&#xff09; Win2003&#xff08;域成员&#xff09; 4、启动环境 二、信息收集 1、端口扫描 2、目录扫描 三、漏洞利用…

IM即时通讯客服聊天系统源码(某站售8千)

即时通讯客服聊天系统已经成为企业提供卓越客户服务的不可或缺的工具。为了帮助您快速搭建强大的客服聊天系统&#xff0c;提供了一款全面的源码模板&#xff0c;基于Java和Spring Boot微服务架构构建&#xff0c;支持多种功能模块&#xff0c;以及消息加密、红包、消息撤回等功…

基于JSP的学生综合考评管理系统

你好&#xff0c;我是专注于教育信息化的开发者&#xff0c;很高兴为您介绍本系统。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSPJava 工具&#xff1a;Eclipse、MySQL数据库管理工具 系统展示 首页 管理员功能 教师功能 学生功能 摘要 本文…

笔记本怎么手动设置ip地址:一步步教你操作

在使用笔记本电脑连接网络时&#xff0c;有时候我们需要手动设置IP地址以满足特定的网络需求或解决网络问题。手动设置IP地址可以帮助我们更好地控制网络连接&#xff0c;确保设备在网络中的稳定性和安全性。然而&#xff0c;对于不熟悉网络设置的用户来说&#xff0c;手动设置…

EPLAN EDZ 文件太大导入很慢如何解决?

目前各个品牌都在提供 EPLAN EDZ部件库文件,但是一般都是一个总的EDZ文件,导入过程中,因为电脑配置和其他问题,导致导入过程中EPLAN会崩溃或者长时间不动。 我们分析下EDZ文件的构成,这是个压缩文件,换了个壳而已。用压缩软件把edz打开,这里不是解压,直接右键,用解压…

[CP_AUTOSAR]_系统服务_DEM模块(二)功能规范介绍

目录 1、DEM 功能规范描述1.1、Startup behavior1.2、Monitor re-initialization 在前面 《[CP_AUTOSAR]_系统服务_DEM模块&#xff08;一&#xff09;》文中&#xff0c;简要介绍了 DEM 模块的功能、与其它模块之间的功能交互&#xff0c;本文将接着介绍 DEM 模块的功能规范。…

Web前端:Vue开发环境搭建

一、搭建开发环境&#xff1a; 1.nodejs安装&#xff1a; 下载地址&#xff1a; https://nodejs.org/dist/v20.13.1/node-v20.13.1-x64.msi 查看版本号&#xff1a; node -v 2.安装nrm(用于指定国内镜像源&#xff0c;加速依赖包下载速度)&#xff1a; 安装nrm&#xff…

程序员的魔法石!

本文由 ChatMoney团队出品 AI自己写代码&#xff0c;这只是传说&#xff1f;还是摸鱼新指南&#xff1f; AI出现之前&#xff0c;从来都是老板或产品经理提需求&#xff0c;程序员熬夜加班吭哧吭哧写代码或者是从Github&#xff0c;Stackoverflow上controlc&#xff0c;control…

为面试准备的一些内容

开发中使用了什么技术&#xff1f; mvvm、compose、livedata、单例模式、工厂模式、弱引用、线程池、Handler。 对于项目一开始我们打算使用aosp原生的管控方式&#xff0c;如UsageStatManager获取每个app的使用时长&#xff0c;和使用PackageManager的setPackagesSuspended方…

【数据结构入门 】栈

目录 前言 一、栈的概念及结构 二、栈的实现 1. 栈的声明 2.初始化栈 3. 栈的销毁 4.判断是否为空栈 5.入栈&#xff08;只能插入栈顶元素&#xff09; 6. 出栈&#xff08;只能从栈顶删除&#xff09; 7.栈的大小 8.获取栈顶元素 总结 前言 在计算机科学中&#xf…

黑马Java零基础视频教程精华部分_10_面向对象进阶(2)

系列文章目录 文章目录 系列文章目录一、多态1、什么是多态&#xff1f;2、多态的表现形式3、多态的前提4、多态的好处5、多态调用成员的特点6、多态的优势和弊端7、引用数据类型的类型转换 二、包1、什么是包&#xff1f;2、包名的规则3、使用其他类的规则 三、final 最终的&a…

文件包含漏洞Tomato靶机渗透_详解

一、导入靶机 将下载好的靶机拖入到VMware中&#xff0c;填写靶机机名称(随便起一个)和路径 虚拟机设置里修改网络状态为NAT模式 二、信息收集 1、主机发现 用御剑扫描工具扫描虚拟机的NAT网段&#xff0c;发现靶机的IP是192.168.204.141 2、端口扫描 用御剑端口扫描扫描全…

完全免费的 TTS 文字转语音来了!

作者&#xff1a;老余捞鱼 原创不易&#xff0c;转载请标明出处及原作者。 完全免费的 TTS 文字转语音来了&#xff01; 再也不用被国内外各种语音合成割韭菜了。 试了下&#xff0c;确实是不限次数&#xff0c;不限语种&#xff0c;完全免费.输出视频如下&#xff1a; 效果就…