Redis项目:短信验证码登录

这是黑马的黑马点评项目,短信验证码的业务。一开始是使用session做的,后来重构,使用redis缓存来完成。

第一层拦截器:

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 {//获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {return true;}// 基于token获取redis中的用户Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);//判断用户是否存在// 不存在,拦截,返回401状态码if (userMap.isEmpty()) {return true;}// 查询到的hash数据转换成UserDTO对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 存在 保存用户信息到ThreadlocalUserHolder.saveUser(userDTO);// 刷新token有效期stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);//放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//移除用户UserHolder.removeUser();}
}

第一次拦截器的逻辑:

  • preHandle 方法
    1. 从请求头中获取 authorization token。
    2. 如果 token 为空,直接放行(返回 true)。
    3. 根据 token 从 Redis 中查询用户信息(存储在 Hash 结构中)。
    4. 如果用户信息不存在,直接放行(返回 true)。
    5. 如果用户信息存在:
      • 将 Redis 查询到的 Hash 数据转换为 UserDTO 对象。
      • 将用户信息保存到 UserHolder(ThreadLocal)。
      • 刷新 Redis 中 token 的有效期。
      • 返回 true 放行请求。
  • afterCompletion 方法
    • 在请求处理完成后,移除 UserHolder 中的用户信息,清理线程局部变量

在authorization 中它记录的是客户端发送的身份验证凭证

刷新 Redis 中 token 的有效期是通过调用 Redis 的 EXPIRE 命令完成的

在转成对象的时候 参数false:一个布尔参数,通常用于控制填充行为.如果 Map 中的键无法匹配目标对象的属性,或者类型转换失败,会抛出异常(例如 BeanException),填充过程中止

第二次拦截器

第二次拦截就只需要判断线程里有没有用户了

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//这个拦截器只需要判断是不是需要拦截(ThreadLocal中有没有用户)if (UserHolder.getUser() == null) {//没有用户信息 拦截 返回401response.setStatus(UNAUTHORIZED_401);return false;}//有用户 放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//移除用户UserHolder.removeUser();}
}
  • preHandle
    • 检查 UserHolder(ThreadLocal)中是否有用户信息。
    • 如果没有(UserHolder.getUser() == null),设置响应状态码为 401(未授权)并拦截请求(返回 false)。
    • 如果有用户信息,放行请求(返回 true)。
  • afterCompletion
    • 在请求处理完成后,清理 UserHolder 中的用户信息。
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();}
}

 ThreadLocal 是 Java 中的一种线程隔离机制,用于在多线程环境中为每个线程提供独立的变量副本

每个线程访问 ThreadLocal 时,获取的是自己线程的独立副本,互不干扰.

ThreadLocal 中的数据与线程绑定,线程结束时数据自动销毁(如果正确清理)

  • ThreadLocal 维护一个 ThreadLocalMap,存储在每个 Thread 对象中。
  • set 方法将值存入当前线程的 ThreadLocalMap,键是 ThreadLocal 对象,值是用户设置的数据。
  • get 方法从当前线程的 ThreadLocalMap 中取值。
  • remove 方法删除当前线程的 ThreadLocalMap 中的对应键值对

发送验证码

@Overridepublic Result sendCode(String phone, HttpSession session) {//1、校验手机号if (RegexUtils.isPhoneInvalid(phone)) {//2、如果不符合,返回错误信息return Result.fail(cacheConstants.ERROR_USER_PHONE);}//3、符合,生成验证码String code = RandomUtil.randomNumbers(6);//4、保存验证码 把验证码存入redis中//session.setAttribute(cacheConstants.USER_LOGIN_CAPTCHA_KEY, code);stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);//5、发送验证码log.info("验证码发送成功{}", code);//6、返回结果对象return Result.ok();}

 验证码被存在了redis中,而不是session中

校验验证码

@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//校验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {//如果不符合,返回错误信息return Result.fail(cacheConstants.ERROR_USER_PHONE);}//校验验证码 从redis中获取String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();//验证码不一致 报错if (cacheCode == null || !cacheCode.equals(code))return Result.fail(cacheConstants.ERROR_USER_CAPTCHA_KEY);//验证码一致 查找用户 SELECT * FROM tb_user where phone = ?User user = query().eq(cacheConstants.USER_LOGIN_PHONE, phone).one();// 查询用户 是否存在 不存在 创建用户if (user == null)user = createUserWithPhone(phone);//保存用户信息到redis中//生成token作为keyString token = UUID.randomUUID().toString();//将User对象转为hash存储UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);String tokenKey = LOGIN_USER_KEY + token;Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));//存储 这里注意 user中id属性是long 类型 需要的是string类型 利用putAll参数进行转换stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);//设置有效期stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);//返回tokenreturn Result.ok(token);}private User createUserWithPhone(String phone) {//创建用户User user = new User();user.setPhone(phone);user.setNickName(cacheConstants.USER_LOGIN_USER + RandomUtil.randomString(10));save(user);return user;}

先是校验一下手机号是不是合法 也可以再校验一下验证码 

然后就是从redis中取得验证码 进行比对

验证码一致就可以从数据库中查找是否有用户

有用户 用户数据会通过mybatisPlus自动映射到对象user上 

没有用户 调用创建用户 

然后会生成一个token,作为key的一部分(客户端在后续请求中携带 token(通常放在 Authorization 请求头中))

将user的部分信息封装到UserDto之中  然后将对象转成map集合的形式

之后以hash的形式存入redis 设置有效期

trips

CopyOptions 配置了一些转换选项 ,CopyOptions.create() 返回一个默认的 CopyOptions 对象,可以通过链式调用设置选项。.setIgnoreNullValue(true),忽略值为 null 的属性。.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString())自定义字段值的编辑器,通过 Lambda 表达式处理每个属性值,将每个属性值转换为字符串

