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

基于 Redis 实现短信验证码登录功能的完整方案

🧱 一、技术栈与依赖配置

使用 Spring Boot + Redis 实现短信验证码登录,以下是推荐的 Maven 依赖:

<dependencies><!-- Spring Boot Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Validation --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!-- Lombok(简化实体类) --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- 如果使用 JWT 登录验证,可引入 --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>
</dependencies>

二、流程概述

✅ 验证码获取流程:

1.校验手机号码

2.如果不符合,直接返回错误信息

3.符合,则生成验证码(有过期时间)

4.将验证码存入redis中

6.发送验证码

注:key为手机号+前缀

value是code(验证码)String类型

    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, LOGIN_CODE_TTL, TimeUnit.MINUTES);//6.发送验证码
//        smsService.sendCode(phone, code);log.debug(code);return Result.ok();}

短信验证码登录、注册流程

1.校验手机号

2.如果不符合,返回错误信息

3.校验验证码

从redis中获取验证码

4.如果不符合,返回错误信息

5.一致,根据手机号查询用户

6.判断用户是否存在

不存在,则创建

7 .保存用户信息到redis中

7.1随机生成token,作为登录令牌

7.2将user对象转成哈希存储

 7.3将token和用户信息存入redis

8.设置token过期时间

9.返回token

注:

key:随机token+前缀(String)

value:用户信息(采用哈希map存储)

因为UserDTO转map的时候,由于id是Long类型,不能转成String类型 所以我们需要自定义map集合规则

1️⃣ BeanUtil.beanToMap(...)

来自 Hutool 工具库,用于将 Java Bean 转换为 Map。例如:

UserDTO { String name = "Tom"; Integer age = 18; }

将被转换为:

{ "name": "Tom", "age": 18 }


2️⃣ 第二个参数:new HashMap<>()

用于接收转换后的数据,你可以指定已有的 Map 进行填充,也可以传一个新的空 Map,如这里使用的是新建的 HashMap


3️⃣ CopyOptions.create():配置拷贝选项

CopyOptions 是一个用于控制复制行为的配置类。以下是你使用的两个关键配置项:

setIgnoreNullValue(true)

  • 意思是 忽略值为 null 的字段,不把它们放入最终的 Map。

  • 例子:如果 userDTO.getEmail() 为 null,则结果 Map 中不会出现 "email": null

setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString())

  • 提供一个字段值编辑器,在放入 Map 之前将每个字段值转换成字符串

  • 举例:

    • age = 18"18"(注意是字符串)

    • active = true"true"

  • fieldName 是字段名,比如 "age"fieldValue 是字段值,比如 18

⚠️ 注意:这个处理器假设 fieldValue 不为 null,否则调用 toString() 会抛出 NullPointerException,所以它通常要和 setIgnoreNullValue(true) 搭配使用。


✅ 总结作用

这段代码的最终目的是:
将 userDTO 对象转换为一个 Map<String, Object>,只包含非 null 字段,且所有字段值都转为字符串类型。


📌 示例:

假设 userDTO 内容如下:

UserDTO { String name = "Alice"; Integer age = 25; String email = null; }

执行后 userMap 中内容为:

{ "name": "Alice", "age": "25" // 注意:值为字符串类型 // "email" 被忽略,因为是 null }

 Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));

 @Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {String phone = loginForm.getPhone();// 1.校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误");}//3.校验验证码
//        Object cacheCode = session.getAttribute("code");//从session中获取验证码//TODo 3.从redis中获取验证码String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();//从登录表单中获取验证码(用户输入验证码)if (cacheCode == null || !code.equals(cacheCode)) {//4.如果不符合,返回错误信息return Result.fail("验证码不正确");}//5.一致,根据手机号查询用户User user = query().eq("phone", phone).one();//6.判断用户是否存在if (user == null) {//不存在,则创建user = createUserWithPhone(phone);}
//        //7.保存用户信息到session中
//        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));//TODo 7 .保存用户信息到redis中// TODo 7.1随机生成token,作为登录令牌String token = UUID.randomUUID().toString(true);//TODo 7.2将user对象转成哈希存储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()));;//TODo 7.3将token和用户信息存入redisString tokenKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);//TODo 8.设置token过期时间stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);//TODo 9.返回tokenreturn Result.ok(token);}private User createUserWithPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomNumbers(5));//保存用户信息save(user);return user;}

在这个方案中,他确实可以使用对应路径的拦截,同时刷新登录token令牌的存活时间,但是现在这个拦截器他只是拦截需要被拦截的路径,假设当前用户访问了一些不需要拦截的路径,那么这个拦截器就不会生效,所以此时令牌刷新的动作实际上就不会执行,所以这个方案他是存在问题的

解决状态登录刷新问题

既然之前的拦截器无法对不需要拦截的路径生效,那么我们可以添加一个拦截器,在第一个拦截器中拦截所有的路径,把第二个拦截器做的事情放入到第一个拦截器中,同时刷新令牌,因为第一个拦截器有了threadLocal的数据,所以此时第二个拦截器只需要判断拦截器中的user对象是否存在即可,完成整体刷新功能。

第一个拦截器保存用户信息然后刷新

第二个拦截器就是拦截

package com.hmdp.config;import com.hmdp.utils.LoginInterceptor;
import com.hmdp.utils.RefreshTokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
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;@Configuration
public class MvcConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(this.loginInterceptor).addPathPatterns("/**").excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login").order(1);registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);}
}

