黑马的redis实战篇-短信登录

news2024/10/7 18:22:20

目录

四、实战篇-短信登录

4.1 导入黑马点评项目

1、后端:

2、前端

4.2 基于Session实现登录

1、发送验证码

2、短信验证码登录注册

3、校验登录状态

4.3 集群的session共享问题

4.4 基于Redis实现共享session登录

1、发送验证码

2、短信验证码登录注册

3、校验登录状态


四、实战篇-短信登录

4.1 导入黑马点评项目

项目的架构:

 

1、后端:

  • 导入项目后后端,创建数据库,加载sql

  • 修改配置文件

     

  • 启动

    启动后,访问http://localhost:8081/shop-type/list

     

2、前端

在nginx所在目录下打开一个CMD窗口,输入命令:

start nginx.exe

 

4.2 基于Session实现登录

1、发送验证码

 

@Override
    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+phone,code);
​
        //5.发送验证码  真实是调用阿里云等平台短信服务的API
        log.info("发送验证码成功,验证码为: {}",code);
​
        //6.结束 返回ok
        return Result.ok();
    }
​

2、短信验证码登录注册

 

@Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //1.校验手机号是否格式是否正确
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式不正确");
        }
​
        //2.从session中取出验证码,校验验证码是否正确
        Object attribute = session.getAttribute(CODE+phone);
​
        if (attribute == null || !attribute.toString().equals(loginForm.getCode())) {
            //3.验证码不正确,直接返回
            return Result.fail("验证码不正确");
        }
​
        //4.通过,通过手机号查询用户是否存在
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getPhone,phone);
        User user = baseMapper.selectOne(lambdaQueryWrapper);
​
        //5.不存在进行注册
        if (user == null) {
            user = registerUser(phone);
        }
​
        //6.存在 保证用户到session中
          //注意这里需要进行脱敏
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        session.setAttribute(USER_INFO,userDTO);
        return Result.ok();
    }

3、校验登录状态

 

上面的逻辑,不光光是一个Controller要进行校验,其他Controller也需要校验,因此我们将其业务写入到拦截器中,如果有用户就放行,没有就不放行,然后在各自需要的Controller中获取用户,返回用户信息即可。如图所示

 

  • 创建拦截器类实现HandlerInterceptor,

    重写 preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

    和afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)

    package com.hmdp.utils;
    ​
    import com.hmdp.dto.UserDTO;
    import org.springframework.web.servlet.HandlerInterceptor;
    ​
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    ​
    import static com.hmdp.utils.SystemConstants.USER_INFO;
    ​
    /**
     * @packageName: com.hmdp.utils
     * @author: winter
     * @date: 2023/4/7 17:15
     * @version: 1.0
     * @email 1660420659@qq.com
     * @description: 拦截器
     */
    public class LoginInterceptor implements HandlerInterceptor {
        /**
         * 请求进入preHandle之前执行
         * @param request
         * @param response
         * @param handler
         * @return
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
           //1.通过session,获取用户
            Object attribute = request.getSession().getAttribute(USER_INFO);
    ​
            if (attribute == null) {
                //2.用户不存在,拦截器拦截
                response.setStatus(401);
                return false;
            }
    ​
           //3.将用户保证在ThreadLocal中  拦截器放行
            UserHolder.saveUser((UserDTO) attribute);
            return true;
        }
    ​
    ​
        /**
         * 响应结束后,执行这个方法
         * @param request
         * @param response
         * @param handler
         * @param ex
         * @throws Exception
         */
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            //移除 防止内存泄漏
            UserHolder.removeUser();
        }
    }
  • 配置Mvc配置类,拦截器的白名单

    package com.hmdp.config;
    ​
    import com.hmdp.utils.LoginInterceptor;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    ​
    /**
     * @packageName: com.hmdp.config
     * @author: winter
     * @date: 2023/4/7 17:39
     * @version: 1.0
     * @email 1660420659@qq.com
     * @description: mvc配置类
     */
    @Configuration
    public class MvcConfig implements WebMvcConfigurer {
    ​
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
           registry.addInterceptor(new LoginInterceptor())
                   .addPathPatterns("/**")
                   .excludePathPatterns(
                           "/user/login",
                           "/user/code",
                            "/shop/**",
                           "/shop-type/**",
                           "/voucher/**",
                           "/upload/**",
                           "/blog/hot"
                   );
        }
    }
  • 将UserController中的me方法进行获取用户,返回信息

     @GetMapping("/me")
        public Result me(){
            // 获取当前登录的用户并返回
            UserDTO user = UserHolder.getUser();
            return Result.ok(user);
        }

4.3 集群的session共享问题

