黑马点评-项目集成git及redis实现短信验证码登录
目录
IDEA集成git
传统session存在的问题
redis方案
业务流程
选用的数据结构
整体访问流程
发送短信验证码
获取校验验证码
配置登录拦截器
拦截器注册配置类
拦截器
用户状态刷新问题
刷新问题解决方案
IDEA集成git
远程仓库采用码云,创建好仓库,复制仓库的url

在idea中点击,出现git选项,点击ok


之后右击项目,点击remotes
填写url即可集成git
传统session存在的问题
传统的登录认证会采用session进行登录认证,将登录的验证码,用户信息都存放到session中,我们通过session来进行操作数据,这有什么问题呢
每个tomcat服务器中都有一份属于自己的session,假设用户第一次访问第一台tomcat,并且把自己的信息存放到第一台服务器的session中,但是第二次这个用户访问到了第二台tomcat,那么在第二台服务器上,肯定没有第一台服务器存放的session,即session在各个服务器之间是不共通的,所以此时整个登录拦截功能就会出现问题,而redis数据本身就是共享的,就可以避免session共享的问题了
redis方案
业务流程
- 将验证码存储到redis中
- 将用户数据存储到redis中
选用的数据结构
存储验证码时采用String类型,key采用业务代码+手机号
存储user数据时采用hash,key采用业务代码+随机的tonke
hash可以将对象中的每个字段独立存储,可以针对单个字段做CRUD,并且内存占用更少,实际上也可以采用String类型,但hash类型消耗内存较少,故选用String类型
整体访问流程
当注册完成后,用户去登录会去校验用户提交的手机号和验证码,是否一致,如果一致,则根据手机号查询用户信息,不存在则新建,最后将用户数据保存到redis,并且生成token作为redis的key,当我们校验用户是否登录时,会去携带着token进行访问,从redis中取出token对应的value,判断是否存在这个数据,如果没有则拦截,如果存在则将其保存到threadLocal中,简化后续业务获取用户信息的代码,后续业务需要登录用户的信息,只要在threadLocal中取即可,无需从redis中取从而增加复杂度,最后放行。

redis业务代码常量(后续继续补充)
public class RedisConstants {//发送验证码业务标识public static final String LOGIN_CODE_KEY = "login:code:";public static final Long LOGIN_CODE_TTL = 2L;//用户登录业务标识public static final String LOGIN_USER_KEY = "login:token:";public static final Long LOGIN_USER_TTL = 30L;}
发送短信验证码
采用日志打印形式,将验证码打印在控制台
public Result sendCode(String phone) {//校验手机号if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号码格式不正确");}//手机号格式正确生成验证码String code = RandomUtil.randomNumbers(6);//保存到redisstringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);//打印日志log.debug("手机验证码为:" + code);return Result.ok();}
获取校验验证码
我们需要对用户敏感信息进行筛选,只给前端返回必要的用户信息数据,需要封装dto对象,同时在uuid生成token,作为redis取数据的key,最后要设置过期时间,防止reids内存爆炸,设置为30分钟,因为session的过期时间也是30分钟
public Result login(LoginFormDTO loginForm) {String phone = loginForm.getPhone();String code = loginForm.getCode();//校验手机号if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号码格式不正确");}//从redis取验证码,进行比对String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);if (cacheCode == null || !cacheCode.equals(code)) {return Result.fail("验证码错误");}//根据电话号码从数据库查询用户是否存在LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getPhone, phone);User user = userMapper.selectOne(queryWrapper);//不存在则创建if (user == null) {user = createUserWithPhone(phone);}String token = UUID.randomUUID().toString(true);//只返回不敏感的信息,使用dto进行封装UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(user, userDTO);//将dto对象转化为map结构Map<String, String> userMap = new HashMap<>();userMap.put("id", userDTO.getId().toString());userMap.put("icon", userDTO.getIcon());userMap.put("nickName", userDTO.getNickName());
//业务代码+token形成redis中的keyString tokenKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);return Result.ok(token);}
配置登录拦截器
拦截器注册配置类
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}
拦截器
拦截器需要的stringRedisTemplate对象不能通过@Autowired直接注入
因为LoginInterceptor 对象是我们手动创建的,不受spring管控,不在spring容器中,故不能注入spring容器中的bean,只能通过在配置类中通过构造方法注入stringRedisTemplate对象
public class LoginInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {response.setStatus(401);return false;}String tokenKey = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);//3.判断用户是否存在if (userMap.isEmpty()) {//4.不存在,拦截,返回401状态码response.setStatus(401);return false;}UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//5.存在,保存用户信息到ThreadlocalUserHolder.saveUser(userDTO);//刷新登录状态stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);//6.放行return true;}
}
用户状态刷新问题
那就是登录认证和刷新用户状态绑定在一个拦截器中,而需要登录认证的路径并不是全部路径,如果用户不访问需要登录认证的路径,那就刷新不了用户状态,即30分钟之后登录就会退出,这明显不符合我们的常识
刷新问题解决方案
我们可以再加一个拦截器,形成一个拦截器链,第一个拦截器对所有路径进行拦截,而它的功能就是刷新登录状态,登录认证则由第二个拦截器完成

