这篇最初记录的是登录拦截器与 ThreadLocal,并非完整部署流水账。下面保留原笔记,在文末补充课程文档中的本地部署方案;方案笔记不等于已经完成线上部署。

TreadLocal保存当前用户信息

1.1 ThreadLocal概述

  • ThreadLocal是Java中用于实现线程局部变量的工具类,它为每个线程提供独立的变量副本,解决多线程环境下的数据隔离问题。其核心原理是通过ThreadLocalMap存储线程专属数据,避免同步开销,适用于数据库连接管理、用户会话存储等场景,但需注意内存泄露风险
  • ThreadLocal和当前线程绑定到一起,当前线程结束了,ThreadLocal就没有了
  • ThreadLoca没有了具体指的是 当前对象 没有了,值还存在
  • map.set(this, value); this 没有了,value还在内存中
  • 手动清除value值,remove方法,不然造成内存泄露

1.2 使用场景

接口:获取登录用户个人信息

  • 登录成功后,跳转到主页面中,在主页面里面调用接口,获取当前登录成功的用户信息(用户名或者头像等)

  • 分析过程

    • 浏览器发送请求,首先到拦截器,拦截器把请求拦下
    • 在拦截器校验是否登录,如果是登录,把请求放行
    • 放行之后,执行后面接口,接口返回数据
    • 这样完成一次请求过程
    • 简单理解为一次请求开启线程
    • 使用ThreadLocal和当前线程绑定到一起
  • 具体实现

      1. 创建ThreadLocal工具类,封装向ThreaLocal放数据,取数据和清除数据的方法
      1
      2
      3
      4
      5
      6
      7
      8
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class LoginUser {

      private Long userId;
      private String username;
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      public class LoginUserContext {

      private static final ThreadLocal<LoginUser> userThreadLocal = new ThreadLocal<>();
      // 存放
      public static void setLoginUser(LoginUser loginUser) {
      userThreadLocal.set(loginUser);
      }
      // 获取
      public static LoginUser getLoginUser() {
      return userThreadLocal.get();
      }
      // 清除
      public static void clear() {
      userThreadLocal.remove();
      }
      }
    • 在拦截器方法里面使用ThreadLocal,向ThreadLocal中放用户数据

    • 在接口中,直接从ThreadLocal获取拦截器时放数据

    • Token 只在拦截器解析一次,然后通过 ThreadLocal 保存当前登录用户,Controller 和 Service 直接获取,无需重复解析 JWT。

      1. 在拦截器方法中使用、threadLocal,向ThreadLocal里存放用户数据
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      @Component
      public class AuthenticationInterceptor implements HandlerInterceptor {


      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      String token = request.getHeader("access-token");
      //String token = "aaaaabc";

      //String token = "eyJhbGciOiJIUzI1NiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAA_6tWKi5NUrJSCg12DYr39HPzV9JRSq0oULIyNLcwMTUxNjY10lEqLU4t8kxRsrI0hLDzEnNTgXpySpMrlWoBQVhMh0IAAAA.ZEWIGVl1GrcNMOxwIgq_2A4jPC6ktM8VgGFFXVtwaEI";

      // 判断token是否为空
      if (token == null) {
      throw new LeaseException(ResultCodeEnum.ADMIN_LOGIN_AUTH);
      } else {
      // 如果不为空,解析token
      Claims claims = JwtUtil.parseToken(token);
      Long userId = claims.get("userId", Long.class);
      String username = claims.get("username", String.class);
      System.out.println("拦截器解析token获取userId = " + userId);

      // 解析token成功之后,根据userId查询数据库,判断用户是否存在


      // 把userId放到ThreadLocal中
      LoginUser loginUser = new LoginUser();
      loginUser.setUserId(userId);
      loginUser.setUsername(username);
      LoginUserContext.setLoginUser(loginUser);

      }
      return true;


      }
      // 清除ThreadLocal值,防止内存泄露
      @Override
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
      LoginUserContext.clear();
      }
      }
        1. 在接口里面,直接从ThreadLocal获取拦截器时存放数据
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        @GetMapping("info")
        @Operation(summary = "获取登录用户信息")
        public Result<UserInfoVo> info(HttpServletRequest request) {
        // 1. 从请求头获取当前登录的用户id
        // 用户id在请求头token中
        //String token = request.getHeader("access-token");

        // 解析token 使用jwt
        //Claims claims = JwtUtil.parseToken(token);
        //Long userId = claims.get("userId", Long.class);

        // 直接从ThreadLocal获取userId
        LoginUser loginUser = LoginUserContext.getLoginUser();
        Long userId = loginUser.getUserId();


        // 2. 根据用户id查询数据库表,获取用户信息
        UserInfoVo userInfoVo = loginService.getUserInfoById(userId);

        return Result.ok();
        }
      • 测试:

        房间租期管理

本地部署方案补充

项目笔记使用两台虚拟机模拟部署:server01 运行 web-admin 和 web-app 两个后端服务;server02 运行 Nginx,提供后台与移动端的前端静态文件,并把接口请求反向代理到后端。MySQL、Redis、MinIO 等依赖也需要根据实际环境配置连接地址,不能只启动两个 JAR 就认为服务可用。

部署时可按以下顺序核对:

  1. 在前端生产环境配置中确认接口基地址,再分别构建后台和移动端,检查各自的 dist 目录。
  2. 打包 web-admin、web-app,确认两个 JAR 使用正确的环境配置,并在后端服务器安装匹配的 JDK。
  3. 将前端构建产物交给 Nginx 托管,区分静态资源路径与代理到后端的接口路径。
  4. 从浏览器实际发起登录和受保护接口请求,核对令牌 Header 是否被 Nginx 转发;同时检查服务日志、数据库和缓存连接。

上方原笔记中的 ThreadLocal 属于登录请求处理:拦截器解析令牌后保存当前用户,Controller 或 Service 在同一请求线程中读取,afterCompletion 清理。它和部署是两个不同层次的问题,部署验证时可以用“获取当前用户信息”接口检查这条链路是否正常。