您现在的位置是:首页 >技术交流 >微服务-网关路由网站首页技术交流
微服务-网关路由
微服务-网关路由
1.什么是网关?
网关:是网络的的关口,负责请求的路由,转发,身份校验。

我们可以把
网关------>小区的门卫大爷,
各个微服务------>不同的住户,
前端的请求------>询问的人。
在大爷询问来访人的信息时就是身份验证,告诉询问的人他要找的人在几楼几号就是路由,找不到路大爷带路去就是转发。
那么问题来了网关怎么知道所有的服务地址?别忘了我们之前讲过的注册中心,这里网关也作为一个微服务从注册中心拉取服务信息。

有什么好处?
- 可以在网关这里进行身份验证并将信息向后传递,不需要每一个微服务都做一次处理
- 可以不用将自己的微服务地址暴露出来是一种保护
- 对前端来说只需要向网关发送请求,后端就和原来的单体架构一样没什么区别,开发体验更好了
网关的实现包括两种:

不过Spring Cloud Gateway性能更优,也是主流的网关组件。
2.网关路由-快速体验
注意:前端发送的请求到底由网关发送给那个服务很重要!所以路由的核心就是去配置路由的规则。
1.实现步骤
-
创建新模块
-
引入网关依赖
</dependencies> <!--网关--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--nacos discovery--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacosdiscovery</artifactId> </dependency> <!--负载均衡--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> </dependencies> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> -
编写启动类
-
配置路由规则
spring: cloud: gateway: routes: - id:item-service #路由规则id自定义,唯一,一般和你的服务名称相同 url:lb://item-service #路由目标微服务,lb代表负载均衡 predicates: #路由断言,判断请求是否符合规则,符合则路由到目标 - Path=/items/** #以请求路径做判断,以/items开头的则符合 #如果有多个路径,路径间用逗号分隔eg:- Path=/items/**,/xxxx/** - id:xxxx url:lb://xxxx-service predicates: - Path=/xx/** -
启动服务测试(这里我给出我的测试结果)
网关端口是8080

微服务端口是8081

可以发现路由成功转发,当你开启服务的多个实例,会发现实现了负载均衡。
3.网关路由-路由属性
网关路由对应的java类型是RouteDefinition,其中常见的属性有:
- id:路由的唯一标识
- url:路由的目标地址
- predicates:路由断言,判断请求是否符合当前路由
- filters:路由过滤器,对请求或响应做特殊处理
1.路由断言
spring提供了12种基本的RoutePredicatesFactory实现:使用示例可在官网查看

2.路由过滤器
网关中提供了33种路由过滤器,每种过滤器都有独特的作用。

3.网关登录校验
1.如何在网关转发前做登录校验?
网关请求处理流程

从图中发现NettyRoutingFilter这个过滤器负责将请求转发到微服务,所以我们需要在这个过滤器之前自定义一个过滤器,在其中去编写登录校验(JWT校验)的逻辑(在pre阶段)。
2.如何将用户信息传递给微服务?
网关到微服务也是一次Http请求,所以将信息放在请求头中传递给微服务。

3. 如何在微服务之间传递用户信息?
基于OpenFeign发送Http请求,这里和网关的Http请求不同。

4.实现登录校验
1.自定义过滤器
网关过滤器有两种,分别是:
-
GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效。
-
GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效
两者用法大致相同,在这里之后的以GlobalFilter示例

@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取请求
ServerHttpRequest request = exchange.getRequest();
//2.过滤器业务处理
HttpHeaders headers = request.getHeaders();
System.out.println("请求头信息:" + headers);
//放行
return chain.filter(exchange);
}
//优先级,数字越小越优先执行,继承Ordered接口即可,保证在NettyRoutingFilter前执行
@Override
public int getOrder() {
return 0;
}
}
2.实现登录校验
//这里给出的是思路逻辑,具体业务结合实际情况处理
@RequiredArgsConstructor
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
private final AuthProperties authProperties;//这个是我自己不需要登录校验的路径,可以自己处理
private final JwtTool jwtTool;//这里可以使用自己的jwt工具
private final AntPathMatcher antPathMatcher = new AntPathMatcher();//Spring提供的匹配器
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取请求request
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.传递用户信息
System.out.println("用户id"+userId);
//放行
return chain.filter(exchange);
}
public boolean isExclude(String path) {
List<String> excludePaths = authProperties.getExcludePaths();
for(String excludePath : excludePaths){
if(antPathMatcher.match(excludePath,path)){
return true;
}
}
return false;
}
@Override
public int getOrder() {
//优先级,数字越小越优先执行,继承Ordered接口即可,,保证在NettyRoutingFilter前执行
return 0;
}
}
3.网关传递信息
添加拦截器获取用户保存到ThreadLocal

