【SpringBoot篇】详解基于Redis实现短信登录的操作

文章目录

  • 🥰前言
  • 🛸StringRedisTemplate
    • 🌹使用StringRedisTemplate
      • ⭐常用的方法
  • 🛸为什么我们要使用Redis代替Session进行登录操作
    • 🎆具体使用
      • ✨编写拦截器
      • ✨配置拦截器
      • 🌺基于Redis实现发送手机验证码操作
        • 🎈总体思路
        • 🎈具体步骤
      • 🎍基于Redis实现短信登录并注册的操作
        • 🎈总体思路
        • 🎈具体步骤

在这里插入图片描述

🥰前言

使用 Redis 进行登录适用于以下情况:

  • 分布式系统:
    当系统需要支持多个节点的分布式部署时,使用 Redis 存储登录信息能够更好地支持多节点间的共享和同步,确保用户的登录状态能够在整个系统中得到有效的传递和管理。
  • 高并发访问:
    面对大规模的并发访问,使用 Redis 可以提供更好的性能表现。Redis 是一个基于内存的高性能 Key-Value 数据库,能够更快速地读取和写入数据,因此适用于需要处理大量并发请求的场景。
  • 灵活的数据结构需求:
    如果系统需要根据业务需求选择最佳的数据结构,并且对存储和操作登录信息有更多的灵活性,那么使用 Redis 将会是一个不错的选择。Redis 支持多种数据类型的存储和操作,包括字符串、哈希表、列表、集合和有序集合等,能够满足不同的业务需求。
  • 需要持久化支持:
    如果系统需要对登录信息进行持久化存储,以防止数据丢失,Redis 的持久化功能可以很好地满足这一需求。

总的来说,使用 Redis 进行登录适用于需要支持分布式部署、面对高并发访问、有灵活的数据结构需求以及需要持久化支持的系统场景。通过合理地利用 Redis 的特性,可以更好地满足上述情况下的需求,提高系统的可扩展性、性能和稳定性。

虽然 Spring Boot 应用通常是单体应用,但是在实际运行中,我们也经常会遇到多个实例同时运行的情况,这时候就需要使用 Redis 进行分布式 Session 管理。

🛸StringRedisTemplate

StringRedisTemplate是Spring Data Redis提供的一个类,它是一个具体的对象,用于操作Redis数据库中的字符串类型数据。

StringRedisTemplate封装了Redis的操作,并提供了一系列方法来对Redis中的字符串进行读取、写入和删除操作。它是RedisTemplate的一个子类,专门用于处理字符串类型的数据。

🌹使用StringRedisTemplate

首先引入依赖,引入StringRedisTemplate的依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

⭐常用的方法

StringRedisTemplate提供了多个方法来操作Redis中的字符串类型数据。下面是一些常用的方法:

  • opsForValue().set(key, value):将一个字符串类型的值value存储到Redis中,并指定键key。
  • opsForValue().get(key):根据键key获取对应的字符串类型的值。
  • opsForValue().increment(key, delta):将键key所对应的值增加delta,delta可以为负数。
  • opsForValue().size(key):获取值的长度。

🛸为什么我们要使用Redis代替Session进行登录操作

集群session存在共享问题,会导致数据丢失

  • 保存相同的数据,大家互相copy,会有内存空间的浪费
  • 我们copy数据的时候,是需要有一定的时间的,会有延迟,如果在这个延迟之内,如果有人来访问,仍然会造成数据不一致的情况
    请添加图片描述
    如果我们使用Redis的话。
  • Redis是在tomcat外面的存储,如果任意一台tomcat都能访问到Redis,可以实现数据共享,储存在Redis里面的数据,任何tomcat都可以看到,使用就不存在数据丢失的问题
  • Redis读写延迟非常低,方便进行内存存储
  • Redis是key-value结构

在这里插入图片描述

🎆具体使用

✨编写拦截器

在这里插入图片描述

RefreshTokenInterceptor.java
在这里插入图片描述

在拦截器中配置拦截操作

package com.hmdp.utils;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;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中的用户String key  = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);// 3.判断用户是否存在if (userMap.isEmpty()) {return true;}// 5.将查询到的hash数据转为UserDTOUserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6.存在,保存用户信息到 ThreadLocalUserHolder.saveUser(userDTO);// 7.刷新token有效期stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserHolder.removeUser();}
}

在这里插入图片描述

刷新token的目的

用户每访问一次,这个token就会刷新一次,主要用户一直在操作,这个token就不会消失