相关文章:

基于 Redis 实现短信验证码登录功能的完整方案

&#x1f9f1; 一、技术栈与依赖配置 使用 Spring Boot Redis 实现短信验证码登录&#xff0c;以下是推荐的 Maven 依赖&#xff1a; <dependencies><!-- Spring Boot Web --><dependency><groupId>org.springframework.boot</groupId><ar…...

电平匹配电路

1、为什么要电平匹配? 现在很多SOC器件为了降低功耗,都把IO口的电平设计成了1.8V,核电压0.85V,当这种SOC做主平台时,在做接口设计需要格外关注电平的匹配。单板中经常需要将1.8V的电平转换成3.3V或者转成5V。如果没有注意到输入和输出信号之间的电平匹配,系统就无法正常…...

JavaScript 日志和调试工具箱-logger2js

原创功能丰富的 JavaScript 日志和调试工具箱&#xff0c;设计这个工具时考虑到了多种实际开发中的需求。该工具不仅提供了高效强大的日志输出显示功能&#xff0c;还包含了界面风格配置、代码格式化、事件处理、性能测试、方法调用栈输出&#xff0c;右键菜单、控制台显示控制…...

GitHub 自动认证教程

## 简介 在使用 GitHub 时&#xff0c;为了避免每次提交代码都需要输入用户名和密码&#xff0c;我们可以使用 SSH 密钥进行自动认证。本教程将详细介绍如何设置 SSH 密钥并配置 GitHub 自动认证。 ## 步骤一&#xff1a;检查现有 SSH 密钥 首先&#xff0c;检查您的电脑是否…...

zData X zStorage 为什么采用全闪存架构而非混闪架构?

点击蓝字 关注我们 最近有用户问到 zData X 的存储底座 zStorage 分布式存储为什么采用的是全闪存架构而非混闪架构&#xff1f;主要原因还是在于全闪存架构在性能和可靠性方面具有更显著的优势。zData X 的上一代产品 zData 的早期版本也使用了SSD盘作为缓存的技术架构&#x…...

鸿蒙OSUniApp 实现精美的轮播图组件#三方框架 #Uniapp

UniApp 实现精美的轮播图组件 在移动应用开发中&#xff0c;轮播图是一个非常常见且重要的UI组件。本文将深入探讨如何使用UniApp框架开发一个功能丰富、动画流畅的轮播图组件&#xff0c;并分享一些实际开发中的经验和技巧。 一、基础轮播图实现 1.1 组件结构设计 首先&am…...

解决git中断显示中文为八进制编码问题

git config --global core.quotepath false 命令用于配置 Git 如何处理非 ASCII 字符&#xff08;如中文、日文、韩文等&#xff09;的文件名显示 core.quotepath Git 的一个核心配置项&#xff0c;控制是否对非 ASCII 文件名进行转义&#xff08;quote&#xff09;处理。 f…...

SQL次日留存率计算精讲:自连接与多字段去重的深度应用

一、问题拆解&#xff1a;理解次日留存率的计算逻辑 1.1 业务需求转换 题目&#xff1a;运营希望查看用户在某天刷题后第二天还会再来刷题的留存率。 关键分析点&#xff1a; 留存率 &#xff08;第一天刷题且第二天再次刷题的用户数&#xff09; / 第一天刷题的总用户数需…...

使用SQLite Studio导出/导入SQL修复损坏的数据库

