Redis实战——短信登录(二)

Redis代替session

  • redis中设计key
    • 在使用session时,每个用户都会有自己的session,这样虽然验证码的键都是“code”,但是相互不影响,从而确保每个用户获取到的验证码只能够自己使用,当使用redis时,redis的key是共享的,不分用户,就要求在redis中存储验证码时,不能直接将验证码的键设置为"code",这样无法保证其唯一性。
  • redis中设计value
    • 到底该使用redis中什么数据类型存储数据,主要需要看数据样式和使用方式,一般会考虑使用String、Hash,String存储时,会多占一点内存空间,则相对来说Hash存储时,会少占用一点内存空间。
      • String结构:以Json字符串来存储,比较直观
      • Hash结构:,每个对象中每个字段独立存储,可以针对单个字段做CRUD

redis实现登录

发送验证码
发送验证码-redis
发送验证码-redis
  • 添加redis

            <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
    
  • 设置redis的连接信息

    spring:
      redis:
        host: 192.168.175.128
        port: 6379
        password: liang
        lettuce:
          pool:
            max-active: 10
            max-idle: 10
            min-idle: 1
            time-between-eviction-runs: 10s
  • 增加相关常量

    /**
     * 保存验证码的redis中的key
     */

    public static final String LOGIN_CODE_KEY = "login:code:";
    /**
     * 验证码的过期时间
     */

    public static final Long LOGIN_CODE_TTL = 2L;
  • 修改Service层

       @Autowired
        StringRedisTemplate stringRedisTemplate;

        @Override
        public boolean sendCode(String phone, HttpSession session) {
            //获取手机号,验证手机号是否合规
            boolean mobile = PhoneUtil.isMobile(phone);
            //不合规,则提示
            if (!mobile){
                return false;
            }
            //生成验证码
            String code = RandomUtil.randomNumbers(6);
            //保存验证码到redis,并设置过期时间
            stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
            //发送验证码,这里就通过打印验证码模拟了下发送验证码
            System.out.println("验证码:" + code);
            return true;
        }
  • 修改Controller层

    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        String uuid = userService.sendCode(phone, session);
        return uuid.equals("") ? Result.fail("手机号码不合规"): Result.ok(uuid);
    }
验证码登录、注册
验证码登录注册-redis
验证码登录注册-redis
  • 增加相关常量

    public static final String LOGIN_USER_KEY = "login:token:";
    public static final Long LOGIN_USER_TTL = 30L;
  • 修改Service层

    @Override
    public String login(LoginFormDTO loginForm, HttpSession session) {
        //获取手机号
        String phone = loginForm.getPhone();
        //验证手机号是否合理
        boolean mobile = PhoneUtil.isMobile(phone);
        //如果不合理 提示
        if (!mobile){
            //提示用户手机号不合理
            return "";
        }
        //手机号合理 进行验证码验证
        String code = loginForm.getCode();
        //从redis中获取验证码
        String redisCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        //如果验证码输入的是错误的  提示
        if (!code.equals(redisCode)){
            return "";
        }
        //如果验证码也正确 那么通过手机号进行查询
        User user = this.getOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
        // 数据库中没查询到用户信息
        if (ObjectUtil.isNull(user)){
            user = new User();
            user.setPhone(phone);
            user.setNickName("user_"+ RandomUtil.randomString(10));
            this.save(user);
        }
        // 将用户信息保存到Redis中,注意避免保存用户敏感信息
        UserDTO userDTO = BeanUtil.toBean(user, UserDTO.class);
        // 设置UUID保存用户信息
        String uuid = IdUtil.fastSimpleUUID();
        // 将user对象转化为Map,同时将Map中的值存储为String类型的
        Map<String, Object> userDTOMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create().ignoreNullValue()
                        .setFieldValueEditor((key, value) -> value.toString()));
        stringRedisTemplate.opsForHash().putAll( LOGIN_USER_KEY + uuid, userDTOMap);
        //设置过期时间
        stringRedisTemplate.expire(LOGIN_USER_KEY + uuid, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 通过UUID生成简单的token
        String token = uuid + userDTO.getId();
        return token;
    }    
    String login(LoginFormDTO loginForm, HttpSession session);
  • 修改Controller层

    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        String token = userService.login(loginForm, session);
        return StrUtil.isNotBlank(token) ? Result.ok(token) : Result.fail("手机号或验证码错误");
    }
