项目1在线交流平台-2.开发交流社区注册登录模块-6.拦截器-显示登录状态信息

参考牛客网高级项目教程

1. 显示登录信息功能需求

要实现的功能:

在这里插入图片描述

  • 用户在未登录和登录两种状态,访问页面时,应该显示不同的头部信息
    • 即,未登录状态:
      • 访问主页,有登录、注册选项,没有个人信息状态展示
    • 登录状态:
      • 访问主页,没有登录、注册选项,显示个人信息状态

为何使用拦截器及拦截器本质

  • 策略一:如果在每个请求中进行处理,

    • 会增大代码的重复工作量,每个请求中都要处理类似逻辑
    • 耦合性也很高,每个请求中要引入其他请求中获取的用户信息
  • 策略二:采用请求拦截器处理

    • 即,在每个请求前请求后,模板渲染前模板渲染后进行横向拦截

    • 在拦截器中处理相关业务,不改变原有请求逻辑,减少耦合度和重复代码量

      • 查询用户信息,
      • 持有用户信息,
      • 以及交给模板展示用户数据,
      • 清理用户数据
    • 拦截器的使用,本质是面向切面编程,使用了静态代理模式

2. 拦截器使用示例

2.1 设计拦截器实现类(代理角色)

  • 即定义拦截的切入点
交给ioc管理

​ @Component

实现接口,重写方法
HandlerInterceptor
  • 拦截器处理接口
preHandle
  • 在Controller之前执行
  • return true; 表示拦截处理之后继续执行,false就不往下进行了
  • Object handler,拦截请求中的方法名
postHandle
  • 在Controller之后,模板视图渲染前执行
afterCompletion
  • 在模板视图渲染后处理,一般做清理工作

    @Component
    public class AlphaInterceptor implements HandlerInterceptor {
        private Logger logger = LoggerFactory.getLogger(AlphaInterceptor.class);
    
        // 在Controller之前执行
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            logger.debug("preHandle" + handler.toString());
            return true;    // true,表示拦截处理之后继续执行
        }
    
        // 在Controller之后,模板视图渲染前执行
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            logger.debug("postHandle" + handler.toString());
        }
    
        // 在模板视图渲染后处理,一般做清理工作
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            logger.debug("afterHandle" + handler.toString());
        }
    }
    

2.2 配置-拦截哪些请求的哪些方法

  • 即配置拦截的切面和通知方法
交给ioc管理

​ @Configuration

实现接口,重写方法
WebMvcConfigurer
registry.addInterceptor(拦截器类)
  • 注入哪个拦截器类-定义切面
  • 还是返回registry对象,方便连续调用其他函数
excludePathPatterns
  • 配置拦截路径-排除哪些路径不需要拦截
addPathPatterns
  • 添加需要拦截哪些url路径请求

  • 不添加,默认除了排除的路径外所有请求路径

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
        @Autowired
        AlphaInterceptor alphaInterceptor;
    
        // 注入设置:注入哪个拦截器-拦截哪些路径(排除哪些路径,添加哪些路径)
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(alphaInterceptor)
                    .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg")
                    .addPathPatterns("/register", "/login"); // 不添加,默认除了排除的路径外所有请求路径
        }
    }
    

测试结果:

  • 不是在特定拦截路径下的请求:

    • 如主页/index请求,没有相关日志打印信息
  • 在特定拦截路径下的请求:

    在这里插入图片描述

    preHandlepublic java.lang.String com.nowcoder.community.controller.LoginController.getLoginPage() // 拦截请求中的方法名
    postHandlepublic java.lang.String com.nowcoder.community.controller.LoginController.getLoginPage()
    afterHandlepublic java.lang.String com.nowcoder.community.controller.LoginController.getLoginPage()
    
    

3. 登录注册模块拦截器的实例

3.1 设计拦截器实现类

拦截器类要实现的功能需求

在这里插入图片描述

  • 每个请求都需要被服务器验证cookie中的ticket
  • 服务器会通过ticket查询用户信息,并将用户信息交给模板页面渲染
  • 模板页面会根据是否有用户信息进行区别渲染显示-从而区分登录与未登录状态

所以,拦截器要增强请求的这方面功能,帮助每个请求实现这些功能

1)preHandle
  • 目标是在controller处理请求前,要通过请求中传过来的ticket,查询到user信息
    • 1.由于cookie中key很多,要找指定的key,需要设计一个工具类去获取
    • 2.根据ticket查询到有效user用户信息
      • 注意不能直接通过Ticket类查询用户信息,因为需要判断t票是否有效,登出也无效
      • 只有t票有效,才能查询有效的用户信息
    • 3.将user用户信息暂存在当前请求线程容器中,即持有用户
      • 注意不能简单存储到一个类中,
        • 因此多个请求对一个服务器,每个线程处理一个请求,涉及到多线程并发问题
      • 因此,需要线程隔离,存到session中自动会线程隔离,但session中不适合分布式部署
      • 因此,需要设计一个线程隔离的工具类hostHodel,使用ThreadLocal类实现线程隔离
        • 保存在线程容器中,请求没结束前,线程一直活着,一直持有有效用户信息