使用SQLite Studio导出/导入SQL修复损坏的数据库 使用Zotero时遇到了数据库损坏&#xff0c;在软件中寸步难行&#xff0c;遂尝试修复数据库。 一、SQLite Studio简介 SQLite Studio是一款专为SQLite数据库设计的免费开源工具&#xff0c;支持Windows/macOS/Linux。相较于其…...

LSTM-Attention混合模型:美债危机与黄金对冲效率研究

摘要&#xff1a;本文依托多维度量化分析框架&#xff0c;结合自然语言处理&#xff08;NLP&#xff09;技术对地缘文本的情绪挖掘&#xff0c;构建包含宏观因子、风险溢价因子及技术面因子的三阶定价模型&#xff0c;对当前黄金市场的波动特征进行归因分析。实证结果显示&…...

了解 DDD 吗?DDD 和 MVC 的区别是什么?

简介&#xff1a; DDD&#xff08;Domain-driven Design&#xff09; 和 MVC&#xff08;Model-View-Controller&#xff09; 是软件后台开发两种流行的分层架构思想。 MVC 是一种设计模式&#xff0c;主要用来分离用户界面&#xff0c;业务逻辑&#xff0c;和数据模型。 而…...

Unity3D仿星露谷物语开发46之种植/砍伐橡树

1、目标 种植一棵橡树&#xff0c;从种子变成大树。 然后可以使用斧头砍伐橡树。 2、删除totalGrowthDays字段 修改growthDays的含义&#xff0c;定义每个值为到达当前阶段的累加天数。此时最后一个阶段就是totalGrowthDays的含义。所以就可以删除totalGrowthDays字段。 &…...

STM32外设应用详解——从基础到高级应用的全面指南

目录 一、引言&#xff1a;为何选择STM32外设 二、主要外设类别与详细应用解析 1. GPIO&#xff08;通用输入输出&#xff09; 工作原理详解 高级应用设计 硬件连接建议 2. 定时器&#xff08;TIM&#xff09;详解 基本定时器原理 高级配置 实际应用 核心技巧 3. A…...

作业帮C++后台开发面试题及参考答案

Cookie 和 Session 的区别是什么? Cookie 和 Session 是 Web 开发中用于管理用户状态的两种机制,它们在存储位置、安全性、生命周期和数据类型等方面存在显著差异。 存储位置:Cookie 数据存储在客户端浏览器,而 Session 数据存储在服务器端。当浏览器向服务器发送请求时,…...

红队进阶实战

4.1 内网渗透(域渗透、横向移动) 域环境攻击链 初始立足点:通过钓鱼获取域用户凭据(如NTLM Hash)。信息收集: 使用BloodHound自动化分析域内关系。执行nltest /dclist:domain.com获取域控制器列表。横向移动: Pass-the-Hash:利用Mimikatz注入Hash到新会话。sekurlsa::…...

C语言中的指定初始化器

什么是指定初始化器? C99标准引入了一种更灵活、直观的初始化语法——指定初始化器(designated initializer), 可以在初始化列表中直接引用结构体或联合体成员名称的语法。通过这种方式,我们可以跳过某些不需要初始化的成员,并且可以以任意顺序对特定成员进行初始化。这…...

C/C++ 整数类型的长度

参考 cppreference.cn 在某些语言中&#xff0c;整数类型的长度是固定的&#xff0c;如java中 char 8short 16int 32long 64 可是C/C 与机器相关&#xff0c;整数类型长度与平台有关 先可以记一个简单的 按照C标准&#xff1a; char > 8short > 16int > 16long &g…...

gRPC开发指南:Visual Studio 2022 + Vcpkg + Windows全流程配置

前言 gRPC作为Google开源的高性能RPC框架&#xff0c;在微服务架构中扮演着重要角色。本文将详细介绍在Windows平台下&#xff0c;使用Visual Studio 2022和Vcpkg进行gRPC开发的完整流程&#xff0c;包括环境配置、项目搭建、常见问题解决等实用内容。 环境准备 1. 安装必要组…...

高密度服务器机柜散热方案:高风压风机在复杂风道中的关键作用与选型要点

随着云计算、人工智能等技术的飞速发展&#xff0c;数据中心内服务器机柜的集成度不断攀升&#xff0c;高密度部署成为常态。然而&#xff0c;高密度意味着单位空间内服务器数量剧增&#xff0c;发热量呈指数级上升&#xff0c;传统散热方案已难以满足需求。在复杂的机柜风道环…...

Android framework 问题记录

