基于Redis实现(代替)共享session登录

文章目录

  • 一、基本流程
  • 二、代码
    • 1. 发送验证码部分
    • 2、登录/注册部分
    • 三、拦截器部分

一、基本流程

首先了解一下整个登录的流程。

  1. 输入手机号,点击发送验证码
  2. 后台接收请求,校验手机号,随机生成6位验证码,存入redis并设置有效期,发送短信至手机(发送短信模拟实现)
  3. 模拟手机获取到验证码(后端控制台拿或redis里面拿),输入验证码点击登录
  4. 后台接收登录请求,校验手机号,查redis校验当前验证码是否匹配,匹配后查mysql是否存在该用户,若不存在则创建。将用户信息处理后以key:hash的方式存入redis,key为随机生成的唯一uuid作为token,hash为处理后的用户数据,并设置有效时间,将token返回前端
  5. 登录成功后进行页面跳转,点击“我的”后,后端拦截器进行用户登录校验,若登录,显示个人信息,若未登录,则显示输入手机号发送验证码登录界面
  6. 后端拦截器设置,从请求头中获取到当前用户的token,并根据token从redis中取得用户数据,取得通行并刷新用户数据有效时间,取不到则为未登录。

二、代码

配置文件设置之类的略。

1. 发送验证码部分

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1. 校验手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            //2. 不符合,返回错误信息
            return Result.fail("手机号格式错误");
        }
        //3. 符合,生成验证码
        String code = RandomUtil.randomNumbers(6);
        //4. 保存验证码到redis
        stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY+phone,code,RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);
        //5. 发送验证码
        log.debug("发送短信验证码成功,验证码:{}",code);
        //返回
        return Result.ok();
    }

2、登录/注册部分

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        //1. 校验手机号
        if(RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误");
        }
        //2. 校验验证码
        String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY+phone);
        String code = loginForm.getCode();
        if(cacheCode == null || !cacheCode.equals(code)){
            //3. 不一致,报错
            return Result.fail("验证码错误");
        }
        //4. 一致,根据手机号查询用户
        User user = this.lambdaQuery().eq(User::getPhone, phone).one();
        //5. 判断用户是否存在
        if (user == null) {
            //6. 不存在,创建新用户并保存
            user = createUserWithPhone(phone);
        }
        //7. 保存用户信息到redis
        //7.1 随机生产token,作为登录令牌
        String token = UUID.randomUUID().toString(true);
        //7.2 将User对象转为Hash
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
                CopyOptions.create().setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
        //7.3 存储
        String key = RedisConstants.LOGIN_USER_KEY+token;
        stringRedisTemplate.opsForHash().putAll(key,userMap);
        //7.4 设置30分钟有效期
        stringRedisTemplate.expire(key,RedisConstants.LOGIN_USER_TTL,TimeUnit.MINUTES);
        //8. 返回token
        return Result.ok(token);
    }
    //根据手机号创建用户
    private User createUserWithPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
        this.save(user);
        return user;
    }

三、拦截器部分

RefreshTokenInterceptor用来刷新user在redis中存在时间
LoginInterceptor用来拦截是否登录

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate))
                .addPathPatterns("/**")
                .order(0);
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/code","/user/login",
                        "/blog/hot",
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",//TODO 开启拦截
                        "/voucher/**"
                ).order(1);
    }
}
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(UserHolder.getUser()==null){
            response.setStatus(401);
            return false;
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

public class RefreshTokenInterceptor implements HandlerInterceptor {


    private StringRedisTemplate stringRedisTemplate;

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

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1. 获取请求头中的token
        String token = request.getHeader("authorization");
        //2. 基于token获取redis中用户
        if(StrUtil.isBlank(token)){
            return true;
        }
        String key = RedisConstants.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(BeanUtil.copyProperties(userDTO,UserDTO.class));
        //7. 刷新token有效期
        stringRedisTemplate.expire(key,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        //8. 放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

注:在拦截器中用到了ThreadLocal,可以不用,其他板块用到user信息时从redis中取即可

你可能感兴趣的:(Redis,JAVA,项目,redis,缓存,java)