短信登录session-redis

news2025/1/16 4:42:54

1.流程

在这里插入图片描述

1.1 发送验证码

  1. 模拟路径
http://127.0.0.1:8080/api/user/code?phone=1335566
Request Method:POST
  1. controller层
	/**
     * 发送手机验证码
     */
    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        // TODO 发送短信验证码并保存验证码
        return userService.sendCode(phone,session);
    }

2.调用service层Impl

@Override
    public Result sendCode(String phone, HttpSession session) {
        // 1.校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 2.如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        // 3.符合,生成验证码
        String code = RandomUtil.randomNumbers(6);

        // 4.保存验证码到 session
        session.setAttribute("code",code);
        // 5.(模拟)发送验证码
        log.debug("发送短信验证码成功,验证码:{}", code);
        // 返回ok
        return Result.ok();
    }

1.2 验证码登录

1.controller层

 	 /**
     * 登录功能
     * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
     */
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        return userService.login(loginForm, session);
    }

2.service层Impl

 @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1.校验手机号
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 2.如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        // 3.校验验证码
        Object cacheCode = session.getAttribute("code");
        String code = loginForm.getCode();
        if(cacheCode == null || !cacheCode.toString().equals(code)){
             //3.不一致,报错
            return Result.fail("验证码错误");
        }
        //一致,根据手机号查询用户  mybatis-plus
        User user = query().eq("phone", phone).one();

        //5.判断用户是否存在
        if(user == null){
            //不存在,则创建
            user =  createUserWithPhone(phone);
        }
        //7.保存用户信息到session中,返回的数据为了安全性进行Dto封装,使用hutool中的BeanUtil.copyProperties()进行复制
        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));

        return Result.ok();
    }
    
private User createUserWithPhone(String phone) {
		//创建用户配置默认信息
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
        //向数据库中添加用户
        save(user);
        return user;
    }

1.3 登录校验

  1. 在utils层创建拦截器并且实现HandlerInterceptor接口
  2. 快捷键Ctrl + i 重写相关方法
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.获取session
        HttpSession session = request.getSession();
        //2.获取session中的用户
        Object user = session.getAttribute("user");
        //3.判断用户是否存在
        if(user == null){
              //4.不存在,拦截,返回401状态码
              response.setStatus(401);
              return false;
        }
        //5.存在,保存用户信息到Threadlocal
        UserHolder.saveUser((UserDto) user);
        //6.放行
        return true;
    }

 	@Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    //为了防止内存溢出要对ThreadLocal中的数据进行清除
        UserHolder.removeUser();
    }
}
  1. 让拦截器生效(配置拦截器)
    在config层创建配置类并且实现WebMvcConfigurer接口,添加@Configuration注解,重写addInterceptors方法
@Configuration
public class MvcConfig implements WebMvcConfigurer {

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

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();
    }
}

3. session共享问题:多台Tomcat服务器不共享session存储空间,请求到不同服务器是可能出现数据丢失

4.基于Redis实现共享session登录

在这里插入图片描述

4.1 发送验证码

  1. 模拟路径
http://127.0.0.1:8080/api/user/code?phone=1335566
Request Method:POST
  1. controller层
	/**
     * 发送手机验证码
     */
    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        // TODO 发送短信验证码并保存验证码
        return userService.sendCode(phone,session);
    }

2.调用service层Impl
【和之前不同的是:要引入redis相关依赖,不写入session,而是写入redis数据库】



 @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1.校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 2.如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        // 3.符合,生成验证码
        String code = RandomUtil.randomNumbers(6);

        /*4.保存验证码到 session
        session.setAttribute("code",code);*/
        //4.保存验证码到redis并设置过期时间
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone,code,2, TimeUnit.MINUTES);
        // 5.(模拟)发送验证码
        log.debug("发送短信验证码成功,验证码:{}", code);
        // 返回ok
        return Result.ok();
    }

4.2 验证码登录

 @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1.校验手机号
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 2.如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        // 3.从redis获取验证码并校验
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        String code = loginForm.getCode();
        if (cacheCode == null || !cacheCode.equals(code)) {
            // 不一致,报错
            return Result.fail("验证码错误");
        }
        //4一致,根据手机号查询用户
        User user = query().eq("phone", phone).one();

        //5.判断用户是否存在
        if(user == null){
            //不存在,则创建
            user =  createUserWithPhone(phone);
        }
/*        7.保存用户信息到session中
        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));*/

        // 7.保存用户信息到 redis中
        // 7.1.随机生成token,作为登录令牌 UUID hutool中的
        String token = UUID.randomUUID().toString(true);
        // 7.2.将User对象转为DTO和HashMap存储
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create()
                        .setIgnoreNullValue(true)
                        .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
        // 7.3.存储
        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        // 7.4.设置token有效期
        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
        return Result.ok(token);
    }

    private User createUserWithPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
        save(user);
        return user;
    }

