Redis解决Session共享问题

在这里插入图片描述

文章目录

  • 一、集群Session共享问题
  • 二、Redis存储验证码和对象
  • 三、解决状态登录刷新问题

一、集群Session共享问题

session共享问题:多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务器时导致数据丢失的问题
在这里插入图片描述
tomcat可以进行多台tomcat进行session拷贝,但是数据拷贝保存相同的内容会存在资源浪费,而且会有时间延迟,所以这种方案不可行

session的替代方案应该满足:

  • 数据共享
  • 内存存储
  • key、value结构

这里我们可以使用redis

二、Redis存储验证码和对象

发送短信:

@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result sendCode(String phone, HttpSession session) {// 1.校验手机号if (phone == null || str.matches("^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$")) {// 2.如何不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.符合,生成验证码String code = RandomUtil.randomNumbers(6);// 4.保存验证码到RedisstringRedisTemplate.opsForValue().set("login:code:" + phone,code,2, TimeUnit.MINUTES);//具体的发送逻辑 在这里就不实现了return Result.ok();}

首先,我们会校验前端传来的手机号格式,如果格式不正确直接返回。使用hutool的工具类生成6位随机验证码,然后将验证码作为value存入到Redis中,为了避免key重复,我们设置了固定格式的key,并且设置一个2分钟的超时时间,超过两分钟验证码自动失效。

登录功能:

public Result login(LoginFormDTO loginForm, HttpSession session) {// 1. 校验手机号String phone = loginForm.getPhone();if (phone == null || str.matches("^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$")) {// 如何不符合,返回错误信息return Result.fail("手机号格式错误!");}// 2. 校验验证码String cacheCode = stringRedisTemplate.opsForValue().get("login:code:" + phone);String code = loginForm.getCode();// 3. 不一致,报错if(cacheCode == null || !cacheCode.equals(code)) {return Result.fail("验证码错误!");}// 4. 一致,根据手机号查询用户 select * from tb_user where phone = ?User user = query().eq("phone", phone).one();// 5. 判断用户是否存在if (user == null) {// 6. 不存在,创建用户并保存user = createUserWithPhone(phone);}// 7. 保存用户信息到RedisString token = UUID.randomUUID().toString(true);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()));stringRedisTemplate.opsForHash().putAll("login:token:" + token,userMap);stringRedisTemplate.expire("login:token:" + token,30,TimeUnit.MINUTES);return Result.ok(token);}

我们在进行登录时,首先会对手机号格式进行检验,如果手机号格式正确,我们从Redis中获取验证码和客户端传来的验证码进行比较,如果一致我们就放行,先去数据库查询该用户信息,如果用户不存在进行保存。
在这里插入图片描述
可能有的同学会有疑问,为什么这里要进行这么麻烦的操作呢?
在这里插入图片描述
因为我们UserDTO中的id是Long类型的,会报Long转String类型转换异常,因为我们这里使用的是StringRedisTemplate
在这里插入图片描述
该类型要求key和value都是String类型,但是我们将对象转为Map时,id为Long类型,所以就出现了该问题,两种方案:1.自定义Map手动put 2.使用BeanUtil,自定义规则

我们需要将用户对象存储在Redis中,这里用什么作为key呢?我们这里用token作为key,将token返回给客户端,客户端后面请求的时候使用该token来获取value。

我们value保存对象时,使用什么存储呢?
1.String:
在这里插入图片描述

2.Hash:
在这里插入图片描述
我们这里使用Hash存储对象,因为Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD,并且占用内存更少。

我们使用UUID随机生成token,但是我们value是哈希结构,我们使用BeanUtil将对象转为Hash存储,因为Redis是在内存存储的,如果一直只存会存在内存不够用的情况,所以我们这里仍然需要设置一个超时时间,那么设置多长时间呢?我们这里模仿Session的只要超过30分钟不访问就会销毁。
在这里插入图片描述
但是我们现在设置的是,从设置开始不管有没有用户访问30分钟后都会销毁,这样肯定是不行的,我们需要和session一样,只要有用户访问我们就需要更新超时时间,那么怎么做呢?可以借助拦截器
在这里插入图片描述
我们的拦截器不是Spring创建的对象,所以我们无法使用注入的方式获取StringRedisTemplate对象,我们需要使用构造方法的方法,那么谁来调用呢?
在这里插入图片描述
我们可以在MvcConfig注册拦截器时传入StringRedisTemplate对象
由于我们多处都需要用到ThreadLocal存储的对象,所以我们将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();}
}
public class LoginInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1. 获取请求头中的tokenString token = request.getHeader("authorization");if(StrUtil.isBlank(token)) {response.setStatus(401);return false;}// 2. 使用token获取Redis中的对象Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries("login:token:" + token);// 3. 判断用户是否存在if(userMap == null) {response.setStatus(401);return false;}// 4. 将Hash 格式转为UserDTO对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 5. 将用户存入ThreadLocal中UserHolder.saveUser(userDTO);// 6. 刷新token超时时间stringRedisTemplate.expire("login:token:" + token,30,TimeUnit.MINUTES);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}

大家需要注意的是我们需要remove ThreadLocal,因为ThreadLocal可能会存在内存泄露问题,因为强软引用的问题,这里我们不具体介绍。

三、解决状态登录刷新问题

在这里插入图片描述
但是这样会存在一些问题,该拦截器只会拦截需要登录的路径,其他路径是不会拦截了,也就不会进行token有效期的刷新了。怎么解决呢? 新加一个全部路径的拦截器
在这里插入图片描述

