Redis实战(黑马点评)——涉及session、redis存储验证码,双拦截器处理请求

项目整体介绍 

数据库表介绍

基于session的短信验证码登录与注册

controller层

     // 获取验证码
    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        return userService.sendCode(phone, session);
    }


    // 获取验证码之后登录页面
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        // TODO 实现登录功能
        return userService.login(loginForm, session);
    }

service层 

@Override
public Result sendCode(String phone, HttpSession session) {
    // 1. 校验手机号格式是否正确
    if (RegexUtils.isPhoneInvalid(phone)) {
        return Result.fail("手机号格式不正确"); // 如果手机号格式不正确,返回失败结果
    }

    // 2. 生成6位随机数字验证码
    String code = RandomUtil.randomNumbers(6);
    
    // 3. 将验证码存储到HttpSession中
    session.setAttribute("code", code);

    // 4. 模拟发送验证码(实际开发中可以替换为短信发送逻辑)
    log.debug("发送验证码成功,验证码为:" + code);

    // 5. 返回成功结果
    return Result.ok();
}

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
    // 1. 校验手机号格式是否正确(防止用户在发送验证码后修改手机号)
    String phone = loginForm.getPhone();
    if (RegexUtils.isPhoneInvalid(phone)) {
        return Result.fail("手机号格式不正确"); // 如果手机号格式不正确,返回失败结果
    }

    // 2. 从HttpSession中获取存储的验证码
    Object Cachecode = session.getAttribute("code");

    // 3. 校验用户输入的验证码是否正确
    if (!loginForm.getCode().equals(Cachecode.toString()) || Cachecode == null) {
        return Result.fail("验证码错误"); // 如果验证码不匹配或为空,返回失败结果
    }

    // 4. 判断数据库中是否存在该手机号对应的用户
    User user = lambdaQuery().eq(User::getPhone, phone).one(); // 查询用户

    // 5. 如果用户不存在,则创建新用户并保存到数据库
    if (user == null) {
        user = new User();
        user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10)) // 设置随机昵称
                .setPhone(phone); // 设置手机号
        save(user); // 保存新用户到数据库
    }

    // 6. 将用户信息存储到HttpSession中
    session.setAttribute("user", user);

    // 7. 返回登录成功结果
    return Result.ok();
}

基于session登录的拦截器相关配置

创建拦截器

@Slf4j // 使用Lombok自动生成日志对象
@Component // 标识这是一个Spring组件
public class LoginInterceptor implements HandlerInterceptor {
    @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");
        
        // 3. 判断用户是否存在
        if (user == null) {
            // 4. 如果用户不存在,拦截请求,返回401状态码(未授权)
            response.setStatus(401);
            return false; // 返回false表示请求被拦截,不再继续执行后续的处理器
        }
        // 5. 如果用户存在,将用户信息保存到ThreadLocal中,以便在其他地方使用
        BaseContext.setCurrent((User) user);  
        // 6. 放行请求,继续执行后续的处理器
        return true;
    }
    // 视图渲染完毕后运行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 记录日志信息
        log.info("afterCompletion ....");        
        // 通常移除线程池中的用户信息,防止内存泄漏
        BaseContext.removeCurrent();
    }
}

注册拦截器拦截对象

@Configuration // 标识这是一个Spring配置类
public class MvcConfig implements WebMvcConfigurer {
    @Autowired // 自动注入LoginInterceptor的实例
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 向Spring MVC注册拦截器
        registry.addInterceptor(loginInterceptor) // 添加拦截器
                .addPathPatterns("/**") // 拦截所有请求路径
                .excludePathPatterns( // 排除不需要拦截的路径
                        "/shop/**", // 排除/shop/下的请求
                        "/voucher/**", // 排除/voucher/下的请求
                        "/shop-type/**", // 排除/shop-type/下的请求
                        "/upload/**", // 排除/upload/下的请求
                        "/blog/hot", // 排除/blog/hot请求
                        "/user/code", // 排除/user/code请求
                        "/user/login" // 排除/user/login请求
                );
    }
}

基于redis实现token登录与拦截器刷新token的过期时间

拦截器内获取请求头token同时检验与刷新

