Redis实现短信登录

news2024/11/19 0:51:05

文章目录

  • 一、基于Session实现登录
  • 二、基于Redis实现共享Session实现登录

一、基于Session实现登录

在这里插入图片描述

---------------------------------------------------Controller
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
    //发送短信验证码并保存验证码
    return userService.sendCode(phone,session);
}
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
    //实现登录功能  loginForm 登录参数,包含手机号、验证码;或者手机号、密码
    return userService.login(loginForm,session);
}
@GetMapping("/me")
public Result me(){
    // 获取当前登录的用户并返回
    UserDTO user = UserHolder.getUser();
    return Result.ok(user);
}
---------------------------------------------------Service
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Override
    public Result sendCode(String phone, HttpSession session) {
        if(RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号格式错误");
        }
        String code = RandomUtil.randomNumbers(6);
        session.setAttribute("code",code);
        log.debug("短信验证码" + code);

        return Result.ok();
    }
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        if(RegexUtils.isPhoneInvalid(loginForm.getPhone())){
            return Result.fail("手机号格式错误");
        }
        Object catchCode = session.getAttribute("code");
        String code = loginForm.getCode();
        if(catchCode == null || !catchCode.equals(code)){
            Result.fail("验证码错误");
        }
        User user = query().eq("phone", loginForm.getPhone()).one();
        if (user == null) {
            user = createUserWithPhone(loginForm.getPhone());
        }
        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
        return Result.ok();
    }
    private User createUserWithPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomNumbers(3));
        save(user);
        return user;
   	}
}
---------------------------------------------------自定义拦截器
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        Object user = session.getAttribute("user");
        if (user == null) {
            response.setStatus(401);
            return false;
        }
        UserHolder.saveUser((UserDTO) user);
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}
---------------------------------------------------添加拦截器并指定拦截的请求和不拦截的请求
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
   		 //实现WebMvcConfigurer接口中的addInterceptors方法把自定义的拦截器类添加进来即可
        registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(
                "/user/code",
                "/user/login",
                "/blog/hot",
                "/shop/**",
                "/shop-type/**",
                "/voucher/**"
        );
    }
}

总结: 短信验证码用随机工具生成6位数,保存了session中,在用户使用手机号登录时,获取session中的验证码和请求参数中的验证码比对,一致则去库里查该手机号的用户是否存在,不存在则新建用户,并把该用户对象存在在session中。校验登录状态是使用HandlerInterceptor拦截器实现的,在此之前需要配置拦截哪些请求,不拦截哪些请求,从客户端的请求中获取session信息,为空返回401状态,不为空则把用户信息存储在ThreadLocal中,在请求处理完之后销毁用户信息。
问题: 集群的session共享问题。多台Tomcat并不共享session存储空间,当请求切换到不同Tomcat服务时导致数据丢失的问题。早期的Tomcat提供session拷贝功能,但是并不能解决问题,问题有1.多台Tomcat保存相同的数据信息,内存空间浪费;2.拷贝需要时间,在延迟之内有用户访问,多台Tomcat依然存在数据不一致。

二、基于Redis实现共享Session实现登录

在这里插入图片描述
在这里插入图片描述

