使用redis进行短信登录验证(验证码打印在控制台)

使用redis进行短信登录验证

  • 一、流程
    • 1. 总体流程图
    • 2. 流程文字讲解:
    • 3.代码
      • 3.1 UserServiceImpl:(难点)
      • 3.2 拦截器LoginInterceptor:
      • 3.3 拦截器配置类:
    • 4 功能实现,成功存入redis

(黑马点评)

一、流程

1. 总体流程图

在这里插入图片描述

2. 流程文字讲解:

通过前端提供的手机号,进行判断符不符合11位的格式,如果不符合,返回错误信息,并且提示手机格式错误;判断符合格式,则生成一个6位的随机验证码。然后存到redis中。这个时候,只是简单的key-value,所以我们使用string类型。
我们现在生成了验证码,在进行校验之前,还需要验证是否和之前的手机号一致。然后校验验证码和我们redis中的验证码是否一致。不一致,返回错误信息;一致,查询手机号是否已经存在,如果User为空,则创建用户。然后保存信息到redis,作为登录令牌通过拦截器拦截。
生成一个token作为登录令牌,因为我们现在是需要通过token来携带我们的用户信息,所以我们使用Hash类型。第一步,把我们的User对象转为UserDTO类型。我们要存Hash的数据类型,就需要使用HashMap对象,所以,还需要把UserDTO转为HashMap(比较麻烦,详细见代码),然后存入并设置有效期。
对于拦截器:我们要使用StringRedisTemplate对象,但拦截器是我们自己写的,不进入Spring容器里面,所以需要通过构造函数来拿到StringRedisTemplate对象。
然后从请求头中拿到token,如果没有token,拦截,返回错误。然后从token中拿到UserMap对象。然后判断是否存在,不存在拦截。然后将Hash对象转为UserDTO对象,存入ThreadLocal,然后刷新有效期。

3.代码

3.1 UserServiceImpl:(难点)

