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

news2024/9/21 5:33:03

使用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 {
    @Resource
    private StringRedisTemplate stringRedisTemplate;//在之前csdn我们配置过。

    @Override
    public 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 120
        stringRedisTemplate.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);
        //返回ok
        return Result.ok();
    }

    //使用redis的登录和注册
    @Override
    public 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>,所以可以使用mybatisPlus
        User 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里面
    @Override
    public 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>,所以可以使用mybatisPlus
        User user = query().eq("phone", phone).one();//one()是查询一个,如果查询不到,返回null
        //5.判断用户是否存在
        if (user == null) {
            //6.不存在,创建新用户,保存
          user =  creteUserWithPhone(phone);
        }


        //7.保存用户信息到session
        session.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的

    @Override
    public 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的
@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       //1.获取session
        HttpSession 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;
    }*/

    @Override
    public 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 {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 这个类上面有@Configuration,是由spring来创建的,所以我们可以使用  @Resource来引入对象,再放到下面这个
     * LoginInterceptor构造函数里面,传递给LoginInterceptor类
     * @param registry
     */

    @Override
    public 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.coloradmin.cn/o/1911565.html

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

相关文章

2017年,我成为了技术博主

2017年9月&#xff0c;我已经大三了。 >>上一篇&#xff08;爪哇&#xff0c;我初窥门径&#xff09; 我大二学了很多java技术&#xff0c;看似我一会就把javaweb/ssh/ssm这些技术栈给学了。 这些技术确实不难&#xff0c;即便是我&#xff0c;我都能学会&#xff0c;…

深入理解 LXC (Linux Containers)

目录 引言LXC 的定义LXC 的架构LXC 的工作原理LXC 的应用场景LXC 在 CentOS 上的常见命令实验场景模拟总结 1. 引言 在现代 IT 基础设施中&#xff0c;容器技术已经成为一种重要的应用和部署方式。与虚拟机相比&#xff0c;容器具有更高的效率、更轻量的特性和更快的启动速度…

MySQL GROUP_CONCAT 函数详解与实战应用

提示&#xff1a;在需要将多个值组合成一个列表时&#xff0c;GROUP_CONCAT() 函数为 MySQL 提供了一种强大的方式来处理数据 文章目录 前言什么是 GROUP_CONCAT()基本语法 示例使用 GROUP_CONCAT()去除重复值排序结果 前言 提示&#xff1a;这里可以添加本文要记录的大概内容…

第一次作业--数据库-搭建MySQL环境

一、下载 二、进入安装向导 1.选择Custom &#xff0c;然后点击next 2.选择安装地址 点击第一个MySQL Servers然后依次点击打开到MySQL Server 8.0.37-X64 点击向右的绿色箭头 点击MySQL Server 8.0.37-X64 然后看到下面的蓝色Advanced Options 更改安装路径 然后点击next …

刷题(day02)

1、leetcode136.删除链表的结点 给定单向链表的头指针和一个要删除的节点的值&#xff0c;定义一个函数删除该节点。 返回删除后的链表的头节点。 示例 1: 输入: head [4,5,1,9], val 5 输出: [4,1,9] 解释: 给定你链表中值为 5 的第二个节点&#xff0c;那么在调用了你的函数…

说说iOS苹果的“开发者模式”什么时候需要打开 需不需要提前打开

在 iOS 开发过程中&#xff0c;开发者模式&#xff08;Developer Mode&#xff09;是一个非常重要的功能&#xff0c;它允许开发者在设备上运行和调试自己的应用程序。 经常有人私信或在群里问到&#xff0c;“我没有开发者模式&#xff0c;怎么办”&#xff0c;“开发者模式是…

优秀策划人必逛的地方,你不会还不知道吧?

道叔今天依然记得当初刚入行的时候&#xff0c;每天为完成策划任务&#xff0c;焦虑的整晚睡不着觉的痛苦。 但其实……很多时候&#xff0c;选择比努力更重要 优秀的策划和文案&#xff0c;也从来不是天生&#xff0c;你要走的路&#xff0c;前人都已经走过,你要做的仅仅是整…

windows JDK11 与JDK1.8自动切换,以及切换后失效的问题

1.windows安装不同环境的jdk 2.切换jdk 3.切换失败 原因&#xff1a;这是因为当我们安装并配置好JDK11之后它会自动生成一个环境变量&#xff08;此变量我们看不到&#xff09;&#xff0c;此环境变量优先级较高&#xff0c;导致我们在切换回JDK8后系统会先读取到JDK11生成的…

Windows11配置WSL2支持代理上网

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、安装WSL2分发版二、配置步骤三、测试总结 前言 说起来本来这个功能我也不需要的&#xff0c;只是最近突然有个需求就顺便研究了下&#xff0c;WSL2默认的网…

【漏洞复现】29网课交单平台 SQL注入

声明&#xff1a;本文档或演示材料仅用于教育和教学目的。如果任何个人或组织利用本文档中的信息进行非法活动&#xff0c;将与本文档的作者或发布者无关。 一、漏洞描述 29网课交单平台是一个在线学习平台&#xff0c;用于帮助学生完成网络课程的学习任务。这个平台提供了包括…

泛微E9开发 控制Radio框字段打印是否仅显示选中项文字

控制Radio框字段打印是否仅显示选中项文字 1、需求说明2、实现方法3、扩展知识点控制Radio框字段打印是否仅显示选中项文字格式参数说明样例 1、需求说明 当我们对单选框进行打印时&#xff0c;往往会把所有的选项一起打印出来&#xff08;如下图所示&#xff09;&#xff0c;现…

【Linux进阶】文件系统4——文件系统特性

1.磁盘组成与分区的复习 首先说明一下磁盘的物理组成&#xff0c;整块磁盘的组成主要有&#xff1a; 圆形的碟片&#xff08;主要记录数据的部分&#xff09;&#xff1b;机械手臂&#xff0c;与在机械手臂上的磁头&#xff08;可擦写碟片上的数据);主轴马达&#xff0c;可以…

Redis学习 - 基础篇

Redis学习 - 基础篇 一. 简介 Redis 是一个高性能的key-value数据库&#xff0c;常用的数据类型如下&#xff1a;string&#xff0c;list&#xff0c;set&#xff0c;zset&#xff0c;hash 二. 安装 Widows和Linux下如何安装Redis-CSDN博客 三. 常用命令 配置及数据库操作…

[ TOOLS ] JFLASH 使用说明

一、使用everything查找JFLASH everything是指这个软件&#xff0c;使用这个方便查找想要的文件 二、创建一个工程并配置 创建完后进行配置&#xff1a; Target devic: 板子的芯片型号&#xff0c;比如R7FA6M4Target interface: 一般是SWDSpeed: 一般是4000kHz, 不能下载则将Sp…

数学建模美赛入门

数学建模需要的学科知识 高等数学线性代数 有很多算法的掌握是需要高等数学和线代的相关知识 如&#xff1a;灰色预测模型需要微积分知识&#xff1b;神经网络需要用到导数知识&#xff1b;图论和层次分析法等都需要用到矩阵计算的相关知识等&#xff1b; 概率论与数理统计&am…

基于SpringBoot构造超简易QQ邮件服务发送 第二版

目录 追加 邮箱附件 添加依赖 编码 测试 第二版的更新点是追加了 邮箱附件功能 ( 后期追加定时任务 ) 基于SpringBoot构造超简易QQ邮件服务发送(分离-图解-新手) 第一版 追加 邮箱附件 添加依赖 <!-- 电子邮件 --><dependency><groupId>org.spri…

后端登录校验——Filter过滤器和Interceptor拦截器

一、Filter过滤器 前面我们学会了最先进的会话跟踪技术jwt令牌&#xff0c;那么我们要让用户使用某些功能时就要根据jwt令牌来验证用户身份&#xff0c;来决定他是否登陆了、让不让用户访问这个页面&#xff08;或功能&#xff09; 但是这样一来&#xff0c;没发一个请求&…

解决打印PDF文本不清楚的处理办法

之前打印PDF格式的电子书&#xff0c;不清晰&#xff0c;影响看书的心情&#xff0c;有时看到打印的书的质量&#xff0c;根本不想看&#xff0c;今天在打印一本页数不多&#xff0c;但PDF格式的书感觉也不太清楚&#xff0c;我想应该有办法解决&#xff0c;我使用的是解决福昕…

实时监测、智能预警:电缆光纤测温系统|原理、应用与前景

实时监测、智能预警&#xff1a;电缆光纤测温系统|原理、应用与前景 电缆光纤测温系统&#xff0c;作为现代电力系统中不可或缺的一部分&#xff0c;以其独特的优势在电缆安全监控领域发挥着日益重要的作用。该系统利用光纤传感技术&#xff0c;实时监测电缆的运行温度&#x…

【LeetCode刷题笔记】LeetCode.11.盛最多水的容器

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 更多算法知识专栏&#xff1a;算法分析&#x1f525; 给大家跳段街舞感谢…