黑马点评-项目集成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);}
}

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

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

相关文章

【O2O领域】Axure外卖订餐骑手端APP原型图,外卖配送原型设计图

作品概况 页面数量&#xff1a;共 110 页 兼容软件&#xff1a;Axure RP 9/10&#xff0c;不支持低版本 应用领域&#xff1a;外卖配送、生鲜配送 作品申明&#xff1a;页面内容仅用于功能演示&#xff0c;无实际功能 作品特色 本品为外卖订餐骑手端APP原型设计图&#x…

CentOS下MySQL的彻底卸载的几种方法

这里我为大家详细讲解下“CentOS下MySQL的彻底卸载的几种方法”的完整攻略。 一、关闭MySQL服务 在开始操作之前&#xff0c;需要先关闭MySQL服务。可以使用以下命令来关闭MySQL服务&#xff1a; systemctl stop mysqld 或者 service mysqld stop 二、使用yum命令卸载MySQL…

微前端 - qiankun

qiankun 是一个基于 single-spa 的微前端实现库&#xff0c;旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。 本文主要记录下如何接入 qiankun 微前端。主应用使用 vue2&#xff0c;子应用使用 vue3、react。 一、主应用 主应用不限技术栈&#xff0c;只需要提…

[Go版]算法通关村第十二关黄金——字符串冲刺题

目录 题目&#xff1a;最长公共前缀解法1&#xff1a;纵向对比-循环内套循环写法复杂度&#xff1a;时间复杂度 O ( n ∗ m ) O(n*m) O(n∗m)、空间复杂度 O ( 1 ) O(1) O(1)Go代码 解法2&#xff1a;横向对比-两两对比&#xff08;类似合并K个数组、合并K个链表&#xff09;复…

SD WebUI 扩展:prompt-all-in-one

sd-webui-prompt-all-in-one 是一个基于 Stable Diffusion WebUI 的扩展&#xff0c;旨在提高提示词/反向提示词输入框的使用体验。它拥有更直观、强大的输入界面功能&#xff0c;它提供了自动翻译、历史记录和收藏等功能&#xff0c;它支持多种语言&#xff0c;满足不同用户的…

[MAUI]在.NET MAUI中实现可拖拽排序列表

文章目录 创建可拖放控件创建绑定服务类拖拽&#xff08;Drag&#xff09;拖拽悬停&#xff0c;经过&#xff08;DragOver&#xff09;释放&#xff08;Drop&#xff09; 创建页面元素最终效果项目地址 .NET MAUI 中提供了拖放(drag-drop)手势识别器&#xff0c;允许用户通过拖…

Mysql驱动包下载

第一步&#xff1a;下载地址 MySQL :: Download Connector/J 第二步&#xff1a; 第三步&#xff1a; 第四步&#xff1a;解压 第五步&#xff1a;找到驱动包&#xff0c;放入项目使用即可

管理类联考——逻辑——真题篇——按知识分类——汇总篇——三、综合推理

文章目录 题-综合推理-分类1-排序真题&#xff08;2016-54-55&#xff09;-难度最高*****-综合推理-分类1-排序-画表排除法真题&#xff08;2016-54&#xff09;真题&#xff08;2016-55&#xff09;真题&#xff08;2019-36&#xff09;-综合推理-分类1-排序真题&#xff08;2…

【AIGC】 国内版聊天GPT

国内版聊天GPT 引言一、国内平台二、简单体验2.1 提问2.2 角色扮演2.3 总结画图 引言 ChatGPT是OpenAI发开的聊天程序&#xff0c;功能强大&#xff0c;可快速获取信息&#xff0c;节省用户时间和精力&#xff0c;提供个性化的服务。目前国产ChatGPT&#xff0c;比如文心一言&a…

OJ练习第151题——克隆图

