黑马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);
    }
    // 将用户信息存入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

​```

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

逻辑回归不能解决非线性问题,而svm可以解决

逻辑回归和支持向量机&#xff08;SVM&#xff09;是两种常用的分类算法&#xff0c;它们在处理数据时有一些不同的特点&#xff0c;特别是在面对非线性问题时。 1. 逻辑回归 逻辑回归本质上是一个线性分类模型。它的目的是寻找一个最适合数据的直线&#xff08;或超平面&…

41.兼职网站管理系统(基于springbootvue的Java项目)

目录 1.系统的受众说明 2.相关技术 2.1 B/S架构 2.2 Java技术介绍 2.3 mysql数据库介绍 2.4 Spring Boot框架 3.系统分析 3.1 需求分析 3.2 系统可行性分析 3.2.1技术可行性&#xff1a;技术背景 3.2.2经济可行性 3.2.3操作可行性&#xff1a; 3.3 项目设计目…

MS08067练武场--WP

免责声明&#xff1a;本文仅用于学习和研究目的&#xff0c;不鼓励或支持任何非法活动。所有技术内容仅供个人技术提升使用&#xff0c;未经授权不得用于攻击、侵犯或破坏他人系统。我们不对因使用本文内容而引起的任何法律责任或损失承担责任。 注&#xff1a;此文章为快速通关…

Elasticsearch:如何使用 Elastic 检测恶意浏览器扩展

作者&#xff1a;来着 Elastic Aaron Jewitt 当你的 CISO 询问你的任何工作站上是否安装过特定的浏览器扩展时&#xff0c;你多快能得到正确答案&#xff1f;恶意浏览器扩展是一个重大威胁&#xff0c;许多组织无法管理或检测。这篇博文探讨了 Elastic Infosec 团队如何使用 os…

检测网络安全漏洞 工具 网络安全 漏洞扫描 实验

实验一的名称为信息收集和漏洞扫描 实验环境&#xff1a;VMware下的kali linux2021和Windows7 32&#xff0c;网络设置均为NAT&#xff0c;这样子两台机器就在一个网络下。攻击的机器为kali,被攻击的机器为Windows 7。 理论知识记录&#xff1a; 1.信息收集的步骤 2.ping命令…

esxi添加内存条因为资源不足虚拟机无法开机——避坑

exsi8.0我加了6根内存条&#xff0c;然后将里面的ubuntu的内存增加 haTask-2-vim.VirtualMachine.powerOn-919 描述 打开该虚拟机电源 虚拟机 ub22 状况 失败 - 模块“MonitorLoop”打开电源失败。 错误 模块“MonitorLoop”打开电源失败。无法将交换文件 /vmfs/volumes…

Vision Transformer:打破CNN垄断,全局注意力机制重塑计算机视觉范式

目录 引言 一、ViT模型的起源和历史 二、什么是ViT&#xff1f; 图像处理流程 图像切分 展平与线性映射 位置编码 Transformer编码器 分类头&#xff08;Classification Head&#xff09; 自注意力机制 注意力图 三、Coovally AI模型训练与应用平台 四、ViT与图像…

自动驾驶---如何打造一款属于自己的自动驾驶系统

在笔者的专栏《自动驾驶Planning决策规划》中&#xff0c;主要讲解了行车的相关知识&#xff0c;从Routing&#xff0c;到Behavior Planning&#xff0c;再到Motion Planning&#xff0c;以及最后的Control&#xff0c;笔者都做了相关介绍&#xff0c;其中主要包括算法在量产上…

vulnhub 靶场 —— NullByte

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

Unity做2D小游戏2------创建地形和背景

我是跟着这个up主做的&#xff1a;【unity/2d/超基础】教你做一款2d横版游戏 打开Unity Hub后&#xff0c;点击项目--新项目&#xff0c;进入下面的界面&#xff0c;可以根据想要做的项目选择对应的模型&#xff0c;我现在要做2D小游戏&#xff0c;所以选择第一个2D核心模板。…

判断函数是否为react组件或lazy包裹的组件

function Modal(){return <p>123</p> } 实参里填入函数名,是false 实参里填入标签形式的函数,是true isValidElement(Modal)//false isValidElement(<Modal></Modal>)//true 官方说明 isValidElement – React 中文文档 但是官方并不建议用isValidE…

Vue笔记(八)

一、Pinia &#xff08;一&#xff09;手动添加Piaia到Vue项目 1.安装Pinia&#xff1a;使用包管理器进行安装&#xff0c;在项目目录下运行 npm install pinia 或 yarn add pinia &#xff0c;为项目引入Pinia状态管理库。 2.创建Pinia实例&#xff1a;在项目的JavaScript代…

如何将3DMAX中的3D文件转换为AutoCAD中的2D图形?

大家好,今天我们来探讨一下如何将3DMAX中的3D文件转换为AutoCAD中的2D图形。无论是出于设计交流、施工准备还是其他实际需求,这种转换在工程设计领域都是一项非常实用的技能。接下来,我将为大家详细介绍几种实现这一转换的方法,帮助大家轻松跨越3D与2D设计之间的鸿沟。让我…

javaEE-11.javaScript入门

目录 一.什么是javaScript 二.快速实现 三.JS引入方式 1.行内引入: 2.内部引入: 3.外部引入: 四.基础语法 1.变量 变量命名规则: 2.数据类型 3.运算符 五.JS对象 1.数组 创建数组: 2.操作数组 3.函数 函数注意事项: 函数参数: 4.对象 1.使用字面量 创建对象:…

机器学习 - 进一步理解最大似然估计和高斯分布的关系

一、高斯分布得到的是一个概率吗&#xff1f; 高斯分布&#xff08;也称为正态分布&#xff09;描述的是随机变量在某范围内取值的概率分布情况。其概率密度函数&#xff08;PDF&#xff09;为&#xff1a; 其中&#xff0c;μ 是均值&#xff0c;σ 是标准差。 需要注意的是…

SaaS+AI应用架构:业务场景、智能体、大模型、知识库、传统工具系统

SaaSAI应用架构&#xff1a;业务场景、智能体、大模型、知识库、传统工具系统 大家好&#xff0c;我是汤师爷~ 在SaaS与AI应用的演进过程中&#xff0c;合理的架构设计至关重要。本节将详细介绍其五个核心层次&#xff1a; 业务场景层&#xff1a;发现和确定业务场景智能体层…

三、k8s pod详解

pod详解的相关的基础知识和初始化容器&#xff0c;以及私有化的镜像仓库*。 pod进阶&#xff1a;pod的状态&#xff0c;pod的探针 pod的详解&#xff1a; pod是k8s集群管理的最小单位&#xff0c;最小的资源组件&#xff0c;也是最小化运行容器的资源对象。 容器运行在pod里…

OpenCV 相机标定流程指南

OpenCV 相机标定流程指南 前置准备标定流程结果输出与验证建议源代码 OpenCV 相机标定流程指南 https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html https://learnopencv.com/camera-calibration-using-opencv/ 前置准备 制作标定板&#xff1a;生成高精度棋…

【Windows】PowerShell 缓存区大小调节

PowerShell 缓存区大小调节 方式1 打开powershell 窗口属性调节方式2&#xff0c;修改 PowerShell 配置文件 方式1 打开powershell 窗口属性调节 打开 CMD&#xff08;按 Win R&#xff0c;输入 cmd&#xff09;。右键标题栏 → 选择 属性&#xff08;Properties&#xff09;…

127,【3】 buuctf [NPUCTF2020]ReadlezPHP

进入靶场 吓我一跳 查看源码 点击 审计 <?php// 定义一个名为 HelloPhp 的类&#xff0c;该类可能用于执行与日期格式化相关的操作 class HelloPhp {// 定义一个公共属性 $a&#xff0c;用于存储日期格式化的模板public $a;// 定义一个公共属性 $b&#xff0c;用于存储…