redis实战-项目集成git及redis实现短信验证码登录

news2025/1/15 6:59:52

 

目录

 

 

IDEA集成git

传统session存在的问题 

redis方案

业务流程

选用的数据结构

整体访问流程

发送短信验证码

 获取校验验证码

配置登录拦截器

拦截器注册配置类

拦截器

用户状态刷新问题

刷新问题解决方案


 

IDEA集成git

远程仓库采用码云,创建好仓库,复制仓库的url

2b81c37fc98443f18a1d0fb2bc188eab.png

 

 在idea中点击,出现git选项,点击ok

f7b350cd46a64df3ae76751c89607036.png

ce290b018ee94e3f8e315fbd3c00e387.png

 之后右击项目,点击remotes654dd9e6be9541b8a4db410eeab3d6d8.png

 填写url即可集成gitee31465e142b4a249aea4bd7fc51d503.png

传统session存在的问题 

传统的登录认证会采用session进行登录认证,将登录的验证码,用户信息都存放到session中,我们通过session来进行操作数据,这有什么问题呢

每个tomcat服务器中都有一份属于自己的session,假设用户第一次访问第一台tomcat,并且把自己的信息存放到第一台服务器的session中,但是第二次这个用户访问到了第二台tomcat,那么在第二台服务器上,肯定没有第一台服务器存放的session,即session在各个服务器之间是不共通的,所以此时整个登录拦截功能就会出现问题,而redis数据本身就是共享的,就可以避免session共享的问题了

redis方案

业务流程

  • 将验证码存储到redis中
  • 将用户数据存储到redis中

选用的数据结构

存储验证码时采用String类型,key采用业务代码+手机号

存储user数据时采用hash,key采用业务代码+随机的tonke

hash可以将对象中的每个字段独立存储,可以针对单个字段做CRUD,并且内存占用更少,实际上也可以采用String类型,但hash类型消耗内存较少,故选用String类型

整体访问流程

 当注册完成后,用户去登录会去校验用户提交的手机号和验证码,是否一致,如果一致,则根据手机号查询用户信息,不存在则新建,最后将用户数据保存到redis,并且生成token作为redis的key,当我们校验用户是否登录时,会去携带着token进行访问,从redis中取出token对应的value,判断是否存在这个数据,如果没有则拦截,如果存在则将其保存到threadLocal中,简化后续业务获取用户信息的代码,后续业务需要登录用户的信息,只要在threadLocal中取即可,无需从redis中取从而增加复杂度,最后放行。

66fa57b113e7443a80b588a092f4d75c.png

redis业务代码常量(后续继续补充)

public class RedisConstants {
    //发送验证码业务标识
    public static final String LOGIN_CODE_KEY = "login:code:";
    public static final Long LOGIN_CODE_TTL = 2L;
    //用户登录业务标识
    public static final String LOGIN_USER_KEY = "login:token:";
    public static final Long LOGIN_USER_TTL = 30L;

   
}

发送短信验证码

采用日志打印形式,将验证码打印在控制台

 public Result sendCode(String phone) {
        //校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号码格式不正确");
        }
        //手机号格式正确生成验证码
        String code = RandomUtil.randomNumbers(6);
        //保存到redis
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
        //打印日志
        log.debug("手机验证码为:" + code);
        return Result.ok();
    }

 获取校验验证码

我们需要对用户敏感信息进行筛选,只给前端返回必要的用户信息数据,需要封装dto对象,同时在uuid生成token,作为redis取数据的key,最后要设置过期时间,防止reids内存爆炸,设置为30分钟,因为session的过期时间也是30分钟

 public Result login(LoginFormDTO loginForm) {
        String phone = loginForm.getPhone();
        String code = loginForm.getCode();
        //校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号码格式不正确");
        }
        //从redis取验证码,进行比对
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        if (cacheCode == null || !cacheCode.equals(code)) {
            return Result.fail("验证码错误");
        }
        //根据电话号码从数据库查询用户是否存在
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getPhone, phone);
        User user = userMapper.selectOne(queryWrapper);
        //不存在则创建
        if (user == null) {
            user = createUserWithPhone(phone);
        }
        String token = UUID.randomUUID().toString(true);
        //只返回不敏感的信息,使用dto进行封装
        UserDTO userDTO = new UserDTO();
        BeanUtils.copyProperties(user, userDTO);
        //将dto对象转化为map结构
        Map<String, String> userMap = new HashMap<>();
        userMap.put("id", userDTO.getId().toString());
        userMap.put("icon", userDTO.getIcon());
        userMap.put("nickName", userDTO.getNickName());
//业务代码+token形成redis中的key
        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);

        return Result.ok(token);
    }

配置登录拦截器

拦截器注册配置类

@Configuration
public class MvcConfig implements WebMvcConfigurer {
  @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 登录拦截器
        registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
                .excludePathPatterns(
                        "/shop/**",
                        "/voucher/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                );
    }
}

拦截器

拦截器需要的stringRedisTemplate对象不能通过@Autowired直接注入

因为LoginInterceptor 对象是我们手动创建的,不受spring管控,不在spring容器中,故不能注入spring容器中的bean,只能通过在配置类中通过构造方法注入stringRedisTemplate对象

