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

news2025/3/12 19:13:48

目录

一.短信登录

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);
    }
    // 将用户信息存入session
    session.setAttribute("user", user);
    return Result.ok();
}

拦截器 LoginInterceptor

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取session
        HttpSession 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 {
    @Override
    public 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 {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public 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);
    }
    // 生成唯一的Token
    String token = UUID.randomUUID().toString(true);
    // 将用户信息转换为UserDTO
    UserDTO 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);
    // 返回Token
    return Result.ok(token);
}

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

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public 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;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.获取请求头中的token
        String 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.保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);
        //7.更新token的有效时间,只要用户还在访问我们就需要更新token的存活时间
        stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);
        //8.放行
        return true;
    }


    @Override
    public 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 {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public 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;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从请求头中获取Token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            // 如果Token为空,直接放行
            return true;
        }
        // 构造Redis中的Key
        String 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 {
    @Override
    public 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.coloradmin.cn/o/2299718.html

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

相关文章

基于N-gram模型的中文文本分析系统设计与实现

前言 在数字化人文研究快速发展的背景下&#xff0c;中文古典文本的量化分析面临着独特的挑战。古典文献中繁简异体字共存、语义单元边界模糊、意象隐喻密集等特征&#xff0c;使得传统的词频统计方法难以准确捕捉其深层语言规律。现有文本分析工具多面向现代汉语设计&#xff…

零基础购买阿里云服务器,XShell连接云服务器

目录 1.环境搭建方式 2. 使用云服务器 3.使用终端软件登录到Linux 4.使用XShell登录主机 5.连接失败的原因&#xff1a; 下一篇更新&#xff1a;Linux的基础指令以及如何Linux的环境搭建 1.环境搭建方式 主要有四种: 1.直接安装在物理机上&#xff0c;虽然Linux有图形化…

CNN手写数字识别1——模型搭建与数据准备

模型搭建 我们这次使用LeNet模型&#xff0c;LeNet是一个经典的卷积神经网络&#xff08;Convolutional Neural Network, CNN&#xff09;架构&#xff0c;最初由Yann LeCun等人在1998年提出&#xff0c;用于手写数字识别任务 创建一个文件model.py。实现以下代码。 源码 #…

深度学习04 数据增强、调整学习率

目录 数据增强 常用的数据增强方法 调整学习率 学习率 调整学习率 ​调整学习率的方法 有序调整 等间隔调整 多间隔调整 指数衰减 余弦退火 ​自适应调整 自定义调整 数据增强 数据增强是通过对训练数据进行各种变换&#xff08;如旋转、翻转、裁剪等&#xff09;&am…

PH热榜 | 2025-02-16

1. Cal.com Routing 标语&#xff1a;根据客户线索&#xff0c;系统会智能地自动安排约会。 介绍&#xff1a;告别繁琐的排期&#xff01;Cal.com 推出了新的路由功能&#xff0c;能更智能地分配预约&#xff0c;让你的日程安排更顺畅。这项功能运用智能逻辑和深入的数据分析…

数据库基本概念及基本使用

数据库基本概念 什么是数据库&#xff1a; 数据库特点&#xff1a; 常见的数据库软件&#xff1a; 不同的公司进行不同的实践&#xff0c;生成了不同的产品。 比如买汽车&#xff0c;汽车只是一个概念&#xff0c;你要买哪个牌子哪个型号的汽车&#xff0c;才是真正的汽车的一…

gozero实现数据库MySQL单例模式连接

在 GoZero 框架中实现数据库的单例连接可以通过以下步骤来完成。GoZero 使用 gorm 作为默认的数据库操作框架&#xff0c;接下来我会展示一个简单的单例模式实现。 ### 1. 定义数据库连接的单例结构 首先&#xff0c;你需要定义一个数据库连接的结构体&#xff0c;并在初始化…

CSS flex布局 列表单个元素点击 本行下插入详情独占一行

技术栈&#xff1a;Vue2 javaScript 简介 在实际开发过程中有遇到一个场景&#xff1a;一个list&#xff0c;每行个数固定&#xff0c;点击单个元素后&#xff0c;在当前行与下一行之间插入一行元素详情&#xff0c;便于更直观的查看到对应的数据详情。 这种情形&#xff0c…

无人机航迹规划: 梦境优化算法(Dream Optimization Algorithm,DOA)求解无人机路径规划MATLAB

一、梦境优化算法 梦境优化算法&#xff08;Dream Optimization Algorithm&#xff0c;DOA&#xff09;是一种新型的元启发式算法&#xff0c;其灵感来源于人类的梦境行为。该算法结合了基础记忆策略、遗忘和补充策略以及梦境共享策略&#xff0c;通过模拟人类梦境中的部分记忆…

权限五张表

重点&#xff1a;权限五张表的设计 核心概念&#xff1a; 在权限管理系统中&#xff0c;经典的设计通常涉及五张表&#xff0c;分别是用户表、角色表、权限表、用户角色表和角色权限表。这五张表的设计可以有效地管理用户的权限&#xff0c;确保系统的安全性和灵活性。 用户&…

Docker-数据卷

1.数据卷 容器是隔离环境&#xff0c;容器内程序的文件、配置、运行时产生的容器都在容器内部&#xff0c;我们要读写容器内的文件非常不方便。大家思考几个问题&#xff1a; 如果要升级MySQL版本&#xff0c;需要销毁旧容器&#xff0c;那么数据岂不是跟着被销毁了&#xff1…

IT : 是工作還是嗜好? Delphi 30周年快乐!

又到2月14日了, 自从30多年前收到台湾宝蓝(Borland)公司一大包的3.5 磁盘片, 上面用黑色油性笔写着Delphi Beta开始, Delphi便和我的工作生涯有了密不可分的关系. 一年后Delphi大获成功, 自此对于使用Delphi的使用者来说2月14日也成了一个特殊的日子! 我清楚记得Delphi Beta使用…

DeepPose

目录 摘要 Abstract DeepPose 算法框架 损失函数 创新点 局限性 训练过程 代码 总结 摘要 DeepPose是首个将CNN应用于姿态估计任务的模型。该模型在传统姿态估计方法的基础上&#xff0c;通过端到端的方式直接从图像中回归出人体关键点的二维坐标&#xff0c;避免了…

[HarmonyOS]鸿蒙(添加服务卡片)推荐商品 修改卡片UI(内容)

什么是服务卡片 &#xff1f; 鸿蒙系统中的服务卡片&#xff08;Service Card&#xff09;就是一种轻量级的应用展示形式&#xff0c;它可以让用户在不打开完整应用的情况下&#xff0c;快速访问应用内的特定功能或信息。以下是服务卡片的几个关键点&#xff1a; 轻量级&#…

DeepSeek R1 本地部署和知识库搭建

一、本地部署 DeepSeek-R1&#xff0c;是幻方量化旗下AI公司深度求索&#xff08;DeepSeek&#xff09;研发的推理模型 。DeepSeek-R1采用强化学习进行后训练&#xff0c;旨在提升推理能力&#xff0c;尤其擅长数学、代码和自然语言推理等复杂任务 。 使用DeepSeek R1, 可以大大…

领域驱动设计叕创新,平安保险申请DDD专利

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 见下图&#xff1a; 这个名字拼得妙&#xff1a;领域驱动设计模式。 是领域驱动设计&#xff1f;还是设计模式&#xff1f;还是领域驱动设计设计模式&#xff1f;和下面这个知乎文章的…

团体程序设计天梯赛-练习集——L1-041 寻找250

前言 10分的题&#xff0c;主要的想法就一个&#xff0c;按这个想法可以出几个写法 L1-041 寻找250 对方不想和你说话&#xff0c;并向你扔了一串数…… 而你必须从这一串数字中找到“250”这个高大上的感人数字。 输入格式&#xff1a; 输入在一行中给出不知道多少个绝对值…

C#控制台大小Console.SetWindowSize函数失效解决

在使用C#修改控制台大小相关API会失效. 由于VS将控制台由命令提示符变成了终端&#xff0c;因此在设置大小时会出现问题 测试代码&#xff1a; Console.SetWindowSize(100, 50);

spring boot 对接aws 的S3 服务,实现上传和查询

1.aws S3介绍 AWS S3&#xff08;Amazon Simple Storage Service&#xff09;是亚马逊提供的一种对象存储服务&#xff0c;旨在提供可扩展、高可用性和安全的数据存储解决方案。以下是AWS S3的一些主要特点和功能&#xff1a; 1.1. 对象存储 对象存储模型&#xff1a;S3使用…

25/2/16 <算法笔记> DirectPose

DirectPose 是一种直接从图像中预测物体的 6DoF&#xff08;位姿&#xff1a;6 Degrees of Freedom&#xff09;姿态 的方法&#xff0c;包括平移和平面旋转。它在目标检测、机器人视觉、增强现实&#xff08;AR&#xff09;和自动驾驶等领域中具有广泛应用。相比于传统的位姿估…