校验登录状态
校验登录状态-redis
校验登录状态-redis
  • 修改LoginInterceptor拦截器

    private StringRedisTemplate stringRedisTemplate;

    /**
     * 构造函数
     * @param stringRedisTemplate
     */

    public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * preHandle方法的返回值决定是否放行,该方法在控制层方法执行前执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //从请求头中获取token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)){
            return false;
        }

        String uuid = token.substring(0,token.lastIndexOf("-"));
        System.out.println(uuid);
        //从redis中获取值
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + uuid);
        if (ObjectUtil.isNull(entries)){
            return false;
        }
        //将map转化为UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(entries, new UserDTO(), true);
        //将用户信息保存到 ThreadLocal
        UserHolder.saveUser(userDTO);
        return true;
    }
    @Configuration
    public class MvcConfig implements WebMvcConfigurer {

        @Resource
        StringRedisTemplate stringRedisTemplate;

        /**
         * 添加拦截器
         * @param registry
         */

        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //添加拦截器
            registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
                    //放行资源
                    .excludePathPatterns(
                            "/shop/**",
                            "/voucher/**",
                            "/shop-type/**",
                            "/upload/**",
                            "/blog/hot",
                            "/user/code",
                            "/user/login"
                    )
                    // 设置拦截器优先级
                    .order(1);
        }
    }
登录状态的刷新问题
  • 因为设置了redis中存储的用户的有效期,所以在用户访问界面的时,需要更新token令牌的存活时间,例如修改LoginInterceptor拦截器,在此拦截器中刷新过期时间

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //从请求头中获取token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)){
            return false;
        }

        String uuid = token.substring(0,token.lastIndexOf("-"));
        System.out.println(uuid);
        //从redis中获取值
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + uuid);
        if (ObjectUtil.isNull(entries)){
            return false;
        }
        //将map转化为UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(entries, new UserDTO(), true);
        //将用户信息保存到 ThreadLocal
        UserHolder.saveUser(userDTO);
        //刷新token有效期
        stringRedisTemplate.expire(LOGIN_USER_KEY + uuid, LOGIN_USER_TTL, TimeUnit.MINUTES);
        return true;
    }
  • 但是需要注意的是,自定义的登录拦截器只是针对需要登录访问的请求进行了拦截,如果用户访问没被拦截的请求,该拦截器不会生效,则token令牌不能进行更新,当用户长时间访问不需要登录的页面,token令牌失效,再去访问被拦截的请求,则需要重新登录,这是不合理的。所有我们还需要在定义一个拦截器,进行token令牌刷新。

拦截器优化-redis
拦截器优化-redis
  • 刷新令牌的Interceptor

    /**
     * 刷新令牌的拦截器
     * @author liang
     */

    public class RefreshTokenInterceptor implements HandlerInterceptor {

        private StringRedisTemplate redisTemplate;

        public RefreshTokenInterceptor(StringRedisTemplate redisTemplate) {
            this.redisTemplate = redisTemplate;
        }

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

            //从请求头中获取token
            String token = request.getHeader("authorization");
            if (StrUtil.isBlank(token)){
                return false;
            }
            String uuid = token.substring(0, token.lastIndexOf("-"));
            //从Redis中获取值
            Map<Object, Object> userMap = redisTemplate.opsForHash().entries(LOGIN_USER_KEY + uuid);
            if (ObjectUtil.isNull(userMap)){
                return false;
            }
            //将map转换为UserDTO对象
            UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
            //将用户信息保存到 ThreadLocal
            UserHolder.saveUser(userDTO);
            //刷新token有效期
            redisTemplate.expire(LOGIN_USER_KEY + uuid, LOGIN_USER_TTL, TimeUnit.MINUTES);
            return true;

        }

        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            UserHolder.removeUser();
        }
    }
  • 修改登录的Interceptor

    public class LoginInterceptor implements HandlerInterceptor {

        /**
         * preHandle方法的返回值决定是否放行,该方法在控制层方法执行前执行
         * @param request
         * @param response
         * @param handler
         * @return
         * @throws Exception
         */

        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            UserDTO user = UserHolder.getUser();
            return ObjectUtil.isNotNull(user);
        }
    }    
  • 修改WebMvcConfigurer配置类

    @Configuration
    public class MvcConfig implements WebMvcConfigurer {

        @Resource
        StringRedisTemplate stringRedisTemplate;

        /**
         * 添加拦截器
         * @param registry
         */

        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //添加拦截器
            registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate));
            registry.addInterceptor(new LoginInterceptor())
                    //放行资源
                    .excludePathPatterns(
                            "/shop/**",
                            "/voucher/**",
                            "/shop-type/**",
                            "/upload/**",
                            "/blog/hot",
                            "/user/code",
                            "/user/login"
                    )
                    // 设置拦截器优先级
                    .order(1);
        }
    }

本文由 mdnice 多平台发布

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【永久服务器】EUserv

