Redis实战案例及问题分析之redis实现短信登陆

短信登陆

基于session短信的登陆存在的问题:

集群的session共享问题:多台tomcat并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失的问题。Redis实战案例及问题分析之redis实现短信登陆_第1张图片

session的替代方案应该满足:

  • 数据共享
  • 内存数据:读写效率高
  • key、value结构:方便存储方案

使用redis代替session:

  • redis是tomcat以外的存储方案,所以任意一台tomcat都能访问到redis拿到数据实现数据共享
  • redis是内存存储
  • redis是key、value结构

Redis实战案例及问题分析之redis实现短信登陆_第2张图片

 基于redis实现共享session登录

Redis实战案例及问题分析之redis实现短信登陆_第3张图片

发送验证码功能:

生成验证码之后,以手机号为key,验证码为value,类型为string存入redis。

@Resource
    private StringRedisTemplate stringRedisTemplate;

    public Result sendcode(String phone, HttpSession session){
        //1.判断手机是否正确
        if (RegexUtils.isPhoneInvalid(phone)) {
            //验证失败,返回错误信息
            return Result.fail("手机格式输入错误!");
        }
        //2.生成验证码
        String code = RandomUtil.randomNumbers(6);
        //3.把验证码保存到redis,设置短信验证码有效期
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);

        //4.发送验证码
        log.debug("发送短信验证码成功,验证码{}",code);
        return Result.ok();
    }

短信验证码登陆注册:

以手机号码为key去redis读取验证码,校验是否一致,如果一致以随机生成的token为key,用户信息以hash类型为value存入redis数据库,并返回给客户端(一般是浏览器)

@Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //1.校验手机
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            //验证失败,返回错误信息
            return Result.fail("手机格式输入错误!");
        }
        //TODO 2.从redis获取验证码并校验验证码
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        if (cacheCode == null || !cacheCode.equals(loginForm.getCode())){
            return Result.fail("验证码错误请重试!");
        }
        // 3.查询手机是否在数据库里
        User user = query().eq("phone", phone).one();
        //4.如果不在就把该用户注册进数据库
        if (user == null){
            user = creatUserByPhone(phone);

        }
        // 5.把用户放入redis
        //5.1随机生成token,作为登录令牌
        String token = UUID.fastUUID().toString(true);
        // 5.2将user对象转换为Hashmap存储
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create().
                setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));
        // 5.3存储到redis
        stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token,userMap);
        //5.4设置token有效期
        stringRedisTemplate.expire(LOGIN_USER_KEY+token,LOGIN_USER_TTL,TimeUnit.MINUTES);

        // 6返回token给客户端
        return Result.ok(token);


    }

Redis实战案例及问题分析之redis实现短信登陆_第4张图片 校验登录功能:

因为在短信验证码登陆注册功能中把用户的token保存在了客户端(一般是浏览器),那么用户每次访问都会携带这个token,就可以根据这个token去redis里面查询用户信息。

拦截器优化:

一开始的拦截器设置是要用户有操作才会刷新用户的token保存时间,优化拦截器之后无论访问的是不是需要用户登录的页面都可以刷新token有效期Redis实战案例及问题分析之redis实现短信登陆_第5张图片

public class RefreshTokenInterceptor implements HandlerInterceptor {

    /*
    因为这个类RefreshTokenInterceptor对象是我们自己在需要使用到它的时候手动New出来的,
    不是由spring创建的,所以没办法用@Resource/@Autowired这些注解来注入StringRedisTemplate属性
    那谁来注入这个StringRedisTemplate呢?就看是谁用到了RefreshTokenInterceptor类的对象,
    就在用到的那里注入那谁来注入这个StringRedisTemplate,比如这里是在MvcConfig类中
     */
    private StringRedisTemplate stringRedisTemplate;

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

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求头中的token,根据前端代码中定义的变量名来取
        String token = request.getHeader("authorization");
        //判断token是否为空
        if (StrUtil.isBlank(token)) {

            return true;

        }
        //基于token获取redis用户
        Map userMap = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);
        //判断用户是否存在
        //如果不存在
        if (userMap.isEmpty()) {

            return true;
        }
        //将查询到的Hash数据转化为UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //用户存在,保存到threadlocal
        UserHolder.saveUser(userDTO);
        //刷新token有效期
        stringRedisTemplate.expire(LOGIN_USER_KEY + token,LOGIN_USER_TTL, TimeUnit.MINUTES);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //移除用户
        UserHolder.removeUser();
    }
}
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;
    }

}

你可能感兴趣的:(redis实战,java,开发语言,redis)