但是如果仅仅拦截的是需要登录的路径,用户 访问 不需要登录 的路径 的时候(比如首页),这个拦截器就不生效,此时token就不会刷新,这样子,过了token的有效期后,尽管用户还在访问,用户的登录状态却消失了,这样肯定不太合理

那么我们就需要在原来的拦截器基础上再加上一个拦截器
请添加图片描述

LoginInterceptor.java
在这里插入图片描述

在拦截器中配置拦截操作

package com.hmdp.utils;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.判断是否需要拦截(ThreadLocal中是否有用户)if (UserHolder.getUser() == null) {// 没有,需要拦截,设置状态码response.setStatus(401);// 拦截return false;}// 有用户,则放行return true;}
}

在这里插入图片描述

✨配置拦截器

我们上面编写了拦截器,我们还需要配置拦截器,使这个拦截器生效

MvcConfig.java
这里是引用

package com.hmdp.config;import com.hmdp.utils.LoginInterceptor;
import com.hmdp.utils.RefreshTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic 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);}
}

在这里插入图片描述

🌺基于Redis实现发送手机验证码操作

🎈总体思路

在这里插入图片描述

🎈具体步骤

我们首先引入上面说的依赖,然后在application.yml文件(或yaml文件)中进行配置,如下
在这里插入图片描述

下面我们编写发送手机验证码的核心代码

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result sendCode(String phone, HttpSession session) {// 1.校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.符合,生成验证码String code = RandomUtil.randomNumbers(6);// 4.保存验证码到 redisstringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);// 5.发送验证码log.debug("发送短信验证码成功,验证码:{}", code);// 返回okreturn Result.ok();}
}

在这里插入图片描述

上面代码里面的RegexUtils.isPhoneInvalid(phone)这段代码是什么用法

在这里插入图片描述

stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);这段代码有什么用

这段代码的作用是将一个验证码(即code)存储到Redis中,并设置了过期时间为LOGIN_CODE_TTL分钟。以便在一定时间后自动删除该键值对。

🎍基于Redis实现短信登录并注册的操作

🎈总体思路

在这里插入图片描述

🎈具体步骤

我们首先引入上面说的依赖,并且在application.yml文件(或yaml文件)中进行配置(同上)
然后我们来编写核心代码

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic 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.一致,根据手机号查询用户 select * from tb_user where phone = ?User user = query().eq("phone", phone).one();// 5.判断用户是否存在if (user == null) {// 6.不存在,创建新用户并保存user = createUserWithPhone(phone);}// 7.保存用户信息到 redis中// 7.1.随机生成token,作为登录令牌String token = UUID.randomUUID().toString(true);// 7.2.将User对象转为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);// 8.返回tokenreturn Result.ok(token);}private User createUserWithPhone(String phone) {// 1.创建用户User user = new User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));// 2.保存用户save(user);return user;}}

在这里插入图片描述
User user = query().eq(“phone”, phone).one();这段代码使用了mybatisplus,相当于select * from tb_user where phone = ?

为什么要使用HashMap进行存储

在这段代码中,使用HashMap进行存储是为了将用户对象转换成键值对形式,便于统一保存到Redis中,并且可以方便地进行序列化和反序列化操作。具体来说:

  • 便于存储和读取:将用户对象转为HashMap后,可以方便地通过stringRedisTemplate.opsForHash().putAll()方法一次性将整个用户对象存储到Redis的Hash数据结构中,而不需要对用户对象的每个字段分别进行存储。
  • 数据结构清晰:使用HashMap可以清晰地表示用户对象的各个字段和对应的数值,便于管理和维护。
  • 方便序列化和反序列化:HashMap作为Java中的常用数据结构,可以方便地进行序列化(将数据转换为字节序列)反序列化(将字节序列转换为数据)操作,便于在存储到Redis或者从Redis中读取时进行数据格式的转换。

总之,使用HashMap进行存储能够简化代码逻辑,提高数据存储和读取的效率,并且方便进行数据结构的转换和管理。

Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
这段代码为什么要这样写,这些参数有什么用

其中,beanToMap是一个方法,用于将Java对象(Bean)转换为Map类型的数据结构。

在这段代码中,BeanUtil.beanToMap()方法被使用,它是一个工具类方法,可以通过反射机制将Java对象的属性和对应的值转换为键值对形式,并存储到一个Map对象中。

