Redis实战—验证码登录注册
目录
基于Session
Controller层
Service层
ServiceImpl层
编辑校验登录状态
ThreadLocal
登录拦截器
添加拦截器到Config
Controller层实现
基于Redis
ServiceImpl
新增刷新拦截器
添加拦截器到Config
基于Session

Controller层
/*** 发送手机验证码*/@PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {// TODO 发送短信验证码并保存验证码return userService.sendCode(phone,session);}/*** 登录功能,不存在用户则自动创建用户* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码* @RequestBody注解用于把前端的json数据转换成DTO对象*/@PostMapping("/login")public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){//将请求体中的数据转换为特定的对象或数据类型绑定到方法的参数上// TODO 实现登录功能return userService.login(loginForm,session);}
Service层
Result sendCode(String phone, HttpSession session);Result login(LoginFormDTO loginForm, HttpSession session);
ServiceImpl层
//登录@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//发送验证码和登录是两次不同的请求,要对手机号做二次校验//1.再次获取手机号并再次校验手机号String phone = loginForm.getPhone();if(RegexUtils.isPhoneInvalid(phone)){//2.不符合返回错误信息return Result.fail("手机号格式错误");}//3.手机号校验成功,校验验证码,验证码从session获取String code = session.getAttribute("code");//发送的验证码String logincode = loginForm.getCode();//用户填写的验证码if (code==null||!code.toString().equals(logincode)){//4.验证码错误,返回信息return Result.fail("验证码错误");}//5.验证码一致,查询是否存在用户//query()是Mybatis-Plus提供的,本类继承了extends ServiceImpl<UserMapper, User>User user = query().eq("phone", phone).one();if (user==null){//6.用户不存在则创建新用户,新用户信息要保存到session,所以这里把新建的user返回user=createUserWithPhone(phone);}// 7.保存用户信息到session//user信息过多,不易直接保存到session,会加大内存负载,此处转化成userDTO,还可以隐藏用户敏感信息UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);session.setAttribute("user",userDTO);//访问tomcat时。sessionID自动写到了cookie中作为以后的登录凭证return Result.ok();}//自动注册用户private User createUserWithPhone(String phone) {User user=new User();user.setPhone(phone);//生成随机用户名user.setNickName("user_"+RandomUtil.randomString(5));save(user);//保存用户到数据库,mybatis-plus提供的return user;}//发送验证码@Overridepublic Result sendCode(String phone, HttpSession session) {//1. 校验手机号if(RegexUtils.isPhoneInvalid(phone)){//2. 不符合,返回错误信息return Result.fail("手机号格式错误");}//3.符合,生成6位验证码String code = RandomUtil.randomNumbers(6);//4.验证码保存到session稍后返回给服务端,以便服务端进行登录验证session.setAttribute("code",code);//5.发送验证码,这里验证码输出到控制台log.info("验证码发送成功:{}",code);//6.返回return Result.ok();}
校验登录状态
上面完成了短信验证码的发送及登录注册,用户如果登陆成功就要进入个人主页,但是进入个人主页需要验证用户的登录状态,接下来使用拦截器和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();}
}
登录拦截器
//这是一个自定义的拦截器类,用于校验登陆状态
//需要在config中添加到拦截器中才会生效
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removerUser();}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取sessionHttpSession session=request.getSession();//2.获取session中的用户Object user=session.getAttribute("user");//3.判断用户是否存在if(user=null){//4.不存在,拦截response.setStatus(401);return false; }//5.存在,保存到ThreadLocal,并放行UserHolder.saveUSer(user);return true;}
}
添加拦截器到Config
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}
Controller层实现
//登录校验状态@GetMapping("/me")//个人主页public Result me(){// TODO 获取当前登录的用户并返回//在拦截器LoginInterceptor中已经把用户保存到localthreadUserDTO user = UserHolder.getUser();return Result.ok(user);}
基于Redis



