• Redis学习笔记-003


    Redis企业实战—基于Redis短信验证功能

    Redis可以进行如下操作

    在这里插入图片描述

    一、短信登录实现

    1.1、导入黑马点评项目

    表的基本信息
    在这里插入图片描述
    前后端分离
    在这里插入图片描述
    导入后端项目
    在这里插入图片描述
    导入前端项目

    在这里插入图片描述
    运行前端项目

    在这里插入图片描述

    1.2、基于Session实现

    实现流程
    在这里插入图片描述
    发送验证码
    在这里插入图片描述
    发送验证码

    主要进行如下操作:

    • 对手机号进行校验
    • 模拟生成一个验证码并保存在session中
     @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.发送验证码--aliyun的第三方库
            log.debug("验证码发送成功,验证码:"+code);
            // 5.返回ok
            return Result.ok();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    登录

    主要进行如下操作:

    • 通过数据绑定获取到前端的表单数据
    • 对手机号进行校验
    • 对验证码进行检验看是否和存在session中的验证码一致
    • 按手机号对user表进行查询
    • 没有当前用户,就创建为新用户并保存到session中
     @Override
        public Result login(LoginFormDTO loginForm, HttpSession session) {
    
            String phone = loginForm.getPhone();
    
            // 1.校验手机号
            if (RegexUtils.isPhoneInvalid(phone)) {
                // 不符合就返回错误信息
                return Result.fail("手机号格式错误!");
            }
            // 2.继手机号之后,校验验证码
            String code = loginForm.getCode();
            String cacheCode = (String) session.getAttribute("code");
            if (cacheCode == null || RegexUtils.isCodeInvalid(code) || cacheCode == code) {
                // 3.不一致,报错
                return Result.fail("验证码错误!");
            }
    
            // 4.一致,根据手机号查询用户
            User user = query().eq("phone", phone).one();
    
            // 5.判断用户是否存在
            if (user == null){
                // 6.不存在,创建新用户并保存
                 user = createUserWithPhone(phone);
            }
    
    
            session.setAttribute("user",user);
            //  每一个session都有一个SessionID
            return Result.ok();
        }
    
        private User createUserWithPhone(String phone) {
            // 1. 创建用户
            User user = new User();
            user.setPhone(phone);
            user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));
            // 2.保存用户
            save(user);
            return user;
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    登录验证功能
    在这里插入图片描述

    定义拦截器

    主要进行如下操作:

    • 获取session中的用户
    • 判断用户是否存在,
    • 存在,保存用户信息到 ThreadLocal
    • 放行
    public class LoginInterceptor implements HandlerInterceptor {
    
        // 1. 前置拦截器
        @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. 不存在,拦截,返回401代码
                response.setStatus(401);
                return false;
            }
            // 5. 存在,保存用户信息到 ThreadLocal
            UserHolder.saveUser((User) user);
            // 6. 放行
            return true;
        }
    
    
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    配置拦截器

    主要进行如下操作:

    • 将定义的LoginInterceptor登录拦截器添加进去
    • 并排除一些不需要拦截的请求
    @Configuration
    public class MvcConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
        // 将定义的LoginInterceptor登录拦截器添加进去
      	// 并排除一些不需要拦截的请求
            registry.addInterceptor(new LoginInterceptor())
                    .excludePathPatterns(
                            "/shop/**",
                            "/shop-type/**",
                            "/voucher/**",
                            "/upload/**",
                            "/blog/hot",
                            "/user/code",
                            "/user/login"
                    );
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    1.3、集群的session的共享问题

    session实现共享所面临的问题:
    在这里插入图片描述
    Redis代替session需要考虑的问题

    • 选择合适的数据结构
    • 选择合适的key
    • 选择合适粒度

    短信验证和登录注册
    在这里插入图片描述
    验证码发送

    主要进行如下操作:

    • 检验手机号是否合法
    • 生成一个模拟验证码,并将验证码保存到redis中
    • 给保证验证码的key设置有效期
      @Autowired
        private StringRedisTemplate stringRedisTemplate;
        //引入ObjectMapperJSON处理类
        @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.将验证码保存到Redis中 --- 一般加个前缀即能保证唯一性,又能指明当前key-value的功能
            // 5.设置验证码的key的有效期  LOGIN_CODE_KEY:前缀  LOGIN_CODE_TTL:有效期时间
            stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);
    
            // 6.发送验证码--aliyun的第三方库
            log.debug("验证码发送成功,验证码:"+code);
            // 7.返回ok
            return Result.ok();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    校验登录状态
    在这里插入图片描述
    登录凭证token的存储方式
    在这里插入图片描述
    登录代码

    主要操作分为以下几点:

    • 获取表单的数据,进行手机号的验证
    • 从redis中读取保存的验证码(前缀+手机号为key),并判断是否正确
    • 当数据符合要求时,利用手机号进行查询user表
    • 当没有数据的时候,进行创建新用户并保存
    • 保存用户信息到redis
      • 用随机token 作为登录令牌
      • 将Java对象转换为Hash进行存储
      • 设置token有效期
    @Override
        public Result login(LoginFormDTO loginForm, HttpSession session) {
    
            String phone = loginForm.getPhone();
    
            // 1.校验手机号
            if (RegexUtils.isPhoneInvalid(phone)) {
                // 不符合就返回错误信息
                return Result.fail("手机号格式错误!");
            }
            // TODO 2.从redis获取到验证码并进行验证
            String code = loginForm.getCode();
            String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);
            if (cacheCode == null || RegexUtils.isCodeInvalid(code) || cacheCode == code) {
                // 3.不一致,报错
                return Result.fail("验证码错误!");
            }
    
            // 4.一致,根据手机号查询用户
            User user = query().eq("phone", phone).one();
    
            // 5.判断用户是否存在
            if (user == null){
                // 6.不存在,创建新用户并保存
                 user = createUserWithPhone(phone);
            }
    
            // TODO 7. 保存用户信息到redis,问题进行保存的只能是用户的部分信息,不能包括完整信息,用户的敏感数据必须被隐藏。UserDTD类只包含数据的部分信息
            // 7.1、用随机token 作为登录令牌
            String token = UUID.randomUUID().toString(true);
            //  7.2、将Java对象转换为Hash进行存储
            UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
            Map<String, Object> map = BeanUtil.beanToMap(userDTO);
            String key = LOGIN_USER_KEY+token;
            map.put("id",userDTO.getId().toString());
            stringRedisTemplate.opsForHash().putAll(key,map);
            //  7.3、设置token有效期 30分钟 CACHE_SHOP_TTL常量
            stringRedisTemplate.expire(key,CACHE_SHOP_TTL,TimeUnit.MINUTES);
            return Result.ok(token);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    拦截器配置

    主要获得请求头携带的token(登录动作的返回值,在前端作为数据存储在请求头中),方便后续的操作。主要操作:

    • 创建构造函数,方便MvcConfig 注入StringRedisTemplate对象
    • 获取请求头中的token
    • 基于token获取reids中的用户
    • 判断用户是否为空
    • 将查询到的hash数据转为UserDTD对象
    • 存在,保存用户信息到 ThreadLocal
    • 刷新token的有效期
    • 放行
    public class LoginInterceptor implements HandlerInterceptor {
    
        private StringRedisTemplate stringRedisTemplate;
    
        public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
            this.stringRedisTemplate = stringRedisTemplate;
        }
    
        // 1. 前置拦截器
        @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获取reids中的用户
            Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);
            // 3. 判断是否是空值
            if (map.isEmpty()){
                // 4. 不存在,拦截,返回401代码
                response.setStatus(401);
                return false;
            }
            // 5. 将查询到的hash数据转为UserDTD对象
            UserDTO userDTO = BeanUtil.fillBeanWithMap(map, new UserDTO(), false);
            // 6. 存在,保存用户信息到 ThreadLocal
            UserHolder.saveUser(userDTO);
            // 7. 刷新token的有效期
            String key = LOGIN_USER_KEY + token;
            stringRedisTemplate.expire(key,30, TimeUnit.MINUTES);
            // 8. 放行
            return true;
        }
    
    
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    注入拦截器

    将定义的LoginInterceptor登录拦截器注入到spring中,需要进行以下操作:

    • 排序一些不需要拦截的请求
    • 注入StringRedisTemplate 对象
    @Configuration
    public class MvcConfig implements WebMvcConfigurer {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            // 排除一些不需要拦截的请求
            registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
                    .excludePathPatterns(
                            "/shop/**",
                            "/shop-type/**",
                            "/voucher/**",
                            "/upload/**",
                            "/blog/hot",
                            "/user/code",
                            "/user/login"
                    );
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    1.4、基于Redis实现共享session登录

  • 相关阅读:
    mac每次重启后都会出现zsh: command not found: code
    编程-设计模式 2:抽象工厂模式
    项目管理高手是如何应对冲突的?这几点很重要!
    前端常见安全问题
    NVIDIA NCCL 源码学习(五)- 路径计算
    神经网络正则化技术防过拟合和R语言CNN卷积神经网络手写数字图像数据MNIST分类
    “扫一扫,不一定是二维码” ScanCan GitHub开源项目发起
    nvm安装及使用,nodejs版本切换使用
    uiautomation函数讲解(中)
    Java LinkedList类详解
  • 原文地址:https://blog.csdn.net/weixin_46625488/article/details/133231989