Redis实战—验证码登录注册

目录

基于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);
        //后注册的拦截器先执行
    }
}

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

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

相关文章

ROS 手眼标定 realsense435i+ur5e

手眼标定的原理 基坐标系&#xff08;base_tree&#xff09;和相机&#xff08;camera_tree&#xff09;两个坐标系属于不同的tree&#xff0c;通过将标签贴到手上&#xff0c;相机识别出标签的position和orention&#xff0c;并通过easy_handeye标定包得到tool0(机械手)&…

Java面试八股之HashMap和HashTable有什么区别

Java中HashMap和HashTable有什么区别 线程安全性&#xff1a; HashMap&#xff1a;非线程安全。在多线程环境下&#xff0c;如果没有采取适当的同步措施&#xff0c;直接并发访问可能会导致数据不一致、死锁等问题。如果需要在多线程环境中安全地使用HashMap&#xff0c;通常…

中国开源 AI 大模型之光-InternLM2

今天给大家带来 AI 大模型领域的国产之光 - InternLM2&#xff0c;在10B量级开源大模型领域取得了全球 Top 3 的成绩&#xff0c;仅次于 Meta 发布的 Llama-3&#xff0c;在国内则是第一名的存在&#xff01; 简介 InternLM2是由上海人工智能实验室和商汤科技联合研发的一款大型…

【软件测试】需求概念|软件的⽣命周期|开发模型|测试模型