用putAll可以一次性把数据全部传进去

疑问:

authorization基于什么生成的?

......

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

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

相关文章

Docker下载,包含Win、Mac

介绍 Docker 是一种开源的容器化平台&#xff0c;通过操作系统级虚拟化技术实现应用的快速开发、部署和运行。以下从多个维度对 Docker 进行详细介绍&#xff1a; 一、Docker 的核心概念与功能 容器化技术 Docker 利用 Linux 内核的容器隔离技术&#xff08;如 Cgroups 和 Nam…

使用 ESP8266 和 Android 应用程序实现基于 IOT 的语音控制家庭自动化

使用 ESP8266 实现基于 IOT 的语音控制家庭自动化 欢迎来到另一个令人兴奋的项目,我们将使用 Wi-Fi 模块构建一个语音控制ESP8266家庭自动化系统,您可以在其中通过语音通过 Android 应用程序从世界任何地方控制您的家用电器。是的,您只需使用语音命令即可打开或关闭负载(L…

【HarmonyOS Next】鸿蒙中自定义弹框OpenCustomDialog、CustomDialog与DialogHub的区别详解

【HarmonyOS Next】鸿蒙中自定义弹框OpenCustomDialog、CustomDialog与DialogHub的区别详解 一、三者的区别与关系 1. 官方迭代过程为&#xff1a; CustomDialog 》 OpenCustomDialog 》 DialogHub 迭代过程表明&#xff0c;弹框的调用越来越便捷&#xff0c;与UI解耦&…

【C++】stack和queue的使用及模拟实现(含deque的简单介绍)

文章目录 前言一、deque的简单介绍1.引入deque的初衷2.deque的结构3.为什么选择deque作为stack和queue的底层默认容器 二、stack1.stack的介绍2.stack的使用3.stack的模拟实现 三、queue1.queue的介绍2.queue的使用3.queue的模拟实现 前言 一、deque的简单介绍&#xff08;引入…

Leetcode 刷题笔记1 图论part01

图论的基础知识&#xff1a; 图的种类&#xff1a; 有向图&#xff08;边有方向&#xff09; 、 无向图&#xff08;边无方向&#xff09;、加权有向图&#xff08;边有方向和权值&#xff09; 度&#xff1a; 无向图中几条边连接该节点&#xff0c;该节点就有几度&#xff1…

《基于Workspace.java的Launcher3改造:HotSeat区域动态阻断文件夹生成机制》

1. 需求背景与技术挑战 在Android 13系统Launcher3定制化开发中&#xff0c;需实现禁止HotSeat区域创建文件夹的功能。原始逻辑中&#xff0c;当用户拖拽应用图标至HotSeat区域相邻图标时&#xff0c;会触发FolderIcon的实例化。本文将深入分析Launcher3的文件夹创建机制&…

重生之我在学Vue--第14天 Vue 3 国际化(i18n)实战指南

重生之我在学Vue–第14天 Vue 3 国际化(i18n)实战指南 文章目录 重生之我在学Vue--第14天 Vue 3 国际化(i18n)实战指南前言一、Vue I18n 核心配置1.1 基础环境搭建1.2 初始化配置1.3 全局挂载 二、多语言实现方案2.1 基础使用2.2 动态切换语言2.3 高级功能实现复数处理日期/货币…

开源PACS(dcm4che-arc-light)部署教程,源码方式

目录 文件清单下载地址安装概述OpenLDAP、Apache Directory StudioWildflydcm4che 安装部署MySQL源码编译dcm4cheedcm4chee-arc-light OpenLDAP安装ApacheDirectoryStudio安装配置WildFly服务器 部署完成 文件清单 下载地址 Apache directory studio - linkOpenLDAP - linkdcm…

java使用(Preference、Properties、XML、JSON)实现处理(读写)配置信息或者用户首选项的方式的代码示例和表格对比

在Java应用程序中&#xff0c;处理应用首选项&#xff08;preferences&#xff09;有多种方法&#xff0c;包括使用java.util.prefs.Preferences类、属性文件&#xff08;如.properties文件&#xff09;、XML文件和JSON文件。下面是每种方法的详细说明和代码示例&#xff0c;最…

工作记录 2017-02-04

工作记录 2017-02-04 序号 工作 相关人员 1 修改邮件上的问题。 更新RD服务器。 郝 更新的问题 1、DataExport的设置中去掉了ListPayors&#xff0c;见DataExport\bin\dataexport.xml 2、“IPA/Group Name” 改为 “Insurance Name”。 3、修改了Payment Posted的E…

dataframe数据形式操作中的diff和shift函数区别与对比

问题分析 从错误日志中可以看到&#xff0c;代码在 report_services.py 的 gnss_monthly_report 函数中出现了 ValueError&#xff0c;具体错误信息是&#xff1a; ValueError: either both or neither of x and y should be given这个错误发生在以下代码行&#xff1a; rep…

C语言动态内存管理(下)

欢迎拜访&#xff1a;雾里看山-CSDN博客 本篇主题&#xff1a;C语言动态内存管理(下) 发布时间&#xff1a;2025.3.18 隶属专栏&#xff1a;C语言 目录 动态内存常见错误内存泄漏&#xff08;Memory Leak&#xff09;典型场景后果解决方案 悬挂指针&#xff08;Dangling Pointe…

Vue:Vue2和Vue3创建项目的几种常用方式以及区别

前言 Vue.js 和 Element UI 都是用 JavaScript 编写的。 1、Vue.js 是一个渐进式 JavaScript 框架。2、Element UI 是基于 Vue.js 的组件库。3、JavaScript 是这两个项目的主要编程语言。 而Element Plus是基于TypeScript开发的。 一、Vue2 1、基于vuecli工具创建 vue2 …

机器人曲面跟踪Surface-Tracking

定义 机器人曲面跟踪&#xff08;Surface-Tracking&#xff09;是指机器人通过实时感知工件曲面的三维形貌&#xff0c;动态调整运动轨迹和位姿&#xff0c;以精确跟随曲面进行加工&#xff08;如打磨、抛光、喷涂等&#xff09;的技术。 力 - 位姿协同控制 力控模式&#xff…

网络安全运维应急响应与溯源分析实战案例

在日常运维过程中&#xff0c;网络安全事件时有发生&#xff0c;快速响应和精准溯源是保障业务稳定运行的关键。本文将通过一个实际案例&#xff0c;详细解析从发现问题到溯源定位&#xff0c;再到最终解决的完整流程。 目录 一、事件背景 二、事件发现 1. 监控告警触发 2…

【AVRCP】蓝牙协议栈深度解析:AVCTP互操作性核心机制与实现细节

目录 一、事务标签&#xff08;Transaction Label&#xff09;机制 1.1 事务标签核心规则 1.2 事务标签作用域与并发性 1.3 实现建议与陷阱规避 1.4 协议设计思考 1.5 调试与验证 二、消息分片&#xff08;Fragmentation&#xff09;机制 2.1 分片触发条件 2.2 分片支…

harmonyOS NEXT开发与前端开发深度对比分析

文章目录 1. 技术体系概览1.1 技术栈对比1.2 生态对比 2. 开发范式比较2.1 鸿蒙开发范式2.2 前端开发范式 3. 框架特性对比3.1 鸿蒙 Next 框架特性3.2 前端框架特性 4. 性能优化对比4.1 鸿蒙性能优化4.2 前端性能优化 5. 开发工具对比5.1 鸿蒙开发工具5.2 前端开发工具 6. 学习…

OpenWebUI:一站式 AI 应用构建平台体验

&#x1f680; 大家好&#xff0c;今天给大家分享一个超棒的 AI 应用构建工具——OpenWebUI&#xff01;体验下来&#xff0c;只能说丝滑&#xff01;必须强烈推荐&#xff01; &#x1f525; 听说过阿里巴巴的 Qwen 吗&#xff1f;他们最新的 Qwen Chat 网站就是用 OpenWebUI…

多线程—进程与线程

1 进程 1.1 进程概念 进程&#xff1a;操作系统提供的一种抽象&#xff0c;当程序在运行时&#xff0c;好像计算机的所有硬件资源都在为其服务。换言之&#xff0c;进程就是程序的一次运行过程。进程是操作系统分配资源的基本单位。 注意&#xff1a;区分进程和程序&#xff0…

[原创](Modern C++)现代C++的关键性概念: 灵活多变的绑定: std::bind

[作者] 常用网名: 猪头三 出生日期: 1981.XX.XX 企鹅交流: 643439947 个人网站: 80x86汇编小站 编程生涯: 2001年~至今[共24年] 职业生涯: 22年 开发语言: C/C、80x86ASM、Object Pascal、Objective-C、C#、R、Python、PHP、Perl、 开发工具: Visual Studio、Delphi、XCode、C …