public class LoginInterceptor implements HandlerInterceptor {
    private StringRedisTemplate stringRedisTemplate;

    public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            response.setStatus(401);
            return false;
        }
        String tokenKey = LOGIN_USER_KEY + token;

        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);
        //3.判断用户是否存在
        if (userMap.isEmpty()) {
            //4.不存在,拦截,返回401状态码
            response.setStatus(401);
            return false;
        }
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        //5.存在,保存用户信息到Threadlocal
        UserHolder.saveUser(userDTO);
        //刷新登录状态
        stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);
        //6.放行
        return true;
    }
}

用户状态刷新问题

那就是登录认证和刷新用户状态绑定在一个拦截器中,而需要登录认证的路径并不是全部路径,如果用户不访问需要登录认证的路径,那就刷新不了用户状态,即30分钟之后登录就会退出,这明显不符合我们的常识

刷新问题解决方案

我们可以再加一个拦截器,形成一个拦截器链,第一个拦截器对所有路径进行拦截,而它的功能就是刷新登录状态,登录认证则由第二个拦截器完成

 

2bad35b884d24d54ab1d2d27bfd97849.png

 

 刷新状态拦截器

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

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

    @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中的用户
        String key  = LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 3.判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        // 5.将查询到的hash数据转为UserDTO
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 6.存在,保存用户信息到 ThreadLocal
        UserHolder.saveUser(userDTO);
        // 7.刷新token有效期
        stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户
        UserHolder.removeUser();
    }

登录认证拦截器

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.判断是否需要拦截(ThreadLocal中是否有用户)
        if (UserHolder.getUser() == null) {
            // 没有,需要拦截,设置状态码
            response.setStatus(401);
            // 拦截
            return false;
        }
        // 有用户,则放行
        return true;
    }
}