接下来在上面代码的基础上修改serviceImpl和拦截器及config
ServiceImpl
//注入stringRedisTemplate@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 code = stringRedisTemplate.opsForValue().get("login:code:"+phone);String logincode = loginForm.getCode();if (code==null||!code.toString().equals(logincode)){//4.验证码错误return Result.fail("验证码错误");}//5.验证码一致,查询是否存在用户//query()是mp提供的,本类继承了extends ServiceImpl<UserMapper, User>User user = query().eq("phone", phone).one();if (user==null){//6.不存在用户,创建新用户user=createUserWithPhone(phone);}//7.用户存在,生成用户的token作为登录凭证String token = UUID.randomUUID().toString(true);//8.存入redis时用的hash存储,因此这里要把userDto转换成hashmapUserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(), CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));//9.token存入redis并设置token有效期stringRedisTemplate.opsForHash().putAll("login:token:"+token,userMap); stringRedisTemplate.expire("login:token:",30,TimeUnit.MINUTES);return Result.ok(token);} @Overridepublic Result sendCode(String phone, HttpSession session) {//1.校验手机号if(RegexUtils.isPhoneInvalid(phone)){//2.不符合返回错误信息return Result.fail("手机号格式错误");}//2.符合生成验证码String code = RandomUtil.randomNumbers(6);//3.保存到redis中,设置有效期1分钟stringRedisTemplate.opsForValue().set("login:code:"+phone,code,1L, TimeUnit.MINUTES);//4.发送验证码给客户log.info("验证码发送成功:{}",code);//返回return Result.ok();}
新增刷新拦截器
用于刷新Token有效期,这里做了拦截器的优化。在第一个拦截器会获取token,根据token去redis查询用户,如果查到就说明用户已经登陆过了,此时只需要刷新token有效期并保存到threadlocal然后放行。如果没有查到说明是未登录用户或者登录已经过期,那么token也是null的,也直接放行,所以第一个拦截器虽然拦截一切路径,但不论如何最后都会放行,其主要作用是对已登录用户刷新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中的用户String key = "login:token" + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);// 3.判断用户是否存在if (userMap.isEmpty()) {return true;}// 5.将查询到的hash数据转为UserDTOUserDTO user = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6.存在,保存用户信息到 ThreadLocalUserHolder.saveUser(user);// 7.刷新token有效期stringRedisTemplate.expire(key, 30L, TimeUnit.MINUTES);// 8.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserHolder.removeUser();}
}
添加拦截器到Config
@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实战—验证码登录注册
目录 基于Session Controller层 Service层 ServiceImpl层 编辑校验登录状态 ThreadLocal 登录拦截器 添加拦截器到Config Controller层实现 基于Redis ServiceImpl 新增刷新拦截器 添加拦截器到Config 基于Session Controller层 /*** 发送手机验证码*/PostMappi…...
对话机器人技术解说
一、RAG介绍 如何不通过微调模型来提高LLM性能,检索增强生成(RAG)是未来的发展方向。 Embedding:将文档的句子或单词块转换为数字向量。就向量之间的距离而言,彼此相似的句子应该很近,而不同的句子应该离…...
红黑树底层封装map、set C++
目录 一、框架思考 三个问题 问题1的解决 问题2的解决: 问题3的解决: 二、泛型编程 1、仿函数的泛型编程 2、迭代器的泛型编程 3、typename: 4、/--重载 三、原码 红黑树 map set 一、框架思考 map和set都是使用红黑树底层&…...
压力给到 Google,OpenAI 发布 GPT-4o 来了
北京时间5月14日凌晨1点,OpenAI 开启了今年的第一次直播,根据官方消息,这次旨在演示 ChatGPT 和 GPT-4 的升级内容。在早些时候 Sam Altman 在 X 上已经明确,「我们一直在努力开发一些我们认为人们会喜欢的新东西,对我…...
【SpringSecurity源码】过滤器链加载流程
theme: smartblue highlight: a11y-dark 一、前言及准备 1.1 SpringSecurity过滤器链简单介绍 在Spring Security中,过滤器链(Filter Chain)是由多个过滤器(Filter)组成的,这些过滤器按照一定的顺序对进…...
第9章.Keil5-MDK软件简介
目录 0. 《STM32单片机自学教程》专栏 9.1 主界面 9.2 文本格式编辑 9.3 代码提示&语法检测&代码模版 9.4 其他小技巧 9.4.1 TAB 键的妙用 9.4.2 快速定位函数/变量被定义的地方 9.4.3 快速注释与快速消注释 9.4.4 快速打开头文件 9.4.5 查找替换…...
mysql中utf8字符集中文字节长度统计如何统计到2个字节一个汉字
在 MySQL 的 utf8 字符集中(也被称为 utf8mb3),中文字符实际上并不是用2个字节来表示的,而是使用3个字节。这是 UTF-8 编码的一个特性,它使用1到4个字节来表示一个字符,具体取决于字符的 Unicode 码点。 对…...
如何实现Linux双网卡同时连接内网和外网的配置?
博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的宝典!《IDEA开发秘籍》 — 提升你的IDEA技能!《100天精通鸿蒙》 …...
ASCLL码表以及字符的相加减
ASCLL码表完整版及解释_acssll码-CSDN博客 #include <getopt.h> #include <stdio.h> #include <stdlib.h>#define MAX_PATH 256 char filename[MAX_PATH 5];int isdigit(int c) {if (c > 0 && c < 9)return 1;return 0; }int main(int argc…...
一键修复所有dll缺失,教大家解决丢失的dll文件
修复所有DLL(动态链接库)文件缺失的问题通常不可能通过单一的"一键修复"按钮来实现,因为DLL文件缺失可能由各种不同的原因导致,比如应用程序安装不正确、病毒感染、或系统文件损坏等。 使用内置的系统文件检查器&#x…...
wsl2安装rancher并导入和创建k8s集群
环境准备 安装wsl2点击此文]ubuntu20.04安装docker 点击此文,安装完成后docker镜像仓库改成阿里云镜像加速地址.如果不熟请点击此文 docker 安装rancher 启动wsl,根据官方文档以root身份执行 sudo docker run -d --restartunless-stopped -p 80:80 -p 443:443 --privileged …...
内网环境ubuntu设置静态ip、DNS、路由,不影响网络访问
内网环境通常是有线的,通过服务器的ip、mac、dns地址访问网络才生效的,如果ip地址变了,就不能访问网络了。 如果你的ip地址变了,或者要防止ip变更影响网络访问,就要设置 1、依次点击右上角的电源-设置,在打…...
学习前端第三十七天(静态属性静态方法、类检查、错误处理)
一、静态属性和静态方法 1、静态属性静态方法 在属性和方法前加上static,创建属于类自己的属性和方法 class Person {// 加static,属于类自己的static name "xc"; // 类的name属性static height 183; // 类的height属性static age 20;…...
全网最全的基于电机控制的38类simulink仿真全家桶----新手大礼包
整理了基于电机的38种simulink仿真全家桶,包含多种资料,类型齐全十分适合新手学习使用。包括但是不局限于以下: 1、基于多电平逆变器的无刷直流电机驱动simulink仿真 2、基于负载转矩的感应电机速度控制simulink仿真 3、基于滑膜观测器的永…...
Python使用asyncio包实现异步编程
1. 异步编程 异步编程是一种编程范式,用于处理程序中需要等待异步操作完成后才能继续执行的情况。异步编程允许程序在执行耗时的操作时不被阻塞,而是在等待操作完成时继续执行其他任务。这对于处理诸如文件 I/O、网络请求、定时器等需要等待的操作非常有…...
获取文件夹下的vue文件形成组件,require.context
前言:项目中现有一个文件里面包含所有需要用到的组件,如果一个个的去import,则会非常麻烦,现有require.context去实现, 1、require.context var request require.context(‘./module’, true, /.js$/) require.cont…...
2024软件测试必问的常见面试题1000问!
01、您所熟悉的测试用例设计方法都有哪些?请分别以具体的例子来说明这些方法在测试用例设计工作中的应用。 答:有黑盒和白盒两种测试种类,黑盒有等价类划分法,边界分析法,因果图法和错误猜测法。白盒有逻辑覆盖法&…...
C++列表实现
文章目录 一、listView相关内容主要思想实例全部代码 二、QTreeView 一、listView 相关内容 QAbstractItemModel:一个抽象的类,为数据项模型提供抽象的接口,常见的的数据模型列如:QStringListModel,QStandardItemMode,QDirModel…...
论文合集整理推荐2024.5.15
2012年论文合集:论文入口 2019年论文合集:论文入口 2022年论文合集:论文入口 2023年论文合集:论文入口 2024年论文合集:论文入口...
JavaScript的跳转传参方式
在JavaScript中,页面跳转并传递参数通常可以通过几种不同的方式来实现。下面是一些常见的方法: 1.URL参数(Query String) 这是最常见的方式,通过在URL的末尾添加参数来实现。例如: javascriptwindow.loc…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...
解析两阶段提交与三阶段提交的核心差异及MySQL实现方案
引言 在分布式系统的事务处理中,如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议(2PC)通过准备阶段与提交阶段的协调机制,以同步决策模式确保事务原子性。其改进版本三阶段提交协议(3PC…...
leetcode73-矩阵置零
leetcode 73 思路 记录 0 元素的位置:遍历整个矩阵,找出所有值为 0 的元素,并将它们的坐标记录在数组zeroPosition中置零操作:遍历记录的所有 0 元素位置,将每个位置对应的行和列的所有元素置为 0 具体步骤 初始化…...