目录 推荐 一、什么是需求 1.1 ⽤⼾需求 1.2 软件需求 二、开发模型 2.1 什么是“模型” 2.2 软件的⽣命周期 2.3 常⻅开发模型 2.3.1 瀑布模型 2.3.2 螺旋模型 2.3.3 增量模型、迭代模型 2.3.4 敏捷模型 2.4 测试模型 2.4.1 V模型 2.4.2 W模型(双V模型&#xff0…

安装ArcGIS失败,提示无效驱动器Error1327.Invalid Drive G错误

安装ArcGIS的时候&#xff0c;出现图中错误该怎么解决呢&#xff1f; Error 1327.Invalid Drive:G:\ 即错误代码&#xff1a;1327。无效驱动器G盘 出现以上问题的原因是 注册表中包含了该硬盘驱动器或网络驱动器的引用 但是在我的电脑中又没有该盘符 一般是已经卸载或者更换…

NSSCTF | [SWPUCTF 2021 新生赛]easyupload2.0

先传一个普通的一句话木马试一试 GIF89a <?php eval($_POST[shell]);?> 可以看到回显&#xff0c;不允许上传php文件。 使用Burpsuite抓包只修改ContentType后发现也不能绕过&#xff0c;说明服务器使用了黑名单后缀限制&#xff0c;那么我们可以使用其他的后缀代替ph…

dubbo复习:(3) 服务超时时间配置

在dubbo admin中 可以进行类似如下配置 configVersion: v2.7 enabled: true configs:- side: consumeraddresses:- 0.0.0.0parameters:timeout: 55这样配置之后&#xff0c;当服务端响应超过55毫秒时&#xff0c;在服务消费者的控制台就会看到超时信息

鸿蒙应用布局ArkUI:【其他常用布局容器和组件】介绍

其他常用布局容器和组件 创建轮播&#xff08;Swiper&#xff09;实现轮播图功能 开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 栅格布局&#xff08;GridRow/GridCol&#xff09;和Grid布局类似…

TiDB学习1:TiDB体系架构概览

目录 1. TiDB体系结构 2. TiDBsever 3. TiKV 4. PD(Placement Driver) 5. TiFlash 1. TiDB体系结构 水平扩容或者缩容金融级高可用实时 HTAP云原生的分布式数据库兼容MySQ 5.7 协议 2. TiDBsever 处理客户端的连接SQL语句的解析和编译关系型数据与 kv 的转化(insert语句)S…

2024自学网络安全的三个必经阶段(含路线图)_网络安全自学路线

一、为什么选择网络安全&#xff1f; 这几年随着我国《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》等一系列政策/法规/标准的持续落地&#xff0c;网络安全行业地位、薪资随之水涨船高。 未来3-5年&#xff0c;是安全行业的黄金发展期&#xff0c;提前踏入…

Postman基础功能-返回值获取

大家好&#xff0c;之前给大家分享关于Postman的接口关联&#xff0c;我们平时在做接口测试时&#xff0c;请求接口返回的数据都是很复杂的 JSON 数据&#xff0c;有着多层嵌套&#xff0c;这样的数据层级在 Postman 中要怎么获取呢&#xff1f; 接下来给大家展示几个获取 JSO…

腾讯中视频项目,日均收益1000+,简单搬运无限做,执行就有收入

兄弟们今天给大家分享的项目-腾讯视频的中视频计划项目&#xff0c;项目简单&#xff0c;低门槛&#xff0c;不需要考虑带货等问题&#xff0c;是2024年目前最火的变现赛道了。 因为目前来说的话&#xff0c;腾讯视频中视频是刚开始启动&#xff0c;是项目的红利期&#xff0c;…

在抖音做电商,没有货源,不懂直播怎么办?分享一种解决方案!

大家好&#xff0c;我是电商糖果 糖果做电商的时间也挺久了&#xff0c;天猫&#xff0c;京东&#xff0c;闲鱼都搞过。 从学校进入社会工作&#xff0c;创业&#xff0c;一直都是围绕电商打转。 做的时间久了&#xff0c;好像只会做这一件事儿了。 2020年开始专攻抖音小店&…

Galxe已投资Pencils Protocol,投资者阵营正不断扩大

近日&#xff0c;Scroll 生态项目 Penpad 将品牌进一步升级为 Pencils Protocol&#xff0c;全新升级后其不仅对 LaunchPad 平台进行了功能上的升级&#xff0c;同时其也进一步引入了 Staking、Vault 以及 Shop 等玩法&#xff0c;这也让 Pencils Protocol 的叙事方向不再仅限于…

计算机网络 -- 序列化与反序列化

一 协议的重要性 我们都知道&#xff0c;在进行网络通信的过程中&#xff0c;通信的双方可以是不同的设备&#xff0c;不同的平台&#xff0c;不同的平台&#xff0c;比如说&#xff0c;手机用户和电脑用户进行通信&#xff0c;ios系统和安卓系统进行通信。 自己的数据&#xf…

Android实践:查看Activity信息

问题&#xff1a;本地Android SDK的monitor无法正常运行&#xff0c;看不了进程相关信息&#xff0c;确认当前显示Activity十分不便 解决办法&#xff1a;使用adb shell指令可以快速查看 命令&#xff1a; adb shell dumpsys activity activities 这个命令用于获取Android设…

MySQL 进阶使用【函数、索引、视图、存储过程、存储函数、触发器】

前言 做数仓开发离不开 SQL &#xff0c;写了很多 HQL 回头再看 MySQL 才发现&#xff0c;很多东西并不是 HQL 所独创的&#xff0c;而是几乎都来自于关系型数据库通用的 SQL&#xff1b;想到以后需要每天和数仓打交道&#xff0c;那么不管是 MySQL 还是 Oracle &#xff0c;都…

MS5173M-16bit、单通道、200kSPS、 SAR 型 ADC

MS5173M 是单通道、 16bit 、电荷再分配逐次 逼近型模数转换器&#xff0c;采用单电源供电。 MS5173M 包含一个低功耗、高速数据采样且 无失码的真 16 位 SAR ADC 和一个内部转换时钟。 MS5173M 使用通用的串口接口实现转换结果 的接收&#xff0c;还包含低噪声、宽…

AI助力内容创作:让效率与质量齐飞

简述&#xff1a; 本文介绍了AI如何帮助创作者在保持内容质量的同时&#xff0c;大幅度提升生产效率的一些方法&#xff0c;希想 对大家有帮助。 一、自动化内容生成 1. 文本内容生成 使用GPT等模型&#xff1a;利用如GPT-3或GPT-4等大型语言模型&#xff0c;可以直接输入关…

好烦啊,我真的不想写增删改查了!

大家好&#xff0c;我是程序员鱼皮。 很想吐槽&#xff1a;我真的不想写增删改查这种重复代码了&#xff01; 大学刚做项目的时候&#xff0c;就在写增删改查&#xff0c;万万没想到 7 年后&#xff0c;还在和增删改查打交道。因为增删改查是任何项目的基础功能&#xff0c;每…