为了保证拦截器的执行先后顺序,配置类需设置order的大小,设定拦截器执行的先后的顺序,如若不设置,就按照注册顺序的先后来执行

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 登录拦截器
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/shop/**",
                        "/voucher/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                ).order(1);
        // token刷新的拦截器
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

相关文章

js定位到元素底部

文字的一行一行添加的&#xff0c;每次添加要滚动条自动定位到元素底部 <div class"An">//要父元素包裹&#xff0c;父元素设置max-height&#xff0c;overflow啥的<div class"friendly_pW"></div></div>//添加文字时找子元素的高…

数据管理,是企业管理的上帝视角

在早期&#xff0c;企业整体规模较小&#xff0c;市场以及产品也比较匮乏&#xff0c;用户对商品没有过多的需求&#xff0c;更多是出于对必需品的需要&#xff0c;选择购买商品。伴随着经济的飞速发展&#xff0c;企业规模在扩大的同时&#xff0c;市场和用户对商品的需求日益…

【pyqt5界面化工具开发-8】窗口开发-QDialog对话框

目录 一、调用父类的菜单 二、添加更多的布局在对话框内 一、调用父类的菜单 和前面Qwedget一样的结构&#xff08;不做过多介绍&#xff09; 可以参考代码中的注释 import sys from PyQt5.QtWidgets import QApplication, QPushButton, QDialog# 对话框&#xff08;多运用…

Windows服务器使用Mysqldump备份MySQL数据库方法

Windows服务器使用Mysqldump备份MySQL数据库方法 1.进入到MySQL安装目录的bin目录下&#xff0c;进入cmd F:\20220601\dev_software\mysql-8.0.11-winx64 2.执行备份命令&#xff1a; mysqldump -u root -p zj_bak test_bak -r D:\backup.sql3.导入备份 数据&#xff1a; m…

DDS器件原理与应用(AD9833 AD9834)

1、DDS模块的主要构成 DDS模块原理框图 2、DDS器件的工作原理 直接数字合成是生成模拟信号的一种常用方法&#xff0c;简单意义上的DDS&#xff0c;主要由相位调制器、波形查找表和DAC组成。相位调制器产生一个相位信息&#xff0c;使用该相位信息去波形查找表中查找对应的幅…

智慧安全3.0这两年:数智破冰 护航千行百业安全发展

数智化时代下&#xff0c;网络安全作为网络强国、数字中国的底座&#xff0c;是我国现代化产业发展中不可或缺的部分。在安全实战上&#xff0c;传统的基于边界防护&#xff0c;单次静态安全策略配置的安全措施已无法满足业务发展的安全需求&#xff0c;真正的安全防护必须是主…

如何使用敏捷开发方法管理项目

敏捷开发方法是一种灵活且高效的项目管理方法&#xff0c;旨在应对不断变化的需求和快速发展的项目环境。使用敏捷开发方法可以帮助团队更好地应对不确定性&#xff0c;提高项目的质量和效率。以下是使用敏捷开发方法管理项目的具体步骤&#xff1a; 明确项目目标和范围 在项…

2023 天府杯“ 室温超导+可再生能源应用国赛预测模拟专题赛“——参考代码答案与解析

室温超导材料在可再生能源传输中的应用研究 题目要求&#xff1a; 假设某国家有多个可再生能源发电站点&#xff0c;分布在不同的地理位置。每个发电站点产生的能量大小和时间段都有所不同。同时&#xff0c;假设已经研发出室温超导材料 LK-6&#xff0c;LK-66&#xff0c;L…

A卡崩溃有救了 AMD推出RGD专用工具:找出报错原因

现在的三大显卡GPU厂商中&#xff0c;谁家的显卡好争议不断&#xff0c;粉丝不仅争硬件性能&#xff0c;软件驱动也是容易被诟病的地方&#xff0c;AMD显卡这方面就吃过亏&#xff0c;好在AMD这次推出了一个RGD工具&#xff0c;可以找出显卡崩溃的原因。 AMD日前发布了一个Rade…

智慧高速领域数字孪生应用思考及实践

近些年&#xff0c;国家和相关部委陆续发布多项政策指导智慧公路建设&#xff0c;逐步制定相关建设标准规范&#xff0c;加快推动全国公路数字化、智能化、协同化升级。对于智慧高速的发展探索在这几年不停提出各类创新性的技术手段&#xff0c;并且进行了局部的试点和示范性应…

高等职业学校物联网实训室建设方案

一、概述 1.1专业背景 物联网&#xff08;Internet of Things&#xff09;被称为继计算机、互联网之后世界信息产业第三次浪潮&#xff0c;它并非一个全新的技术领域&#xff0c;而是现代信息技术发展到一定阶段后出现的一种聚合性应用与技术提升&#xff0c;是随着传感网、通…

..\SYSTEM\delay\delay.h(20): error: #18: expected a “)“

在KEIL中有时编译过程中会出现下面这样的错误。 明明代码里面并没有缺少小括号&#xff0c;但是就是找不到错误。 真正的原因是因为 在20行的地方形参 SYSCLK 与系统的中某个文件的宏名字相同导致这样的问题。 解决方案&#xff1a; 把函数的形参随便修改下就OK了。

危险的套娃:攻击者在 PDF 文件中隐藏恶意Word 文档

据BleepingComputer消息&#xff0c;日本计算机紧急响应小组 (JPCERT) 日前分享了在2023 年 7 月检测到的利用PDF文档的新型攻击——PDF MalDoc攻击&#xff0c;能将恶意 Word 文件嵌入 PDF 来绕过安全检测。 JPCERT采样了一种多格式文件&#xff0c;能被大多数扫描引擎和工具识…

详解JS 和CSS 代码利用率统计利器Coverage以及高版本chrome中coverage的一个致命问题

在做前端性能测试时较大的文件需要更多时间来下载&#xff0c;并可能导致我们的网站加载缓慢&#xff0c;从而导致用户体验欠佳。因此删除JS和CSS中未被使用的代码就很有必要了&#xff01;Coverage工具就可以非常方便的实现这一需求。 Coverage使用方法 Coverage 是chrome开…

已知两地经纬度,计算两地直线距离

文章目录 1 原理公式2 代码实现2.1 JavaScript2.2 C2.3 Python2.4 MATLAB 1 原理公式 在地球上&#xff0c;计算两点之间的直线距离通常使用地理坐标系&#xff08;例如WGS84&#xff09;。计算两地直线距离的公式是根据经纬度之间的大圆距离&#xff08;Great Circle Distanc…

C语言练习题解析:挑战与突破,开启编程新篇章!(1)

&#x1f493;博客主页&#xff1a;江池俊的博客⏩收录专栏&#xff1a;C语言刷题专栏&#x1f449;专栏推荐&#xff1a;✅C语言初阶之路 ✅C语言进阶之路&#x1f4bb;代码仓库&#xff1a;江池俊的代码仓库&#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐&a…

正则表达式 之 断言详解

正则表达式的先行断言和后行断言一共有 4 种形式&#xff1a; (?pattern) 零宽正向先行断言(zero-width positive lookahead assertion)(?!pattern) 零宽负向先行断言(zero-width negative lookahead assertion)(?<pattern) 零宽正向后行断言(zero-width positive lookb…

QML Book 学习基础5(An Image Viewer)

目录 桌面版&#xff08;win端&#xff09; 移动端 下面我们用更有挑战性例子来使用Qt控件&#xff0c;将创建一个简单的图像查看器。 桌面版&#xff08;win端&#xff09; 程序主要由四个主要区域组成&#xff0c;如下所示。菜单栏、工具栏和状态栏&#xff0c;通常由控件…

ApiPost软件会对数据进行预处理,有可能会导致数据报错

文章目录 测试数据正确的请求方式当URL有数据被修改之后&#xff08;数据就不一致了&#xff09; 测试数据 %257B%2522pageNum%2522:1,%2522pageSize%2522:10,%2522param%2522:%257B%2522flowType%2522:1,%2522workcardType%2522:%2522作者的请求方便大家一键复制 localhost:…

记录windows7无权安装

项目场景&#xff1a; electron 安装程序 windows上的C盘 progrom files 、 问题描述 安装过程中出现 不能打开写入文件 提示如上。 { "name": "intergeOM-goldwind","version": "1.0.0","author": "weile",&q…