redis的共享session应用

news2024/12/23 10:07:08

项目背景:

该项目背景就是黑马的黑马点评项目。

一:基于Session实现验证码登录流程

基本的登录流程我们做了很多了。这个是短信登录流程

其实和普通的登录流程就多了一个生成验证码,并将验证码保存在session中,并且呢,返回给前端,前端登录的时候需要多提交一个表单项而言

整体流程:

整体的流程就是生成了一个验证码并且保存在session中

当用户登录时,我们就在session中取出对应的验证码然后和用户传的验证码是否相同来比对

如果用户不存在,就注册之后,将用户存储到当前的线程中

如果用户存在,直接存储到当前的线程中

这个是前端拦截器的实现


关于这个cookie和session的会话技术,中间具体的过程就是:

当登录成功之后,在tomcat服务器会生成具有唯一ID的session,前端会有一个Cookie保存在浏览器中,每次前端发送请求,就会带上这个Cookie,根据这个Cookie会匹配一个Session,我们就可以通过这个Session是否存在来知道用户是否登录

具体的在:SpringMVC(包括Servlet,会话技术)理解-CSDN博客

这里需要有一个重要的思维:就是Tomcat也是有内存的,

在一开始的时候,黑马的课是直接将整个User实体类存储到当前线程中,但User实体类属性很多,会占用更多的内存,所以后面就存储UserDTO这个类,相对实体类属性较少,会节省空间,所以我们需要对Tomcat的线程和内存的量都需要有概念,可能以后项目优化就可以从这个点下手。

Redis代替session进行短信验证登录流程

 首先为什么要用这个Redis代替传统的Session进行存储呢?

这个是关于分布式的知识:

Redis-CSDN博客这一篇有

先来看个图也行

具体是下面的五个步骤:

1:先将生成的验证码放到redis中

@Override
    public Result sendCode(String phone, HttpSession session) {
        //1: 对传递的电话进行合法校验
        if (RegexUtils.isPhoneInvalid(phone)) {
            //2:不合法直接返回
            return Result.fail("手机号不合法");
        }
        //3:合法,生成验证码
        final String code = RandomUtil.randomNumbers(6);
        //4:将验证码保存到Redis中,且key为当前用户手机号
        redisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code);
        //5:模拟发送验证码
        log.debug("发送短信验证码成功,验证码:{}", code);
        //6:返回
        return Result.ok();
    }

 整体的代码逻辑:

先用正则表达式来校验电话是否合法

再用Hutool的RandomUtil生成6位随机数字

最后将具有业务意义的Redis常量+用户的电话做为Key存储到redis中

最后只是模拟发送了验证码

2:login登录接口去redis中查找验证码

3:login登录接口中将登录成功之后的用户信息存储到redis中

这里我们需要先明确的一点是:

我们用Redis中的那种数据结构来存储用户信息

我们常用的String,如果用来存储的话,肯定可以不过不便于扩展

最好的数据结构是Hash

这两步在一个方法中

@Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        String code = loginForm.getCode();
        //1:校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号不合法");
        }
        //校验验证码
        // 从redis中取出验证码
        String cacheCode = (String) redisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone);
        if (StringUtils.isBlank(code)
                || StringUtils.isBlank(cacheCode)
                || !code.equals(cacheCode)) {
            return Result.fail("验证码不能为空或验证码错误");
        }
        //2:根据手机号查询用户
        User user = query().eq("phone", phone).one();
        //3:如果这个用户不存在,就创建新用户
        if (user == null) {
            user.setPhone(phone);
            user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
            boolean save = this.save(user);
            if (!save) {
                return Result.fail("创建用户失败");
            }
        }
        //4:保存用户到Redis 这里要有一个内存的概念,就是你将整个User实体全都存进去,占用的内存就比UserDTO大很多
        //4.1:生成token
        String token = UUID.randomUUID().toString();
        //4.2:将User对象转为UserDTO
        final UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        //4.3:将UserDTO转换成map对象
        final Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create()
                .setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
        redisTemplate.opsForHash().putAll(RedisConstants.LOGIN_USER_KEY + token, userMap);
        //4.4:设置token的过期时间
        redisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        return Result.ok(token);
    }