克隆图 力扣链接&#xff1a;133. 克隆图 题目描述 给你无向 连通 图中一个节点的引用&#xff0c;请你返回该图的 深拷贝&#xff08;克隆&#xff09;。 示例 分析 对于一张图而言&#xff0c;它的深拷贝即构建一张与原图结构&#xff0c;值均一样的图&#xff0c;但是…

Electron基础篇

人生有些事,错过一时,就错过一世。 官网&#xff1a;简介 | Electron Electron-大多用来写桌面端软件 Electron介绍 Electront的核心组成是Chromium、Node.js以及内置的Native API&#xff0c;其中Chromium为Electron提供强大的UI能力&#xff0c;可以在不考虑兼容的情况下利…

使用神卓互联内网穿透搭建远程访问公司ERP系统

神卓互联是一款企业级内网穿透软件&#xff0c;可以将内网中的服务映射到公网上&#xff0c;实现内网服务的访问。通过神卓互联&#xff0c;您可以远程访问ERP系统。在使用神卓互联进行内网穿透时&#xff0c;您只需要在生成的公网地址后面加上ERP系统的端口号&#xff0c;即可…

NVIDIA vGPU License许可服务器高可用全套部署秘籍

第1章 前言 近期遇到比较多的场景使用vGPU&#xff0c;比如Citrix 3D场景、Horizon 3D场景&#xff0c;还有AI等&#xff0c;都需要使用显卡设计研发等&#xff0c;此时许可服务器尤为重要&#xff0c;许可断掉会出现掉帧等情况&#xff0c;我们此次教大家部署HA许可服务器。 …

【.net】本地调试运行只能用localhost的问题

【.net】本地调试运行只能用localhost的问题 解决方案 找到到项目目录下 隐藏文件夹 .vs /项目名称/config/applicationhost.config <bindings><binding protocol"http" bindingInformation"*:1738:localhost" /></bindings> 再加一条你…

职业学院物联网实训室建设方案

一、概述 1.1专业背景 物联网&#xff08;Internet of Things&#xff09;被称为继计算机、互联网之后世界信息产业第三次浪潮&#xff0c;它并非一个全新的技术领域&#xff0c;而是现代信息技术发展到一定阶段后出现的一种聚合性应用与技术提升&#xff0c;是随着传感网、通…

麦肯锡发布《2023科技趋势展望报告》,生成式AI、下一代软件开发成为趋势,软件测试如何贴合趋势?

近日&#xff0c;麦肯锡公司发布了《2023科技趋势展望报告》。报告列出了15个趋势&#xff0c;并把他们分为5大类&#xff0c;人工智能革命、构建数字未来、计算和连接的前沿、尖端工程技术和可持续发展。 类别一&#xff1a;人工智能革命 生成式AI 生成型人工智能标志着人工智…

CSRF

文章目录 CSRF(get)CSRF(post)CSRF Token CSRF(get) 根据提示的用户信息登录 点击修改个人信息 开启bp代理&#xff0c;点击submit 拦截到请求数据包 浏览器关闭代理 刷新页面 CSRF(post) 使用BP生成CSRF POC post请求伪造&#xff0c;可以通过钓鱼网站&#xff0c;诱导用户去…

css 实现文字横向循环滚动

实现效果 思路 ## 直接上代码,html部分 //我这里是用的uniapp <view class"weather_info_wrap"><view class"weather_info">当前多云&#xff0c;今晚8点转晴&#xff0c;明天有雨&#xff0c;温度32摄氏度。</view><view class&qu…

解决IDEA tomcat控制台只有server日志

解决IDEA tomcat控制台只有server日志 确认tomcatxxx/conf/logging.properties文件是否存在&#xff0c;存在就会有。前提是在run configuration配置了打印多个日志

uniapp封装组件,选中后右上角显示对号√样式(通过css实现)

效果&#xff1a; 一、组件封装 1、在项目根目录下创建components文件夹&#xff0c;自定义组件名称&#xff0c;我定义的是xc-button 2、封装组件代码 <template><view class"handle-btn"><view :class"handleIdCode 1 ? select : unSelec…