4.3 登录校验

  1. 在utils层创建拦截器并且实现HandlerInterceptor接口
  2. 快捷键Ctrl + i 重写相关方法
  3. 需要注入Redis相关依赖
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;
        }
        // 2.基于TOKEN获取redis中的用户
        String key  = LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 3.判断用户是否存在
        if (userMap.isEmpty()) {
            response.setStatus(401);
            return false;
        }
        // 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);
        //6.放行
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}
  1. 让拦截器生效(配置拦截器)
    在config层创建配置类并且实现WebMvcConfigurer接口,添加@Configuration注解,重写addInterceptors方法
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    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"
                );
    }
}

5. 拦截器优化

问题:
在这个方案中,他确实可以使用对应路径的拦截,同时刷新登录token令牌的存活时间,但是现在这个拦截器他只是拦截需要被拦截的路径,假设当前用户访问了一些不需要拦截的路径,那么这个拦截器就不会生效,所以此时令牌刷新的动作实际上就不会执行,所以这个方案他是存在问题的
优化:
既然之前的拦截器无法对不需要拦截的路径生效,那么我们可以添加一个拦截器,在第一个拦截器中拦截所有的路径,把第二个拦截器做的事情放入到第一个拦截器中,同时刷新令牌,因为第一个拦截器有了threadLocal的数据,所以此时第二个拦截器只需要判断拦截器中的user对象是否存在即可,完成整体刷新功能。

5.1 定义一个拦截器用来拦截所有请求,因此每个请求都可以对redis中的数据进行刷新,并且优先使用。

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();
   }
}

5.2 在定义一个拦截器,判断是否拦截

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;
    }

}

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

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

相关文章

GitHub/R3D3项目环境配置踩坑记录

1、前言 项目链接地址&#xff1a;SysCV/r3d3 (github.com) 按照安装步骤容易出现的问题&#xff0c;environment.yaml文件中安装相关包&#xff0c;其中还有两个pip install githttps://github.com/..........这两个建议注释掉&#xff0c;后面再来安装这两个。 2、问题及解…

电脑本地搭建privateGPT流程

文章目录 前言效果所需文件流程1,python版本2,工程文件安装2.1服务端搭建2.2客户端搭建 3,模型下载 前言 当我的电脑主机在本地运行privateGPT的时候我听到了cpu风扇在呼啸, 至于privateGPT是干什么的, 其实就相当于一个文档分析软件,只不过内置了一个gpt,你把文件丟给他,你可…

鸿蒙OpenHarmony【搭建Ubuntu环境】

搭建Ubuntu环境 在嵌入式开发中&#xff0c;很多开发者习惯于使用Windows进行代码的编辑&#xff0c;比如使用Windows的Visual Studio Code进行OpenHarmony代码的开发。但当前阶段&#xff0c;大部分的开发板源码还不支持在Windows环境下进行编译&#xff0c;如Hi3861、Hi3516…

【论文源码实战】轻量化MobileSAM,分割一切大模型出现,模型缩小60倍,速度提高40倍

前言 MobileSAM模型是在2023年发布的&#xff0c;其对之前的SAM分割一切大模型进行了轻量化的优化处理&#xff0c;模型整体体积缩小了60倍&#xff0c;运行速度提高40倍&#xff0c;但分割效果却依旧很好。 MobileSAM在使用方法上沿用了SAM模型的接口&#xff0c;因此可以与…

matlab学习003-绘制由差分方程表示的离散系统图像

目录 1&#xff0c;题目 2&#xff0c;使用函数求解差分方程 1&#xff09;基础知识 ①filter函数和impz函数 ②zeros函数 ☀ 2&#xff09;绘制图像 ​☀ 3&#xff09;对应代码 如果连简单的信号都不会的&#xff0c;建议先看如下文章&#x1f447;&#xff0c;之…

UE4 相机围绕某点旋转

关卡&#xff08;一个相机CameraActor&#xff0c;一个Cube(名叫Target)&#xff09;&#xff1a; 关卡蓝图里的逻辑(为了大家看得清楚&#xff0c;特意连得很紧凑&#xff0c;也比较乱&#xff0c;不然一张截图放不下)&#xff1a; 只对Yaw 只Pitch: 同样对Roll: 围绕任…

Java maven项目打包自动测试并集成jacoco生成代码测试覆盖度报告

引入Junit 引入 junit5 单元测试依赖 <properties><junit.version>5.10.2</junit.version><jacoco.version>0.8.12</jacoco.version></properties><dependencies><!-- 单元测试 --><dependency><groupId>org.jun…

墨子web3时事周报

蚂蚁集团Web3研发进展与布局 国内Web3赛道的领军企业——蚂蚁集团&#xff0c;凭借其在前沿科技领域的深耕不辍&#xff0c;已在Web3技术研发疆域缔造了卓越战绩。特别是在引领行业革新的关键时刻&#xff0c;集团于今年四月末震撼推出了颠覆性的Web3全套解决方案&#xff0c…

easyui datagrid单元格点击进入编辑时,行会自动向上错位