一般我们会将Redis的Key中的字符串抽象成一个常量 

package com.hmdp.utils;

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 = 36000L;
}

整体的代码逻辑:

先用正则表达式来校验电话是否合法

用相同的Key从redis中取出验证码进行校验

根据手机号查询用户,根据是否存在然后进行注册或者登录

生成一个token,这个就是redis中的key,并且将这个token返回给前端,前端请求其它接口的时候,在请求头中带上token,后端根据这个token在redis中查找

这里生成token之后,还需要将用户User(从数据库中查出来的)转成UserDTO,在转成Map

这里可以用一个Hutool的BeanUtil.beanToMap,并且呢

这里有一个问题就是,UserDTO中有一个字段是Long类型的,我们的userMap都是<String,Object>,所以我们可以用这个工具类提供的第三个参数:

CopyOptions.create()
                .setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()))

来自定义我们自己的转换规则

最后我们再设置一下过期时间,过期时间的作用肯定不用多说,非常重要。


但是我们现在这样写还是有问题:就是我们已经设置了这个redis中的session过期时间,但是,我们会发现,我们就只在这个登录接口中设置了,比如用户调用了其它的接口,这说明用户还是在我们这个应用中,不过到时间了,用户的redis中的session自动过期,直接下线,这样用户体验不好,所以,我们下一个解决策略就是在拦截器中刷新这个过期时间。

4:在拦截器中重新刷新redis的过期时间,并且将用户信息保存到ThreadLocal中

但这里我们可以同时把第五个步骤抛出来

就是:我们光在拦截器这里刷新就够了嘛?比如有些接口在这个LoginInterceptor中没有被拦截到),我们在这个LoginInterceptor之前再加一个接口,专门做redis的刷新,在下一个接口再进行拦截,有点像微服务网关后面再加一个这个MVC拦截器的流程一样

5:解决一些未被第一个拦截器拦截到的路径的接口redis过期的问题

package com.hmdp.utils;


public class RefreshRedisIntercepor implements HandlerInterceptor {

    private RedisTemplate redisTemplate;
    public RefreshRedisIntercepor(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.3:从前端请求中取出token
        final String token = request.getHeader("authorization");
        //1.4:判断token是否为空
        if(StringUtils.isBlank(token)){
            return true;
        }
        //1.6:从redis中取出userMap
        final Map userMap = redisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY+token);
        //1.7 :判断userMap是否为空
        if (userMap.isEmpty()) {
            return true;
        }
        // 手动转换 id 字段
        if (userMap.containsKey("id")) {
            Object idValue = userMap.get("id");
            if (idValue instanceof String) {
                userMap.put("id", Long.valueOf((String) idValue)); // 转换为 Long
            }
        }
        final UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //2:保存用户的信息到ThreadLocal
        UserHolder.saveUser((UserDTO) userDTO);
        //3:刷新Redis的过期时间
        redisTemplate.expire(RedisConstants.LOGIN_USER_KEY + userDTO.getId(), RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //3:在请求结束后,移除ThreadLocal中的用户信息
        UserHolder.removeUser();
    }
}

这个是Redis刷新拦截器

上面的注释就是代码流程了

这里有一个小小的思维点,就是这个拦截器,叫拦截器,但是它不拦截,就是如果你的参数不合法,我不拦截,我直接给你通过,到下一个点去去拦截

package com.hmdp.utils;

import com.hmdp.dto.UserDTO;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginIntercepor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1:判断ThreadLocal是否有userDTO
        final UserDTO user = UserHolder.getUser();
        if(user == null){
            response.setStatus(401);
            return false;
        }
        return true;
    }
}

 这个是第二个拦截器,

这个拦截器的逻辑很简单,判断当前线程中是否有这个userDTO,如果没有,就拦截,有就放行


这里有个小技巧,

就是我们这个RefreshRedisIntercepor不是bean对象,所以不能依赖注入RedisTemplate。

