Redis的使用(三)常见使用场景-session共享

1.绪论

redis是一款高性能的缓存框架,那它在实际开发过程中有哪些作用呢?今天我们就来聊一聊。

2.session共享

2.1  问题描述

现在所有的网页都有一个功能,就是在用户第一次登陆成功过后,再次进入到这个页面,就不用二次登陆了,那这种场景式如何实现的呢?

2.2 传统登录流程

传统方式是在用户登录过后,用户会校验登录是否成功,如果登录成功,会将用户信息保存到session中,后面用户再次访问的时候,会通过cookie的方式将用户的sessionid携带过来,如果用户在session中根据sessionId查询到用户信息,便表示当前用户已经登录过。步骤如下:

2.2.1 发送短信验证码

发送短信验证码:

1.用户提交手机号,点击登录;

2.查询数据库,校验用户是否存在;

3.如果用户不存在,生成验证码,并将验证码保存到session中;

4.给用户返回验证码

 public Result sendCode(String phone, HttpSession session) {
        //1.校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 2.如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        //2.生成验证码
        String code = RandomUtil.randomNumbers(6);
        //3.将验证码放入到session中
        session.setAttribute("code", code);
        //4.给用户发送验证码
        log.debug("用户验证码为;{}", code);
        //5.返回成功
        return Result.ok();
    }

2.2.2 登录注册 

用户登录注册流程:

1.用户填写手机号和验证码;

2.在session中根据前端携带的cookie中的sessionId查询到当前用户的验证码,并于用户的验证码进行比对;

3.验证通过,根据手机号查询数据库,如果数据库存在该用户,便将用户保存到session中;

4.如果用户不存在,便创建用户并插入到数据库,同时保存到session中;

public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        //1.校验手机号
        if (RegexUtils.isPhoneInvalid(loginForm.getPhone())) {
            // 2.如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }

        // 2.获取缓存中的验证码
        String code = loginForm.getCode();
        Object codeCache = session.getAttribute("code");

        //3.比较验证码是否正确
        if (codeCache == null || !code.equals(codeCache)) {
            return Result.fail("登录失败");
        }
        //4.如果比对通过,查询数据库
        User user = query().eq("phone", phone).one();
        if (user == null) {
            //5.未查询到用户,表示第一次注册,直接新建一个用户并插入
             User user = new User();
            user.setPhone(phone);
            user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
            // 2.保存用户
            save(user);
            return user;
        }
        //5.保存到session中
        session.setAttribute("user", user);
        return Result.ok();
    }

2.2.3 再次登录 

用户再次登录流程:

1.用户调用其他接口,会携带cookie;

2.根据cookie中的sessionId在session中查询;

3.如果session中存在用户,便将用户封装到ThreadLocal中,调用接口;

4.如果不存在用户,便返回登录失败;

public class LoginInterceptor implements HandlerInterceptor {

    @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;
    }
}

2.3 传统登录流程的缺点

1.用于用户信息是存储在session中的,而session是属于一个Tomcat实例,如果采用分布式部署的话,当请求到tomcat1的话,保存到tomcat1的话,下次登录请求到tomcat2的话,便查询不到用户信息。所以如何将用户信息同步到整个集群,便成了一个问题。

2.4 基于redis实现的登录

redis的登录其实就是将用户信息放到redis中,下次登录的时候,直接从redis的中获取。

2.4.1 发送短信验证码

发送短信验证码:

1.用户提交手机号,点击登录;

2.查询数据库,校验用户是否存在;

3.如果用户不存在,生成验证码,并将验证码保存到redis中;

4.给用户返回验证码

 public Result sendCode(String phone) {
        //1.校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 2.如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        //2.生成验证码
        String code = RandomUtil.randomNumbers(6);
        //3.将验证码放入到redis中
        stringRedisTemplate.opsForValue().set("code:" + phone, code);
        //4.给用户发送验证码
        log.debug("用户验证码为;{}", code);
        //5.返回成功
        return Result.ok();
    }

2.4.2 登录注册

用户登录注册流程:

1.用户填写手机号和验证码;

2.在redis中根据用户的手机号作为key查询,查询到用户的验证码,并校验验证码

3.验证通过,根据手机号查询数据库,如果数据库存在该用户,便生成一个token用来标识该用户,并且将其作为key,user信息作为value存储到redis中。

4.如果用户不存在,便创建用户并插入到数据库,同时保存到redis中;

   public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1.校验手机号
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 2.如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        // 3.从redis获取验证码并校验
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        String code = loginForm.getCode();
        if (cacheCode == null || !cacheCode.equals(code)) {
            // 不一致,报错
            return Result.fail("验证码错误");
        }

        // 4.一致,根据手机号查询用户 select * from tb_user where phone = ?
        User user = query().eq("phone", phone).one();

        // 5.判断用户是否存在
        if (user == null) {
            // 6.不存在,创建新用户并保存
            user = createUserWithPhone(phone);
        }

        // 7.保存用户信息到 redis中
        // 7.1.随机生成token,作为登录令牌
        String token = UUID.randomUUID().toString(true);
        // 7.2.将User对象转为HashMap存储
        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()));
        // 7.3.存储
        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        // 7.4.设置token有效期
        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);

        // 8.返回token
        return Result.ok(token);
    }

注:为什么这里要生成一个sessionId作为唯一标识。原因前端请求用户信息的时候,会将token放到请求头中,如果是将phone放到请求头中,会有安全问题。

2.4.3 再次登录

1.获取到用户的token信息

2.根据token从redis中取出userInfo信息

3.如果userInfo信息不存在,便登录失败

4.如果userInfo信息存在,便重新设置toke在redis中的存活时间,并且将userInfo存储到ThreadLocal中。

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        // 2.基于TOKEN获取redis中的用户
        String key  = LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 3.判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        // 5.将查询到的hash数据转为UserDTO
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 6.存在,保存用户信息到 ThreadLocal
        UserHolder.saveUser(userDTO);
        // 7.刷新token有效期
        stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8.放行
        return true;
    }

相关推荐

  1. Redis使用常见使用场景-session共享

    2024-07-10 08:40:04       32 阅读
  2. Redis 常见使用场景

    2024-07-10 08:40:04       33 阅读
  3. redis常见使用场景

    2024-07-10 08:40:04       28 阅读
  4. redis常见使用场景

    2024-07-10 08:40:04       24 阅读
  5. 怎么使用Redis模拟Session

    2024-07-10 08:40:04       40 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-10 08:40:04       99 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-10 08:40:04       107 阅读
  3. 在Django里面运行非项目文件

    2024-07-10 08:40:04       90 阅读
  4. Python语言-面向对象

    2024-07-10 08:40:04       98 阅读

热门阅读

  1. DS200CVMAG1AEB处理器 控制器 模块

    2024-07-10 08:40:04       37 阅读
  2. 插8张显卡的服务器有哪些?

    2024-07-10 08:40:04       29 阅读
  3. react antd table拖拽

    2024-07-10 08:40:04       32 阅读
  4. VB 关键字

    2024-07-10 08:40:04       35 阅读
  5. 前端面试题(13)答案版

    2024-07-10 08:40:04       35 阅读
  6. 智能警卫:Conda包依赖的自动监控之道

    2024-07-10 08:40:04       34 阅读
  7. vue处理重复请求

    2024-07-10 08:40:04       29 阅读
  8. 深度学习:从数据采集到模型测试的全面指南

    2024-07-10 08:40:04       25 阅读
  9. jQuery Mobile 实例

    2024-07-10 08:40:04       31 阅读
  10. Electron 简单搭建项目

    2024-07-10 08:40:04       35 阅读