黑马Redis详细笔记(实战篇---短信登录)

目录

一.短信登录

1.1 导入项目

1.2 Session 实现短信登录

1.3 集群的 Session 共享问题

1.4 基于 Redis 实现共享 Session 登录


一.短信登录

1.1 导入项目

数据库准备

-- 创建用户表

CREATE TABLE `user` (`id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',`phone` VARCHAR(20) NOT NULL UNIQUE COMMENT '手机号',`nick_name` VARCHAR(50) COMMENT '昵称',`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) COMMENT='用户表';

导入项目

cd nginx目录
start nginx.exe

1.2 Session 实现短信登录

发送验证码

@Override
public Result sendCode(String phone, HttpSession session) {// 校验手机号格式是否正确if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式不正确!");}// 生成6位随机数字验证码String code = RandomUtil.randomNumbers(6);// 将验证码保存到session中session.setAttribute("code", code);// 日志记录验证码(实际开发中应发送短信)log.info("验证码为: " + code);return Result.ok();
}

登录功能

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {// 获取手机号String phone = loginForm.getPhone();// 校验手机号格式if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式错误!");}// 从session中获取验证码Object cacheCode = session.getAttribute("code");String code = loginForm.getCode();// 校验验证码是否正确if (code == null || !cacheCode.toString().equals(code)) {return Result.fail("验证码错误!");}// 根据手机号查询用户User user = query().eq("phone", phone).one();if (user == null) {// 如果用户不存在,则自动注册user = new User();user.setPhone(phone);user.setNickName("user_" + RandomUtil.randomString(10));save(user);}// 将用户信息存入sessionsession.setAttribute("user", user);return Result.ok();
}

拦截器 LoginInterceptor

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 获取sessionHttpSession session = request.getSession();// 从session中获取用户信息Object user = session.getAttribute("user");// 判断用户是否存在if (user == null) {// 用户未登录,返回401状态码response.setStatus(401);response.getWriter().write("用户未登录!");return false;}// 用户已登录,放行return true;}
}

在MvcConfig加上拦截器