package com.hmdp.service.impl;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RegexUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;import static com.hmdp.utils.RedisConstants.LOGIN_CODE_KEY;
import static com.hmdp.utils.RedisConstants.LOGIN_CODE_TTL;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Resourceprivate StringRedisTemplate stringRedisTemplate;//在之前csdn我们配置过。@Overridepublic Result sendCode(String phone, HttpSession session) {//1.校验手机号if (RegexUtils.isPhoneInvalid(phone)){//RegexUtils去校验手机号是否无效,再utils中//2.如果不符合,返回错误信息return Result.fail("手机号格式错误");}//3.符合,生成验证码String code = RandomUtil.randomNumbers(6);//生成一个6位的随机数字验证码/*    //4.保存验证码到session,session.setAttribute("code",code);*///4.保存验证码到redis中  = set key value ex 120stringRedisTemplate.opsForValue().set("login:code:"+phone,code,2, TimeUnit.MINUTES);//这里就把验证码保存到了redis中,为了防止数据存储过多,设置存储时间stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);//这里是把我们用到的常量定义到常量类里面。//5.发送验证码,如果我们要发送短信的话,需要通过第三方平台,比如阿里云之类的,所以我们直接使用log日志输出log.debug("发送短信验证码成功,验证码:"+ code);//返回okreturn Result.ok();}//使用redis的登录和注册@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//1.校验手机号//因为我们需要确定,登录的时候,手机号还是不是一个String phone =loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)){//RegexUtils去校验手机号是否无效,再utils中//2.如果不符合,返回错误信息return Result.fail("手机号格式错误");}//2.从redis过去验证码然后校验验证码String CacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);//从redis中获取验证码String code = loginForm.getCode();if (CacheCode==null||!CacheCode.equals(code)){//3.不一致,报错return Result.fail("验证码错误");}//4.一致,根据手机号查询用户 select * from tb_user where phone = ?  =query,因为,我们在这个类上面extends ServiceImpl<UserMapper, User>,所以可以使用mybatisPlusUser user = query().eq("phone", phone).one();//one()是查询一个,如果查询不到,返回null//5.判断用户是否存在if (user == null) {//6.不存在,创建新用户,保存user =  creteUserWithPhone(phone);}//7.保存用户信息到Redis//7.1 生成一个token,作为登录令牌String token = UUID.randomUUID().toString();//toString是转为String类型//7.2 将User对象转为hash存储UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);//7.3 存储 难点/*** 因为  Map<String, Object>是我们所有属性都是string的时候,才可以完全存进去,但我们的id是Long类型的* 所以我们使用工具类对其中的失败的值进行转化为string*   Map<String, Object>是可以自定义方法的,我们现在需要的是一个HashMap,所以userDTO,new HashMap<>(),*   然后使用  CopyOptions.create().setIgnoreNullValue(true).忽略空的值*    setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));失败的name和value我们只把失败的*    value转化为string类型不改id*/Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));//把userDTO转为Map<>String tokenKey="login:token:"+token;stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);//因为我们要往hash中存储对象,多个值,但putall存储的是Map<>//                              我们不要直接存这个名字,和前面phone一样,防止我们混淆//7.4 设置token的有效期stringRedisTemplate.expire(tokenKey,30,TimeUnit.MINUTES);//session是30min没有访问,才删除,如果要做到一样,可以使用拦截器return Result.ok(token);}//每一个session都有一个sessionid,当我们访问tomcat的时候,就已经自动带着了,所以,又sessionId就能知道是哪个用户private User creteUserWithPhone(String phone) {//1.创建用户User user =new User();user.setPhone(phone);user.setNickName("User_"+RandomUtil.randomString(10));//2.保存用户save(user);return user;}
}/*传统session里面@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//1.校验手机号//因为我们需要确定,登录的时候,手机号还是不是一个String phone =loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)){//RegexUtils去校验手机号是否无效,再utils中//2.如果不符合,返回错误信息return Result.fail("手机号格式错误");}//2.校验验证码Object CacheCode = session.getAttribute("code");String code = loginForm.getCode();if (CacheCode==null||!CacheCode.toString().equals(code)){//3.不一致,报错return Result.fail("验证码错误");}//4.一致,根据手机号查询用户 select * from tb_user where phone = ?  =query,因为,我们在这个类上面extends ServiceImpl<UserMapper, User>,所以可以使用mybatisPlusUser user = query().eq("phone", phone).one();//one()是查询一个,如果查询不到,返回null//5.判断用户是否存在if (user == null) {//6.不存在,创建新用户,保存user =  creteUserWithPhone(phone);}//7.保存用户信息到sessionsession.setAttribute("User", BeanUtil.copyProperties(user, UserDTO.class));//将user对象转换成UserDTO对象,再保存到session中return Result.ok();}//每一个session都有一个sessionid,当我们访问tomcat的时候,就已经自动带着了,所以,又sessionId就能知道是哪个用户private User creteUserWithPhone(String phone) {//1.创建用户User user =new User();user.setPhone(phone);user.setNickName("User_"+RandomUtil.randomString(10));//2.保存用户save(user);return user;}
}
*/

3.2 拦截器LoginInterceptor:

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 javax.servlet.http.HttpSession;
import java.util.Map;
import java.util.concurrent.TimeUnit;public class LoginInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;//这里是由下面这个构造函数,在MVC拦截器中传递进来的对象得到的public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}//这个类的对象不是由spring创建的,所以我们不能使用@Rouse,只能使用构造函数//redis的@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取请求头中的token,String token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {//使用工具类StrUtil中的方法isBlank//2.如果请求头中没有token,拦截  返回401状态码  未授权的意思response.setStatus(401);return false;}//2.基于token获取redis的用户Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);//这里使用的是redis的hash结构
/*session.setAttribute("sessionName",Object); //保存//用来设置session值的,sessionName是名称,object是你要保存的对象。session.getAttribute("sessionName");  //取得//用来得到对应名称的session值,即得到object对象,注意需要进行类型转换!
*///3.判断用户是否存在if (userMap.isEmpty()) {//如果为空//4.不存在,拦截  返回401状态码  未授权的意思response.setStatus(401);return false;}//将查询到的Hash转为UserDTO对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//将userMap转存为UserDTp,不忽略异常
//5.存在,保存到ThreadLocal  对ThreadLocal进行了规范和重写。UserHolder.saveUser(userDTO);//把user保存到ThreadLocal,对user进行强转//刷新token有效期stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);//6.放行return true;}/*    session的
@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取sessionHttpSession session = request.getSession();//2.获取session的用户Object user = session.getAttribute("user");*//*session.setAttribute("sessionName",Object); //保存//用来设置session值的,sessionName是名称,object是你要保存的对象。session.getAttribute("sessionName");  //取得//用来得到对应名称的session值,即得到object对象,注意需要进行类型转换!
*//*//3.判断用户是否存在if (user == null) {//4.不存在,拦截  返回401状态码  未授权的意思response.setStatus(401);return false;}//5.存在,保存到ThreadLocal  对ThreadLocal进行了规范和重写。UserHolder.saveUser((UserDTO)user);//把user保存到ThreadLocal,对user进行强转//6.放行return true;}*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();//移除用户}
}