session共享问题:当并发访问量多的时候,我们的解决办法可以说添加tomcat集群,但是就会出现问题

多态tomcat并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失问题

 

如图所示,因为使用nginx轮训方式,可能一个用户两次访问的tomcat并不是同一个,并且session不共享,会出现登录进去后,往往提示还需登录,用户的体验感极差,因此出现了解决方案,将数据都存储到redis集群中

4.4 基于Redis实现共享session登录

1、发送验证码

 

public static final String LOGIN_CODE_KEY = "login:code:";
public static final Long LOGIN_CODE_TTL = 2L;
 @Resource
    private StringRedisTemplate stringRedisTemplate;
​
    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1.验证手机号是否合法
        if(RegexUtils.isPhoneInvalid(phone)) {
​
            //2.如果不符合,返回错误信息
            return Result.fail("手机号不合法");
        }
​
        //3.符合,生成验证码
        String code = RandomUtil.randomNumbers(6);
​
        //4.将验证码保存到session中,并设置过期时间为2分钟
       stringRedisTemplate.opsForValue().
               set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);
​
        //5.发送验证码
        log.info("发送验证码成功,验证码为: {}",code);
​
        //6.结束 返回ok
        return Result.ok();
    }

2、短信验证码登录注册

 常量中的值

public static final String LOGIN_USER_KEY = "login:token:";
​
public static final Long LOGIN_USER_TTL = 36000L;

@Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //1.校验手机号是否格式是否正确(其实这里应该也将手机号加入到session中,然手机号和验证码一一对应)
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式不正确");
        }
​
        //2.从redsi中取出验证码,校验验证码是否正确
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
​
        if (cacheCode == null || !cacheCode.equals(loginForm.getCode())) {
            //3.验证码不正确,直接返回
            return Result.fail("验证码不正确");
        }
​
        //4.通过,通过手机号查询用户是否存在
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getPhone,phone);
        User user = baseMapper.selectOne(lambdaQueryWrapper);
​
        //5.不存在进行注册
        if (user == null) {
            user = registerUser(phone);
        }
​
        //6.存在 保证用户到redis中
          //6.1 注意这里需要进行脱敏
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
​
          //6.2 转换生成Map 
        //这里因为使用的StringRedisTemplate 因此都是String
        //这里如果不装换成string  map中存储userDto对象的id是long类型
        //存储在redis的hash时 会出现String转换long 数字转换异常
        Map<String, Object> map = BeanUtil.beanToMap(userDTO,new HashMap<>(),
                CopyOptions.create().setIgnoreNullValue(true)
                        .setFieldValueEditor((fieldName,fieldValue) ->
                                fieldValue.toString()));
​
          //6.3.生成一个随机的token
        String token = UUID.fastUUID().toString(true);
​
          //6.4 存到到redis中
        String key = LOGIN_USER_KEY+token;
         stringRedisTemplate.opsForHash().putAll(key,map);
         //设置有效时间
         stringRedisTemplate.expire(key,LOGIN_USER_TTL,TimeUnit.MINUTES);
        return Result.ok(token);
    }
​
    /**
     * 注册
     * @param phone
     * @return
     */
    private User registerUser(String phone) {
        User user = new User();
        user.setPhone(phone);
        String nickName = RandomUtil.randomString(8);
        user.setNickName(USER_NICK_NAME_PREFIX+nickName);
        //保存用户到数据库
        baseMapper.insert(user);
        return user;
    }

3、校验登录状态

 

这里需要进行优化一下,需要使用两个拦截器,因为session的时候使用一个拦截器,只有登录等被拦截的请求,才会增加redis键值的时间,例如访问主页不经过拦截器,可能半小时后键值就自动销毁的,这样是不符合业务要求的,因此要在增加一个拦截器

 

  • 拦截器1 拦截一切路径

package com.hmdp.utils;
​
import cn.hutool.core.bean.BeanUtil;
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 java.util.Map;
import java.util.concurrent.TimeUnit;
​
import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;
​
/**
 * @packageName: com.hmdp.utils
 * @author: winter
 * @date: 2023/4/7 17:15
 * @version: 1.0
 * @email 1660420659@qq.com
 * @description: 拦截器所有请求
 */