具体来说,beanToMap方法接收三个参数:

  • userDTO:表示要转换的源对象,即需要将其转换为Map的对象。

  • new HashMap<>():表示用于存储转换结果的目标HashMap对象,这里使用了一个新的空HashMap,用于接收转换后的键值对数据。

  • CopyOptions.create().setIgnoreNullValue(true):这是使用BeanUtil进行对象转换时的配置选项。setIgnoreNullValue(true)表示忽略源对象中值为null的属性,不将其放入目标Map中。

  • .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()):这个配置项表示对转换过程中的字段值进行编辑处理。在这里,它的作用是将字段值转换为字符串类型,确保最终存储到Map中的值都是字符串类型。

综合起来,这段代码的目的是将UserDTO对象转换为Map类型,同时忽略空值属性,并确保所有属性值都被转换为字符串类型。这样做的原因可能是为了在存储到Redis中时,确保数据的统一性和一致性,便于后续从Redis中读取并进行处理。

在技术的道路上,我们不断探索、不断前行,不断面对挑战、不断突破自我。科技的发展改变着世界,而我们作为技术人员,也在这个过程中书写着自己的篇章。让我们携手并进,共同努力,开创美好的未来!愿我们在科技的征途上不断奋进,创造出更加美好、更加智能的明天!

在这里插入图片描述

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

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

相关文章

EarCMS 前台任意文件上传漏洞复现

0x01 产品简介 EarCMS是一个APP内测分发系统的平台。 0x02 漏洞概述 EarCMS前台put_upload.php中,存在pw参数硬编码问题,同时sql语句pdo使用错误,没有有效过滤sql语句,可以控制文件名和后缀,导致可以任意文件上传。 0x03 复现环境 FOFA:app="EearCMS" 0x0…

Flutter实现自定义二级列表

在Flutter开发中&#xff0c;其实系统已经给我们提供了一个可靠的二级列表展开的API&#xff08;ExpansionPanelList&#xff09;&#xff0c;我们先看系统的二级列表展开效果&#xff0c;一次只能展开一个&#xff0c;用ExpansionPanelList.radio实现 由此可见&#xff0c;已经…

容器化升级对服务有哪些影响?

容器技术是近几年计算机领域的热门技术&#xff0c;特别是随着各种云服务的发展&#xff0c;越来越多的服务运行在以 Docker 为代表的容器之内。 本文我们就来分享一下容器化技术相关的知识。 容器化技术简介 相比传统虚拟化技术&#xff0c;容器技术是一种更加轻量级的操作…

分治法求最大子列和

给定N个整数的序列{ A1, A2, …, AN}&#xff0c;其中可能有正数也可能有负数&#xff0c;找出其中连续的一个子数列&#xff08;不允许空序列&#xff09;&#xff0c;使它们的和尽可能大&#xff0c;如果是负数&#xff0c;则返回0。使用下列函数&#xff0c;完成分治法求最大…

CorelDRAW软件2024版本好用吗?有哪些功能优势

CorelDRAW是一款综合性强大的专业平面设计软件&#xff0c;其功能覆盖了矢量图形设计、高级文字编辑、精细绘图以及多页文档和页面设计。该软件不仅适用于广告设计、包装设计&#xff0c;还广泛应用于出版、网页设计和多媒体制作等多个领域。下面就给大家介绍一下CorelDRAW这款…

0012Java安卓程序设计-ssm记账app

文章目录 **摘要**目 录系统设计5.1 APP端&#xff08;用户功能&#xff09;5.2后端管理员功能模块开发环境 编程技术交流、源码分享、模板分享、网课分享 企鹅&#x1f427;裙&#xff1a;776871563 摘要 网络的广泛应用给生活带来了十分的便利。所以把记账管理与现在网络相…

arkts编译报错-arkts-limited-stdlib错误【Bug已完美解决-鸿蒙开发】

文章目录 项目场景:问题描述原因分析:解决方案:适配指导案例此Bug解决方案总结项目场景: arkts编译报错-arkts-limited-stdlib错误。 我用Deveco studio4.0 beta2开发应用,报arkts-limited-stdlib错误 报错内容为: ERROR: ArKTS:ERROR File: D:/prRevivw/3792lapplica…

Android 11.0 systemui锁屏页面时钟显示样式的定制功能实现

1.前言 在11.0的系统ROM定制化开发中,在进行systemui的相关开发中,当开机完成后在锁屏页面就会显示时间日期的功能,由于 开发产品的需求要求时间显示周几上午下午接下来就需要对锁屏显示时间日期的相关布局进行分析,然后实现相关功能 效果图如图: 2.systemui锁屏页面时钟显…

mysql原理--B+树索引

1.没有索引的查找 1.1.在一个页中的查找 (1). 以主键为搜索条件 可以在 页目录 中使用二分法快速定位到对应的槽&#xff0c;然后再遍历该槽对应分组中的记录即可快速找到指定的记录。 (2). 以其他列作为搜索条件 这种情况下只能从 最小记录 开始依次遍历单链表中的每条记录&am…

