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

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性能&#xff0c;检索增强生成&#xff08;RAG&#xff09;是未来的发展方向。 Embedding&#xff1a;将文档的句子或单词块转换为数字向量。就向量之间的距离而言&#xff0c;彼此相似的句子应该很近&#xff0c;而不同的句子应该离…...

红黑树底层封装map、set C++

目录 一、框架思考 三个问题 问题1的解决 问题2的解决&#xff1a; 问题3的解决&#xff1a; 二、泛型编程 1、仿函数的泛型编程 2、迭代器的泛型编程 3、typename&#xff1a; 4、/--重载 三、原码 红黑树 map set 一、框架思考 map和set都是使用红黑树底层&…...

压力给到 Google,OpenAI 发布 GPT-4o 来了

北京时间5月14日凌晨1点&#xff0c;OpenAI 开启了今年的第一次直播&#xff0c;根据官方消息&#xff0c;这次旨在演示 ChatGPT 和 GPT-4 的升级内容。在早些时候 Sam Altman 在 X 上已经明确&#xff0c;「我们一直在努力开发一些我们认为人们会喜欢的新东西&#xff0c;对我…...

【SpringSecurity源码】过滤器链加载流程

theme: smartblue highlight: a11y-dark 一、前言及准备 1.1 SpringSecurity过滤器链简单介绍 在Spring Security中&#xff0c;过滤器链&#xff08;Filter Chain&#xff09;是由多个过滤器&#xff08;Filter&#xff09;组成的&#xff0c;这些过滤器按照一定的顺序对进…...

第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 字符集中&#xff08;也被称为 utf8mb3&#xff09;&#xff0c;中文字符实际上并不是用2个字节来表示的&#xff0c;而是使用3个字节。这是 UTF-8 编码的一个特性&#xff0c;它使用1到4个字节来表示一个字符&#xff0c;具体取决于字符的 Unicode 码点。 对…...

如何实现Linux双网卡同时连接内网和外网的配置?

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《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&#xff08;动态链接库&#xff09;文件缺失的问题通常不可能通过单一的"一键修复"按钮来实现&#xff0c;因为DLL文件缺失可能由各种不同的原因导致&#xff0c;比如应用程序安装不正确、病毒感染、或系统文件损坏等。 使用内置的系统文件检查器&#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、路由,不影响网络访问

内网环境通常是有线的&#xff0c;通过服务器的ip、mac、dns地址访问网络才生效的&#xff0c;如果ip地址变了&#xff0c;就不能访问网络了。 如果你的ip地址变了&#xff0c;或者要防止ip变更影响网络访问&#xff0c;就要设置 1、依次点击右上角的电源-设置&#xff0c;在打…...

学习前端第三十七天(静态属性静态方法、类检查、错误处理)

一、静态属性和静态方法 1、静态属性静态方法 在属性和方法前加上static&#xff0c;创建属于类自己的属性和方法 class Person {// 加static&#xff0c;属于类自己的static name "xc"; // 类的name属性static height 183; // 类的height属性static age 20;…...

全网最全的基于电机控制的38类simulink仿真全家桶----新手大礼包

整理了基于电机的38种simulink仿真全家桶&#xff0c;包含多种资料&#xff0c;类型齐全十分适合新手学习使用。包括但是不局限于以下&#xff1a; 1、基于多电平逆变器的无刷直流电机驱动simulink仿真 2、基于负载转矩的感应电机速度控制simulink仿真 3、基于滑膜观测器的永…...

Python使用asyncio包实现异步编程

1. 异步编程 异步编程是一种编程范式&#xff0c;用于处理程序中需要等待异步操作完成后才能继续执行的情况。异步编程允许程序在执行耗时的操作时不被阻塞&#xff0c;而是在等待操作完成时继续执行其他任务。这对于处理诸如文件 I/O、网络请求、定时器等需要等待的操作非常有…...

获取文件夹下的vue文件形成组件,require.context

前言&#xff1a;项目中现有一个文件里面包含所有需要用到的组件&#xff0c;如果一个个的去import&#xff0c;则会非常麻烦&#xff0c;现有require.context去实现&#xff0c; 1、require.context var request require.context(‘./module’, true, /.js$/) require.cont…...

2024软件测试必问的常见面试题1000问!

01、您所熟悉的测试用例设计方法都有哪些&#xff1f;请分别以具体的例子来说明这些方法在测试用例设计工作中的应用。 答&#xff1a;有黑盒和白盒两种测试种类&#xff0c;黑盒有等价类划分法&#xff0c;边界分析法&#xff0c;因果图法和错误猜测法。白盒有逻辑覆盖法&…...

C++列表实现

文章目录 一、listView相关内容主要思想实例全部代码 二、QTreeView 一、listView 相关内容 QAbstractItemModel&#xff1a;一个抽象的类&#xff0c;为数据项模型提供抽象的接口&#xff0c;常见的的数据模型列如&#xff1a;QStringListModel,QStandardItemMode,QDirModel…...

论文合集整理推荐2024.5.15

‍2012年论文合集&#xff1a;论文入口 ‍2019年论文合集&#xff1a;论文入口 2022年论文合集&#xff1a;论文入口 2023年论文合集&#xff1a;论文入口 2024年论文合集&#xff1a;论文入口...

JavaScript的跳转传参方式

在JavaScript中&#xff0c;页面跳转并传递参数通常可以通过几种不同的方式来实现。下面是一些常见的方法&#xff1a; 1.URL参数&#xff08;Query String&#xff09; 这是最常见的方式&#xff0c;通过在URL的末尾添加参数来实现。例如&#xff1a; javascriptwindow.loc…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...