08.利用Redis实现签到功能

news2024/11/20 4:47:59

学习目标:

来源:黑马教程
使用Redis中BitMap数据结构使用签到功能和连续签到功能


学习产出:

请添加图片描述

解决方案:

1. 准备pom环境

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.17</version>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.23.1</version>
        </dependency>

2. 配置ThreadLocal和过滤器

public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUser(UserDTO user){
        tl.set(user);
    }

    public static UserDTO getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Autowired
    private StringRedisTemplate redis;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/code","/user/login","/blog/hot","/shop/**","/shop-type/**","/voucher/**").order(2);
        registry.addInterceptor(new RefreshTokenInterceptor(redis)).addPathPatterns("/**").order(1);
    }
}
---------------------------------------------
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    //controller执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.判断是否需要拦截ThreadLocal
        if (UserHolder.getUser()==null) {
            response.setStatus(401);
            return false;
        }
        //7.放行
        return true;
    }
    //渲染后返回给前台数据前
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //移除用户,避免内存泄露
        UserHolder.removeUser();
    }
}
---------------------------------------------------
@Slf4j
public class RefreshTokenInterceptor implements HandlerInterceptor {
    //这个对象不是由spring管理的所以不能用注解自动注入

    private StringRedisTemplate redis;

    public RefreshTokenInterceptor(StringRedisTemplate redis) {
        this.redis = redis;
    }

    //controller执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        //2.基于token获取redis中的用户
        //通过key取到hash中的map集合数据
        Map<Object, Object> userMap = redis.opsForHash().entries("login:token:" + token);
        //3.判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        //5.将查询到的hash数据转为userDto对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //6.存在,保存用户信息到ThreadLocal中
        UserHolder.saveUser(userDTO);
        //7.刷新token有效期
        redis.expire(LOGIN_USER_KEY + token, 30, TimeUnit.MINUTES);
        log.info("我是第一个拦截器当前拦截所有请求的用户为,线程为{},{}",UserHolder.getUser(),Thread.currentThread());
        //8.放行
        return true;
    }

3. Controller层:负责接收请求和向下分配

@RestController
@RequestMapping("/user")
public class UserController{
	@Resource
	private IUserService userService;
    @PostMapping("/sign")
    public Result sign(){
        return userService.sign();
    }
}

4. Service层:负责业务的处理逻辑签到功能

@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    private static final ObjectMapper mapper = new ObjectMapper();
    @Resource
    private StringRedisTemplate redis;
    @Override
    public Result sign() {
        //1. 获取当前登录用户Id
        Long userId = UserHolder.getUser().getId();
        //2.获取日期:Redis中的offset偏移量是从0开始算,偏移量n代表的这个月的第n+1天
        int offset = LocalDateTime.now().getDayOfMonth() - 1;
        //3.拼接key存入Redis
        LocalDateTime now = LocalDateTime.now();
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = "sign:" + userId + keySuffix;
        //true代表的是存入BitMap中的二进制是1
        redis.opsForValue().setBit(key, offset, true);
        return Result.ok();
    }
}

`5. Service层实现连续签到功能`
```java
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    private static final ObjectMapper mapper = new ObjectMapper();
    @Resource
    private StringRedisTemplate redis;
    @Override
    public Result signCount() {
        //1.获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        //2.获取日期
        LocalDateTime nowTime = LocalDateTime.now();
        //3.拼接key
        String keySuffix = nowTime.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = "sign:" + userId + keySuffix;
        //4.获取今天是第几天
        int day = nowTime.getDayOfMonth();
        //5.获取本月截至今天为止所有的签到记录,返回的是一个十进制数字
        List<Long> result = redis.opsForValue().bitField(
                key,
                BitFieldSubCommands.create().get(
                        BitFieldSubCommands.BitFieldType.unsigned(day)
                ).valueAt(0)
        );
        if (result.isEmpty() || result == null) {
            return Result.ok(0);
        }
        Long signCount = result.get(0);
        if (signCount == null || signCount == 0) {
            return Result.ok(0);
        }
        //6.遍历
        int count=0;
        while (true) {
            if ((signCount & 1) == 0) {
                //不为0,跳出循环
                break;
            } else {
                count++;
                signCount = signCount >> 1;
            }
        }
        //7.签到记录返回的十进制数字与1做 与运算

        return Result.ok(count);
    }
}

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

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

相关文章

vue组件的scope 以及 如何样式穿透

个人复习&#xff01;&#xff01;&#xff01; 有什么用 让当前组件的样式不会修改到其它地方的样式&#xff0c;使用了data-v-hash的方式来使css有了它对应模块的标识 实现原理 1、给HTML的dom节点添加一个不重复的data属性(例如: data-v-5558831a)来唯一标识这个dom 元素…

openCV实战-系列教程4:图像梯度计算(Sobel算子/开运算/梯度计算方法/scharr算子/lapkacian算子)、源码解读

1、sobel算子 先读进来一个原型白色图 img cv2.imread(pie.png,cv2.IMREAD_GRAYSCALE) cv2.imshow("img",img) cv2.waitKey() cv2.destroyAllWindows() 打印结果&#xff1a; 如图有两个3*3的卷积核&#xff0c;其中A是原始图片中的一个3*3的区域&#xff0c;这个…

阿里云轻量级应用服务器和ECS有什么区别?对比表来了

阿里云轻量应用服务器和云服务器ECS有什么区别&#xff1f;ECS是专业级云服务器&#xff0c;轻量应用服务器是轻量级服务器&#xff0c;轻量服务器使用门槛更低&#xff0c;适合个人开发者或中小企业新手使用&#xff0c;可视化运维&#xff0c;云服务器ECS适合集群类、高可用、…

MySQL进阶篇之Explain执行计划

MySQL&#xff1a;Explain执行计划 使用explain关键字可以模拟优化器执行SQL查询语句&#xff0c;从而知道MySQL是如何处理你的SQL语句的&#xff0c;分析你的查询语句或是表结构的性能瓶颈。 认识explain EXPLAIN SELECT * FROM user_info i LEFT JOIN user_grade g on i.id …

朴素贝叶斯==基于样本特征来预测样本属于的类别y

目录 朴素贝叶斯基于样本特征来预测样本属于的类别y 朴素贝叶斯算法的基本概念与核心思想 假设两个特征维度之间是相互独立的 拉普拉斯平滑增加出现次数保证0不出现 ​编辑 基于样本特征来预测样本属于的类别y 什么是拉普拉斯平滑 朴素贝叶斯基于样本特征来预测样本属于的…

Springboot配置高级

临时属性设置 带属性数启动SpringBoot java –jar springboot.jar –-server.port80携带多个属性启动SpringBoot&#xff0c;属性间使用空格分隔 属性加载优先顺序 参看https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-fea…

Linux学习之vsftpd配置文件

/etc/vsftpd/vsftpd.conf是主要的vsftpd配置文件&#xff0c;主要涉及大的调整&#xff0c;cat /etc/vsftpd/vsftpd.conf | wc -l可以看到有128行内容。 /etc/vsftpd/ftpusers是禁止用户名单&#xff0c;/etc/vsftpd/user_list可以是白名单&#xff0c;也可以是黑名单。 /et…

1268. 搜索推荐系统

链接&#xff1a; 1268. 搜索推荐系统 题解&#xff1a; class Solution { public: struct Trie {Trie() {end false;next.resize(26, nullptr);}bool end;std::set<std::string> words;std::vector<Trie*> next; };void insert_trie(const std::string& w…

Linux共享库基础及实例

共享库是将库函数打包成一个可执行文件&#xff0c;使得其在运行时可以被多个进程共享。 目标库 回顾下构建程序的一种方式&#xff1a; 将每个源文件编译成目标文件&#xff0c;再通过链接器将这些目标文件链接组成一个可执行程序。 gcc -g -c prog.c mod1.c mod2.c gcc -g …

面试热题(复原ip地址)

有效 IP 地址 正好由四个整数&#xff08;每个整数位于 0 到 255 之间组成&#xff0c;且不能含有前导 0&#xff09;&#xff0c;整数之间用 . 分隔。 例如&#xff1a;"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址&#xff0c;但是 "0.011.255.24…

linux/centos zookeeper 使用记录

配置cfg 下载zookeeper-3.4.14.tar.gz负责到centos服务器解压 /xxx/zookeeper-3.4.14/conf/下创建zoo.cfg文件并配置以下属性&#xff0c;/bsoft/zookeeperdata/目录先预先创建 tickTime2000 initLimit10 syncLimit5 dataDir/bsoft/zookeeperdata/ clientPort2181zk启动/重启/关…

使用Nacos与Spring Boot实现配置管理

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

JWT-Token

一、JWT 需要在 HTTP 这种无状态的机制下&#xff0c;记录下&#xff08;标识&#xff09;出来是不是连续&#xff08;逻辑上的连续&#xff09;的请求。 思路&#xff1a;如果多次请求&#xff0c;携带了相同的标识型数据&#xff0c;则认为是逻辑上连续的。这个标识&#xff…

TouchGFX之触摸控制器

必须能够从触摸控制器读取触摸坐标&#xff0c;以便用户与应用程序进行交互。 此处开发的代码将被用于以后开发TouchGFX抽象层。 由于开发板触摸芯片没有连接在I2C接口上&#xff0c;因此本节采用普通IO口模拟I2C接口 1.配置IO口 2.配置定时器 3.编写延时函数 delay.c#include…

算法练习- 其他算法练习5

文章目录 宜居星球改造计划 宜居星球改造计划 yes no na 每个值为一个格子&#xff1b;每天yes的值可以向上下左右扩展一个格子&#xff0c;将no改为yes&#xff1b;矩形区域no是否可以全部转为yes&#xff0c;可以的话需要几天&#xff1f;不可以的话输出-1输入&#xff1a; …

高性能服务器Nodejs业务实战

目录 1 项目初始化1.1 创建项目1.2 配置 cors 跨域1.3 配置解析表单数据的中间件1.4 初始化路由相关的文件夹1.5 初始化用户路由模块1.6 抽离用户路由模块中的处理函数 2 登录注册2.1 新建 ev_users 表2.2 安装并配置 mysql 模块2.3 注册2.4 优化 res.send() 代码2.5 优化表单数…

cuml机器学习GPU库 sklearn升级版AutoDL使用

CUML库 最近在做机器学习任务的时候发现我自己的数据集太大&#xff0c;直接用sklearn 跑起来时间很长&#xff0c;然后问GPT得知了有CUML库&#xff0c;后来去研究了一下&#xff0c;发现这个库只支持linux系统&#xff0c;从官网直接获取下载命令基本上也实现不了最后&#…

自学设计模式(类图、设计原则、单例模式 - 饿汉/懒汉)

设计模式需要用到面向对象的三大特性——封装、继承、多态&#xff08;同名函数具有不同的状态&#xff09; UML类图 eg.—— 描述类之间的关系&#xff08;设计程序之间画类图&#xff09; : public; #: protected; -: private; 下划线: static 属性名:类型&#xff08;默认值…

如果将PC电脑变成web服务器:利用Nignx反向代理绕过运营商对80端口封锁

如果将PC电脑变成web服务器&#xff1a;利用Nignx反向代理绕过运营商对80端口封锁 在上一篇文章中&#xff0c;我们已经实现了内网主机的多次端口映射&#xff0c;将内网主机的端口映射到了公网&#xff0c;可以通过公网访问该主机了。 因为电信的家庭宽带&#xff0c;默认是…

SpringBoot读取Nacos配置文件

断点到ClientWorker类的getServerConfig方法&#xff0c;反向Debug。