public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1. 获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {return true;}// 2. 基于token获取Redis中的用户Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);// 3. 判断用户是否存在if(userMap == null) {return true;}// 将查询到的Hash转为UserDTO对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 5. 存在 保存用户到ThreadLocalUserHolder.saveUser(userDTO);stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}

我们创建一个拦截全部路径的拦截器来进行token有效期的刷新
在这里插入图片描述
我们在登录拦截器里,只需要判断ThreadLocal里是否存在有效的用户,如果有放行,否则拦截。

public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/code","/user/login","/blog/hot","/shop/**","/shop-type/**","/upload/**","/voucher/**");registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**");}
}

我们在注册刷新Token的拦截器,并且增加所有路径。
但是我们如何保证刷新Token的拦截器在登录拦截器之前执行呢?其实在MvcConfig中注册拦截器的顺序也就是拦截的顺序,但是这样不保险
在这里插入图片描述
其实我们在addInterceptor时会生成一个拦截器注册器对象
在这里插入图片描述
拦截器注册器中又有一个order属性,默认都是0,这个值决定拦截器的执行顺序,值越小执行优先级越高。
在这里插入图片描述
我们可以通过设置order来决定它们的执行顺序

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式内核及驱动开发高级

一、起源 仅devfs&#xff0c;导致开发不方便以及一些功能难以支持&#xff1a; 热插拔 不支持一些针对所有设备的统一操作&#xff08;如电源管理&#xff09; 不能自动mknod 用户查看不了设备信息 设备信息硬编码&#xff0c;导致驱动代码通用性差&#xff0c;即没有分离…

算法与数据结构(二)--【2】链表进阶

一.循环链表 1.单循环链表/循环链表 【1】概念&#xff1a;在单链表中&#xff0c;将终端结点的指针域NULL改为指向第一个结点&#xff0c;就使整个链表形成一个环&#xff0c;这种首尾详解的链表成为循环链表。 【2】特点&#xff1a;从表中任一结点出发均可找到表中其他结点…

某网站提交登陆信息加密JS逆向实战分析

1. 写在前面 对于爬虫开发者来说&#xff0c;职业生涯中可能或多或少会遇到各种各样的网站&#xff0c;其中有些必要要求登陆才能浏览。那么模拟登陆的时候发现提交的登陆信息&#xff08;用户名、密码&#xff09;都是经过加密后的&#xff0c;如何处理&#xff1f;这里找到了…

手把手教你如何发布体验

发布工具集&#xff1a;体验中心 体验中心 (Experience Hub) 是发布流程的起点&#xff0c;也是其他工具的可扩展永久中心。从这里你们可以验证每个步骤&#xff0c;以便发布你们的体验&#xff1a; 具有当前状态的可视化任务列表 工具摘要按钮 发布/取消发布按钮 - 自动批…

【雕爷学编程】Arduino动手做(161)---16路PWM舵机驱动板2

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

【刷题】在二叉树中分配硬币

在二叉树中分配硬币 https://leetcode.cn/problems/distribute-coins-in-binary-tree/description/ 描述 给定一个有 N 个结点的二叉树的根结点 root&#xff0c;树中的每个结点上都对应有 node.val 枚硬币&#xff0c;并且总共有 N 枚硬币。 在一次移动中&#xff0c;我们…

Unity DOTS纯ECS实现虚拟摇杆Joystick控制角色移动

上篇已经实现了ECS框架下的IBeginDragHandler、IDragHandler、IEndDragHandler这几个拖动事件&#xff0c;使得可以任意给ECS框架下的UI(2D entity)响应拖动事件。本篇分享下在前篇实现的功能的基础上再实现一个常用的摇杆控制角色移动的功能。 需要注意的一点&#xff0c;目前…

如何从一个仪表盘管理多个WordPress网站?

您是否正在寻找一种管理多个WordPress网站的简单方法&#xff1f; 监控多个网站并使其保持更新可能非常耗时。 幸运的是&#xff0c;有几种 WordPress 管理工具可以让您从单个仪表板管理多个 WordPress 网站变得非常容易。这将帮助您节省大量时间&#xff0c;同时使所有 Word…

Android复杂UI的性能优化实践 - PTQBookPageView 性能优化记录

作者&#xff1a;彭泰强 1 评价指标&优化成果 要做性能优化&#xff0c;首先得知道性能怎么度量、怎么表示。因为性能是一个很抽象的词&#xff0c;我们必须把它量化、可视化。那么&#xff0c;因为是UI组件优化&#xff0c;我首先选用了GPU呈现模式分析这一工具。 在手机…

如何应对ChatGPT这一波AI浪潮

最近我在写一系列文章&#xff0c;其中包括《ChatGPT 实战系列》和《WPS Office AI实战系列》。想通过这些文章提供实践指导&#xff0c;既自己动手实践了&#xff0c;也能与大家分享我的实践结果&#xff0c;这是一个学习的过程。在实践过程中&#xff0c;我发现有些实用的方面…

7.kafka+ELK连接

文章目录 kafkaELK连接部署Kafkakafka操作命令kafka架构深入FilebeatKafkaELK连接 kafkaELK连接 部署Kafka ###关闭防火墙systemctl stop firewalld systemctl disable firewalldsetenforce 0vim /etc/selinux/configSELINUXdisabled###下载安装包官方下载地址&#xff1a;ht…

【数据结构】初识

&#x1f341; 博客主页:江池俊的博客_CSDN博客-C语言——探索高效编程的基石领域博主 &#x1f341; 专栏&#xff1a;https://blog.csdn.net/2201_75743654/category_12348274.html &#x1f341; 如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏&#x1f…