gateway(二)信息传递的三种情况
1、通过过滤器在网关内获取用户信息(网关内传递)
通过下面的代码,就可以实现网关转发请求到微服务的时候携带用户信息
package com.hmall.gateway.filters; import com.hmall.common.exception.UnauthorizedException; import com.hmall.gateway.config.AuthProperties; import com.hmall.gateway.utils.JwtTool; import lombok.RequiredArgsConstructor; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.List; @Component @RequiredArgsConstructor public class AuthGlobalFilter implements GlobalFilter, Ordered { private final AuthProperties authProperties; private final JwtTool jwtTool; private final AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1.获取用户信息 ServerHttpRequest request = exchange.getRequest(); // 2.判断是否需要做登录拦截 if(isExclude(request.getPath().toString())) return chain.filter(exchange); // 放行 // 3.获取token String token = null; List<String> headers = request.getHeaders().get("authorization"); if(headers!=null && !headers.isEmpty()) { token = headers.get(0); } // 4.解析并校验token Long userId = null; try { userId = jwtTool.parseToken(token); } catch (UnauthorizedException e) { // 拦截,设置响应状态码401 ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } // 5.传递用户信息 String userInfo = userId.toString(); ServerWebExchange serverWebExchange = exchange.mutate() .request(builder -> builder.header("user-info", userInfo)) .build(); // 6.放行 return chain.filter(serverWebExchange); } private boolean isExclude(String path) { for(String pathPattern: authProperties.getExcludePaths()) { if(antPathMatcher.match(pathPattern, path)) return true; } return false; } @Override public int getOrder() { return 0; } }
2、微服务解析网关传过来的用户信息(网关和微服务之间传递)
解析有很多种方法,一种是最原始的,也就是在controller形参处加上@RequestHeader注解来获取请求头内的用户信息,但是这样就太麻烦了,因为每个controller都要这么做。
第二种方法就是基于SpringMVC的拦截器,如果每个拦截器都写也挺麻烦的,所以最终我们把拦截器写到了common模块里,在这里面获取请求头里面的信息,并保存到ThreadLocal,这样后续的所有业务都能从ThreadLocal里获取到用户信息。(简而言之,就是定义一个通过用的拦截器,用于获取网关传递过来的用户信息)。
在把拦截器定义在common模块下我们也碰到了很多问题,就是我们把拦截器的配置放在了common模块下,这样就导致了我们其它微服务是扫描不到的,没有办法生效,在这里,我们用到了SpringBoot自动装配的原理(Spring通过扫描指定的包路径,自动发现标注了特定注解 如@Component,@Service,@Repository的类,并将其注册为Spring容器中的bean):我们将定义的配置类放到了resource/META-INF下的spring.factories文件下,这样的话就能实现自动装配。
但是这又带来另外一个问题,就是我们希望这个配置类在微服务里生效,在网关里不要生效,因此我们又用了一个条件注解@ConditionalOnClass(DispatcherServlet.class)
来去判断当前项目下有没有DispatcherServlet,网关里没有,这样就不会生效了
用来获取网关传过来的用户:
package com.hmall.common.interceptors; import cn.hutool.core.util.StrUtil; import com.hmall.common.utils.UserContext; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class UserInfoInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // controller之前执行 // 1.获取用户信息 String userInfo = request.getHeader("user-info");// 和网关保持一致 // 2.判断是否获取了用户,如果有,存入ThreadLocal if(StrUtil.isNotBlank(userInfo)) { UserContext.setUser(Long.valueOf(userInfo)); // 保存到ThreadLocal } // 3.放行 return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // controller完成之后 // 清理用户 UserContext.removeUser(); } }
拦截器生效的配置类
package com.hmall.common.config; import com.hmall.common.interceptors.UserInfoInterceptor; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @ConditionalOnClass(DispatcherServlet.class) // 判断一个类是否存在,当DispatcherServlet.class存在时,配置生效 public class MvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 拦截器的注册器 registry.addInterceptor(new UserInfoInterceptor()); } }
3、微服务之间传递
package com.hmall.api.config; import com.hmall.common.utils.UserContext; import feign.Logger; import feign.RequestInterceptor; import feign.RequestTemplate; import org.springframework.context.annotation.Bean; public class DefaultFeignConfig { // 千万不要在这个类上面加@Configuration注解,因为这个配置类会在其它启动类上的注解引用 @Bean public Logger.Level feignLogLevel() { return Logger.Level.BASIC; } @Bean public RequestInterceptor userInfoInterceptor() { return new RequestInterceptor() { @Override public void apply(RequestTemplate requestTemplate) { Long userId = UserContext.getUser(); // 为什么上面这行代码可以从UserContext里取用户信息,因为之前的服务中的信息是网关给的,这个时候请求头里是带了用户信息的 if(userId != null) { requestTemplate.header("user-info", userId.toString()); } } }; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构