黑马点评_登录模块

news/2025/2/22 20:59:11

 

java">    /**
     * 发送验证码
     */
    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1、判断手机号是否合法
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式不正确");
        }
        // 2、手机号合法,生成验证码,并保存到Session中
        String code = RandomUtil.randomNumbers(6);
        session.setAttribute(SystemConstants.VERIFY_CODE, code);
        // 3、发送验证码
        log.info("验证码:{}", code);
        return Result.ok();
    }

    /**
     * 用户登录
     */
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        String code = loginForm.getCode();
        // 1、判断手机号是否合法
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式不正确");
        }
        // 2、判断验证码是否正确
        String sessionCode = (String) session.getAttribute(LOGIN_CODE);
        if (code == null || !code.equals(sessionCode)) {
            return Result.fail("验证码不正确");
        }
        // 3、判断手机号是否是已存在的用户
        User user = this.getOne(new LambdaQueryWrapper<User>()
                .eq(User::getPassword, phone));
        if (Objects.isNull(user)) {
            // 用户不存在,需要注册
            user = createUserWithPhone(phone);
        }
        // 4、保存用户信息到Session中,便于后面逻辑的判断(比如登录判断、随时取用户信息,减少对数据库的查询)
        session.setAttribute(LOGIN_USER, user);
        return Result.ok();
    }

    /**
     * 根据手机号创建用户
     */
    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;
    }

 

 

java">public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 前置拦截器,用于判断用户是否登录
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        // 1、判断用户是否存在
        User user = (User) session.getAttribute(LOGIN_USER);
        if (Objects.isNull(user)){
            // 用户不存在,直接拦截
            response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
            return false;
        }
        // 2、用户存在,则将用户信息保存到ThreadLocal中,方便后续逻辑处理
        // 比如:方便获取和使用用户信息,session获取用户信息是具有侵入性的
        ThreadLocalUtls.saveUser(user);

        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
}

配置完拦截器后,还需要将我们自定义的拦截器添加到SpringMVC的拦截器列表中,才能生效

通过这种方式,你不需要在XML文件中配置拦截器,而是直接在Java代码中进行配置,

它是一个配置类,用于配置Web MVC相关的设置。

java">@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加登录拦截器
        registry.addInterceptor(new LoginInterceptor())
                // 设置放行请求
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/voucher/**"
                );
    }
}

 数据脱敏 将session里面的数据量减小 去掉敏感信息 session是内存空间,存储信息越多对服务器压力越大

  1. 封装UserDTO,返回给前端的Entity数据使用BeanUtil工具类转成DTO
  2. 存储到ThreadLocal中的数据也进行数据脱敏

 Session集群共享

 在分布式集群环境中,会话(Session)共享是一个常见的挑战。默认情况下,Web 应用程序的会话是保存在单个服务器上的,当请求不经过该服务器时,会话信息无法被访问。

服务器之间无法实现会话状态的共享。比如:在当前这个服务器上用户已经完成了登录,Session中存储了用户的信息,能够判断用户已登录,但是在另一个服务器的Session中没有用户信息,无法调用显示没有登录的服务器上的服务

 

 

 

 

 

 选择数据类型

  • String 数据结构是以 JSON 字符串的形式保存,更加直观,操作也更加简单,但是 JSON 结构会有很多非必须的内存开销,比如双引号、大括号,内存占用比 Hash 更高
  • Hash 数据结构是以 Hash 表的形式保存,可以对单个字段进行CRUD,更加灵活Redis替代
  • Session需要考虑的问题:
  • 选择合适的数据结构,了解 Hash 比 String 的区别选择合适的key,为key设置一个业务前缀,方便区分和分组,为key拼接一个UUID,避免key冲突防止数据覆盖选择合适的存储粒度,
  • 对于验证码这类数据,一般设置TTL为3min即可,防止大量缓存数据的堆积,而对于用户信息这类数据可以稍微设置长一点,比如30min,防止频繁对Redis进行IO操作

 

java">    /**
     * 发送验证码
     *
     * @param phone
     * @param session
     * @return
     */
    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1、判断手机号是否合法
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式不正确");
        }
        // 2、手机号合法,生成验证码,并保存到Redis中
        String code = RandomUtil.randomNumbers(6);
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code,
                RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);
        // 3、发送验证码
        log.info("验证码:{}", code);
        return Result.ok();
    }

    /**
     * 用户登录
     *
     * @param loginForm
     * @param session
     * @return
     */
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        String code = loginForm.getCode();
        // 1、判断手机号是否合法
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式不正确");
        }
        // 2、判断验证码是否正确
        String redisCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        if (code == null || !code.equals(redisCode)) {
            return Result.fail("验证码不正确");
        }
        // 3、判断手机号是否是已存在的用户
        User user = this.getOne(new LambdaQueryWrapper<User>()
                .eq(User::getPhone, phone));
        if (Objects.isNull(user)) {
            // 用户不存在,需要注册
            user = createUserWithPhone(phone);
        }
        // 4、保存用户信息到Redis中,便于后面逻辑的判断(比如登录判断、随时取用户信息,减少对数据库的查询)
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        // 将对象中字段全部转成string类型,StringRedisTemplate只能存字符串类型的数据
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create().setIgnoreNullValue(true).
                        setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
        String token = UUID.randomUUID().toString(true);
        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);

        return Result.ok(token);
    }

    /**
     * 根据手机号创建用户并保存
     *
     * @param phone
     * @return
     */
    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;
    }

