【黑马头条】微服务架构下统一获取登录用户 ID 的完整流程(解决 ThreadLocal.getUser ()=null)

张开发
2026/4/11 17:04:15 15 分钟阅读

分享文章

【黑马头条】微服务架构下统一获取登录用户 ID 的完整流程(解决 ThreadLocal.getUser ()=null)
一、问题背景在微服务架构中我们不会在每个微服务都重复解析用户 Token而是采用 **「网关统一认证 微服务直接获取」的模式。如果直接在业务层调用AppThreadLocalUtil.getUser()得到null本质是缺少了「网关转发用户 ID → 微服务拦截器接收 → 存入 ThreadLocal」** 这一整套链路。二、完整流程5 步走缺一不可1. 网关层统一解析 Token把 userId 放到请求头转发核心作用一次解析 Token所有微服务共享避免重复工作统一认证入口。网关拦截器解析完用户 Token 后必须把userId放到请求头中再转发给下游微服务。// 网关全局过滤器核心代码 Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1. 获取请求和响应 ServerHttpRequest request exchange.getRequest(); ServerHttpResponse response exchange.getResponse(); // 2. 放行登录接口 if (request.getURI().getPath().contains(/login)) { return chain.filter(exchange); } // 3. 获取Token String token request.getHeaders().getFirst(token); if (StringUtils.isEmpty(token)) { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } // 4. 解析Token try { Claims claims JwtUtil.parseJWT(token); String userId claims.get(userId).toString(); // 【关键】把userId放到请求头转发给微服务 ServerHttpRequest build request.mutate() .headers(httpHeaders - httpHeaders.add(userId, userId)) .build(); // 5. 放行带着userId转发 return chain.filter(exchange.mutate().request(build).build()); } catch (Exception e) { // Token无效返回401 response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } }2. 微服务层编写登录拦截器从请求头获取 userId作用拦截微服务的所有请求从网关转发的请求头中拿到userId组装成用户对象。package com.heima.user.config.interceptor; import com.heima.model.user.pojos.ApUser; import com.heima.user.service.ApUserService; import com.heima.utils.thread.AppThreadLocalUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; Component public class UserInterceptor implements HandlerInterceptor { Autowired private ApUserService apUserService; // 请求进入Controller之前执行 Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 从请求头获取网关转发的userId名字必须和网关一致 String userIdStr request.getHeader(userId); if (userIdStr null || userIdStr.isEmpty()) { // 无用户ID未登录返回401 response.setStatus(401); return false; } // 2. 转换为Integer Integer userId Integer.valueOf(userIdStr); // 3. 【可选】查询完整用户信息如果只需要ID直接new ApUser().setId(userId)即可不用查库 ApUser apUser apUserService.getById(userId); if (apUser null) { response.setStatus(401); return false; } // 4. 把用户存入ThreadLocal AppThreadLocalUtil.setUser(apUser); // 放行请求 return true; } // 请求结束后执行必做 Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 清理ThreadLocal避免线程复用导致的用户信息串线和内存泄漏 AppThreadLocalUtil.remove(); } }3. 工具类ThreadLocal 存储用户信息线程隔离作用在当前请求线程中存储用户信息整个请求链路Controller→Service→Mapper共享且线程之间互不干扰。package com.heima.utils.thread; import com.heima.model.user.pojos.ApUser; public class AppThreadLocalUtil { // ThreadLocal容器每个线程独立存储 private final static ThreadLocalApUser AP_USER_THREAD_LOCAL new ThreadLocal(); // 存入用户拦截器调用 public static void setUser(ApUser apUser) { AP_USER_THREAD_LOCAL.set(apUser); } // 获取用户业务层调用 public static ApUser getUser() { return AP_USER_THREAD_LOCAL.get(); } // 清理用户请求结束后调用 public static void remove() { AP_USER_THREAD_LOCAL.remove(); } }4. 注册拦截器让拦截器生效作用把我们写的拦截器注册到 SpringMVC 中否则拦截器不会执行。package com.heima.user.config; import com.heima.user.config.interceptor.UserInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; Configuration public class WebMvcConfig implements WebMvcConfigurer { Autowired private UserInterceptor userInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(userInterceptor) .addPathPatterns(/**) // 拦截所有请求 .excludePathPatterns(/api/v1/user/login); // 排除登录接口登录时还没有用户 } }5. 业务层直接获取用户 ID 使用作用在任何需要当前登录用户信息的地方直接调用工具类即可不用再传参。Override Transactional(rollbackFor Exception.class) public ResponseResult userFollow(UserRelationDto dto) { // 直接获取当前登录用户再也不会是null了 ApUser user AppThreadLocalUtil.getUser(); Integer operatorId user.getId(); // 拿到当前用户ID // 后续业务逻辑... }

更多文章