@Configuration
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/code",    // 验证码接口"/user/login",   // 登录接口"/blog/hot",     // 热门博客"/shop/**",      // 商户信息"/shop-type/**", // 商户类型"/upload/**",    // 文件上传"/voucher/**"    // 优惠券);}
}

1.3 集群的 Session 共享问题

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

所以我们把数据存入Redis,集群的Redis可以替代session

1.4 基于 Redis 实现共享 Session 登录

我们应该选择String类型存验证码即可,value:验证码,但是key要区分开来

​ 选择Hash存储用户信息,因为每个字段独立,比较好去DRUD,内存占用少,key用token即可(随机字符串)

之前的session的话,tomcat会自动把session的Id存入Cookie,每次请求都会携带Cookie,所以我们需要手动把token返回给客户端,每次请求客户端都会携带着token

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result sendCode(String phone, HttpSession session) {// 校验手机号格式是否正确if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式不正确!");}// 生成6位随机数字验证码String code = RandomUtil.randomNumbers(6);// 将验证码保存到Redis中,设置过期时间为2分钟stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code, RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);// 日志记录验证码(实际开发中应发送短信)log.info("验证码为: " + 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 cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone);String code = loginForm.getCode();// 校验验证码是否正确if (cacheCode == null || !cacheCode.equals(code)) {return Result.fail("验证码错误!");}// 根据手机号查询用户User user = query().eq("phone", phone).one();if (user == null) {// 如果用户不存在,则自动注册user = new User();user.setPhone(phone);user.setNickName("user_" + RandomUtil.randomString(10));save(user);}// 生成唯一的TokenString token = UUID.randomUUID().toString(true);// 将用户信息转换为UserDTOUserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);// 将用户信息以Hash类型存储到Redis中,设置过期时间为36000分钟stringRedisTemplate.opsForHash().putAll(RedisConstants.LOGIN_USER_KEY + token, BeanUtil.beanToMap(userDTO));stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);// 返回Tokenreturn Result.ok(token);
}

MvcConfig注入stringRedisTemplate,然后传给LoginInterceptor,因为LoginInterceptor不是bean不能用spring注入其他bean

@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns("/user/code","/user/login","/blog/hot","/shop/**","/shop-type/**","/upload/**","/voucher/**");}
}

LoginInterceptor

public class LoginInterceptor implements HandlerInterceptor{private final 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)){//不存在,拦截 设置响应状态吗为401(未授权)response.setStatus(401);return false;}//2.基于token获取redis中用户String key=RedisConstants.LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);//3.判断用户是否存在if (userMap.isEmpty()){//4.不存在则拦截,设置响应状态吗为401(未授权)response.setStatus(401);return false;}//5.将查询到的Hash数据转化为UserDTO对象UserDTO userDTO=new UserDTO();BeanUtil.fillBeanWithMap(userMap,userDTO, false);//6.保存用户信息到ThreadLocalUserHolder.saveUser(userDTO);//7.更新token的有效时间,只要用户还在访问我们就需要更新token的存活时间stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);//8.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//销毁,以免内存泄漏UserHolder.removeUser();}
}

用户请求进去拦截器,我们试着去获取请求头内的token,根据token去查询用户信息,判断是否拦截,保存在ThreadLocal,刷新token的有效期

但是,这个拦截器是拦截需要登录之后才需要进行请求的路径,那我如果一直在访问的是不需要拦截的页面的话,我还是会过期?这就不合理。所以我们需要在这个拦截器前面再加个拦截器,然后在新增拦截器上进行保存ThreadLocal和刷新有效期,不理解其他

其实就是对之前的拦截器进行功能拆分

MvcConfig

@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/code","/user/login","/blog/hot","/shop/**","/shop-type/**","/upload/**","/voucher/**").order(1);//RefreshTokenInterceptor 先于 LoginInterceptor 执行registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).order(0);//默认拦截所有请求}}

RefreshTokenInterceptor

public class RefreshTokenInterceptor implements HandlerInterceptor {private final 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)) {// 如果Token为空,直接放行return true;}// 构造Redis中的KeyString key = RedisConstants.LOGIN_USER_KEY + token;// 从Redis中获取用户信息Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);if (userMap.isEmpty()) {// 如果用户信息为空,直接放行return true;}// 将用户信息转换为UserDTO对象UserDTO userDTO = new UserDTO();BeanUtil.fillBeanWithMap(userMap, userDTO, false);// 将用户信息保存到ThreadLocal中UserHolder.saveUser(userDTO);// 刷新Token的有效期stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);return true;}
}

LoginInterceptor

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 从ThreadLocal中获取用户信息UserDTO user = UserHolder.getUser();if (user == null) {// 如果用户未登录,返回401状态码response.setStatus(401);response.getWriter().write​```

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

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

相关文章

大前端之前端开发接口测试工具postman的使用方法-简单get接口请求测试的使用方法-简单教学一看就会-以实际例子来说明-优雅草卓伊凡

大前端之前端开发接口测试工具postman的使用方法-简单get接口请求测试的使用方法-简单教学一看就会-以实际例子来说明-优雅草卓伊凡 背景 前端开发接口请求&#xff0c;调试&#xff0c;联调&#xff0c;接入数据&#xff0c;前端必不可少工具&#xff0c;postman是一个非常好…

开源身份和访问管理方案之keycloak(一)快速入门

文章目录 什么是IAM什么是keycloakKeycloak 的功能 核心概念client管理 OpenID Connect 客户端 Client Scoperealm roleAssigning role mappings分配角色映射Using default roles使用默认角色Role scope mappings角色范围映射 UsersGroupssessionsEventsKeycloak Policy创建策略…

java项目之直销模式下家具工厂自建网站源码(ssm+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的直销模式下家具工厂自建网站源码。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 直销模式下家具…

C基础(十)动态内存分配和结构体

动态内存分配 堆区与栈区空间&#xff1a;申请堆区空间需手动操作&#xff0c;使用完要手动释放&#xff1b;栈区空间由系统自动分配和释放。相关函数与概念 malloc&#xff1a;从堆区申请指定字节数的空间&#xff0c;返回首地址&#xff0c;需搭配头文件#include <stdlib.…

图7.1-7.6《分析模式》第7章使用会计模型-原图和UML图对比

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 图7.1 TT示例的包。 账户包持有抽象的会计类型&#xff0c;电话服务包为这个特定领域扩展这些类型。 图7.2 TT的账户模型。 7.3 电话服务的结构模型。 图7.4 创建新电话服务的事件图。…

大模型基本原理(四)——如何武装ChatGPT

传统的LLM存在几个短板&#xff1a;编造事实、计算不准确、数据过时等&#xff0c;为了应对这几个问题&#xff0c;可以借助一些外部工具或数据把AI武装起来。 实现这一思路的框架包括RAG、PAL、ReAct。 1、RAG&#xff08;检索增强生成&#xff09; LLM生成的内容会受到训练…

Qt监控设备离线检测/实时监测设备上下线/显示不同的状态图标/海康大华宇视华为监控系统

一、前言说明 监控系统中一般有很多设备&#xff0c;有些用户希望知道每个设备是否已经上线&#xff0c;最好有不同的状态图标提示&#xff0c;海康的做法是对设备节点的图标和颜色变暗处理&#xff0c;离线的话就变暗&#xff0c;有可能是加了透明度&#xff0c;而大华的处理…

数据结构-栈和队列的应用

目录 前言一、栈的应用二、队列的应用&#xff08;农夫过河问题&#xff09;2.1 问题描述2.2 算法选择2.3 算法精化2.4 算法实现2.5 问题结果 总结 前言 本篇文章使用两个例子说明栈和队列的应用&#xff0c; 对于迷宫问题&#xff0c;使用栈实现深度优先策略解决迷宫问题&…

Acwing-基础算法课笔记之基础算法(差分)

Acwing-基础算法课笔记之基础算法&#xff08;差分&#xff09; 一、一维差分1、差分的概念2、差分思想 二、二维差分操作流程 一、一维差分 1、差分的概念 对于一个给定的序列a&#xff0c;它的差分序列b定义为&#xff1a; b [ 1 ] a [ 1 ] b[1]a[1] b[1]a[1]&#xff0c…

SkyWalking 10.1.0 实战:从零构建全链路监控,解锁微服务性能优化新境界

文章目录 前言一、集成SkyWalking二、SkyWalking使用三、SkyWalking性能剖析四、SkyWalking 告警推送4.1 配置告警规则4.2 配置告警通知地址4.3 下发告警信息4.4 测试告警4.5 慢SQL查询 总结 前言 在传统监控系统中&#xff0c;我们通过进程监控和日志分析来发现系统问题&…

【动态规划】风扫枯杨,满地堆黄叶 - 9. 完全背包问题

本篇博客给大家带来的是完全背包问题之动态规划解法技巧. &#x1f40e;文章专栏: 动态规划 &#x1f680;若有问题 评论区见 ❤ 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 . 王子,公主请阅&#x1f680; 要开心要快乐顺…

MyBatis的工作流程是怎样的?

大家好&#xff0c;我是锋哥。今天分享关于【MyBatis的工作流程是怎样的&#xff1f;】面试题。希望对大家有帮助&#xff1b; MyBatis的工作流程是怎样的&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 MyBatis 的工作流程可以分为几个主要的步骤&…

python-leetcode 25.环形链表

题目&#xff1a; 给定一个链表的头节点head,判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪next指针再次到达&#xff0c;则链表中存在环。为了表示给定链表中的环&#xff0c;评测系统内部使用整数pos来表示链表尾连接到链表中的位置&#xff08;…

瑞芯微开发板/主板Android调试串口配置为普通串口方法 深圳触觉智能科技分享

本文介绍瑞芯微开发板/主板Android调试串口配置为普通串口方法&#xff0c;不同板型找到对应文件修改&#xff0c;修改的方法相通。触觉智能RK3562开发板演示&#xff0c;搭载4核A53处理器&#xff0c;主频高达2.0GHz&#xff1b;内置独立1Tops算力NPU&#xff0c;可应用于物联…

Datawhale 组队学习 Ollama教程 task1

一、Ollama 简介 比喻&#xff1a;Ollama 就像是一个“魔法箱子”&#xff0c;里面装满了各种大型语言模型&#xff08;LLM&#xff09;。你不需要懂复杂的魔法咒语&#xff08;配置&#xff09;&#xff0c;只需要轻轻一按&#xff08;一条命令&#xff09;&#xff0c;就能让…

vulnhub 靶场 —— NullByte

免责声明 本博客文章仅供教育和研究目的使用。本文中提到的所有信息和技术均基于公开来源和合法获取的知识。本文不鼓励或支持任何非法活动&#xff0c;包括但不限于未经授权访问计算机系统、网络或数据。 作者对于读者使用本文中的信息所导致的任何直接或间接后果不承担任何…

使用 meshgrid函数绘制网格点坐标的原理与代码实现

使用 meshgrid 绘制网格点坐标的原理与代码实现 在 MATLAB 中&#xff0c;meshgrid 是一个常用函数&#xff0c;用于生成二维平面网格点的坐标矩阵。本文将详细介绍如何利用 meshgrid 函数生成的矩阵绘制网格点的坐标&#xff0c;并给出具体的代码实现和原理解析。 实现思路 …

【STM32系列】利用MATLAB配合ARM-DSP库设计FIR数字滤波器(保姆级教程)

ps.源码放在最后面 设计IIR数字滤波器可以看这里&#xff1a;利用MATLAB配合ARM-DSP库设计IIR数字滤波器&#xff08;保姆级教程&#xff09; 前言 本篇文章将介绍如何利用MATLAB与STM32的ARM-DSP库相结合&#xff0c;简明易懂地实现FIR低通滤波器的设计与应用。文章重点不在…

使用mermaid画流程图

本文介绍使用mermaid画流程图&#xff0c;并给出几个示例。 背景 目前&#xff0c;除有明确格式要求的文档外&#xff0c;笔者一般使用markdown写文档、笔记。当文档有图片时&#xff0c;使用Typora等软件可实时渲染&#xff0c;所见即所得。但如果文档接收方没有安装相关工具…

12.项目结构

后端结构 ruoyi-admin 项目启动的入口 提供了两种启动方式 1.RuoYiApplication基于springboot,内置tomcat,直接运行。 2.RuoYiServletInitializer将springboot项目打成一个war包,用外置的servlet容器来运行。 通用功能的controller 后台登录相关的、权限控制相关的、数据字…