现象描述&#xff0c;点击第20行可编辑的单元格进入编辑状态时&#xff0c;滚动条自动滚动到第19行了。导致第20行被分页遮挡&#xff0c;看不到无法编辑。 排查了一天百度AI说是滚动定位问题&#xff0c;最后发现是自己设置的列有问题&#xff0c;表格总共五列&#xff0c;全…

如何搭建线下陪玩系统(本地伴游、多玩圈子)APP小程序H5多端前后端源码交付,支持二开!

一、卡顿的优化方法 1、对陪玩系统源码中流媒体传输的上行进行优化&#xff0c;通过提升推流端的设备性能配置、推流边缘CDN节点就近选择等方式解决音视频数据源流的卡顿。 2、对陪玩系统源码中音视频数据的下载链路进行优化&#xff0c;通过选择更近更优质的CDN边缘节点来减少…

软航H5 PDF签章产品经nginx代理之后浏览器中PDF盖章时提示:签章失败:网络错误 的问题排查及解决办法

目录 问题现象 问题排查思路 问题处理办法 附&#xff1a;软航H5 PDF签章产品介绍 软航电子签章系统 软航版式文档签批系统 问题现象 问题描述&#xff1a;在系统中集成了软航H5 PDF签章产品&#xff0c;软航H5 PDF签章产品的对应服务是通过nginx代理的&#xff0c;在奇安…

FPGA_verilog语法整理

FPGA_verilog语法整理 verilog的逻辑值 verilog的常数表达 位宽中指定常数的宽度&#xff08;表示成二进制数的位数&#xff09;&#xff0c;单引号加表示该常数为几进制的底数符号。 二进制底数符号为b&#xff0c;八进制为 o&#xff0c;十进制为d&#xff0c;十六进制为 h…

Windows 下使用 CMake + Visual Studio 2022 编译 OpenCV 4.8.1 及其扩展模块

一. 背景 目前维护的某个项目是在 Windows 下运行的&#xff0c;并且使用了 OpenCV 4.5.2 版本。 我本地的开发环境是 Mac 并使用了比较新的 OpenCV 4.8.1 版本。为了和本地开发环境保持一致&#xff0c;我打算对项目中使用的 OpenCV 进行升级&#xff0c;因为该项目还是用了扩…

BI数据分析有什么优势?引入BI工具为何能加快企业数字化进程?

进化是人类社会永恒不变的主题。从早期的猿人到现在的人类&#xff0c;从久远的石器时代到现在的信息时代&#xff0c;人类社会历经一次次的进化才积攒了今日的科技与智慧。人类的文明史&#xff0c;实质是科学和信息的进化史。 如今&#xff0c;数字化浪潮席卷全球&#xff0…

React Hooks(常用)笔记

一、useState&#xff08;保存组件状态&#xff09; 1、基本使用 import { useState } from react;function Example() {const [initialState, setInitialState] useState(default); } useState(保存组件状态) &#xff1a;React hooks是function组件(无状态组件) &#xf…

ruby 配置代理 ip(核心逻辑)

在 Ruby 中配置代理 IP&#xff0c;可以通过设置 Net::HTTP 类的 Proxy 属性来实现。以下是一个示例&#xff1a; require net/http// 获取代理Ip&#xff1a;https://www.kuaidaili.com/?refrg3jlsko0ymg proxy_address 代理IP:端口 uri URI(http://www.example.com)Net:…

【EI会议征稿】2024年先进机械电子、电气工程与自动化国际学术会议(ICAMEEA 2024)

2024 International Conference on Advanced Mechatronic, Electrical Engineering and Automation ●会议简介 2024年先进机械电子、电气工程与自动化国际学术会议&#xff08;ICAMEEA 2024&#xff09;将汇聚全球机械电子、电气工程与自动化领域的专家学者&#xff0c;共同…

网际互联及OSI七层模型

1什么是OSI七层模型 2OSI每一个Layer的定义 及用途 3如何使用OSI参考模型分析网络通信过程 一、网际互联 &#xff08;一&#xff09;OSI的概念&#xff1a; open system interconnect开放系统互联参考模型&#xff0c;是有ISO&#xff08;国际标准化组织&#xff09;定义…

科技的崛起:国内机器视觉蓬勃发展

文 | BFT机器人 在工业4.0的浪潮下&#xff0c;随着科技的蓬勃发展&#xff0c;机器视觉逐渐走入大众视野&#xff0c;机器视觉产品的普及范围也越来越广。 大家知道机器视觉的由来吗&#xff1f; 机器视觉的由来可以追溯到20世纪70年代&#xff0c;美国麻省理工学院&#xff…

【Leetcode每日一题】 动态规划 - 地下城游戏(难度⭐⭐⭐)(61)

1. 题目解析 题目链接&#xff1a;174. 地下城游戏 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 一、状态表定义 在解决地下城游戏问题时&#xff0c;我们首先需要对状态进行恰当的定义。一个直观的想法是&#x…