基于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指明不拦截的请求
相关推荐
Java爱好狂.5 小时前
Java初学者如何一周速通SpringBoot!
java·spring boot·后端·spring·高并发·架构师·java面试
马良神笔7 小时前
2026 年Rag最近技术前沿总结
后端·python·django
程序猿DD7 小时前
同样10美元,OpenDesign和Command Code的DeepSeek V4.1 Flash额度到底有多少?
后端
高频因子挖掘机7 小时前
A股量化从日线升级到5分钟线:数据量增加后最先暴露哪些工程问题?
后端·github·api
高频因子挖掘机7 小时前
历史行情返回顺序变了,为什么按行号计算指标很危险?
后端·github·api
团子哒哒9 小时前
数组、切片和Map
后端
专注仿真9 小时前
ThinkPHP6 API接口开发实例:从零搭建RESTful风格接口
后端·php·restful
开心猴爷9 小时前
应对iOS审核4.3:可以使用 IpaGuard 来给属性名混淆的技术细节与平衡之道
后端·ios
1m555555559 小时前
RAG 工程实战(五):正确知识已经进入 Prompt,为什么大模型还是会答错?
后端·面试
易小尊9 小时前
我的模型在 65% 以上时准得可怕,在 50% 时像在掷硬币
后端