public class ExtraTimeInterceptor implements HandlerInterceptor {
​
​
​
​
    private StringRedisTemplate stringRedisTemplate;
    public ExtraTimeInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
​
    /**
     * 请求进入preHandle之前执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.通过请求获取到token信息
        String token = request.getHeader("authorization");
        //2.通过token从redis中获取对象信息
        Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);
        if (map.isEmpty()) {
            return  true;
        }
        //将map转换为对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(map, new UserDTO()
                , false);
​
        //3.将用户保证在ThreadLocal中  拦截器放行
        UserHolder.saveUser( userDTO);
​
        //4.刷新用户存储有效期
        stringRedisTemplate.expire(LOGIN_USER_KEY + token,LOGIN_USER_TTL, TimeUnit.MINUTES);
        return true;
    }
​
​
    /**
     * 响应结束后,执行这个方法
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //移除 防止内存泄漏
        UserHolder.removeUser();
    }
​
}
​

  • 拦截器2 拦截部分需要登录才能访问的请求

package com.hmdp.utils;
​
import com.hmdp.dto.UserDTO;
import org.springframework.web.servlet.HandlerInterceptor;
​
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
​
/**
 * @packageName: com.hmdp.utils
 * @author: winter
 * @date: 2023/4/7 17:15
 * @version: 1.0
 * @email 1660420659@qq.com
 * @description: 拦截部门请求,拦截需要登录的路径
 */
public class LoginInterceptor implements HandlerInterceptor {
​
​
​
    /**
     * 请求进入preHandle之前执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.取到用户
        UserDTO user = UserHolder.getUser();
​
        //2.判断用户是否为空
        if (user == null) {
            response.setStatus(401);
            return false;
        }
        return true;
    }
​
​
​
}
 
  • mvc配置一下拦截器需要拦截什么请求

package com.hmdp.config;
​
import com.hmdp.utils.ExtraTimeInterceptor;
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;
​
/**
 * @packageName: com.hmdp.config
 * @author: winter
 * @date: 2023/4/7 17:39
 * @version: 1.0
 * @email 1660420659@qq.com
 * @description: mvc配置类
 */
@Configuration
public class MvcConfig implements WebMvcConfigurer {
​
    @Resource
    private StringRedisTemplate stringRedisTemplate;
​
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //默认先添加执行  ,也可以设置order  值越小越先执行
       registry.addInterceptor(new ExtraTimeInterceptor(stringRedisTemplate))
               .addPathPatterns("/**").order(0);
       registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")
                .excludePathPatterns(
                "/user/login",
                "/user/code",
                "/shop/**",
                "/shop-type/**",
                "/voucher/**",
                "/upload/**",
                "/blog/hot"
        ).order(1);
    }
}
具体代码gitee上:

redis实战篇-hmdp-短信登录: 存放黑马点评中redis进行短信登录的代码 ,包括前端后后端

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

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

相关文章

NumPy 秘籍中文第二版:六、特殊数组和通用函数

原文&#xff1a;NumPy Cookbook - Second Edition 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 在本章中&#xff0c;我们将介绍以下秘籍&#xff1a; 创建通用函数查找勾股三元组用chararray执行字符串操作创建一个遮罩数组忽略负值和极值使用recarray函数创建一…

蓝桥杯之单片机学习(终)——关于之前文章的错误及更正(附:第十四届蓝桥杯单片机赛题)

文章目录零、吐槽一、关于自创模板&#xff0c;和自写模板库的问题二、关于 详解A/D、D/A、PCF8591 这篇文章一些小错误三、模板最终版本main.cds1302,hds1302.conewire.honewire.ciic.hiic.c附、第十四届蓝桥杯单片机赛题零、吐槽 今年是矩阵键盘三个协议一起调用啊。真是一年…

“AI+机器人”持续为多领域增“智”添“质”,开启效益增长飞轮

近期&#xff0c;工信部等17部门联合推出《“机器人”应用行动实施方案》&#xff0c;全面加快机器人领域应用拓展。据方案提出&#xff0c;至2025年&#xff0c;制造业机器人密度较2020年将实现翻番&#xff0c;服务机器人及特种机器人行业应用深度与广度显著提升。机器人融合…

服务器被DDoS攻击,怎么破?

文章目录前言网站受到DDoS的症状判断是否被攻击查看网络带宽占用查看网络连接TCP连接攻击SYN洪水攻击防御措施TCP/IP内核参数优化iptables 防火墙预防防止同步包洪水&#xff08;Sync Flood&#xff09;Ping洪水攻击&#xff08;Ping of Death&#xff09;控制单个IP的最大并发…

基于SpringBoot的私人健身和教练的预约管理系统源码数据库论文

目 录 第一章 概述 1.1研究背景 1.2开发意义 1.3研究现状 1.4研究内容 1.5论文结构 第二章 开发技术介绍 2.1系统开发平台 2.2平台开发相关技术 2.2.1 Javar技术 2.2.2 Mysql数据库介绍 2.2.3 Mysql环境配置 2.2.4 B/S架构 2.2.5 Springboot框架 …

主动配电网故障恢复的重构与孤岛划分统一模型研究【升级版本】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

V8引擎执行原理

v8是C编写的Google开源高性能JavaScript和WebAssembly引擎&#xff0c;它用于Chrome和Node.js等。 它实现ECMAScript和WebAssembly。 v8可独立运行&#xff0c;也可嵌入到任何C应用程序中。 parse模块 parse模块会将JavaScript代码转换成AST(抽象语法树)&#xff0c;因为解…

[LeetCode周赛复盘] 第 340 场周赛20230409

[LeetCode周赛复盘] 第 340 场周赛20230409 一、本周周赛总结二、 6361. 对角线上的质数1. 题目描述2. 思路分析3. 代码实现三、6360. 等值距离和1. 题目描述2. 思路分析3. 代码实现四、6359. 最小化数对的最大差值1. 题目描述2. 思路分析3. 代码实现五、 6353. 网格图中最少访…

【排序】排序这样写才对Ⅰ --插入排序与选择排序

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 我会一直往里填充内容哒&#xff01; &…

Axios请求(对于ajax的二次封装)——Axios请求的响应结构、默认配置

Axios请求&#xff08;对于ajax的二次封装&#xff09;——Axios请求的响应结构、默认配置知识回调&#xff08;不懂就看这儿&#xff01;&#xff09;场景复现核心干货axios请求的响应结构响应格式详解实际请求中的响应格式axios请求的默认配置全局axios默认值&#xff08;了解…

Debug | wget 的安装与使用(Windows)

!wget -nc http://labfile.oss.aliyuncs.com/courses/780/WeatherData.zip 报错信息&#xff1a; wget 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。 分析&#xff1a; 在jupyter notebook中做机器学习时导入数据使用!wget遇到了这个问题&#xff0c;查到…

轻松上手git代码版本管理工具--协同开发-冲突解决、线上分支合并以及使用pycharm操作git

一、协同开发 多人合作开发一个项目---->多人公用一个远程仓库 以后台项目为例: git init # git管理设置忽略文件.gitignore git add .git commit -m 第一次提交,写完了首页功能远程新建一个远程仓库(空) 创建一个origin git remote add origin git@gitee.com:xx…

穿戴规范智能识别系统 yolov7

穿戴规范智能识别系统通过yolov7python网络模型AI深度视觉学习算法&#xff0c;穿戴规范智能识别系统对工厂画面中人员穿戴行为自动识别分析&#xff0c;发现现场人员未按照规定穿戴着装&#xff0c;立即抓拍告警。YOLOv7 的发展方向与当前主流的实时目标检测器不同&#xff0c…

垃圾满溢检测系统 yolov5

垃圾满溢检测系统通过pythonyolov5网络模型技术&#xff0c;垃圾满溢检测系统对控画面中小区内的垃圾桶进行7*24小时不间断监控&#xff0c;发现垃圾桶溢满周围有堆积物立即触发预警推送给相关人员处理。YOLOv5中在训练模型阶段仍然使用了Mosaic数据增强方法&#xff0c;该算法…

kubeadm方式部署k8s最新版本V1.26.2

Kubernetes核心概念 Master主要负责资源调度&#xff0c;控制副本&#xff0c;和提供统一访问集群的入口。--核心节点也是管理节点 Node是Kubernetes集群架构中运行Pod的服务节点。Node是Kubernetes集群操作的单元&#xff0c;用来承载被分配Pod的运行&#xff0c;是Pod运行的宿…

测试7年,去过阿里也去过小公司,给你们年轻人一个忠告...

你眼中的软件测试岗位是怎样的&#xff1f;大部分人可能会给出这样的回答&#xff1a;“测试&#xff1f;简单啊&#xff0c;没什么技术含量&#xff0c;无非就是看需求、看业务手册、看设计文档、然后点点功能是否实现&#xff0c;麻烦点的就是测试下部署安装是否出现兼容性问…

分布式事务Seata原理

Seata 是一款开源的分布式事务解决方案&#xff0c;致力于提供高性能与简单易用的分布式事务服务&#xff0c;为用户提供了 AT、TCC、SAGA 和 XA 几种不同的事务模式。Seata AT模式是基于XA事务演进而来&#xff0c;需要数据库支持。AT 模式的特点就是对业务无入侵式&#xff0…

【故障定位】基于多元宇宙算法的主动配电网故障定位方法研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

LeetCode 37. 解数独

一、题目描述 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#xff08;请参考示例图&#x…

如果重回大学时光

目录如果重回大学时光1. 多参加社团活动&#xff0c;结交更多的朋友2. 认真考虑专业分流方向3. 根据兴趣和能力选择适合自己的出路4. 为未来做好准备&#xff0c;规划职业发展如果重回大学时光 当前我是一名普通的码农&#xff0c;还有几个月&#xff0c;又将有一批大学生将毕…