Redis实战—验证码登录注册

news2024/11/22 19:25:42

目录

基于Session

Controller层

Service层

ServiceImpl层

​编辑校验登录状态

ThreadLocal

登录拦截器

添加拦截器到Config

Controller层实现

基于Redis

ServiceImpl

新增刷新拦截器

添加拦截器到Config


基于Session

Controller层

    /**
     * 发送手机验证码
     */
    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        // TODO 发送短信验证码并保存验证码
        return userService.sendCode(phone,session);
    }


    /**
     * 登录功能,不存在用户则自动创建用户
     * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
     * @RequestBody注解用于把前端的json数据转换成DTO对象
     */
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
    //将请求体中的数据转换为特定的对象或数据类型绑定到方法的参数上
        // TODO 实现登录功能
        return userService.login(loginForm,session);
    }


    

Service层

   Result sendCode(String phone, HttpSession session);

   Result login(LoginFormDTO loginForm, HttpSession session);

ServiceImpl层

   
    //登录
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {

        //发送验证码和登录是两次不同的请求,要对手机号做二次校验
        //1.再次获取手机号并再次校验手机号
        String phone = loginForm.getPhone();
        if(RegexUtils.isPhoneInvalid(phone)){
            //2.不符合返回错误信息
            return Result.fail("手机号格式错误");
        }

        //3.手机号校验成功,校验验证码,验证码从session获取
        String code = session.getAttribute("code");//发送的验证码
        String logincode = loginForm.getCode();//用户填写的验证码
        if (code==null||!code.toString().equals(logincode)){
            //4.验证码错误,返回信息
            return Result.fail("验证码错误");
        }

        //5.验证码一致,查询是否存在用户
        //query()是Mybatis-Plus提供的,本类继承了extends ServiceImpl<UserMapper, User>
        User user = query().eq("phone", phone).one();
        if (user==null){
            //6.用户不存在则创建新用户,新用户信息要保存到session,所以这里把新建的user返回
            user=createUserWithPhone(phone);
        }

        // 7.保存用户信息到session
        //user信息过多,不易直接保存到session,会加大内存负载,此处转化成userDTO,还可以隐藏用户敏感信息
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        session.setAttribute("user",userDTO);


        //访问tomcat时。sessionID自动写到了cookie中作为以后的登录凭证
        return Result.ok();
    }


    //自动注册用户
    private User createUserWithPhone(String phone) {
        User user=new User();
        user.setPhone(phone);
        //生成随机用户名
        user.setNickName("user_"+RandomUtil.randomString(5));
        save(user);//保存用户到数据库,mybatis-plus提供的
        return user;
    }


    //发送验证码
    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1. 校验手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            //2. 不符合,返回错误信息
            return Result.fail("手机号格式错误");
        }
        //3.符合,生成6位验证码
        String code = RandomUtil.randomNumbers(6);


        //4.验证码保存到session稍后返回给服务端,以便服务端进行登录验证
        session.setAttribute("code",code);
        
        //5.发送验证码,这里验证码输出到控制台
        log.info("验证码发送成功:{}",code);

        //6.返回
        return Result.ok();
    }

校验登录状态

上面完成了短信验证码的发送及登录注册,用户如果登陆成功就要进入个人主页,但是进入个人主页需要验证用户的登录状态,接下来使用拦截器和ThreadLocal完成登录状态校验

ThreadLocal

public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUser(UserDTO user){
        tl.set(user);
    }

    public static UserDTO getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}

登录拦截器

//这是一个自定义的拦截器类,用于校验登陆状态
//需要在config中添加到拦截器中才会生效
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removerUser();
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    //1.获取session
    HttpSession session=request.getSession();

    //2.获取session中的用户
    Object user=session.getAttribute("user");

    //3.判断用户是否存在
    if(user=null){
    //4.不存在,拦截
        response.setStatus(401);
        return false;   
    }

    //5.存在,保存到ThreadLocal,并放行
    UserHolder.saveUSer(user);

    return true;

    }
}