一、休眠唤醒&#xff0c;很快熄屏 1.1 问题描述 机器休眠唤醒后&#xff0c;没有按照约定的熄屏timeout 进行熄屏&#xff0c;很快就熄屏&#xff08;约2s~3s左右&#xff09; 1.2 原因分析&#xff1a; 抓取相关log&#xff0c;打印休眠背光 相关调用栈 //具体打印调用栈…...

框架之下再看HTTP请求对接后端method

在当今的软件开发中&#xff0c;各类框架如雨后春笋般不断涌现&#xff0c;极大地提升了开发效率。以 Java 开发为例&#xff0c;Spring 框架历经多次迭代演进&#xff0c;而 Spring Boot 更是将开发便捷性提升到了新高度。如今&#xff0c;开发者只需简单引入 Maven 包&#x…...

Oracle APEX IR报表列宽调整

目录 1. 问题&#xff1a;如何调整Oracle APEX IR报表列宽 2. 解决办法 1. 问题&#xff1a;如何调整Oracle APEX IR报表列宽 1-1. 防止因标题长而数据短&#xff0c;导致标题行的文字都立起来了&#xff0c;不好看。 1-2. 防止因数据太长而且中间还没有空格&#xff0c;把列…...

【笔记】与PyCharm官方沟通解决开发环境问题

#工作记录 2025年5月20日 星期二 背景 在此前的笔记中&#xff0c;我们提到了向PyCharm官方反馈了几个关于Conda环境自动激活、远程解释器在社区版中的同步问题以及Shell脚本执行时遇到的问题。这些问题对日常开发流程产生了一定影响&#xff0c;因此决定联系官方支持寻求解…...

深入解析:如何基于开源OpENer开发EtherNet/IP从站服务

一、EtherNet/IP协议概述 EtherNet/IP(Industrial Protocol)是一种基于以太网的工业自动化通信协议,它将CIP(Common Industrial Protocol)封装在标准以太网帧中,通过TCP/IP和UDP/IP实现工业设备间的通信。作为ODVA(Open DeviceNet Vendors Association)组织的核心协议…...

node.js文件系统(fs) - 创建文件、打开文件、写入数据、追加数据、读取数据、创建目录、删除目录

注意&#xff1a;以下所有示例均是异步语法&#xff01; 注意&#xff1a;以下所有示例均是异步语法&#xff01; 创建文件 node.js 允许我们在计算机本地创建文件&#xff0c;例如创建一个 word 文件&#xff1a; // 引入核心模块(fs) var fs require(fs)// API fs.writeF…...

SQL:MySQL函数:空值处理函数(NULL Handling Functions)

目录 什么是空值&#xff08;NULL&#xff09;&#xff1f; 常用空值处理函数总览 1️⃣ IFNULL() – 空值替换函数&#xff08;If Null&#xff09; 2️⃣ COALESCE() – 多参数空值判断&#xff08;返回第一个非 NULL 值&#xff09; 3️⃣ NULLIF() – 相等则返回 NULL…...

利用ffmpeg截图和生成gif

从视频中截取指定数量的图片 ffmpeg -i input.mp4 -ss 00:00:10 -vframes 1 output.jpgffmpeg -i input.mp4 -ss 00:00:10 -vframes 180 output.jpg -vframes 180代表截取180帧, 实测后发现如果视频是60fps,那么会从第10秒截取到第13秒-i input.mp4&#xff1a;指定输入视频文…...

初始化一个Springboot项目

初始化一个Springboot项目 文章目录 初始化一个Springboot项目1、新建项目2、配置yml3、自定义异常4、通用相应类5、全局跨域配置6、总结 1、新建项目 首先&#xff0c;我们需要创建一个新的 Spring Boot 项目。这里我们使用 IntelliJ IDEA 作为开发工具&#xff0c;它提供了方…...

YOLOv8在单目向下多车辆目标检测中的应用

大家读完觉得我有帮助记得关注&#xff01;&#xff01;&#xff01; 摘要 自动驾驶技术正逐步改变传统的汽车驾驶方式&#xff0c;标志着现代交通运输的一个重要里程碑。目标检测是自主系统的基石&#xff0c;在提高驾驶安全性、实现自主功能、提高交通效率和促进有效的应急…...

23种设计模式解释+记忆

一、创建型模式&#xff08;5种&#xff09;—— “怎么造对象&#xff1f;” 单例模式&#xff08;Singleton&#xff09; 场景&#xff1a;公司的CEO只能有一个。 核心&#xff1a;确保一个类只有一个实例&#xff0c;全局访问。 关键词&#xff1a;唯一、全局访问。 工厂方…...