-------------------只记录有变化的--------------------------------Service
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result sendCode(String phone, HttpSession session) {
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误");
        }
        String code = RandomUtil.randomNumbers(6);
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
        log.debug("短信验证码" + code);
        return Result.ok();
    }
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误");
        }
        String cacheCode = stringRedisTemplate.opsForValue().get(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 = createUserWithPhone(phone);
        }
        String token = UUID.randomUUID().toString(true);
        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()));
        stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token, userMap);
        stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);
        return Result.ok(token);
    }
}
---------------------------------------------------添加拦截器并指定拦截的请求和不拦截的请求
@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/**",
                "/voucher/**"
        ).order(1);
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
    }
}
---------------------------------------------------自定义拦截器
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断ThreadLocal中是否有用户
        if(UserHolder.getUser() == null){
            response.setStatus(401);
            return false;
        }
        return true;
    }
}
public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);
        if (userMap.isEmpty()) {
            return true;
        }
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //保存在ThreadLocal中
        UserHolder.saveUser(userDTO);
        //刷新token有效期
        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
}

总结: 短信验证码修改为以手机号为key验证码为value保存在redis中,在用户使用手机号登录时,获取redis中的验证码和请求参数中的验证码比对,一致则去库里查该手机号的用户是否存在,不存在则新建用户,并把该用户对象存在在redis中。校验登录状态是使用HandlerInterceptor拦截器实现的,在此之前需要配置拦截哪些请求,不拦截哪些请求,从客户端的请求头中获取token信息,并从redis中获取用户信息, 为空返回401状态,不为空则把用户信息存储在ThreadLocal中,还把验证码和用户信息设置有效时间,时间一过,则退出用户登录。在请求处理完之后销毁用户信息。
改造的点:
  发送短信验证码时,key使用手机号来确保唯一存储在redis中,在用户登录时可以根据key来取出验证码校对。
  短信验证码登录时,key使用UUID来确保唯一存储在redis中,为确保将来前端能把token发送过来进行校验,在请求结束前把token返回给客户端。
问题:
1.拦截器能否真正的实现只要用户一直在访问,token就不会过期?
不行,因为拦截器只拦截需要登录的路径,如果用户在有效期内一直访问不需要登录的路径,那么redis中的token就会过期。
问题解决如下图。
在这里插入图片描述

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

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

相关文章

JavaScript进阶教程——面向对象、原型对象、this关键字、bind appl call方法

文章目录面向对象ES2015的面向对象语法&#xff1a;ES5的面向对象语法&#xff1a;原型对象原型链Object对象的原型this关键字**指向调用方法的对象&#xff1a;****构造函数&#xff1a;**触发事件指向全局对象&#xff1a;箭头函数总结bind appl call方法call的用法参数apply…

【计算机图形学入门】笔记4:变换(模型、视图、投影)

目录04变换&#xff08;模型、视图、投影&#xff09;1.使用齐次坐标表示的三维变换2.view/Camera 视图变换3.Projection 投影变换1.Orthographic projection 正交投影2.Perspective projection 透视投影 ——更适合人眼成像&#xff08;近大远小&#xff0c;鸽子为什么这么大&…

AlphaControls 读取存贮数据VCL OnChange事件的处理

AlphaControls 读取存贮数据VCL OnChange事件的处理 AlphaControls控件&#xff0c;窗口时显示&#xff0c;对于VCL控件的处理&#xff0c;需要通过调用皮肤模板重新画出来。因此&#xff0c;在窗口的显示过程中&#xff0c;对VCL控件的数据装载&#xff08;变更&#x…

从获评毕马威中国领先地产科技50强 看贝壳的长期主义

11月9日&#xff0c;在上海进博会现场&#xff0c;毕马威发布了2022年度&#xff08;第二届&#xff09;“毕马威中国领先地产科技企业50”报告&#xff0c;并正式发布了年度毕马威中国领先地产科技企业50榜及地产科技新锐企业榜。作为国内居住产业数字服务平台的贝壳上榜。 众…

前辈给的 Spring Cloud 与 Docker 微服务实战,挽救了要被辞退的我

前言 还记得那天天气阴冷&#xff0c;整个人心都是拔凉拔凉的&#xff1b;原因是因为领导找我聊了一下&#xff0c;委婉地说觉得我的技术不太行&#xff0c;能力不突出&#xff1b;要么自己做出改变&#xff0c;要么选择离开。 说实话&#xff0c;我是有点难过的&#xff0c;…

Lambda完整学习指南

什么是Lambda表达式 Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。Lambda表达式还增强了集合库。 Java La…

大学生HTML作业篮球网页 HTML作业篮球网页期末作业 HTML+CSS篮球网页 HTML学生作业体育篮球网页

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

ARS系列毫米波雷达技术一览

近年来&#xff0c;自动驾驶成为了当下最热门的话题。随着自动驾驶技术的发展&#xff0c;作为其关键传感器之一的毫米波雷达&#xff0c;也得到了广泛关注。但就目前来看&#xff0c;海外巨头公司主导着全球毫米波雷达产业的发展&#xff0c;其中&#xff0c;大陆ARS540更以九…

吊打面试官,聊聊:Java中String对象的大小?(史上最全)

下面是一个常见的Java 面试题&#xff1a; 聊聊&#xff1a;Java中String对象的大小&#xff1f; 首先&#xff0c;看看空String占用的空间 当前内存大小是在默认开启压缩指针的条件下 对象头 12char[]数组引用 4int 类型 hash数据大小 4loss due to the next object alignm…

MacOS好用的系统清理工具CleanMyMac有哪些特点功能?

怎么样为Mac选择一款专业靠谱的清理工具&#xff1f;需要满足那些功能呢&#xff1f;对于大多数的新手用户可能并不知道如何来清理Mac系统&#xff0c;当然也不知道在众多清理工具中如何选择。选择一款清理工具最重要的是它的性能如何&#xff0c;能不能有效的帮助用户清理系统…

[附源码]JAVA毕业设计小说阅读网站(系统+LW)

[附源码]JAVA毕业设计小说阅读网站&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&…

听说你还不了解二叉树?赶紧进来轻松解决

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; 数据结构 | C语言 &#x1f38a;每篇一句&#xff1a; 图片来源 Only by self-respect will you compel others to respect you. 只有自尊才能迫使他人尊敬你。 文章目录&#x1f4d8;前言&#x1f4d8;正文…

java的vo实体类字段和返回前端接收的字段大小写不一致

java的vo实体类字段和返回前端接收的字段不一致 后台的vo字段和前端接收到的出现大小写一直的情况 例如: 后台字段 /** a_lwa */private String aLwa;前端实际接收到的 这是由于使用lombok插件导致的 解决办法 实体类添加注解强制绑定 /** a_lwa */JsonProperty("aLwa&…

【JavaWeb开发-Servlet】day07-学生成绩管理系统-实现登录注册功能

目录 一、登录功能 1、实现登录页面 ①新建一张登录表&#xff1a;login ②插入一条测试数据 ③新建一个login.jsp登录界面 ④此表的实体Teacher类&#xff1a; ⑤ 然后在写一个dao方法&#xff0c;需要根据用户名查询登录表中的用户信息 ⑥业务层中我们直接调用dao层方法 ⑦在…

【负荷预测】基于灰色理论负荷预测的应用研究(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

南京邮电大学编译原理实验一(词法分析器的构造)

文章目录一、 实验目的和要求二、实验环境(实验设备)三、实验原理及内容&#xff08;一&#xff09;设计概要1、C语言子集2、单词及编码3、状态转换图&#xff08;二&#xff09;实现分析&#xff08;三&#xff09;结果分析四、实验小结&#xff08;包括问题和解决方法、心得体…

文本纠错--N-gram--Macbert模型的调用以及对返回结果的处理

文本根据词典进行纠错 输入一段可能带有错误信息的文字&#xff0c; 通过词典来检测其中可能错误的词。 例如&#xff1a;有句子如下&#xff1a;中央人民政府驻澳门特别行政区联络办公室1日在机关大楼设灵堂    有词典如下&#xff1a;中国人民&#xff0c;中央人民&#x…

红帽曹衡康:开源是企业数字化转型的机遇

在数据成为企业核心资产的今天&#xff0c;为了更好地降本增效&#xff0c;实现业务创新和增长&#xff0c;越来越多的企业都开启了数字化转型之路。然而对于绝大多数企业来说&#xff0c;这条转型之路都并非一帆风顺。事实上&#xff0c;数字化转型在为企业开启更多机遇的同时…

爆火Chatgpt注册 chatgpt使用 完全指南

1 chatgpt 简介 ChatGPT是一种语言模型&#xff0c;它被训练来对对话进行建模。它能够通过学习和理解人类语言来进行对话&#xff0c;并能够生成适当的响应。ChatGPT使用了一种叫做Transformer的神经网络架构&#xff0c;这是一种用于处理序列数据的模型&#xff0c;能够在输入…

Java System类

JavaSystem类\huge{Java \space System类}Java System类 System类概述 简而言之&#xff1a;SystemSystemSystem类就是一个工具类&#xff0c;直接调用对应的方法来使用即可&#xff0c;不需要也不能被实例化。 常用方法 ①. exit() 退出Java虚拟机 //执行这个exit(0)代码之…