步骤:
-
完善网关向服务传递用户信息
// 5. 传递用户信息 String userInfo = userId.toString(); // 使用 ServerWebExchange 的 mutate 方法创建一个新的请求对象 // 在请求头中添加自定义的 "user-Info" 字段,值为用户信息 ServerWebExchange newRequest = exchange.mutate() .request(builder -> builder.header("user-Info", userInfo)) // 添加请求头 .build(); // 构建新的请求对象 // 6. 放行请求 // 将修改后的请求对象传递给过滤器链,继续后续的处理 return chain.filter(newRequest); -
在微服务共同引入的工具模块中定义拦截器(比如common模块被cart,item…服务引入依赖)
public class UserInfoIntercepter implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //获取请求头信息 String userInfo = request.getHeader("user-Info"); //判断是否获取了用户,如果有则存入ThreadLocal if(StrUtil.isNotBlank(userInfo)) { UserContext.setUser(Long.valueOf(userInfo)); } //放行 return true; } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //清理 UserContext.removeUser(); } }这里我给出UserContext
public class UserContext { private static final ThreadLocal<Long> tl = new ThreadLocal<>(); /** * 保存当前登录用户信息到ThreadLocal * @param userId 用户id */ public static void setUser(Long userId) { tl.set(userId); } /** * 获取当前登录用户信息 * @return 用户id */ public static Long getUser() { return tl.get(); } /** * 移除当前登录用户信息 */ public static void removeUser(){ tl.remove(); } } -
让拦截器生效
import com.xxxx.common.interceptors.UserInfoIntercepter; 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 MvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new UserInfoIntercepter()); } } -
配置类需要被Spring扫描到,此时拦截器和其他服务不在一个包怎么办?
在拦截器模块中的resources中配置spring.factories(老版本),将MvcConfig配置。

-
此时运行可能会报错(具体情况具体分析,我这里网关服务引入了Common模块(放了一些工具类和其他公共属性))
-
注意:之前写网关服务是非阻塞式编程,底层并不是SpringMvc,而WebMvcConfigurer的底层是SpringMvc,所以会出现报错,怎么处理?
提示:只要是微服务都有SpringMvc,只要是SpringMvc就都有SpringMvc核心API—>DispatcherServlet,所以我们只需要判断有没有这个类就可以了。
import com.xxxx.common.interceptors.UserInfoIntercepter; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @ConditionalOnClass(DispatcherServlet.class)//这个注解判断某个类是否存在 public class MvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new UserInfoIntercepter()); } }更改后就可以正常运行了。
4.OpenFeign传递用户(服务间)
示例:创建完成订单,需要发送用户ID给其他服务来进行处理。

如图:这里给出示例的运行结果,购物车服务并未删除掉该用户的购物车信息。

这里大家可能会有疑惑,我们之前不是在common中写的拦截器用来保存用户ID到ThreadLocal中么,并且common被每个服务都引入,不是应该都可以拿到ID吗?
这个就和ThreadLocal的特性有关了:在微服务架构中,前端请求通过网关验证身份后,用户ID存储在 ThreadLocal 中。然而,当一个服务向另一个服务发起请求时,被请求的服务无法获取到用户ID,这是因为 ThreadLocal 的存储是线程隔离的,不会在跨进程或跨线程的调用中自动传播。
解决:Openfeign中提供了一个拦截器接口,所有由Openfeign发起的请求都会先调用拦截器处理请求:

其中RequestTemplate类包含了一些方法可以帮助我们修改请求头:

步骤:
- 在你的公共组件中加入RequestInterceptor
public class DefaultFeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor(){
@Override
public void apply(RequestTemplate template) {
Long userId = UserContext.getUser();
template.header("user-info",userId.toString());
}
};
}
}
注意DefaultFeignConfig需要在相应调用者服务的启动类上声明
eg:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
添加RequestInterceptor后

这里给出微服务登录的简单流程图






QT多线程的5种用法,通过使用线程解决UI主界面的耗时操作代码,防止界面卡死。...
U8W/U8W-Mini使用与常见问题解决
stm32使用HAL库配置串口中断收发数据(保姆级教程)
分享几个国内免费的ChatGPT镜像网址(亲测有效)
Allegro16.6差分等长设置及走线总结