@Override  
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  

    // 从请求头中获取 "authorization" 的值  
    String token = request.getHeader("authorization");  
    
    // 检查 token 是否为空或者空串  
    if(StrUtil.isBlank(token)){   
        // 如果 token 为空,设置响应状态为 401(未授权)  
        response.setStatus(401);  
        return false; // 返回 false,表示请求未被处理  
    }  

    // 从 Redis 中获取与 token 关联的用户信息  
    Map<Object, Object> userMap = redisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);  
    
    // 检查用户信息是否为空  
    if(userMap.isEmpty()){  
        // 如果用户信息为空,设置响应状态为 401(未授权)  
        response.setStatus(401);  
        return false; // 返回 false,表示请求未被处理  
    }  
    
    // 将用户信息填充到 UserDTO 对象中  
    UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);  
    
    // 将当前用户信息设置到上下文中  
    BaseContext.setCurrent(userDTO);  
    
    // 刷新 token 的过期时间
    String key = LOGIN_USER_KEY + token;  
    redisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);  

    // 返回 true,表示请求可以继续处理  
    return true;  
}

service层业务逻辑 

@Autowired  
private RedisTemplate redisTemplate;  

// 发送验证码的方法  
@Override  
public Result sendCode(String phone, HttpSession session) {  
    // 1、校验手机号格式  
    if(RegexUtils.isPhoneInvalid(phone)){  
        return Result.fail("手机号格式不正确"); // 返回失败结果,提示手机号格式不正确  
    }  
    
    // 生成一个随机的6位验证码  
    String code = RandomUtil.randomNumbers(6);  
    
    // 将验证码存储到 Redis 中,设置过期时间  
    redisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);  
    
    // 模拟发送验证码(此处仅为日志记录,实际应用中应调用短信发送服务)  
    log.debug("发送验证码成功,验证码为:" + code);  
    
    return Result.ok(); // 返回成功结果  
}  

// 登录的方法  
@Override  
public Result login(LoginFormDTO loginForm, HttpSession session) {  
    // 校验手机号,可能在收到验证码后修改了手机号  
    String phone = loginForm.getPhone();  
    if(RegexUtils.isPhoneInvalid(phone)){  
        return Result.fail("手机号格式不正确"); // 返回失败结果,提示手机号格式不正确  
    }  
    
    // 从 Redis 中获取与手机号相关的验证码  
    String code = (String) redisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);  
    
    // 校验输入的验证码是否与 Redis 中的验证码匹配,且验证码不为空  
    if(!loginForm.getCode().equals(code) || code == null){  
        return Result.fail("验证码错误"); // 返回失败结果,提示验证码错误  
    }  
    
    // 判断数据库中是否存在此电话号码的用户,如果没有就插入数据库  
    User user = lambdaQuery().eq(User::getPhone, phone).one();  
    if(user == null) {  
        // 如果用户不存在,创建新用户  
        user = new User();  
        user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10)) // 设置用户昵称  
                .setPhone(phone); // 设置用户手机号  
        save(user); // 保存新用户到数据库  
    }  
    
    // 生成一个新的 token  
    String token = UUID.randomUUID().toString();  
    
    // 将用户信息复制到 UserDTO 对象中  
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);  
    
    // 将 UserDTO 转换为 Map 以便存储到 Redis  
    Map<String, Object> userMap = BeanUtil.beanToMap(userDTO);  
    
    // 生成 Redis 中的 token 键  
    String tokenkey = LOGIN_USER_KEY + token;  
    
    // 将用户信息存储到 Redis 中  
    redisTemplate.opsForHash().putAll(tokenkey, userMap);  
    
    // 设置 token 的过期时间  
    redisTemplate.expire(tokenkey, LOGIN_USER_TTL, TimeUnit.MINUTES);  

    // 返回成功结果,携带生成的 token  
    return Result.ok(token);  
}

双拦截器实现登录与未登录功能差别 

第一层拦截器 

@Autowired
    private RedisTemplate redisTemplate;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String token = request.getHeader("authorization");
        if(StrUtil.isBlank(token)){ // 检查是否为空或者空串
            return true;
        }

        Map<Object, Object> userMap = redisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);
        if(userMap.isEmpty()){
            return true;
        }
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        BaseContext.setCurrent(userDTO);
        String key = LOGIN_USER_KEY + token;
        redisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);

        return true;
    }

    //视图渲染完毕后运行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion ....RefreshTokenInterceptor");
        BaseContext.removeCurrent(); // 通常移除线程池
    }

 第二层拦截器

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(BaseContext.getCurrent() == null){
            response.setStatus(401);
            return false;
        }

        return true;
    }

