Redis学习笔记-003

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

文章目录

  • Redis企业实战---基于Redis短信验证功能
  • 一、短信登录实现
    • 1.1、导入[黑马点评项目](https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwd=eh11)
    • 1.2、基于Session实现
    • 1.3、集群的session的共享问题
    • 1.4、基于Redis实现共享session登录

Redis可以进行如下操作

Redis学习笔记-003_第1张图片

一、短信登录实现

1.1、导入黑马点评项目

表的基本信息
Redis学习笔记-003_第2张图片
前后端分离
Redis学习笔记-003_第3张图片
导入后端项目
Redis学习笔记-003_第4张图片
导入前端项目

Redis学习笔记-003_第5张图片
运行前端项目

Redis学习笔记-003_第6张图片

1.2、基于Session实现

实现流程
Redis学习笔记-003_第7张图片
发送验证码
Redis学习笔记-003_第8张图片
发送验证码

主要进行如下操作:

  • 对手机号进行校验
  • 模拟生成一个验证码并保存在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();
    }

登录

主要进行如下操作:

  • 通过数据绑定获取到前端的表单数据
  • 对手机号进行校验
  • 对验证码进行检验看是否和存在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;

    }

登录验证功能
Redis学习笔记-003_第9张图片

定义拦截器

主要进行如下操作:

  • 获取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);
    }
}

配置拦截器

主要进行如下操作:

  • 将定义的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.3、集群的session的共享问题

session实现共享所面临的问题:
Redis学习笔记-003_第10张图片
Redis代替session需要考虑的问题

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

短信验证和登录注册
Redis学习笔记-003_第11张图片
验证码发送

主要进行如下操作:

  • 检验手机号是否合法
  • 生成一个模拟验证码,并将验证码保存到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();
    }

校验登录状态
Redis学习笔记-003_第12张图片
登录凭证token的存储方式
Redis学习笔记-003_第13张图片
登录代码

主要操作分为以下几点:

  • 获取表单的数据,进行手机号的验证
  • 从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);
    }

拦截器配置

主要获得请求头携带的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);
    }
}

注入拦截器

将定义的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.4、基于Redis实现共享session登录

你可能感兴趣的:(redis,redis,学习,笔记)