1.获取ticket值的cookie工具类设计
request.getCookies()
  • 注意边界条件:判空处理

    • 不为空,遍历cookie数组,找到指定key的cookie
    • 如果为空,返回值设为null,交给上级去判断处理
    public class CookieUtil {
        public static String getValue(HttpServletRequest request, String name) {
            Cookie[] cookies = request.getCookies();
            if(cookies != null) {
                for (Cookie cookie : cookies) {
                    if (cookie.getName().equals(name)) {
                        return cookie.getValue();
                    }
                }
            }
            return null;
        }
    }
    
2.根据ticket查询用户信息
  • 在service层添加查询业务,只能查询到t票类,还有进一步验证t票是否有效

    // 根据用户凭证ticket查询t票类
        public LoginTicket selectByTicket(String ticket) {
            return loginTicketMapper.selectByTicket(ticket);
        }
    
.after(new Date()
  • 验证t票是否有效,有效才查询有效用户信息

    • t票不为空
    • 状态为0,有效状态
    • 有效时间在当前时间之后
    // 2.根据ticket查询user
    if(ticket != null) {
        // 向数据库中查询凭证
        LoginTicket loginTicket = userService.selectByTicket(ticket);
        if(loginTicket != null &&
           loginTicket.getStatus() == 0 &&
           loginTicket.getExpired().after(new Date())) {
            // 有效,就根据凭证找到用户信息
            User user = userService.findUserById(loginTicket.getUserId());
            // 在本次请求中持有用户信息,在请求结束前一直保存在请求的当前线程容器中
            hostHolder.setUsers(user);
        }
    }
    
3.设计存储user的线程隔离工具类
ThreadLocal<>
  • ThreadLocal:线程处理工具类,提供了线程安全的set,get方法
set(user)
  • 往单个个线程都存user信息

    • 先拿到当前线程,再往当前线程里存值
    在这里插入图片描述
users.get()
  • 先拿到当前线程,再从当前线程里取值

    在这里插入图片描述
users.remove()
  • 清理当前线程的值

    在这里插入图片描述
  • 总体代码实现:

    @Component
    public class HostHolder {
        private ThreadLocal<User> users = new ThreadLocal<>();
    
        // 往单个个线程都存user信息
        // 先拿到当前线程,再往当前线程里存值
        public void setUsers(User user) {
            users.set(user);
        }
    
        // 先拿到当前线程,再从当前线程里取值
        public User getUser() {
            return users.get();
        }
    
        // 同理,清理当前线程的user值
        public void clear() {
            users.remove();
        } 
    }
    
2)postHandle
  • 目标是用持有的用户信息,即Controller处理后,模板渲染前,将用户信息放到modelAndView中

  • 模板页面根据持有的用户信息不同(是否有)区别渲染页面

    // 在Controller之后,模板视图渲染前执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 在渲染前获取用户信息具体,进行渲染
        User user = hostHolder.getUser();
        if(user != null && modelAndView != null) {
            logger.debug("postHandle " + user.toString());
            modelAndView.addObject("loginUser", user);
        }
    }
    
3)afterCompletion
  • 渲染页面之后,即将渲染页面渲染显示给浏览器,这个请求处理完成后,将线程容器中的user信息清空

    // 在模板视图渲染后处理,一般做清理工作
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            logger.debug("afterHandle " + handler.toString());
            hostHolder.clear();
        }
    }
    

3.2 拦截器配置

  • 排除静态资源后,默认是所有项目下的请求路径

     	@Autowired
        LoginTicketInterceptor loginTicketInterceptor;
    
        // 注入设置:注入哪个拦截器-拦截哪些路径(排除哪些路径,添加哪些路径)
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(loginTicketInterceptor)
                    .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");      
        }
    

3.3 在模板页面区分处理携带user状态信息

th:if="${loginUser != null}"
  • 根据请求线程中持有的user用户信息是否有,动态判断是否显示
    • 消息:不为空才渲染
    • 注册、登录: 为空才渲染
    • 用户信息:不为空才渲染
      • 将用户头像、用户名动态显示为线程中持有用户的信息

测试结果

  • 登录前

    在这里插入图片描述

  • 登录后

    在这里插入图片描述

  • 退出登录后

    在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值