如果访问首页这样的不需要验证token的页面 30min以上就要重新登录 麻烦

所以设置两个拦截器 第一个就验证 

为什么不把刷新的操作放到一个拦截器中呢,因为之前的那个拦截器只是用来拦截一些需要进行登录校验的请求,对于哪些不需要登录校验的请求是不会走拦截器的,刷新操作显然是要针对所有请求比较合理,所以单独创建一个拦截器拦截一切请求,刷新Redis中的Key

 用order设置顺序

刷新token的拦截器:

java">public class RefreshTokenInterceptor implements HandlerInterceptor {

    // new出来的对象是无法直接注入IOC容器的(LoginInterceptor是直接new出来的)
    // 所以这里需要再配置类中注入,然后通过构造器传入到当前类中
    private StringRedisTemplate stringRedisTemplate;

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

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1、获取token,并判断token是否存在
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)){
            // token不存在,说明当前用户未登录,不需要刷新直接放行
            return true;
        }
        // 2、判断用户是否存在
        String tokenKey = LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);
        if (userMap.isEmpty()){
            // 用户不存在,说明当前用户未登录,不需要刷新直接放行
            return true;
        }
        // 3、用户存在,则将用户信息保存到ThreadLocal中,方便后续逻辑处理,比如:方便获取和使用用户信息,Redis获取用户信息是具有侵入性的
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        ThreadLocalUtls.saveUser(BeanUtil.copyProperties(userMap, UserDTO.class));
        // 4、刷新token有效期
        stringRedisTemplate.expire(token, LOGIN_USER_TTL, TimeUnit.MINUTES);
        return true;
    }
}

将自定义的拦截器添加到SpringMVC的拦截器表中,使其生效:

java">@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    // new出来的对象是无法直接注入IOC容器的(LoginInterceptor是直接new出来的)
    // 所以这里需要再配置类中注入,然后通过构造器传入到当前类中
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加登录拦截器
        registry.addInterceptor(new LoginInterceptor())
                // 设置放行请求
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/voucher/**"
                ).order(1); // 优先级默认都是0,值越大优先级越低
        // 添加刷新token的拦截器
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
    }
}

 

 

 


http://www.niftyadmin.cn/n/5862755.html

相关文章

Tesseract OCR使用

1. 准备tif图片 可以使用 Tesseract 自带的 text2image 工具来生成图像可以使用jTessBoxEditor将图片生成tif文件 tif命名规则&#xff1a;[lang].[fontname].exp[num].tif lang 语言fontname 字体num 图片序号 比如我们要训练自定义字库 testlang、字体名normal&#x…

请解释 Vue 中的生命周期钩子,不同阶段触发的钩子函数及其用途是什么?

vue生命周期钩子详解&#xff08;Vue 3版本&#xff09; 一、生命周期阶段划分 Vue组件的生命周期可分为四大阶段&#xff0c;每个阶段对应特定钩子函数&#xff1a; 创建阶段&#xff1a;初始化实例并准备数据挂载阶段&#xff1a;将虚拟DOM渲染为真实DOM更新阶段&#xff…

【mysql共享锁与排他锁】

MySQL共享锁&#xff08;Shared Lock&#xff09;与排他锁&#xff08;Exclusive Lock&#xff09;总结 1. 基本概念 共享锁&#xff08;S锁&#xff09; 作用&#xff1a;用于读取操作&#xff08;读锁&#xff09;。特点&#xff1a;允许多个事务同时持有共享锁&#xff0c;…

如何保证bug在改完之后不会引起新bug

一、测试前&#xff1a;影响范围分析 代码关联分析&#xff1a;依据开发代码关联性检查获取改动信息&#xff0c;聚焦影响范围&#xff0c;明确代码改动影响的业务功能、是否涉及公共组件或接口&#xff0c;检查 Swagger 文档更新情况。若开发无法明确影响范围&#xff0c;在测…

算法-哈希表篇08-四数之和

四数之和 力扣题目链接 题目描述 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] &#xff08;若两个四元组元素一一对应&#xff0c;则认为两个四元组重复&…

Python 循环中的隐藏宝藏:Else 子句深度剖析

在 Python 编程里&#xff0c;循环结构是基础且高频使用的部分&#xff0c;不过其中的 else 子句却常被大家忽略。本文将深入、全面地解析 Python 循环中 else 子句的工作原理、使用方法&#xff0c;通过丰富的代码示例、直观的图表&#xff0c;以及与其他相关知识点的对比&…

简讯:Rust 2024 edition and v1.85.0 已发布

详见 https://blog.rust-lang.org/2025/02/20/Rust-1.85.0.html 升级方法&#xff1a;rustup update stable

51c大模型~合集69

我自己的原文哦~ https://blog.51cto.com/whaosoft/12221979 #7项基于SAM万物分割模型研究工作 1、CC-SAM: SAM with Cross-feature Attention and Context for Ultrasound Image Segmentation #ECCV2024 #SAM #图像分割 #医学图像 Segment Anything Model (SAM) 在自…