当前位置: 首页 > article >正文

使用Redis解决使用Session登录带来的共享问题

        在学习项目的过程中遇到了使用Session实现登录功能所带来的共享问题,此问题可以使用Redis来解决,也即是加上一层来解决问题。

        接下来介绍一些Session的相关内容并且采用Session实现登录功能(并附上代码),进行分析其存在的问题,并使用Redis来解决这一问题。

一、什么是Session

可以参考一下这个博客的内容,比较详细:

Session详解

        简而言之,session是服务器为了保存用户状态而创建的一个特殊的对象,服务器会为每一个浏览器(客户端)创建一个唯一的session,session有一个JSESSIONID,这个是session的唯一标识。

        当浏览器(客户端)发送请求的时候,会在Cookie中携带JSESSIONID,以便服务器可以找到对应的Session,服务器就能知道客户端的状态了。

比如:在登陆一些网站成功之后,你再访问这个网站的其他内容的时候就不需要重新登陆了,因为此时服务器可以基于浏览器Cookie中的JSESSIONID(或者其他校验技术)来判断你的状态。但是当一段时间未登录之后,你重新访问网站,往往需要重新登录,这是因为浏览器访问时没有携带JSESSIONID,浏览器携带的JSESSIONID对应的session不存在(或者失效)的原因。

二、基于Session实现登录(短信登录)

1.流程:

2.代码实现:

(1)Controller层:(接收请求)

    /*** 发送手机验证码*/@PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {//发送短信验证码并保存验证码return userService.sendCode(phone, session);}/*** 登录功能* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码*/@PostMapping("/login")public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){// 实现登录功能return userService.login(loginForm, session);}

(2)Service层:(处理业务逻辑)

    /*** 发送短信验证码并保存验证码* @param phone* @param session* @return*/@Overridepublic Result sendCode(String phone, HttpSession session) {//1.校验手机号是否合法boolean phoneInvalid = RegexUtils.isPhoneInvalid(phone);//2.若不合法,返回错误信息if(!phoneInvalid){return Result.fail("手机号不合法");}//3.生成验证码 6位String code = RandomUtil.randomNumbers(6);//4.保存验证码到session中session.setAttribute("code",code);//5.发送验证码 (这里可以结合阿里云提供的第三方服务来做,但是为了方便,输出一下就可以)log.debug("发送短信验证码成功,验证码:{}",code);//6.返回okreturn Result.ok();}
     /*** 登录功能* @param loginForm* @param session* @return*/@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {String phone = loginForm.getPhone();//1.校验手机号是否合法if(!RegexUtils.isPhoneInvalid(phone)){return Result.fail("手机号不合法");}//2.校验验证码是否一致String sessionCode = (String) session.getAttribute("code");String userCode = loginForm.getCode();//3.验证码不一致返回错误信息if(sessionCode == null ||!sessionCode.equals(userCode)){return Result.fail("验证码错误");}//4.根据手机号到数据库查询用户User user = query().eq("phone", phone).one();//5.若用户不存在则创建用户并保存到数据库if(user == null){user = createUserByPhone(phone);}//6.保存用户信息到session中UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(user,userDTO);session.setAttribute("user",userDTO);//7.返回okreturn Result.ok();}

(3)LoginInterceptor:(拦截请求,进行登录校验)

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取sessionHttpSession session = request.getSession();//2.获取session里面的userUser user = (User) session.getAttribute("user");//3.用户不存在,则拦截if(user == null){//4.拦截,返回401response.setStatus(401);return false;}//5.存在,保存到ThreadLocal中UserDTO userDTO = new UserDTO();userDTO.setId(user.getId());userDTO.setIcon(user.getIcon());userDTO.setNickName(user.getNickName());UserHolder.saveUser(userDTO);//6.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//在页面渲染完成返回给用户前,删除用户信息,避免内存泄漏UserHolder.removeUser();}
}

(4)MVCconfig:(用于注册拦截器,使得拦截器生效)

@Configuration//由Spring创建的对象 可以使用 Autowired
public class MVCconfig implements WebMvcConfigurer {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns("/user/code","/user/login","/shop/**","/shop-type/**","/blog/hot","/upload/**","/voucher/**");}
}

三、基于Session实现登录存在的共享问题

1.问题分析

session共享问题:多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失的问题。

        也就是说一台浏览器可能会被负载均衡到不同的服务器上,但是服务器的Session是不共享的,那当浏览器先被负载均衡到服务器A,后负载均衡到服务器B时,浏览器所携带的JSESSIONID只在服务器A生效,而服务器B找不到其对应的Session,也就无法获知用户的状态。这就是基于Session实现登录存在的共享问题。

2.解决方案:

session的替代方案应该满足以下条件:

(1)数据共享:

        首先必须满足数据可共享,否则还是会引发共享问题。这个时候就应该想到常用的思路“加一层”,加上一层中间件,上层都到这个中间件来存取数据,这样不就实现了数据共享吗。

(2)内存存储

        由于每次请求的时候,服务器都需要对请求进行校验,这个操作是十分频繁的,如果可以将数据存储在内存中,就可以极大地提高服务器处理请求的效率。

(3)key、value结构

        服务器应该根据服务器所携带key去找到其对应的value

通过以上分析,使用Redis来解决这个问题是再合适不过的了。

使用Redis实现登录功能总思路:

1.用户提交手机号,后端收到手机号之后生成验证码并以手机号为key,验证码为value,并设置过期时间,存储到redis中。存储完成之后发送验证码给用户。

2.用户填写验证码,浏览器发送协带用户填写的手机号和验证码的请求。后端接收请求,根据用户的手机号到redis中查找对应的验证码值,查找后将redis中的验证码和用户提交的验证码进行比较。

3.验证成功,则生成一个token,将token作为key,用户信息作为value(这里使用redis的hash结构存储),设置过期时间,存入redis中,并返回一个token。

4.此后,前端发送请求的时候,就携带这个token,服务器就可以根据这个token到redis中去获取用户的信息。

5.由于redis是单独的一层,所有浏览器拿到token之后都是去这个redis中查找数据,这就解决了用户被负载均衡到不同服务器时,session引发的共享问题。

四、基于Redis实现登录

1.流程:

简单来说就是把对session存取操作改为对redis的存取操作。

2.代码实现:

(1)Controller层

     /*** 发送手机验证码*/@PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {//发送短信验证码并保存验证码return userService.sendCode(phone, session);}/*** 登录功能* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码*/@PostMapping("/login")public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){// 实现登录功能return userService.login(loginForm, session);}@GetMapping("/me")public Result me(){//获取当前登录的用户并返回UserDTO user = UserHolder.getUser();return Result.ok(user);}

(2)Service层:(处理业务逻辑)

    @Autowiredprivate StringRedisTemplate stringRedisTemplate;/*** 发送短信验证码并保存验证码* @param phone* @param session* @return*/@Overridepublic Result sendCode(String phone, HttpSession session) {//1.校验手机号是否合法boolean phoneInvalid = RegexUtils.isPhoneInvalid(phone);//2.若不合法,返回错误信息if(!phoneInvalid){return Result.fail("手机号不合法");}//3.生成验证码 6位String code = RandomUtil.randomNumbers(6);//4.保存验证码到redis中stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY+phone,code,RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);//5.发送验证码 (这里可以结合阿里云提供的第三方服务来做,但是为了方便,输出一下就可以)log.debug("发送短信验证码成功,验证码:{}",code);//6.返回okreturn Result.ok();}/*** 登录功能* @param loginForm* @param session* @return*/@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {String phone = loginForm.getPhone();//1.校验手机号是否合法if(!RegexUtils.isPhoneInvalid(phone)){return Result.fail("手机号不合法");}//2.TODO 从Redis获取验证码并校验验证码是否一致String reidsCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY+phone);String userCode = loginForm.getCode();//3.验证码不一致返回错误信息if(reidsCode == null ||!reidsCode.equals(userCode)){return Result.fail("验证码错误");}//4.根据手机号到数据库查询用户User user = query().eq("phone", phone).one();//5.若用户不存在则创建用户并保存到数据库if(user == null){user = createUserByPhone(phone);}//6.保存用户信息到redis中//6.1 生成随机token 作为登录令牌String token = UUID.randomUUID().toString(true);//6.2 将UserDTO转为 HashMapUserDTO userDTO = new UserDTO();BeanUtils.copyProperties(user,userDTO);Map<String, Object>usertMap = new HashMap<>();usertMap.put("id", userDTO.getId().toString());usertMap.put("nickName", userDTO.getNickName());usertMap.put("icon", userDTO.getIcon());//6.3 存储到redis中String tokenKey = RedisConstants.LOGIN_USER_KEY+token;stringRedisTemplate.opsForHash().putAll(tokenKey,usertMap);//6.4 设置有效期stringRedisTemplate.expire(tokenKey,RedisConstants.LOGIN_USER_TTL,TimeUnit.MINUTES);//7.返回tokenreturn Result.ok(token);}/*** 根据手机号创建用户* @param phone* @return*/private User createUserByPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(6));save(user);return user;}

(3)LoginInterceptor:(拦截请求,进行登录校验)

      @Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的tokenString token = request.getHeader("authorization");if(StringUtils.isEmpty(token)){//token 为空 拦截,返回401response.setStatus(401);return false;}//2.通过token获取redis中的用户信息String tokenKey = RedisConstants.LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);//3.用户不存在,则拦截if(userMap.isEmpty()){//4.拦截,返回401response.setStatus(401);return false;}//5. 将HashMap转为UserDTOUserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//6.存在,保存到ThreadLocal中UserHolder.saveUser(userDTO);//7.重置token有效期stringRedisTemplate.expire(tokenKey,RedisConstants.LOGIN_USER_TTL,TimeUnit.MINUTES)//8.放行return true;}

(4)MVCconfig:(用于注册拦截器,使得拦截器生效)

@Configuration//由Spring创建的对象 可以使用 Autowired
public class MVCconfig implements WebMvcConfigurer {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns("/user/code","/user/login","/shop/**","/shop-type/**","/blog/hot","/upload/**","/voucher/**");}
}

相关文章:

使用Redis解决使用Session登录带来的共享问题

在学习项目的过程中遇到了使用Session实现登录功能所带来的共享问题&#xff0c;此问题可以使用Redis来解决&#xff0c;也即是加上一层来解决问题。 接下来介绍一些Session的相关内容并且采用Session实现登录功能&#xff08;并附上代码&#xff09;&#xff0c;进行分析其存在…...

STM32F1学习——USART串口通信

一、USART通用同步异步收发机 USART的全称是Universal Synchronous/Asynchronous Receiver Transmitter &#xff0c; 通用同步异步收发机&#xff0c;但由于他主要以异步通信为主&#xff0c;所以他也叫UART。它遵循TTL电平标准&#xff0c;是一种全双工异步通信标准&#xff…...

[概率论] 随机变量

Kolmogorov 定义的随机变量是基于测度论和实变函数的。这是因为随机变量的概念需要精确地定义其可能的取值、发生的概率以及这些事件之间的依赖关系。 测度论&#xff1a;在数学中&#xff0c;测度论是用来研究集合大小的理论&#xff0c;特别是无穷可数集和无界集的大小。对于…...

Docker 部署 MinIO | 国内阿里镜像

一、导读 Minio 是个基于 Golang 编写的开源对象存储套件&#xff0c;基于Apache License v2.0开源协议&#xff0c;虽然轻量&#xff0c;却拥有着不错的性能。它兼容亚马逊S3云存储服务接口。可以很简单的和其他应用结合使用&#xff0c;例如 NodeJS、Redis、MySQL等。 二、…...

探索Aviator:轻量级Java动态表达式求值引擎的使用指南

目录 一、快速介绍 &#xff08;一&#xff09;Aviator &#xff08;二&#xff09;Aviator、IKExpression、QLExpress比较和建议 二、基本应用使用手册 1.执行表达式 2.使用变量 3.exec 方法 4.调用函数 调用内置函数 调用字符串函数 调用自定义函数 5.编译表达式…...

编译加速工具ccache

1、什么是ccache ccache&#xff08;Compilation Cache&#xff09;是一个开源的编译缓存工具&#xff0c;最初为 C/C 设计&#xff0c;但也可以用于其他语言的编译过程&#xff08;如 Objective-C、CUDA 等&#xff09;。它的核心思想是通过缓存编译结果&#xff0c;避免重复…...

【R语言】相关系数

一、cor()函数 cor()函数是R语言中用于计算相关系数的函数&#xff0c;相关系数用于衡量两个变量之间的线性关系强度和方向。 常见的相关系数有皮尔逊相关系数&#xff08;Pearson correlation coefficient&#xff09;、斯皮尔曼秩相关系数&#xff08;Spearmans rank corre…...

排序合集(一)

以下是更完善和人性化的版本&#xff0c;增加了一些细节和解释&#xff0c;同时让内容更易读&#xff1a; 一、直接插入排序 (Insertion Sort) 基本思想 直接插入排序是一种简单直观的排序算法&#xff0c;就像我们打扑克牌时的操作&#xff1a;每次摸到一张牌&#xff0c;都…...

深入解析 FFmpeg 的 AAC 编解码过程

深入解析 FFmpeg 的 AAC 编解码过程 —— 技术详解与代码实现 AAC(Advanced Audio Coding) 是一种高效的有损音频压缩格式,因其高压缩效率和良好的音质而被广泛应用于流媒体、广播和音频存储等领域。FFmpeg 是一个强大的多媒体处理工具,支持 AAC 的编码和解码。本文将详细…...

DeepSeek-R1技术报告快速解读

相关论文链接如下&#xff1a; DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language ModelsDeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning 文章目录 一、论文脑图二、论文解读2.1 研究背景2.2 研究方法2.3 …...

windows蓝牙驱动开发-蓝牙常见问题解答

Windows 可以支持多少个蓝牙无线电&#xff1f; Windows 中的蓝牙堆栈仅支持一个蓝牙无线电。 此无线电必须符合蓝牙规范和最新的 Windows 硬件认证计划要求。 蓝牙和 Wi-Fi 无线电如何有效共存&#xff1f; 蓝牙和 Wi-Fi 无线电都在 2.4 GHz 频率范围内运行&#xff0c;因此…...

基于SpringBoot+Vue实现航空票务管理系统

作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参与学生毕业答辩指导&#xff0c;…...

让文物“活”起来,以3D数字化技术传承文物历史文化!

文物&#xff0c;作为不可再生的宝贵资源&#xff0c;其任何毁损都是无法逆转的损失。然而&#xff0c;当前文物保护与修复领域仍大量依赖传统技术&#xff0c;同时&#xff0c;文物管理机构和专业团队的力量相对薄弱&#xff0c;亟需引入数字化管理手段以应对挑战。 积木易搭…...

java项目之美妆产品进销存管理系统的设计与开发源码(ssm+mysql)

项目简介 美妆产品进销存管理系统的设计与开发实现了以下功能&#xff1a; 美妆产品进销存管理系统的设计与开发的主要使用者分为管理员登录后修改个人的密码。产品分类管理中&#xff0c;对公司内的所有产品分类进行录入&#xff0c;也可以对产品分类进行修改和删除。产品管…...

UMLS初探

什么是UMLS UMLS&#xff08;Unified Medical Language System&#xff0c;统一医学语言系统&#xff09;&#xff0c;简单来说就是将不同的医学标准统一到一套体系的系统&#xff0c;主要为了医疗系统的统一而构建出的。 UMLS的主要组成部分 Metathesaurus&#xff1a;一个…...

保姆级教程Docker部署Zookeeper模式的Kafka镜像

目录 一、安装Docker及可视化工具 二、Docker部署Zookeeper 三、单节点部署 1、创建挂载目录 2、运行Kafka容器 3、Compose运行Kafka容器 4、查看Kafka运行状态 5、验证生产消费 四、部署可视化工具 1、创建挂载目录 2、Compose运行Kafka-eagle容器 3、查看Kafka-e…...

idea插件开发dom4j报错:SAXParser cannot be cast to class org.xml.sax.XMLReader

手打不易&#xff0c;如果转摘&#xff0c;请注明出处&#xff01; 注明原文&#xff1a;https://blog.csdn.net/q258523454/article/details/145512328 dom4j报错 idea插件使用到了dom4j依赖&#xff0c;但是报错&#xff1a; I will print the stack trace then carry on…...

【Go语言圣经】第八节:Goroutines和Channels

DeepSeek 说 Goroutines 和 Channels 最近非常流行询问DeepSeek某些相关概念或热点的解释&#xff0c;因此在开始系统性地学习《Go语言圣经》之前&#xff0c;我首先向DeepSeek进行了提问。具体的Prompt如下&#xff1a; 有关Golang当中的Goroutines和Channels&#xff0c;我现…...

第3章 使用 Vue 脚手架

第3章 使用 Vue 脚手架 3.1 初始化脚手架3.1.1 说明3.1.2. 具体步骤3.1.3 分析脚手架结构1 总结2 细节分析1 配置文件2 src文件1 文件结构分析2 例子 3 public文件4 最终效果 3.2 ref属性3.3 props配置项3.4 mixin混入3.5 插件3.6 scoped样式3.7 Todo-list 案例3.7.1 组件化编码…...

XILINX硬件设计-(1)LVDS接口总结

1.LVDS差分信号电路原理 LVDS指的是低压差分信号&#xff0c;是一种电平标准。 差分信号在串行通信中有着非常广泛的应用&#xff0c;典型应用有PCIE中的gen1&#xff0c;gen2&#xff0c;gen3&#xff0c;gen4&#xff0c;gen5&#xff0c;SATA接口&#xff0c;USB接口等。 …...

RestTemplate Https 证书访问错误

错误信息 resttemplate I/O error on GET request for “https://21.24.6.6:9443/authn-api/v5/oauth/token”: java.security.cert.CertificateException: No subject alternative names present; nested exception is javax.net.ssl.SSLHandshakeException: java.security.c…...

单张照片可生成写实3D头部模型!Adobe提出FaceLift,从单一的人脸图像中重建出360度的头部模型。

FaceLift是Adobe和加州大学默塞德分校推出的单图像到3D头部模型的转换技术,能从单一的人脸图像中重建出360度的头部模型。FaceLift基于两阶段的流程实现:基于扩散的多视图生成模型从单张人脸图像生成一致的侧面和背面视图;生成的视图被输入到GS-LRM重建器中,产出详细的3D高斯表…...

【JavaScript】《JavaScript高级程序设计 (第4版) 》笔记-Chapter7-迭代器与生成器

七、迭代器与生成器 ECMAScript 6 规范新增了两个高级特性&#xff1a;迭代器和生成器。使用这两个特性&#xff0c;能够更清晰、高效、方便地实现迭代。 理解迭代 循环是迭代机制的基础&#xff0c;这是因为它可以指定迭代的次数&#xff0c;以及每次迭代要执行什么操作。每次…...

每日一题——数组中出现次数超过一半的数字

数组中出现次数超过一半的数字 题目描述数据范围 输入描述输出描述示例示例1示例2示例3 题解解题思路摩尔投票算法步骤&#xff1a; 代码实现代码解析时间与空间复杂度 题目描述 给定一个长度为 n 的数组&#xff0c;数组中有一个数字出现的次数超过数组长度的一半&#xff0c…...

【AI】DeepSeek知识类任务和推理能力均表现优秀

2024 年 12 月 26 日&#xff0c;杭州深度求索&#xff08;DeepSeek AI&#xff09;发布 DeepSeek-V3 并同步开源&#xff0c;据介绍&#xff0c;DeepSeek-V3 多项评测成绩超越了 Qwen2.5-72B 和 Llama-3.1-405B 等其他开源模型&#xff0c;并在性能上和世界顶尖的闭源模型 GPT…...

编程领域的IO模型(BIO,NIO,AIO)

目前对于市面上绝大多数的应用来说&#xff0c;不能实现的业务功能太少了。更多的是对底层细节&#xff0c;性能优化的追求。其中IO就是性能优化中很重要的一环。Redis快&#xff0c;mysql缓冲区存在的意义。都跟IO有着密切关系。IO其实我们都在用&#xff0c;输入输出流这块。…...

DeepSeek为何能爆火

摘要&#xff1a;近年来&#xff0c;DeepSeek作为一款新兴的社交媒体应用&#xff0c;迅速在年轻人群体中走红&#xff0c;引发了广泛关注。本文旨在探讨DeepSeek为何能在短时间内爆火&#xff0c;从而为我国社交媒体的发展提供参考。首先&#xff0c;通过文献分析&#xff0c;…...

【AIGC】语言模型的发展历程:从统计方法到大规模预训练模型的演化

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;语言模型的发展历程&#xff1a;从统计方法到大规模预训练模型的演化1 统计语言模型&#xff08;Statistical Language Model, SLM&#xff09;&#xff1a;统…...

基于 Nginx 的 CDN 基础实现

概览 本文是对基于Nginx的CDN网络的学习笔记&#xff0c;阅读的代码为&#xff1a;https://github.com/leandromoreira/cdn-up-and-running 其中&#xff0c;先确定CDN中的一些基础概念&#xff1a; Balancer&#xff1a;负载均衡&#xff0c;即请求数据的流量最开始打到Bal…...

【04】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架二次开发准备工作-以及建立初步后端目录菜单列-优雅草卓伊凡商业项目实战

【04】Java若依vue.js技术栈实现钱包积分管理系统项目-若依框架二次开发准备工作-以及建立初步后端目录菜单列-优雅草卓伊凡商业项目实战 项目背景 本项目经费43000元&#xff0c;需求文档如下&#xff0c;工期25天&#xff0c;目前已经过了8天&#xff0c;时间不多了&#x…...