我们可以在MvcConfig中进行配置:

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Autowired
    private RedisTemplate redisTemplate;

    //配置拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginIntercepor())
                .excludePathPatterns("/user/code", "/user/login", "/shop/**", "/shop-type/**", "/upload/**", "/blog/hot", "/blog/search").order(1);
        registry.addInterceptor(new RefreshRedisIntercepor(redisTemplate)).order(0);
        //order :调整优先级
    }

}

在RefreshRedisIntercepor留一个构造方法

在这个MvcConfig进行依赖注入

并且这里可以通过.order(0)这个方法进行优先级的排行

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

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

相关文章

20240831 每日AI必读资讯

Runway 突然删除清空了 HuggingFace 上的所有内容&#xff01;原因不明... - 之前的项目也无法访问了&#xff0c;比如 Stable Diffusion v1.5也被删了 &#x1f517;https://huggingface.co/runwayml/stable-diffusion-v1-5 &#x1f517;GitHub 也空了&#xff1a;https:…

ue Rotate to face BB entry转向不对

可能原因&#xff1a; 角色模型没有到正向。 错误&#xff1a; 正确&#xff1a;

C语言:ASCII码表和字符操作

目录 目录 1. 引言 2. ASCII码表 2.1 控制字符 2.2 可显示字符 3. 例子 3.1 相关函数 3.2 打印能够显示的 ASCII码 3.3 字母大小写转换 3.4 数字转数字字符 1. 引言 因为计算机只是认识 0 和 1组成的一串串的二进制数字&#xff0c;为了将人类认识的文…

【时间盒子】-【1.序言】高效人士都在用的时间管理方法。我是如何通过鸿蒙元服务APP实现?

一、介绍 【时间盒子】系列内容将帮助开发者学习如何构建一个全新的HarmonyOS元服务应用&#xff0c;学习使用DevEco Studio创建新项目、使用预览器预览页面、使用真机调试APP、自定义弹窗、使用系统提醒能力&#xff08;闹钟&#xff09;、使用首选项数据持久化、熟悉ArkUI页…

Centos 下载和 VM 虚拟机安装

1. Centos 下载 阿里云下载地址 centos-7.9.2009-isos-x86_64安装包下载_开源镜像站-阿里云 2. VM 中创建 Centos 虚拟机 2.1 先打开 VM 虚拟机&#xff0c;点击首页的创建新的虚拟机 2.2 选择自定义&#xff0c;然后点击下一步。 2.3 这里默认就好&#xff0c;继续选择下一…

PE文件结构详解(非常详细)

最近在参考OpenShell为任务栏设置图片背景时&#xff0c;发现里面使用了IAT Hook&#xff0c;这一块没有接触过&#xff0c;去查资料的时候发现IAT Hook需要对PE文件结构有一定的了解&#xff0c;索性将PE文件结构的资料找出来&#xff0c;系统学习一下。 PE文件结构 Portable…

C++基础(1)——入门知识

目录 1.C版本更新 2.C参考⽂档&#xff1a; 3.C书籍推荐 4.C的第⼀个程序 5.命名空间 5.1namespace的价值 5.2namespace的定义 5.3 命名空间使⽤ 6.C输⼊&输出 7.缺省参数 8.函数重载 9.引⽤ 9.1引⽤的概念和定义 9.2引⽤的特性 9.3引⽤的使用 9.4const引⽤…

YOLOv5独家改进:一种高效移动应用的卷积加性自注意Vision Transformer

💡💡💡本文独家改进:高效移动应用的卷积加性自注意Vision Transformer,构建了一个新颖且高效实现方式——卷积加性相似度函数,并提出了一种名为卷积加性标记混合器(CATM) 的简化方法来降低计算开销 收录 YOLOv5原创自研 https://blog.csdn.net/m0_63774211/cat…

opencv/c++的一些简单的操作(入门)

目录 读取图片 读取视频 读取摄像头 图像处理 腐蚀 膨胀 调整图像大小 裁剪和缩放 绘制 绘制矩形 绘制圆形 绘制线条 透视变换 颜色检测 轮廓查找 人脸检测 检测人脸 检测嘴巴 可适当调整参数 读取图片 读取路径widows使用vis sto一定是\斜杠 #include <o…

LoRA 和 DoRA 代码笔记