值得收藏的练习打字网站

本文对一些好用的练习打字的网站进行了汇总整理&#xff0c;方便大家使用 一&#xff1a;程序猿练习打字&#xff1a; 1.Typing Practice for Programmers http://Typing.io 是程序员的打字导师。它的练习课程基于开源代码&#xff0c;让你在不断的练习中提升自己的码字速度…

Python:核心知识点整理大全15-笔记

目录 ​编辑 7.3.2 删除包含特定值的所有列表元素 pets.py 7.3.3 使用用户输入来填充字典 mountain_poll.py 7.4 小结 第8章 函 数 8.1 定义函数 greeter.py 8.1.1 向函数传递信息 8.1.2 实参和形参 8.2.1 位置实参 2. 位置实参的顺序很重要 8.2.2 关键字实参 往…

计算机循环神经网络(RNN)

计算机循环神经网络&#xff08;RNN&#xff09; 一、引言 循环神经网络&#xff08;RNN&#xff09;是一种常见的深度学习模型&#xff0c;适用于处理序列数据&#xff0c;如文本、语音、时间序列等。RNN通过捕捉序列数据中的时间依赖关系和上下文信息&#xff0c;能够解决很…

CLIP的升级版Alpha-CLIP:区域感知创新与精细控制

为了增强CLIP在图像理解和编辑方面的能力&#xff0c;上海交通大学、复旦大学、香港中文大学、上海人工智能实验室、澳门大学以及MThreads Inc.等知名机构共同合作推出了Alpha-CLIP。这一创新性的突破旨在克服CLIP的局限性&#xff0c;通过赋予其识别特定区域&#xff08;由点、…

Could not resolve all dependencies for configuration ‘:app:androidApis‘.

android studio出现Could not resolve all dependencies for configuration ‘:app:androidApis’. 试过很多种方法&#xff0c;但是都不好使&#xff0c;不管怎么样都是提示如下报错&#xff1a; Using insecure protocols with repositories, without explicit opt-in, is un…

丹麦市场开发攻略,带你走进童话王国

说起安徒生&#xff0c;大家多多少少都知道&#xff0c;因为小时候读的安徒生童话书真的太有名了&#xff0c;但是大家可能不知道安徒生是丹麦的。丹麦是高度发达的国家&#xff0c;奉行自由贸易政策&#xff0c;市场潜力是非常不错的&#xff0c;而且中国是丹麦非常重要的贸易…

Python部分基础知识入门学习,十分钟快速上手

文章目录 一、基础语法二、变量类型三、运算符四、条件语句关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五、面试资料六、Python兼职渠道 一、…

初识 WebGPU 以及遇到 WebGPU not supported 错误的解决方法

初识 WebGPU 以及遇到 WebGPU not supported 错误的解决方法 WebGPU学习资源初识WebGPU遇到并解决问题在线示例 因公司需求&#xff0c;开始接触 WebGPU&#xff0c;偶然遇到问题&#xff0c;网上搜索无效&#xff0c;后来通过逐步判断&#xff0c;终于定位到问题&#xff0c;这…

【WPF 按钮点击后异步上传多文件code示例】

前言: WPF中按钮点击事件如何执行时间太长会导致整个UI线程卡顿&#xff0c;现象就是页面刷新卡住&#xff0c;点击其他按钮无反馈。如下是进行异步执行命令&#xff0c;并远程上传文件的代码。 ![异步上传文件](https://img-blog.csdnimg.cn/direct/20c071929b004dcf9223dee2…

听我的,日志还是得好好打!

日志这东西&#xff0c;平时看不出来什么&#xff0c;真要出了问题&#xff0c;那就是救命的稻草。这期就给大家分享一些日志相关的东西。 弄懂日志 SpringBoot项目启动日志 什么是日志&#xff1f; 日志&#xff0c;维基百科中对其的定义是一个或多个由服务器自动创建和维护…

【数学建模】《实战数学建模:例题与讲解》第十一讲-因子分析、聚类与主成分(含Matlab代码)

【数学建模】《实战数学建模&#xff1a;例题与讲解》第十一讲-因子分析、聚类与主成分&#xff08;含Matlab代码&#xff09; 基本概念聚类分析Q型聚类分析R型聚类分析 主成分分析因子分析 习题10.11. 题目要求2.解题过程3.程序4.结果 习题10.21. 题目要求2.解题过程3.程序4.结…