基于redis实现登录校验

实现流程

  1. 发送短信验证码,并将验证码保存在redis。

  2. 根据用户登录数据完成登录。

    1. 如果是新用户还要创建用户。
    2. 如果查询到则转化为DTO保存。
      将用户数据被保存到redis,并将token保存到redis,同时返回给前端。
  3. 创建登录校验拦截器。

    注册登录校验拦截器。

发送验证码接口

请求参数:手机号phone

无结果对象

service层实现

java 复制代码
@Override  
public Result sendCode(String phone) {  
    //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();  
}

接口介绍

  1. 根据正则表达式检验手机号格式。
  2. 通过hutool生成6位随机数作为验证码。
  3. 将验证码保存在redis中。
  4. 将验证码发送给客户端(此处应使用相关云服务完成,笔者使用日志只为记录数据)。

登录接口

请求参数:已经封装好的LoginFormDTO对象

结果对象:token

service层实现

java 复制代码
@Override  
public Result login(LoginFormDTO loginForm) {  
    //1.校验手机号格式  
    String phone = loginForm.getPhone();  
    if (RegexUtils.isPhoneInvalid(phone)) {  
        return Result.fail("手机号格式错误!");  
    }  
    //2.校验验证码  
    String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);  
    String code = loginForm.getCode();  
    if (cacheCode == null || !cacheCode.equals(code)) {  
        return Result.fail("验证码错误!");  
    }  
    //3.根据手机号查询用户  
    User user = query().eq("phone", phone).one();  
    //4.判断用户是否存在  
    if (user == null) {  
        //5.没有用户则创建新用户  
        user = createUserWithPhone(phone);  
    }  
    //6.保存用户信息到redis,(以哈希储存)  
        //1.通过UUID拼装tokenKey  
    String token = UUID.randomUUID().toString(true);  
    String tokenKey = LOGIN_USER_KEY + token;  
        //2.将UserDTO对象转为HashMap存储, 由于使用的是stringRedisTemplate,要保证所有的值都是字符串类型,要把lang转换为string  
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);  
  
    Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),  
            CopyOptions  
                    .create()//创建默认数据拷贝选项  
                    .setIgnoreNullValue(true) //忽略null值  
                    /*字段值修改器,接收实现修改的方法*/  
                    .setFieldValueEditor(  
                            (fieldName, fieldValue) -> fieldValue.toString()  
                    )  
            );  
  
    stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);  
        //3.设置token的有效期  
    stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.SECONDS);  
    //7.返回token  
    return Result.ok(tokenKey);  
}

接口介绍

  1. 校验手机号格式。
  2. 校验验证码。
  3. 根据手机号查询用户信息(使用MybatisPlus)。
  4. 通过UUID生成随机token,所为key,将查询后得到的UserDTO(将User转换为UserDTO,避免暴漏敏感数据 ),封装为hash(使用hutool相关API),存入redis,并设置有效期。
  5. 返回token。

手动创建拦截器

在实现了HandlerInterceptor的类中重写preHandle和afterCompletion来实现对每一次请求的处理。

java 复制代码
public class LoginInInterceptor implements HandlerInterceptor {  
  
    private StringRedisTemplate stringRedisTemplate;  
  
    public LoginInInterceptor(StringRedisTemplate stringRedisTemplate) {  
        this.stringRedisTemplate = stringRedisTemplate;  
    }  
      
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
  
        //获取请求头中的token  
        String token = request.getHeader("authorization");  
        if (token == null) {  
            response.setStatus(401);  
            return false;  
        }  
        //根据token从redis中获取用户信息  
        String key = LOGIN_CODE_KEY + token;  
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);  
        if (userMap.isEmpty()) {  
            response.setStatus(401);  
            return false;  
        }  
  
        //将map转换为UserDTO对象  
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);  
  
        //保存用户信息到ThreadLocal  
        UserHolder.saveUser(userDTO);  
        //刷新token有效期  
        stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.SECONDS);  
        return true;  
    }  
      
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {  
        UserHolder.removeUser();  
    }  
}

用前注意:

该自定义拦截器未交给Spring容器管理,不能使用@Autowired或者@Resource自动注入依赖,此处使用构造器注入stringRedisTemplate。

执行逻辑:
preHandle执行拦截校验

  1. getHeader获取请求头中的token,并进行非空判断。
  2. 根据token从redis中获取userMap,再次使用hutool包的API将map集合转换为实体类。
  3. 使用工具类将userDTO保存到ThreadLocal,实现跨层级数据共享。
  4. 刷新token有效期,提升用户体验。
    afterCompletion执行清理ThreadLocal
    请求完成后清理ThreadLocal中的数据,防止内存泄漏和数据污染。

注册拦截器

在含有@Configuration 且实现了WebMvcConfigurer 的配置类中添加自定义拦截器

java 复制代码
@Configuration  
public class MvcConfig implements WebMvcConfigurer {  
  
    @Resource  
    private StringRedisTemplate stringRedisTemplate;  
  
    @Override  
    public void addInterceptors(InterceptorRegistry registry) {  
        registry.addInterceptor(new LoginInInterceptor(stringRedisTemplate))  
                .excludePathPatterns(  
                        "/user/code",  
                        "/user/login",  
                        "/blog/hot",  
                        "/shop/**",  
                        "/shop-type/**",  
                        "/voucher/**",  
                        "/upload/**"  
                );  
    }  
}

执行逻辑:

  1. 使用@Resource注解注入stringRedisTemplate
  2. 重写addInterceptors方法
  3. 调用registry的addInterceptor方法添加拦截器(形参中new出LoginInInterceptor,并将正确注入的stringRedisTemplate传递给LoginInInterceptor,完成自定义拦截器中的依赖注入)。
  4. 调用excludePathPatterns指明不拦截的请求
相关推荐
zone77392 小时前
005:RAG 入门-LangChain读取表格数据
后端·python·agent
程序员小崔日记2 小时前
一篇文章彻底搞懂 MySQL 和 Redis:原理、区别、项目用法全解析(建议收藏)
redis·mysql·项目实战
用户7344028193422 小时前
mysql如何存储boolean类型
后端
golang学习记2 小时前
Fiber v3 适配器模式:17 种写法随便用,老代码"即插即用"🔌
后端·go
用户020742201752 小时前
从零到一:用 Rust 实现一个简单的区块链
后端
用户7344028193422 小时前
Spring Boot 2.x(十二):Swagger2的正确玩法
后端
狂奔小菜鸡2 小时前
Day40 | Java中的ReadWriteLock读写锁
java·后端·java ee
微学AI2 小时前
【征文计划】基于Rokid 眼镜的AI天气应用+GPS定位+AI旅游规划
后端