刷新状态拦截器
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 {// 1.获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {return true;}// 2.基于TOKEN获取redis中的用户String key = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);// 3.判断用户是否存在if (userMap.isEmpty()) {return true;}// 5.将查询到的hash数据转为UserDTOUserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6.存在,保存用户信息到 ThreadLocalUserHolder.saveUser(userDTO);// 7.刷新token有效期stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserHolder.removeUser();}
登录认证拦截器
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.判断是否需要拦截(ThreadLocal中是否有用户)if (UserHolder.getUser() == null) {// 没有,需要拦截,设置状态码response.setStatus(401);// 拦截return false;}// 有用户,则放行return true;}
}
为了保证拦截器的执行先后顺序,配置类需设置order的大小,设定拦截器执行的先后的顺序,如若不设置,就按照注册顺序的先后来执行
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login").order(1);// token刷新的拦截器registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);}
}
相关文章:
黑马点评-项目集成git及redis实现短信验证码登录
目录 IDEA集成git 传统session存在的问题 redis方案 业务流程 选用的数据结构 整体访问流程 发送短信验证码 获取校验验证码 配置登录拦截器 拦截器注册配置类 拦截器 用户状态刷新问题 刷新问题解决方案 IDEA集成git 远程仓库采用码云,创建好仓库&…...
mac苹果电脑怎么运行Windows软件?怎么安装Win虚拟机?
近年来,苹果电脑的用户群体不断扩大,许多用户对于苹果电脑是否可以运行Windows软件产生了疑问。苹果电脑和Windows操作系统有着明显的区别,是否能够在苹果电脑上运行Windows软件。下面我们就来看苹果电脑可以运行Windows软件吗,苹…...
Jmeter对websocket进行测试
JMeterWebSocketSampler-1.0.2-SNAPSHOT.jar下载 公司使用websocket比较奇怪,需要带认证信息进行长连接,通过websocket插件是请求失败,如下图,后面通过代码实现随再打包jar包完成websocket测试 本地实现代码如下: pa…...
从2023年世界机器人大会发现机器人新趋势
机器人零部件为何成2023年世界机器人大会关注热门? 在原先,机器人的三大核心零部件是控制系统中的控制器、驱动系统中的伺服电机和机械系统中的精密减速器。如今,机器人的主体框架结构已经落实,更多机器人已经开始深入到各类场景中…...
Kafka单节点部署
🎈 作者:互联网-小啊宇 🎈 简介: CSDN 运维领域创作者、阿里云专家博主。目前从事 Kubernetes运维相关工作,擅长Linux系统运维、开源监控软件维护、Kubernetes容器技术、CI/CD持续集成、自动化运维、开源软件部署维护…...
生成式AI和大语言模型 Generative AI LLMs
在“使用大型语言模型(LLMs)的生成性AI”中,您将学习生成性AI的基本工作原理,以及如何在实际应用中部署它。 通过参加这门课程,您将学会: 深入了解生成性AI,描述基于LLM的典型生成性AI生命周期中的关键步骤ÿ…...
Obsidian 入门使用手册
文章目录 一、Obsidian 入门1.1 什么是 Obsidian1.2 安装 Obsidian 二、Obsidian 配置2.1 创建第一个笔记2.2 设置界面语言使用中文2.3 主题 三、小结 一、Obsidian 入门 1.1 什么是 Obsidian Obsidian 是一款基于 Markdown 语法编辑的笔记软件。与传统的 Markdown 软件不同的…...
GuLi商城-前端基础Vue指令-单向绑定双向绑定
什么是指令? 指令 (Directives) 是带有 v- 前缀的特殊特性。 指令特性的预期值是:单个 JavaScript 表达式。 指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM 例如我们在入门案例中的 v-on,代表绑定事…...
前端(十三)——JavaScript 闭包的奥秘与高级用法探索
😶博主:小猫娃来啦 😶文章核心:深入理解 JavaScript 中的闭包 文章目录 不理解闭包?这玩意很难?闭包的定义与原理闭包是什么创建一个闭包 闭包的应用场景闭包与作用域闭包与作用域之间的关系全局作用域、函…...
面试-快速学习计算机网络-UDP/TCP
1. OSI四层和七层映射 区别: 应用层,表示层,会话层合并为了应用层数据链路层和物理层合并为了网络接口层 2. TCP和UDP的区别? 总结: 1 . TCP 向上层提供面向连接的可靠服务 ,UDP 向上层提供无连接不可靠服…...
爱校对如何帮助企业和博客主提高在线可见性?
在数字化时代,内容质量已经成为增强在线曝光率的关键因素。企业和博客主经常面临挑战,如何制作高质量、无误的内容以吸引更多的在线用户。此文将详细分析“爱校对”如何帮助用户优化内容,从而提高在线可见性。 1.互联网内容的挑战 搜索引擎…...
MATLAB中xlsread函数用法
目录 语法 说明 示例 将工作表读取到数值矩阵 读取元胞的范围 读取列 请求数值、文本和原始数据 对工作表执行函数 请求自定义输出 局限性 xlsread函数的功能是读取Microsoft Excel 电子表格文件 语法 num xlsread(filename) num xlsread(filename,sheet) num x…...
Prisma.js:JavaScript中的基于代码的ORM
Prisma是一种流行的用于服务器端JavaScript和TypeScript的数据映射层(ORM)。它的核心目的是简化和自动化数据在存储和应用程序代码之间的传输方式。Prisma支持各种数据存储,并为数据持久化提供了一个强大而灵活的抽象层。通过这个基于代码的…...
解决问题:在cocos create中如何从b文件调用到a文件里用CC.resource.load动态加载的图集
目录 1.在a文件中定义一个公共的变量存储动态加载的图集 2.在a.js中添加一个静态方法,返回动态加载的图集 3.在b.js中使用a.js中定义的静态方法获取图集,并使用它 假设a文件中用CC.resource.load动态加载了一张图集,b文件需要使用这张图集&am…...
分布式 - 消息队列Kafka:Kafka 消费者消费位移的提交方式
文章目录 1. 自动提交消费位移2. 自动提交消费位移存在的问题?3. 手动提交消费位移1. 同步提交消费位移2. 异步提交消费位移3. 同步和异步组合提交消费位移4. 提交特定的消费位移5. 按分区提交消费位移 4. 消费者查找不到消费位移时怎么办?5. 如何从特定…...
如何利用 ChatGPT 进行自动数据清理和预处理
推荐:使用 NSDT场景编辑器助你快速搭建可二次编辑的3D应用场景 ChatGPT 已经成为一把可用于多种应用的瑞士军刀,并且有大量的空间将 ChatGPT 集成到数据科学工作流程中。 如果您曾经在真实数据集上训练过机器学习模型,您就会知道数据清理和预…...
PHP“牵手”淘宝商品评论数据采集方法,淘宝API接口申请指南
淘宝天猫商品评论数据接口 API 是开放平台提供的一种 API 接口,它可以帮助开发者获取商品的详细信息,包括商品的标题、描述、图片等信息。在电商平台的开发中,详情接口API是非常常用的 API,因此本文将详细介绍详情接口 API 的使用…...
你更喜欢哪一个:VueJS 还是 ReactJS?
观点列表: 1、如果你想在 HTML 中使用 JS,请使用 Vue; 如果你想在 JS 中使用 HTML,请使用 React。 当然,如果您希望在 JS 中使用 HTML,请将 Vue 与 JSX 结合使用。 2、Svelte:我喜欢它&#…...
PyTorch学习笔记(十六)——利用GPU训练
一、方式一 网络模型、损失函数、数据(包括输入、标注) 找到以上三种变量,调用它们的.cuda(),再返回即可 if torch.cuda.is_available():mynn mynn.cuda() if torch.cuda.is_available():loss_function loss_function.cuda(…...
【实战】十一、看板页面及任务组页面开发(三) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二十五)
文章目录 一、项目起航:项目初始化与配置二、React 与 Hook 应用:实现项目列表三、TS 应用:JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook&…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...
DiscuzX3.5发帖json api
参考文章:PHP实现独立Discuz站外发帖(直连操作数据库)_discuz 发帖api-CSDN博客 简单改造了一下,适配我自己的需求 有一个站点存在多个采集站,我想通过主站拿标题,采集站拿内容 使用到的sql如下 CREATE TABLE pre_forum_post_…...
【WebSocket】SpringBoot项目中使用WebSocket
1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖,添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...
【实施指南】Android客户端HTTPS双向认证实施指南
🔐 一、所需准备材料 证书文件(6类核心文件) 类型 格式 作用 Android端要求 CA根证书 .crt/.pem 验证服务器/客户端证书合法性 需预置到Android信任库 服务器证书 .crt 服务器身份证明 客户端需持有以验证服务器 客户端证书 .crt 客户端身份…...
Java多线程实现之Runnable接口深度解析
Java多线程实现之Runnable接口深度解析 一、Runnable接口概述1.1 接口定义1.2 与Thread类的关系1.3 使用Runnable接口的优势 二、Runnable接口的基本实现方式2.1 传统方式实现Runnable接口2.2 使用匿名内部类实现Runnable接口2.3 使用Lambda表达式实现Runnable接口 三、Runnabl…...
Copilot for Xcode (iOS的 AI辅助编程)
Copilot for Xcode 简介Copilot下载与安装 体验环境要求下载最新的安装包安装登录系统权限设置 AI辅助编程生成注释代码补全简单需求代码生成辅助编程行间代码生成注释联想 代码生成 总结 简介 尝试使用了Copilot,它能根据上下文补全代码,快速生成常用…...