Improving LoRA: Implementing Weight-Decomposed Low-Rank Adaptation (DoRA) from Scratch LoRA LoRA初始化时&#xff0c;A使用正态分布&#xff0c;B使用0. class LoRALayer(nn.Module):def __init__(self, in_dim, out_dim, rank, alpha):super().__init__()std_dev 1…

第L1周:机器学习-数据预处理

第L1周&#xff1a;机器学习-数据预处理 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 学习要点&#xff1a; **** 学习如何处理缺损数据尝试进行Label编码使用train_test_split进行数据划分学习特征标准化…

EXO项目StandardNode;max_generate_tokens;buffered_token_output;is_finished;

目录 StandardNode max_generate_tokens buffered_token_output 构造函数参数 类属性 总结 is_finished max_generate_tokens self.buffered_token_output StandardNode _process_tensor result是一个np.ndarray ,result.size == 1是什么意思 StandardNode max_g…

【Python机器学习】NLP词频背后的含义——反馈及改进

之前学习的LSA方法都没有考虑文档之间的相似度信息&#xff0c;创建的主题对一组通用规则来说是最优的。在这些特征&#xff08;主题&#xff09;提取模型的无监督学习中&#xff0c;没有任何关于主题向量之间应该多么接近的数据。我们也不允许任何关于主题向量在哪里结束或者它…

力扣刷题(复习版1)

文章目录 题目&#xff1a;最大重复子字符串题解 题目&#xff1a; 面试题 16.07. 最大数值题解 题目&#xff1a; 最大字符串配对数目题解 题目&#xff1a; 字符串中第二大的数字题解 题目&#xff1a; 统计最大组的数目题解 题目&#xff1a; 删除每行中的最大值题解 题目&a…

记录|Chart控件使用

目录 前言一、Series集合1.1 什么是Series1.2 IsValueShownAsLabel1.3 Points 二、ChartArea2.1 轴- Axes2.1.1 Title2.1.2 刻度下的Maximum、Minimum2.1.3 间隔- Interval2.1.4 网格刻度线 2.2 游标- CursorX/CursorYMajorGrid属性中的Enabled 更新时间 前言 参考视频&#xf…

七、库存管理——盘点业务

第一节 库存管理盘点 1、盘点的目的 答&#xff1a;是指通过定期或临时对库存商品的实际数量进行盘查、清点&#xff0c;然后掌握货物的流动情况&#xff08;入库、出库、调拨、在库等&#xff09;&#xff0c;最后对仓库现有物品的实际数量与保管账上记录的数量相核对&…

Spring Boot集成Stripe快速入门demo

1.什么是Stripe&#xff1f; 一体化全球支付平台&#xff0c;开启收入增长引擎&#xff0c;针对不同规模业务打造的支付解决方案&#xff0c;满足从初创公司到跨国企业的多维度需求&#xff0c;助力全球范围内线上线下付款。 转化更多客户: 通过内置的优化功能、100 多种支付…

QT QGraphicsView实现预览图片显示缩略图功能

QT QGraphicsView实现预览图片显示缩略图功能QT creator Qt5.15.2 头文件&#xff1a; #ifndef TGRAPHICSVIEW_H #define TGRAPHICSVIEW_H#include <QGraphicsView> #include <QMainWindow> #include <QObject> #include <QWidget>class TGraphicsVie…

vue页面自适应 动态 postcss postcss-pxtorem

vue页面自适应 动态 postcss postcss-pxtorem postcss-pxtorem实现页面自适应1、安装postcss-pxtorem2、根目录创建postcss.config.js&#xff0c;并配置以下内容3、创建rem.js&#xff0c;动态设置root px4、在main.js中引入rem.js5、在main.js中创建全局处理函数px2rem6、对…

【王树森】Vision Transformer (ViT) 用于图片分类(个人向笔记)

图片分类任务 给定一张图片&#xff0c;现在要求神经网络能够输出它对这个图片的分类结果。下图表示神经网络有40%的信心认定这个图片是狗 ResNet&#xff08;CNN&#xff09;曾经是是图像分类的最好模型在有足够大数据做预训练的情况下&#xff0c;ViT要强于ResNetViT 就是Tr…