1. 请先自行准备网络&#xff08;我用的伦敦还可以&#xff09;、以及visa卡&#xff0c;淘宝可以代付&#xff0c;我总共花了97人民币&#xff08;10.94欧代付费&#xff09; 现在只能申请一台&#xff0c;多了会被删除&#xff0c;也就是两欧元&#xff0c;然后选择visa卡 选…

Appium之xpath定位详解

目录 一、基础定位 二、contains模糊定位 三、组合定位 四、层级定位 前面也说过appium也是以webdriver为基的&#xff0c;对于元素的定位也基本一致&#xff0c;只是增加一些更适合移动平台的独特方式&#xff0c;下面将着重介绍xpath方法&#xff0c;这应该是UI层元素定位…

linux查找文件内容命令之grep -r ‘关键字‘

目录 grep命令介绍参数选项 grep命令的使用1. 在指定的文件中查找包含的关键字2. 在指定目录下多个文件内容中查找包含的关键字3.在追加的文件内容中查找关键字4. 统计文件中关键字出现的次数5. vi或vim打开的文件查找关键字(补充) 总结 grep命令介绍 Linux操作系统中 grep 命…

C++面向对象丨4. 文件操作

操作系统&#xff1a;Windows IDE&#xff1a;Visual Studio 2019 文章目录 1 文本文件1.1 写文件1.2 写文件实例1.3 读文件1.4 读文件实例 2 二进制文件2.1 写文件2.2 写文件实例2.2 读文件2.4 读文件实例 程序运行时产生的数据都属于临时数据&#xff0c;程序一旦运行结束都会…

【虚拟机搭建-VMware设置固定IP】VMWare中CentOS如何设置固定IP【不成功手把手教学】

1、背景 在日常工作学习中&#xff08;比如博主在之前学习k8s过程中&#xff0c;windows本地搭建虚拟机&#xff0c;重启windows后&#xff09;虚拟机的IP会发生变化&#xff0c;所以该篇文章详细记录VMWare中CentOS如何设置固定IP 2、虚拟机安装 参考&#xff1a; https:/…

虚幻引擎(UE5)-大世界分区WorldPartition教程(三)

文章目录 前言LevelInstance的使用1.ALevelInstance2.选择Actor创建关卡3.运行时加载LevelInstance 总结 上一篇&#xff1a;虚幻引擎(UE5)-大世界分区WorldPartition教程(二) 前言 在制作大关卡时&#xff0c;可能会遇到这样一种情况&#xff0c;就是关卡中的某些Actor会重复…

基于51单片机的智能灌溉系统

目录 基于51单片机的智能灌溉系统一、原理图二、部分代码三、视频演示 基于51单片机的智能灌溉系统 功能&#xff1a; 1.通过LCD屏幕显示光照强度、土壤湿度以及温度 2.通过按键调整手自动模式、手动模式下可手动打开灌溉 3.若温湿度不在范围内&#xff0c;实现报警功能 4.通过…

如何在前端写播放音频

ml(html文档、wxml文档等) <audio action{{action}} src"http://music.163.com/song/media/outer/url?id2059780541.mp3"></audio> js文档 action:{"method":"play"}

第十六届CISCN复现MISC——国粹

国粹 不是我说&#xff0c;我当时比赛的时候&#xff0c;在那里叭叭叭的数的老用心了结果他是一道非常不常规的图片密码题&#xff0c;又是一种我没见过的题型 看了一些大佬的解题&#xff0c;知道他是一个坐标类型的图片拼凑 发现很多都提到了opencv&#xff0c;又是一个知识…

Flink报错大全

1.flink版本由1.13.6提升到1.15.2 maven jar依赖问题 官方因为1.15.2弃用了2.11版本的scala,jar的命名也发生改变,部分默认的2.12版本的scala不用再特声明 1.15.2版本maven依赖 <repositories><repository><id>aliyunmaven</id><name>阿里云…

一步一步学OAK之九:通过OAK相机实现视频帧旋转

目录 Setup 1: 创建文件Setup 2: 安装依赖Setup 3: 导入需要的包Setup 4: 定义变量Setup 5: 定义旋转矩形的四个顶点坐标Setup 6: 创建pipelineSetup 7: 创建节点Setup 8: 设置属性Setup 9: 建立链接Setup 10: 连接设备并启动管道Setup 11: 创建与DepthAI设备通信的输入队列和输…

SpringBoot + Vue前后端分离项目实战 || 二:Spring Boot后端与数据库连接

系列文章&#xff1a; SpringBoot Vue前后端分离项目实战 || 一&#xff1a;Vue前端设计 SpringBoot Vue前后端分离项目实战 || 二&#xff1a;Spring Boot后端与数据库连接 SpringBoot Vue前后端分离项目实战 || 三&#xff1a;Spring Boot后端与Vue前端连接 SpringBoot V…