添加拦截器到Config

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 登录拦截器
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/shop/**",
                        "/voucher/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                );

    }
}

Controller层实现


    //登录校验状态
    @GetMapping("/me")
    //个人主页
    public Result me(){
        // TODO 获取当前登录的用户并返回
        //在拦截器LoginInterceptor中已经把用户保存到localthread
        UserDTO user = UserHolder.getUser();
        return Result.ok(user);
    }

基于Redis

接下来在上面代码的基础上修改serviceImpl和拦截器及config

ServiceImpl

    //注入stringRedisTemplate
    @Resource
    private StringRedisTemplate stringRedisTemplate;

 
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        
        //1.校验手机号
        String phone = loginForm.getPhone();
        if(RegexUtils.isPhoneInvalid(phone)){
            //2.不符合返回错误信息
            return Result.fail("手机号格式错误");
        }

        //3.手机号校验成功,校验验证码,验证码从redis中获取
        String code = stringRedisTemplate.opsForValue().get("login:code:"+phone);
        String logincode = loginForm.getCode();
        if (code==null||!code.toString().equals(logincode)){
            //4.验证码错误
            return Result.fail("验证码错误");
        }

        //5.验证码一致,查询是否存在用户
        //query()是mp提供的,本类继承了extends ServiceImpl<UserMapper, User>
        User user = query().eq("phone", phone).one();
        if (user==null){
            //6.不存在用户,创建新用户
            user=createUserWithPhone(phone);
        }
        
        //7.用户存在,生成用户的token作为登录凭证
        String token = UUID.randomUUID().toString(true);
        
        //8.存入redis时用的hash存储,因此这里要把userDto转换成hashmap
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(), CopyOptions.create()
                .setIgnoreNullValue(true)
                .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));

        //9.token存入redis并设置token有效期
        stringRedisTemplate.opsForHash().putAll("login:token:"+token,userMap);     
        stringRedisTemplate.expire("login:token:",30,TimeUnit.MINUTES);
        
        return Result.ok(token);
    }    


    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1.校验手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            //2.不符合返回错误信息
            return Result.fail("手机号格式错误");
        }
        
        //2.符合生成验证码
        String code = RandomUtil.randomNumbers(6);
        
        
        //3.保存到redis中,设置有效期1分钟
        stringRedisTemplate.opsForValue().set("login:code:"+phone,code,1L, TimeUnit.MINUTES);

        //4.发送验证码给客户
        log.info("验证码发送成功:{}",code);

        //返回
        return Result.ok();
    }

新增刷新拦截器

用于刷新Token有效期,这里做了拦截器的优化。在第一个拦截器会获取token,根据token去redis查询用户,如果查到就说明用户已经登陆过了,此时只需要刷新token有效期并保存到threadlocal然后放行。如果没有查到说明是未登录用户或者登录已经过期,那么token也是null的,也直接放行,所以第一个拦截器虽然拦截一切路径,但不论如何最后都会放行,其主要作用是对已登录用户刷新token,在第二个拦截器才会对未登录用户进行限制,决定放不放行。

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(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)) {
            return true;
        }

        // 2.基于TOKEN获取redis中的用户
        String key  = "login:token" + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 3.判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        // 5.将查询到的hash数据转为UserDTO
        UserDTO user = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 6.存在,保存用户信息到 ThreadLocal
        UserHolder.saveUser(user);
        // 7.刷新token有效期
        stringRedisTemplate.expire(key, 30L, TimeUnit.MINUTES);
        // 8.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户
        UserHolder.removeUser();
    }
}

添加拦截器到Config

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public 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.coloradmin.cn/o/1679448.html

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

相关文章

Docker三剑客从0到1

一、docker三剑客介绍 使用"三剑客"可以帮助我们解决docker host维护,多容器编排部署,多个docker host集群的各个难题。 docker-machine 创建虚拟机 我们知道docker使用了linux的内核技术(namespace 资源隔离,cgroup资源限制等),那么如果我想在windows或Mac系统上…

浅析扩散模型与图像生成【应用篇】(二十五)——Plug-and-Play

25. Plug-and-Play: Diffusion Features for Text-Driven Image-to-Image Translation 该文提出一种文本驱动的图像转换方法&#xff0c;输入一张图像和一个目标文本描述&#xff0c;按照文本描述对输入图像进行转换&#xff0c;得到目标图像。图像转换任务其实本质上属于图像编…

【Flask框架】

6.Flask轻量型框架 6.1Flask简介 python提供的框架中已经写好了一个内置的服务器&#xff0c;服务器中的回应response行和头已经写好&#xff0c;我们只需要自己写显示在客户端&#xff0c;的主体body部分。 ---------------------------------------------------------- Fla…

【Linux】常用指令、热键与权限管理

一、常用指令 &#xff08;1&#xff09;ls 功能&#xff1a;列出指定目录下的所有子目录与文件 用法&#xff1a;ls &#xff08;选项&#xff09; &#xff08;目录或文件名&#xff09; 常用选项&#xff1a; -a&#xff1a;列出目录下的所有文件&#xff0c;包括隐藏…

k8s环境部署的集成arthas-spring-boot-starter spingboot项目无法访问控制台

前言 k8s环境部署的集成arthas-spring-boot-starter项目无法访问控制台&#xff0c;springboot项目集成arthas-spring-boot-starter 会自带个控制台 供我们访问 但是当使用k8s环境部署后 这个页面就无法访问了 分析 首先看下arthas对应的配置 arthas-spring-boot-starter 中…

linux基础指令讲解(ls、pwd、cd、touch、mkdir)

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;c大冒险 总有光环在陨落&#xff0c;总有新星在闪烁 这个是我们今天要用到的初始…

超实用的excel进销存管理系统(75份),自带库存预警,直接用!

进销存&#xff08;Inventory Management&#xff09;是企业管理中的一个核心组成部分&#xff0c;它涉及到商品的采购&#xff08;进货&#xff09;、销售和存储&#xff08;库存&#xff09;等环节。有效的进销存管理可以帮助企业降低成本、提高效率和客户满意度。 1. 采购管…

【上海大学计算机组成原理实验报告】五、机器语言程序实验

一、实验目的 理解计算机执行程序的实际过程。 学习编制机器语言简单程序的方法。 二、实验原理 根据实验指导书的相关内容&#xff0c;指令的形式化表示是指采用一种规范化的符号系统&#xff0c;以更清晰、精确地描述和表示指令的逻辑功能和操作步骤。 汇编是一种编程语言…

C++ 中重写重载和隐藏的区别

重写&#xff08;override&#xff09;、重载&#xff08;overload&#xff09;和隐藏&#xff08;overwrite&#xff09;在C中是3个完全不同的概念。我们这里对其进行详细的说明 1、重写&#xff08;override&#xff09;是指派生类覆盖了基类的虚函数&#xff0c;这里的覆盖必…

gitlab webhook触发jenkins任务

配置jenkins 安装gitlab插件 配置jenkins job 选择gitlab webhook触发 在高级中生成token 代码仓设置 新增webhook 配置webhook 测试连接 缺点&#xff0c;不能带gitLab事件的参数&#xff01;&#xff01;&#xff01;

Xilinx RAM IP核的使用及注意事项

对于RAM IP核(Block Memory Generator核)的使用以及界面的配置介绍&#xff0c;文章RAM的使用介绍进行了较详细的说明&#xff0c;本文对RAM IP核使用中一些注意的地方加以说明。 文章目录 三种RAM的区别单端口RAM(Single-port RAM)简单双端口RAM(Simple Dual-port RAM)真双端…

Spring Security入门教程:实现自定义用户配置

在上一篇文章中&#xff1a;Spring Security入门教程&#xff1a;利用Spring Security实现安全控制 我们学会了使用Spring Security实现安全控制&#xff0c;学会了他的基础使用&#xff0c;这节课我们来学习一下它的自定义的功能&#xff0c;更深入的了解和使用Spring Securit…

计算机组成结构—寻址方法

目录 一、指令寻址 二、数据寻址 1.立即寻址 2.直接寻址 3.间接寻址 4.隐含寻址 5.寄存器寻址 6.寄存器间接寻址 7.基址寻址 8.变址寻址 9.相对寻址 10. 堆栈寻址 寻址方式是寻找指令或操作数有效地址的方式&#xff0c;也就是指确定本条指令的数据地址&#xff0c;…

C++错题集(持续更新ing)

Day 1 一、选择题 解析&#xff1a; 在数字不会溢出的前提下&#xff0c;对于正数和负数&#xff0c;有&#xff1a; 1&#xff09;左移n位&#xff0c;相当于操作数乘以2的n次方&#xff1b; 2&#xff09;右移n位&#xff0c;相当于操作数除以2的n次方。 解析&#xff1a…

深度盘点在当今经济形势下资深项目经理或PMO的或去或从

在当今经济形势下&#xff0c;资深项目经理&#xff08;Project Manager&#xff09;或项目管理办公室&#xff08;PMO&#xff09;的去向和选择受到多种因素的影响。以下是对他们可能面临的或去或从的深度盘点&#xff1a; 1、发展去向 1. 深化专业领域&#xff1a;在经济形势…

2024年【制冷与空调设备运行操作】考试内容及制冷与空调设备运行操作考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 制冷与空调设备运行操作考试内容是安全生产模拟考试一点通生成的&#xff0c;制冷与空调设备运行操作证模拟考试题库是根据制冷与空调设备运行操作最新版教材汇编出制冷与空调设备运行操作仿真模拟考试。2024年【制冷…

如何实现Linux双网卡同时连接内网和外网的配置?

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

快速查看字符对应的ASCII码

1、借助gdb查看 打印字符串用双引号括起来打印单个字符用单引号括起来x 表示十六机制d 表示十进制t 表示二进制 2、借助二进制查看软件 第一步&#xff1a;把要查看的字符保存到文本文件中第二步&#xff1a;借助二进制查看工具&#xff08;比如&#xff1a;Hex Editor Neo&am…

Windows 10无法远程桌面连接:原因及解决方案

在信息技术日益发展的今天&#xff0c;远程桌面连接已成为企业日常运维、技术支持乃至个人用户远程办公的必备工具。然而&#xff0c;有时我们可能会遇到Windows 10无法远程桌面连接的问题&#xff0c;这无疑会给我们的工作和生活带来诸多不便。 原因分析 1、远程访问未启用&a…