注册双拦截器 

.order();方法用于指定拦截器的优先级,里面的值越小,那么优先级越高

registry.addInterceptor(loginInterceptor)
                .excludePathPatterns(
                        "/shop/**",
                        "/voucher/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                ).order(1);

        // 拦截所有请求
        registry.addInterceptor(refreshTokenInterceptor).addPathPatterns("/**").order(0);

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Brightness Controller-源码记录

Brightness Controller 亮度控制 一、概述二、ddcutil 与 xrandr1. ddcutil2. xrandr 三、部分代码解析1. icons2. ui3. utilinit.py 一、概述 项目&#xff1a;https://github.com/SunStorm2018/Brightness.git 原理&#xff1a;Brightness Controlle 是我在 Ubuntu 发现上调…

STM32简介

STM32简介 STM32是ST公司基于ARMCortex-M内核开发的32位微控制器 &#xff08;Microcontroller&#xff09; MCU微控制器、MPU微处理器、CPU中央处理器 1.应用领域 STM32常应用于嵌入式领域。 如智能车&#xff1a;循迹小车 读取光电传感器或者摄像头的数据&#xff0c;…

qt-C++笔记之QLine、QRect、QPainterPath、和自定义QGraphicsPathItem、QGraphicsRectItem的区别

qt-C笔记之QLine、QRect、QPainterPath、和自定义QGraphicsPathItem、QGraphicsRectItem的区别 code review! 参考笔记 1.qt-C笔记之重写QGraphicsItem的paint方法(自定义QGraphicsItem) 文章目录 qt-C笔记之QLine、QRect、QPainterPath、和自定义QGraphicsPathItem、QGraphic…

浏览器IndexedDB占用大

使用鲁大师清理后&#xff0c;用 SpaceSniffer 查看C盘占用情况&#xff0c;发现浏览器的 IndexedDB 有3个文件夹占用特别大&#xff0c;从文件名看是 youku&#xff0c;bilibili&#xff0c;v.qq.com&#xff0c;浏览器的数据库并不需要长期保存&#xff0c;删除这3个文件夹&a…

MongoDB部署模式

目录 单节点模式&#xff08;Standalone&#xff09; 副本集模式&#xff08;Replica Set&#xff09; 分片集群模式&#xff08;Sharded Cluster&#xff09; MongoDB有多种部署模式&#xff0c;可以根据业务需求选择适合的架构和部署方式。 单节点模式&#xff08;Standa…

将 OneLake 数据索引到 Elasticsearch - 第二部分

作者&#xff1a;来自 Elastic Gustavo Llermaly 及 Jeffrey Rengifo 本文分为两部分&#xff0c;第二部分介绍如何使用自定义连接器将 OneLake 数据索引并搜索到 Elastic 中。 在本文中&#xff0c;我们将利用第 1 部分中学到的知识来创建 OneLake 自定义 Elasticsearch 连接器…

Formality:时序变换(三)(相位反转)

相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 一、引言 时序变换在Design Compiler的首次综合和增量综合中都可能发生&#xff0c;它们包括&#xff1a;时钟门控(Clock Gating)、寄存器合并(Register Merging)、…

php代码审计2 piwigo CMS in_array()函数漏洞

php代码审计2 piwigo CMS in_array()函数漏洞 一、目的 本次学习目的是了解in_array()函数和对项目piwigo中关于in_array()函数存在漏洞的一个审计并利用漏洞获得管理员帐号。 二、in_array函数学习 in_array() 函数搜索数组中是否存在指定的值。 in_array($search,$array…

房租管理系统的智能化应用助推租赁行业高效运营与决策优化

内容概要 在现代租赁行业中&#xff0c;房租管理系统的智能化应用正在逐步成为一个不可或缺的工具。通过整合最新技术&#xff0c;这些系统为租赁管理的各个方面提供了极大的便利和效率提升。从房源管理到合同签署再到财务监控&#xff0c;智能化功能能够帮助运营者在繁琐的事…

Hive关于数据库的语法,warehouse,metastore

关于数据库的语法 在default数据库下,查看其他数据库的表 in 打开控制台 字体大小的设置 Hive默认的库: default, 1/4说明一共有4个库,现在只展示了1个,单击>>所有架构 数据库的删除 方法一: 语法 删除有表的数据库,加cascade 方法二 当前连接的数据库 切换当前数据库…

【React】PureComponent 和 Component 的区别

前言 在 React 中&#xff0c;PureComponent 和 Component 都是用于创建组件的基类&#xff0c;但它们有一个主要的区别&#xff1a;PureComponent 会给类组件默认加一个shouldComponentUpdate周期函数。在此周期函数中&#xff0c;它对props 和 state (新老的属性/状态)会做一…

AI赋能医疗:智慧医疗系统源码与互联网医院APP的核心技术剖析

本篇文章&#xff0c;笔者将深入剖析智慧医疗系统的源码架构以及互联网医院APP背后的核心技术&#xff0c;探讨其在医疗行业中的应用价值。 一、智慧医疗系统的核心架构 智慧医疗系统是一个高度集成的信息化平台&#xff0c;主要涵盖数据采集、智能分析、决策支持、远程医疗等…

HTML-新浪新闻-实现标题-样式1

用css进行样式控制 css引入方式&#xff1a; --行内样式&#xff1a;写在标签的style属性中&#xff08;不推荐&#xff09; --内嵌样式&#xff1a;写在style标签中&#xff08;可以写在页面任何位置&#xff0c;但通常约定写在head标签中&#xff09; --外联样式&#xf…

【学习笔记】深度学习网络-深度前馈网络(MLP)

作者选择了由 Ian Goodfellow、Yoshua Bengio 和 Aaron Courville 三位大佬撰写的《Deep Learning》(人工智能领域的经典教程&#xff0c;深度学习领域研究生必读教材),开始深度学习领域学习&#xff0c;深入全面的理解深度学习的理论知识。 在之前的文章中介绍了深度学习中用…

如何在IDEA社区版Service面板中管理springboot项目

1、开启service仪表盘 2、在service仪表盘中&#xff0c;添加启动类配置项&#xff0c;专业版是SpringBoot 、社区版是application。 3、控制台彩色日志输出 右键启动类配置项&#xff0c;添加虚拟机参数 -Dspring.output.ansi.enabledALWAYS

如何在data.table中处理缺失值

&#x1f4ca;&#x1f4bb;【R语言进阶】轻松搞定缺失值&#xff0c;让数据清洗更高效&#xff01; &#x1f44b; 大家好呀&#xff01;今天我要和大家分享一个超实用的R语言技巧——如何在data.table中处理缺失值&#xff0c;并且提供了一个自定义函数calculate_missing_va…

.NET9增强OpenAPI规范,不再内置swagger

ASP.NETCore in .NET 9.0 OpenAPI官方文档ASP.NET Core API 应用中的 OpenAPI 支持概述 | Microsoft Learnhttps://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/openapi/overview?viewaspnetcore-9.0https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/ope…

【redis初阶】redis客户端

目录 一、基本介绍 二、认识RESP&#xff08;redis自定的应用层协议名称&#xff09; 三、访问github的技巧 四、安装redisplusplus 4.1 安装 hiredis** 4.2 下载 redis-plus-plus 源码 4.3 编译/安装 redis-plus-plus 五、编写运行helloworld 六、redis命令演示 6.1 通用命令的…

蓝桥杯3518 三国游戏 | 排序

题目传送门 这题的思路很巧妙&#xff0c;需要算出每个事件给三国带来的净贡献&#xff08;即本国士兵量减其他两国士兵量&#xff09;并对其排序&#xff0c;根据贪心的原理累加贡献量直到累加结果不大于0。最后对三国的胜利的最大事件数排序取最值即可。 n int(input()) a …

基于vue框架的的信用社业务管理系统设计与实现4gnx5(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,用户销户,用户存款,用户取款,用户转账,理财类型,投资理财,理财订单,金属类别,贵金属,金属订单,产品分类,保险产品,保险订单 开题报告内容 基于Vue框架的信用社业务管理系统设计与实现开题报告 一、研究背景与意义 随着金融科技的…