3.3 拦截器配置类:

package com.hmdp.config;import com.hmdp.utils.LoginInterceptor;
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;/*** 这个类上面有@Configuration,是由spring来创建的,所以我们可以使用  @Resource来引入对象,再放到下面这个* LoginInterceptor构造函数里面,传递给LoginInterceptor类* @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns(//放行的接口"/blog/hot","/shop/**","/upload/**","/voucher/**","/shop-type/**","/user/code","/user/login");}
}

4 功能实现,成功存入redis

在这里插入图片描述

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

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

相关文章

搜维尔科技为空气分离、氢气、石化和天然气工厂的现场操作员提供虚拟现实(VR)培训

搜维尔科技为空气分离、氢气、石化和天然气工厂的现场操作员提供虚拟现实(VR)培训 搜维尔科技为空气分离、氢气、石化和天然气工厂的现场操作员提供虚拟现实(VR)培训

python 中关于append和extend的区别用法

#方法1 d[1,2,[3,4]] c[] for i in d:if type(i) int:c.append(i)else:c.extend(i)# append方法用于将单个元素添加到列表的末尾&#xff0c;这意味着无论元素是什么类型# &#xff08;如整数、字符串等&#xff09;&#xff0c;它都将作为一个独立的元素添加到列表中。# exten…

UE5.2 AI实时抠像(无需绿幕) + OBS推流直播 全流程

最近通过2个UE5.2插件实现了从AI实时抠像到OBS推流的直播流程搭建&#xff0c;也为了水一篇博客&#xff0c;就在这里记录一下了&#xff0c;觉得没有意思的朋友&#xff0c;这里先说为敬了。 具体教程参考&#xff1a;【UE5 AI抠像OBS推流全流程&#xff08;简单免费&#xf…

华为机考真题 -- 寻找身高相近的小朋友

题目描述: 小明今年升学到z小学—年级,来到新班级后发现其他小朋友们身高参差不齐,然后就想基于各4朋友和自己的身高差q对他们进行排序,请帮他实现排序。 输入描述: 有一行为正整数h和n,0<h<200,为小明的身高,0<n<50,为新班级其他小朋友个数。 第二行为…

java中 使用数组实现需求小案例

Date: 2024.04.08 18:32:57 author: lijianzhan 需求实现&#xff1a; 设计一个java类&#xff0c;java方法&#xff0c;根据用户手动输入的绩点&#xff0c;从而获取到绩点最高的成绩。 实现业务逻辑的代码块 import java.util.Scanner;public class PointDemo {/*** 需求&…

Spring相关面试题(四)

49 JavaConfig方式如何启用AOP?如何强制使用cglib&#xff1f; 在JavaConfig类&#xff0c;加上EnableAspectJAutoProxy 如果要强制使用CGLIB动态代理 &#xff0c;加上(proxyTargetClass true) 加上(exposeProxy true) 就是将对象暴露到线程池中。 50 介绍AOP在Spring中…

【3】迁移学习模型

【3】迁移学习模型 文章目录 前言一、安装相关模块二、训练代码2.1. 管理预训练模型2.2. 模型训练代码2.3. 可视化结果2.4. 类别函数 总结 前言 主要简述一下训练代码 三叶青图像识别研究简概 一、安装相关模块 #xingyun的笔记本 print(xingyun的笔记本) %pip install d2l %…

详解TCP和UDP通信协议

目录 OSI的七层模型的主要功能 tcp是什么 TCP三次握手 为什么需要三次握手&#xff0c;两次握手不行吗 TCP四次挥手 挥手会什么需要四次 什么是TCP粘包问题&#xff1f;发生的原因 原因 解决方案 UDP是什么 TCP和UDP的区别 网络层常见协议 利用socket进行tcp传输代…

【js面试题】深入理解DOM操作:创建、查询、更新、添加和删除节点

面试题&#xff1a;DOM常见的操作有哪些 引言&#xff1a; 在前端开发中&#xff0c;DOM&#xff08;文档对象模型&#xff09;操作是日常工作中不可或缺的一部分。DOM提供了一种以编程方式访问和更新文档内容、结构和样式的接口。 任何html或 xml 文档都可以用dom表示为一个由…

KIVY Button¶

Button — Kivy 2.3.0 documentation Button Jump to API ⇓ Module: kivy.uix.button Added in 1.0.0 The Button is a Label with associated actions that are triggered when the button is pressed (or released after a click/touch). To configure the button, the s…

【论文速读】| 用于安全漏洞防范的人工智能技术

本次分享论文&#xff1a;Artificial Intelligence Techniques for Security Vulnerability Prevention 基本信息 原文作者&#xff1a;Steve Kommrusch 作者单位&#xff1a;Colorado State University, Department of Computer Science, Fort Collins, CO, 80525 USA 关键…

ISO/OSI七层模型

ISO:国际标准化/ OSI:开放系统互联 七层协议必背图 1.注意事项&#xff1a; 1.上三层是为用户服务的&#xff0c;下四层负责实际数据传输。 2.下四层的传输单位&#xff1a; 传输层&#xff1b; 数据段&#xff08;报文&#xff09; 网络层&#xff1a; 数据包&#xff08;报…

1Panel安装命令脚本大全,多Linux操作系统版本

1Panel安装命令脚本大全&#xff0c;包括RedHat、CentOS、Ubuntu、Debian和openEuler等linux操作系统&#xff0c;码笔记整理1Panel安装命令脚本清单&#xff1a; RedHat/CentOS安装命令&#xff1a; curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh…

【Swoole 的生命周期,文件描述符,协程数量,以及默认值】

目录 Swoole 的生命周期 Swoole 文件描述符&#xff08;FD&#xff09;缓存 Swoole设置协程的数量 Swoole 默认值 Swoole 是一个基于 PHP 的高性能网络通信引擎&#xff0c;它采用 C 编写&#xff0c;提供了协程和高性能的网络编程支持。Swoole 支持多种网络服务器和客户端…

python库 - modelscope

ModelScope 是一个集成的机器学习模型库&#xff0c;旨在简化机器学习模型的使用流程&#xff0c;提供多种预训练模型&#xff0c;涵盖计算机视觉、自然语言处理、语音识别等多个领域。用户可以轻松访问、使用和分享各种预训练的机器学习模型&#xff0c;无需从头开始训练模型&…

Vue项目openlayers中使用jsts处理wkt和geojson的交集-(geojson来源zpi解析)

Vue项目openlayers中使用jsts处理wkt和geojson的交集-(geojson来源zpi解析) 读取压缩包中的shape看上一篇笔记&#xff1a;Vue项目读取zip中的ShapeFile文件&#xff0c;并解析为GeoJson openlayers使用jsts官方示例&#xff1a;https://openlayers.org/en/latest/examples/j…

框选table单元格,高亮展示

td单元格内&#xff0c;有未知层dom结构 <style>.highlight {background-color: yellow;} </style> <table id"myTable"><colgroup><col style"background-color: lightblue;"><col style"background-color: light…

#pragma 指令

#pragma 指令作用是设定编译器的状态或者是指示编译器完成一些特定的动作 message 参数能够在编译信息输出窗口中输出相应的信息 #pragma message(“消息文本”) code_seg参数能够设置程序中函数代码存放的代码段&#xff0c;当我们开发驱动程序的时候就会使用到它 #pragma…

科技创新引领水利行业升级:深入分析智慧水利解决方案的核心价值,展望其在未来水资源管理中的重要地位与作用

目录 引言 一、智慧水利的概念与内涵 二、智慧水利解决方案的核心价值 1. 精准监测与预警 2. 优化资源配置 3. 智能运维管理 4. 公众参与与决策支持 三、智慧水利在未来水资源管理中的重要地位与作用 1. 推动水利行业转型升级 2. 保障国家水安全 3. 促进生态文明建设…

下降npm版本

Node版本与NPM版本对应关系 Node版本与NPM版本对应关系_node对应npm版本-CSDN博客 要降低npm的版本&#xff0c;可以按照以下步骤进行操作 1.首先&#xff0c;打开终端或命令提示符窗口。 2.输入以下命令来检査当前安装的npm版本: npm -v 3.如果你想要降低npm的版本&#xff…