# 08.微服务-网关
# 1.Spring Cloud Gateway 的特征
# 1.基本介绍
在微服务架构中,我们有几十甚至上百个服务。如果让前端或客户端直接去调用这些服务,不仅需要记住每一个服务的 IP 和端口,还会面临跨域、鉴权、日志、限流等一系列复杂问题。怎么办?这时候,我们就需要一个“统一收发室”——也就是网关。以下是他的特征
- 基于异步非阻塞模型:底层基于 Spring WebFlux、Netty 和 Reactor 构建。这意味它不走传统的 Servlet 容器(Tomcat),而是使用少量的线程处理大量的并发请求,性能极高。
- 动态路由:能够根据请求的特征(路径、请求头、参数等)动态地将请求转发到不同的微服务。而且支持动态配置,无需重启网关。
- 内置强大的断言和过滤器:开箱即用,且支持自定义。
- 集成电路熔断器:可以无缝对接 Resilience4j 等熔断降级组件。
- 限流支持:内置 Redis 限流算法,保护后端微服务不被大流量压垮。
# 2.核心概念
# 1. Route(路由)
路由是构建网关的基本模块,可以理解为一条转发的规则。它由一个 ID、一个目标 URI、一组 Predicate 和一组 Filter 组成。
# 2. Predicate(断言)
断言相当于“条件判断”。它匹配 HTTP 请求中的所有内容(比如请求路径、请求头、Cookie、请求参数等)。如果请求符合断言的条件,就说明匹配到了这条路由。
类比:收费站的ETC识别系统,识别车牌是不是符合免费条件。
# 3. Filter(过滤器)
过滤器可以在请求被转发到下游微服务之前(前置 Filter)或之后(后置 Filter),对请求或响应进行修改和处理。比如添加请求头、统一鉴权、记录日志等。
类比:收费站的保安,在车开过去之前检查一下后备箱,或者发一张通行卡。
# 2.实战
1.引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<exclusions>
<!-- 必须排除web,否则启动冲突报错 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2.配置
2.1普通配置
server:
port: 8099
spring:
application:
name: api-gateway
cloud:
gateway:
#路由规则
routes:
- id: order-route #路由唯一标识,路由到order
uri: http://localhost:8081 #路由地址
#断言规则 用于路由规则的匹配
predicates:
- Path=/order-serv/** #有order-serv就会被路由到指定地址
# http://localhost:8099/order-serv/order/add 路由到
# http://localhost:8081/order-serv/order/add
filters:
- StripPrefix=1 #去掉路径中的第一个路径参数(order-serv)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2.2nacos配置
server:
port: 8099
spring:
application:
name: api-gateway
cloud:
gateway:
#路由规则
routes:
- id: order-route #路由唯一标识,路由到order
uri: lb://order-service #路由地址
#断言规则 用于路由规则的匹配
predicates:
- Path=/order-serv/** #有order-serv就会被路由到指定地址
# http://localhost:8099/order-serv/order/add 路由到
# http://localhost:8081/order-serv/order/add
filters:
- StripPrefix=1 #去掉路径中的第一个路径参数(order-serv)
nacos:
server-addr: 47.94.9.59:8848
discovery: # 服务发现必须配置账号密码
username: nacos
password: 2eb4e10f
namespace: public # 确保和你的微服务在同一命名空间
config:
username: nacos
password: 2eb4e10f
namespace: public # 与 order-service 保持一致
group: DEFAULT_GROUP
import-check:
enabled: false
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
# 3.路由断言工厂
# 1.内置断言工厂
| 名称 | 说明 | 示例 |
|---|---|---|
| After | 匹配某个时间点之后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
| Before | 匹配某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
| Between | 匹配某两个时间点之间的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
| Cookie | 请求必须包含指定 Cookie | - Cookie=chocolate, ch.p |
| Header | 请求必须包含指定请求头 | - Header=X-Request-Id, \d+ |
| Host | 请求必须访问指定域名 | - Host=**.somehost.org,**.anotherhost.org |
| Method | 请求方式必须为指定方法 | - Method=GET,POST |
| Path | 请求路径必须匹配指定规则 | - Path=/red/{segment},/blue/** |
| Query | 请求参数必须包含指定参数 | - Query=name, Jack 或 - Query=name |
| RemoteAddr | 请求来源 IP 必须在指定网段 | - RemoteAddr=192.168.1.1/24 |
| Weight | 按权重分配路由流量 | - Weight=group1, 2 |
server:
port: 8099
spring:
application:
name: api-gateway
cloud:
gateway:
#路由规则
routes:
- id: order-route #路由唯一标识,路由到order
uri: lb://order-service #路由地址
#断言规则 用于路由规则的匹配
predicates:
- After=2026-01-20T17:42:47.789-07:00[Asia/Shanghai]
- Path=/order/** #有order-serv就会被路由到指定地址
# http://localhost:8099/order-serv/order/add 路由到
# http://localhost:8081/order-serv/order/add
##filters:
#- StripPrefix=1 #去掉路径中的第一个路径参数(order-serv)
nacos:
server-addr: 47.94.9.59:8848
discovery: # 服务发现必须配置账号密码
username: nacos
password: 2eb4e10f
namespace: public # 确保和你的微服务在同一命名空间
config:
username: nacos
password: 2eb4e10f
namespace: public # 与 order-service 保持一致
group: DEFAULT_GROUP
import-check:
enabled: false
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
# 2.自定义断言工厂
必须带RoutePredicateFactory后缀
Config为内部类,用来定义属性,apply方法中写逻辑
package com.xy.gateway.predicates;
import jakarta.validation.constraints.NotEmpty;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.cloud.gateway.handler.predicate.QueryRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
public CheckAuthRoutePredicateFactory() {
super(CheckAuthRoutePredicateFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("name");
}
public Predicate<ServerWebExchange> apply(final CheckAuthRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
if (config.getName().equals("admin")){
return true;
}
return false;
}
};
}
@Validated
public static class Config {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
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
42
43
44
45
46
47
48
49
50
51
# 4.过滤器
# 1.内部局部过滤器
| 过滤器名称 | 作用说明 | 使用示例 |
|---|---|---|
| StripPrefix | 截取路径前缀,去掉第 N 层路径 | - StripPrefix=1 |
| PrefixPath | 给请求路径统一拼接前缀 | - PrefixPath=/api |
| RewritePath | 重写请求路径,正则替换 | - RewritePath=/user/(?<path>.*), /$\{path\} |
| RedirectTo | 3xx 重定向到指定地址 | - RedirectTo=302, https://www.baidu.com |
| SetPath | 直接替换完整请求路径 | - SetPath=/new/{segment} |
| SetRequestHeader | 设置 / 覆盖请求头 | - SetRequestHeader=token, 123456 |
| AddRequestHeader | 追加请求头(不覆盖) | - AddRequestHeader=X-App, gateway |
| RemoveRequestHeader | 删除指定请求头 | - RemoveRequestHeader=Cookie |
| SetResponseHeader | 设置响应头 | - SetResponseHeader=Server, gateway-server |
| AddResponseHeader | 追加响应头 | - AddResponseHeader=X-Source, gateway |
| RemoveResponseHeader | 删除响应头 | - RemoveResponseHeader=X-Powered-By |
| SetStatus | 修改返回 HTTP 状态码 | - SetStatus=401 |
| RequestRateLimiter | 限流过滤器(令牌桶) | - name: RequestRateLimiter |
| CircuitBreaker | 熔断降级(整合 Resilience4j) | - name: CircuitBreaker |
| Retry | 请求失败自动重试 | - name: Retry |
| RequestSize | 限制请求体最大大小 | - name: RequestSize, args: maxSize=5MB |
| DefaultHttpHeaders | 统一添加默认响应头 | 自动全局生效,无需手动配置 |
# 2.局部自定义过滤器
package com.xy.gateway.filters;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpStatusCode;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
@Component
public class CheckAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<CheckAuthGatewayFilterFactory.Config> {
public CheckAuthGatewayFilterFactory() {
super(CheckAuthGatewayFilterFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("value");
}
public GatewayFilter apply(final CheckAuthGatewayFilterFactory.Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (config.getValue().equals("admin")){
return chain.filter(exchange);
}else {
exchange.getResponse().setStatusCode(HttpStatusCode.valueOf(401));
return exchange.getResponse().setComplete();
}
}
};
}
public static class Config {
private String value;
public Config() {
}
public String getValue() {
return this.value;
}
public void setValue(String value) {
this.value = value;
}
}
}
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 3.全局自定义过滤器
//@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1、获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> params = request.getQueryParams();
//2、获取参数中的authorization参数
String auth = params.getFirst("authorization");
//3、判断参数值是否等于 admin
if("admin".equals(auth)){
//4、是,放行
return chain.filter(exchange);
}
//5、否,拦截
//5.1设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//5.2拦截请求
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
return -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
# 5.跨域
什么是跨域?为什么要由网关解决?
当一个请求的协议、域名、端口三者之间任意一个不同,就是跨域。比如前端跑在 http://localhost:8080,而你的网关在 http://localhost:8099,浏览器出于安全限制,默认会拦截这种请求 。
解决原则:统一在网关层解决跨域,下游微服务不要配置!
因为网关是所有流量的唯一入口,在这里配置一次就能解决所有微服务的跨域问题。如果在每个微服务里都配,既繁琐又容易出错。
# 网关跨域配置(两种方式)
Spring Cloud Gateway 基于 WebFlux,提供了非常方便的跨域配置。
# 方式一:配置文件方式(推荐,简单直观)
在 application.yml 中添加 globalcors 配置:
spring:
cloud:
gateway:
globalcors:
# 跨域配置项
cors-configurations:
# 匹配所有请求路径
'[/**]':
# 允许的源(前端地址)。注意:如果允许携带Cookie,不能写 "*",必须写具体地址
allowedOrigins:
- "http://localhost:8080"
- "http://www.yourdomain.com"
# 允许的请求方法
allowedMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
# 允许的请求头
allowedHeaders: "*"
# 是否允许携带Cookie等凭证
allowCredentials: true
# 预检请求的有效期,单位秒(避免频繁发送OPTIONS请求)
maxAge: 3600
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 方式二:Java 代码配置(适合动态配置场景)
如果你需要根据数据库或配置中心动态生成允许的跨域域名,可以使用代码注入 CorsWebFilter:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
// 推荐使用 addAllowedOriginPattern,它兼容 allowCredentials=true 的情况
config.addAllowedOriginPattern("*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
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
# 6.整合sentinel
1.引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
2.配置yml文件
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:9090
2
3
4
5