Spring Cloud Gateway使用指南
概述
Spring Cloud Gateway 是 Spring Cloud 官方推出的第二代网关框架,基于 Spring 5、Project Reactor 和 Spring Boot 2 构建,旨在为微服务架构提供简单、高效的 API 路由管理方式。它是 Netflix Zuul 的替代方案,采用响应式编程模型,具有更高的性能和更低的资源消耗。
核心特性
- 动态路由:能够匹配任何请求属性进行路由
- 断言和过滤器:针对特定路由的断言和过滤器
- 服务发现集成:与 Spring Cloud 服务发现无缝集成
- 负载均衡:内置负载均衡功能
- 限流:支持请求速率限制
- 路径重写:支持请求路径重写
- 熔断集成:与 Hystrix、Resilience4j、Sentinel 集成
核心概念
|
概念 |
说明 |
|
Route(路由) |
网关的基本构建块,由ID、目标URI、断言和过滤器组成 |
|
Predicate(断言) |
Java 8 的 Predicate,用于匹配HTTP请求中的任何内容 |
|
Filter(过滤器) |
对请求和响应进行修改处理 |
工作流程
客户端请求 → Gateway Handler Mapping → 匹配路由
→ Gateway Web Handler → 过滤器链(前置处理)
→ 代理请求到目标服务 → 过滤器链(后置处理)
→ 返回响应给客户端
快速开始
环境要求
- JDK 17+(Spring Boot 3.x)或 JDK 8+(Spring Boot 2.x)
- Spring Boot 2.x / 3.x
- Maven 3.x 或 Gradle
创建项目
引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<properties>
<spring-cloud.version>2023.0.0</spring-cloud.version>
</properties>
<dependencies>
<!-- Gateway 核心依赖 -->
<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-discovery</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
启动类
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
注意:Gateway 基于 WebFlux,不能与 spring-boot-starter-web 同时使用。
路由配置
Gateway 支持两种配置方式:YAML 配置和 Java 代码配置。
YAML 配置方式
server:
port: 8080
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
# 路由1:用户服务
- id: user-service
uri: http://localhost:8081
predicates:
- Path=/user/**
filters:
- StripPrefix=1
# 路由2:订单服务(负载均衡)
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- StripPrefix=1
# 路由3:商品服务
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/product/(?<segment>.*), /product/${segment}
配置说明
|
属性 |
说明 |
|
id |
路由唯一标识,不可重复 |
|
uri |
目标服务地址,lb:// 表明启用负载均衡 |
|
predicates |
断言条件,满足条件才会路由 |
|
filters |
过滤器,对请求/响应进行处理 |
|
order |
路由优先级,数值越小优先级越高 |
Java 代码配置方式
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 用户服务路由
.route("user-service", r -> r
.path("/user/**")
.filters(f -> f.stripPrefix(1))
.uri("lb://user-service"))
// 订单服务路由
.route("order-service", r -> r
.path("/order/**")
.and()
.method(HttpMethod.GET, HttpMethod.POST)
.filters(f -> f
.stripPrefix(1)
.addRequestHeader("X-Request-Source", "gateway"))
.uri("lb://order-service"))
// 外部服务路由
.route("external-api", r -> r
.path("/external/**")
.filters(f -> f
.rewritePath("/external/(?<segment>.*)", "/${segment}")
.addRequestHeader("Authorization", "Bearer xxx"))
.uri("https://api.example.com"))
.build();
}
}
动态路由(服务发现)
开启服务发现后,Gateway 可以自动根据服务名进行路由:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 开启服务发现路由
lower-case-service-id: true # 服务名小写
开启后,可以通过 http://gateway:8080/服务名/接口路径 访问服务。
断言工厂(Predicate Factory)
断言用于判断请求是否满足某种条件,满足条件的请求才会被路由到目标服务。
内置断言工厂
Path 路径断言
predicates:
- Path=/user/** # 匹配 /user/ 开头的路径
- Path=/user/{id} # 匹配 /user/123 等路径
- Path=/api/v1/**,/api/v2/** # 匹配多个路径
Method 请求方法断言
predicates:
- Method=GET,POST,PUT # 匹配指定的请求方法
Header 请求头断言
predicates:
- Header=X-Request-Id, d+ # 请求头包含 X-Request-Id 且值为数字
- Header=Content-Type, application/json
Query 查询参数断言
predicates:
- Query=name # 包含 name 参数
- Query=name, jack # name 参数值为 jack
- Query=name, w+ # name 参数值匹配正则
Host 主机断言
predicates:
- Host=**.example.com # 匹配 *.example.com 的请求
- Host=api.example.com,admin.example.com
Cookie 断言
predicates:
- Cookie=sessionId, w+ # Cookie 包含 sessionId
时间断言
predicates:
# 在指定时间之后的请求
- After=2024-01-01T00:00:00+08:00[Asia/Shanghai]
# 在指定时间之前的请求
- Before=2025-12-31T23:59:59+08:00[Asia/Shanghai]
# 在指定时间范围内的请求
- Between=2024-01-01T00:00:00+08:00[Asia/Shanghai], 2025-12-31T23:59:59+08:00[Asia/Shanghai]
RemoteAddr IP地址断言
predicates:
- RemoteAddr=192.168.1.0/24 # 匹配指定网段的请求
Weight 权重断言
routes:
- id: service-v1
uri: lb://service-v1
predicates:
- Path=/api/**
- Weight=group1, 80 # 80% 流量
- id: service-v2
uri: lb://service-v2
predicates:
- Path=/api/**
- Weight=group1, 20 # 20% 流量
组合断言
predicates:
- Path=/api/**
- Method=GET,POST
- Header=X-Token, w+
多个断言之间是 AND 关系,全部满足才会匹配。
自定义断言工厂
@Component
public class AuthRoutePredicateFactory extends AbstractRoutePredicateFactory<AuthRoutePredicateFactory.Config> {
public AuthRoutePredicateFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("role");
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// 获取请求头中的角色信息
String role = exchange.getRequest().getHeaders().getFirst("X-User-Role");
// 判断是否匹配配置的角色
return config.getRole().equals(role);
};
}
@Data
public static class Config {
private String role;
}
}
使用自定义断言:
predicates:
- Path=/admin/**
- Auth=admin # 自定义断言,要求角色为 admin
过滤器(Filter)
过滤器用于在请求转发前后对请求和响应进行修改处理。
过滤器类型
|
类型 |
说明 |
|
GatewayFilter |
路由过滤器,作用于指定路由 |
|
GlobalFilter |
全局过滤器,作用于所有路由 |
内置过滤器工厂
AddRequestHeader 添加请求头
filters:
- AddRequestHeader=X-Request-Source, gateway
- AddRequestHeader=X-Request-Time, ${now}
AddRequestParameter 添加请求参数
filters:
- AddRequestParameter=source, gateway
AddResponseHeader 添加响应头
filters:
- AddResponseHeader=X-Response-Time, ${now}
RemoveRequestHeader 移除请求头
filters:
- RemoveRequestHeader=Cookie
- RemoveRequestHeader=X-Internal-Token
RemoveResponseHeader 移除响应头
filters:
- RemoveResponseHeader=X-Powered-By
- RemoveResponseHeader=Server
SetRequestHeader 设置请求头
filters:
- SetRequestHeader=X-Request-Version, v2
SetResponseHeader 设置响应头
filters:
- SetResponseHeader=Cache-Control, no-cache
StripPrefix 去除前缀
# 请求 /api/user/list → 转发 /user/list
filters:
- StripPrefix=1
# 请求 /api/v1/user/list → 转发 /user/list
filters:
- StripPrefix=2
PrefixPath 添加前缀
# 请求 /list → 转发 /api/list
filters:
- PrefixPath=/api
RewritePath 路径重写
# 请求 /api/user/123 → 转发 /user/123
filters:
- RewritePath=/api/(?<segment>.*), /${segment}
SetStatus 设置响应状态码
filters:
- SetStatus=401
RedirectTo 重定向
filters:
- RedirectTo=302, https://www.example.com
Retry 重试
filters:
- name: Retry
args:
retries: 3 # 重试次数
statuses: BAD_GATEWAY # 触发重试的状态码
methods: GET,POST # 触发重试的方法
backoff:
firstBackoff: 100ms # 首次重试间隔
maxBackoff: 500ms # 最大重试间隔
factor: 2 # 重试间隔倍数
RequestSize 请求大小限制
filters:
- name: RequestSize
args:
maxSize: 5MB
默认过滤器
对所有路由生效的过滤器:
spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=X-Response-Gateway, true
- RemoveResponseHeader=X-Powered-By
自定义 GatewayFilter
@Component
public class LoggingGatewayFilterFactory extends AbstractGatewayFilterFactory<LoggingGatewayFilterFactory.Config> {
private static final Logger log = LoggerFactory.getLogger(LoggingGatewayFilterFactory.class);
public LoggingGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("enabled");
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
if (config.isEnabled()) {
ServerHttpRequest request = exchange.getRequest();
log.info("Request: {} {}", request.getMethod(), request.getURI());
long startTime = System.currentTimeMillis();
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
long duration = System.currentTimeMillis() - startTime;
log.info("Response: {} - {}ms",
exchange.getResponse().getStatusCode(), duration);
}));
}
return chain.filter(exchange);
};
}
@Data
public static class Config {
private boolean enabled = true;
}
}
使用自定义过滤器:
filters:
- Logging=true
自定义 GlobalFilter
全局过滤器对所有路由生效,常用于统一鉴权、日志记录等场景。
@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// 白名单路径直接放行
if (isWhitePath(path)) {
return chain.filter(exchange);
}
// 获取 Token
String token = request.getHeaders().getFirst("Authorization");
// Token 校验
if (StringUtils.isEmpty(token) || !token.startsWith("Bearer ")) {
return unauthorized(exchange, "Missing or invalid token");
}
try {
// 解析 Token(示例)
String jwt = token.substring(7);
Claims claims = JwtUtils.parseToken(jwt);
// 将用户信息传递给下游服务
ServerHttpRequest newRequest = request.mutate()
.header("X-User-Id", claims.getSubject())
.header("X-User-Name", claims.get("username", String.class))
.build();
return chain.filter(exchange.mutate().request(newRequest).build());
} catch (Exception e) {
log.error("Token validation failed: {}", e.getMessage());
return unauthorized(exchange, "Invalid token");
}
}
private boolean isWhitePath(String path) {
List<String> whitePaths = Arrays.asList("/auth/login", "/auth/register", "/public/**");
return whitePaths.stream().anyMatch(p ->
new AntPathMatcher().match(p, path));
}
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
String body = String.format("{"code":401,"message":"%s"}", message);
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
// 数值越小,优先级越高
return -100;
}
}
过滤器执行顺序
@Component
public class RequestLogFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Pre 过滤器逻辑(请求转发前执行)
log.info("Pre Filter: Request received");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// Post 过滤器逻辑(响应返回后执行)
log.info("Post Filter: Response sent");
}));
}
@Override
public int getOrder() {
return 0;
}
}
限流配置
Gateway 内置了 RequestRateLimiter 过滤器,支持基于 Redis 的分布式限流。
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
配置限流
spring:
redis:
host: localhost
port: 6379
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 每秒允许的请求数
redis-rate-limiter.burstCapacity: 20 # 令牌桶容量
redis-rate-limiter.requestedTokens: 1 # 每次请求消耗的令牌数
key-resolver: "#{@ipKeyResolver}" # 限流键解析器
限流键解析器
@Configuration
public class RateLimiterConfig {
/**
* 基于 IP 限流
*/
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
Objects.requireNonNull(exchange.getRequest().getRemoteAddress())
.getAddress().getHostAddress());
}
/**
* 基于用户 ID 限流
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getHeaders().getFirst("X-User-Id"));
}
/**
* 基于请求路径限流
*/
@Bean
public KeyResolver pathKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getPath().value());
}
/**
* 组合限流(IP + 路径)
*/
@Bean
public KeyResolver compositeKeyResolver() {
return exchange -> {
String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress())
.getAddress().getHostAddress();
String path = exchange.getRequest().getPath().value();
return Mono.just(ip + ":" + path);
};
}
}
自定义限流响应
@Configuration
public class GatewayConfig {
@Bean
public RateLimiterGatewayFilterFactory.RateLimiterConfig rateLimiterConfig() {
return new RateLimiterGatewayFilterFactory.RateLimiterConfig()
.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
}
}
熔断配置
Gateway 支持与 Resilience4j 和 Sentinel 集成实现熔断功能。
集成 Resilience4j
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
配置熔断
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- name: CircuitBreaker
args:
name: userServiceCircuitBreaker
fallbackUri: forward:/fallback/user
resilience4j:
circuitbreaker:
instances:
userServiceCircuitBreaker:
slidingWindowSize: 10 # 滑动窗口大小
minimumNumberOfCalls: 5 # 最小调用次数
failureRateThreshold: 50 # 失败率阈值
waitDurationInOpenState: 10000 # 熔断开启持续时间(毫秒)
permittedNumberOfCallsInHalfOpenState: 3 # 半开状态允许的调用次数
timelimiter:
instances:
userServiceCircuitBreaker:
timeoutDuration: 3s # 超时时间
降级处理
@RestController
@RequestMapping("/fallback")
public class FallbackController {
@GetMapping("/user")
public Mono<Map<String, Object>> userFallback() {
Map<String, Object> result = new HashMap<>();
result.put("code", 503);
result.put("message", "用户服务暂时不可用,请稍后再试");
return Mono.just(result);
}
@GetMapping("/order")
public Mono<Map<String, Object>> orderFallback() {
Map<String, Object> result = new HashMap<>();
result.put("code", 503);
result.put("message", "订单服务暂时不可用,请稍后再试");
return Mono.just(result);
}
}
集成 Sentinel
添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
配置 Sentinel
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
scg:
fallback:
mode: response
response-status: 429
response-body: '{"code":429,"message":"请求过于频繁,请稍后再试"}'
自定义限流处理
@Configuration
public class SentinelGatewayConfig {
@PostConstruct
public void init() {
BlockRequestHandler blockRequestHandler = (exchange, t) -> {
Map<String, Object> result = new HashMap<>();
result.put("code", 429);
result.put("message", "请求过于频繁,请稍后再试");
result.put("timestamp", System.currentTimeMillis());
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(result));
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
跨域配置
YAML 配置方式
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*" # 允许的源
allowedOriginPatterns: "*" # 允许的源模式(支持通配符)
allowedMethods: # 允许的方法
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowedHeaders: "*" # 允许的请求头
exposedHeaders: # 暴露的响应头
- Authorization
- X-Custom-Header
allowCredentials: true # 是否允许携带凭证
maxAge: 3600 # 预检请求缓存时间
Java 配置方式
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
config.setMaxAge(3600L);
config.addExposedHeader("Authorization");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
处理跨域冲突
当下游服务也配置了跨域时,可能出现重复的跨域头。使用过滤器去除重复头:
spring:
cloud:
gateway:
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_UNIQUE
JWT 鉴权实战
完整的 JWT 鉴权示例
添加依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
JWT 工具类
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
private Key getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(secret);
return Keys.hmacShaKeyFor(keyBytes);
}
public String generateToken(String userId, String username, List<String> roles) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration * 1000);
return Jwts.builder()
.setSubject(userId)
.claim("username", username)
.claim("roles", roles)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
public Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
public boolean validateToken(String token) {
try {
parseToken(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
鉴权过滤器
@Component
@Slf4j
public class JwtAuthenticationFilter implements GlobalFilter, Ordered {
@Autowired
private JwtUtils jwtUtils;
// 白名单路径
private static final List<String> WHITE_LIST = Arrays.asList(
"/auth/login",
"/auth/register",
"/auth/refresh",
"/public/**",
"/actuator/**"
);
private final AntPathMatcher pathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// 白名单放行
if (isWhitePath(path)) {
return chain.filter(exchange);
}
// 获取 Token
String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (StringUtils.isEmpty(authHeader) || !authHeader.startsWith("Bearer ")) {
return unauthorized(exchange, "Missing or invalid Authorization header");
}
String token = authHeader.substring(7);
// 验证 Token
if (!jwtUtils.validateToken(token)) {
return unauthorized(exchange, "Invalid or expired token");
}
try {
// 解析 Token
Claims claims = jwtUtils.parseToken(token);
String userId = claims.getSubject();
String username = claims.get("username", String.class);
List<String> roles = claims.get("roles", List.class);
// 将用户信息添加到请求头,传递给下游服务
ServerHttpRequest newRequest = request.mutate()
.header("X-User-Id", userId)
.header("X-User-Name", URLEncoder.encode(username, StandardCharsets.UTF_8))
.header("X-User-Roles", String.join(",", roles))
.build();
return chain.filter(exchange.mutate().request(newRequest).build());
} catch (Exception e) {
log.error("Token parsing error: {}", e.getMessage());
return unauthorized(exchange, "Token parsing failed");
}
}
private boolean isWhitePath(String path) {
return WHITE_LIST.stream().anyMatch(pattern -> pathMatcher.match(pattern, path));
}
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> result = new HashMap<>();
result.put("code", 401);
result.put("message", message);
result.put("timestamp", System.currentTimeMillis());
byte[] bytes;
try {
bytes = new ObjectMapper().writeValueAsBytes(result);
} catch (JsonProcessingException e) {
bytes = "{"code":401,"message":"Unauthorized"}".getBytes(StandardCharsets.UTF_8);
}
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return -200; // 优先级高于其他过滤器
}
}
配置文件
jwt:
secret: your-256-bit-secret-key-here-must-be-at-least-32-characters
expiration: 86400 # 24小时
日志与监控
请求日志记录
@Component
@Slf4j
public class RequestLoggingFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String requestId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis();
// 记录请求信息
log.info("[{}] Request: {} {} from {}",
requestId,
request.getMethod(),
request.getURI(),
request.getRemoteAddress());
// 将 requestId 添加到请求头
ServerHttpRequest newRequest = request.mutate()
.header("X-Request-Id", requestId)
.build();
return chain.filter(exchange.mutate().request(newRequest).build())
.then(Mono.fromRunnable(() -> {
long duration = System.currentTimeMillis() - startTime;
HttpStatusCode statusCode = exchange.getResponse().getStatusCode();
log.info("[{}] Response: {} - {}ms", requestId, statusCode, duration);
}));
}
@Override
public int getOrder() {
return -1000; // 最先执行
}
}
Actuator 监控端点
management:
endpoints:
web:
exposure:
include: gateway,health,info,metrics
endpoint:
gateway:
enabled: true
访问监控端点:
- GET /actuator/gateway/routes – 查看所有路由
- GET /actuator/gateway/globalfilters – 查看全局过滤器
- GET /actuator/gateway/routefilters – 查看路由过滤器
- POST /actuator/gateway/refresh – 刷新路由
动态路由配置
基于 Nacos 的动态路由
添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
动态路由配置类
@Component
@Slf4j
public class DynamicRouteService implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
/**
* 添加路由
*/
public void add(RouteDefinition definition) {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
publisher.publishEvent(new RefreshRoutesEvent(this));
log.info("Route added: {}", definition.getId());
}
/**
* 更新路由
*/
public void update(RouteDefinition definition) {
delete(definition.getId());
add(definition);
}
/**
* 删除路由
*/
public void delete(String routeId) {
routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
publisher.publishEvent(new RefreshRoutesEvent(this));
log.info("Route deleted: {}", routeId);
}
}
Nacos 配置监听
@Component
@Slf4j
public class NacosRouteConfigListener {
@Autowired
private DynamicRouteService dynamicRouteService;
@Autowired
private NacosConfigManager nacosConfigManager;
@Value("${spring.cloud.nacos.config.server-addr}")
private String serverAddr;
@PostConstruct
public void init() throws NacosException {
String dataId = "gateway-routes.json";
String group = "DEFAULT_GROUP";
// 获取初始配置
String config = nacosConfigManager.getConfigService()
.getConfig(dataId, group, 5000);
updateRoutes(config);
// 监听配置变化
nacosConfigManager.getConfigService().addListener(dataId, group, new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
log.info("Received route config update");
updateRoutes(configInfo);
}
});
}
private void updateRoutes(String configInfo) {
if (StringUtils.isEmpty(configInfo)) {
return;
}
try {
List<RouteDefinition> routes = new ObjectMapper()
.readValue(configInfo, new TypeReference<List<RouteDefinition>>() {});
routes.forEach(route -> dynamicRouteService.add(route));
} catch (Exception e) {
log.error("Failed to parse route config: {}", e.getMessage());
}
}
}
Nacos 配置示例
在 Nacos 中创建配置 gateway-routes.json:
[
{
"id": "user-service",
"uri": "lb://user-service",
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/user/**"
}
}
],
"filters": [
{
"name": "StripPrefix",
"args": {
"parts": "1"
}
}
],
"order": 0
}
]
最佳实践
路由配置规范
spring:
cloud:
gateway:
routes:
# 推荐:使用有意义的路由ID
- id: user-service-v1
uri: lb://user-service
predicates:
- Path=/api/v1/user/**
order: 1
# 不推荐:使用无意义的ID
- id: route1
uri: lb://some-service
超时配置
spring:
cloud:
gateway:
httpclient:
connect-timeout: 3000 # 连接超时(毫秒)
response-timeout: 10s # 响应超时
pool:
type: elastic # 连接池类型
max-connections: 200 # 最大连接数
acquire-timeout: 45000 # 获取连接超时
重试配置
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE
methods: GET
backoff:
firstBackoff: 100ms
maxBackoff: 500ms
factor: 2
异常处理
@Component
@Order(-1)
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse response = exchange.getResponse();
if (response.isCommitted()) {
return Mono.error(ex);
}
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> result = new HashMap<>();
result.put("timestamp", System.currentTimeMillis());
result.put("path", exchange.getRequest().getPath().value());
if (ex instanceof ResponseStatusException) {
ResponseStatusException rse = (ResponseStatusException) ex;
response.setStatusCode(rse.getStatusCode());
result.put("code", rse.getStatusCode().value());
result.put("message", rse.getReason());
} else if (ex instanceof ConnectException) {
response.setStatusCode(HttpStatus.SERVICE_UNAVAILABLE);
result.put("code", 503);
result.put("message", "服务不可用");
} else {
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
result.put("code", 500);
result.put("message", "内部服务错误");
}
try {
byte[] bytes = objectMapper.writeValueAsBytes(result);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
} catch (JsonProcessingException e) {
return Mono.error(e);
}
}
}
生产环境配置提议
spring:
cloud:
gateway:
# 开启服务发现
discovery:
locator:
enabled: true
lower-case-service-id: true
# 默认过滤器
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_UNIQUE
- AddResponseHeader=X-Response-Time, %{now}
# HTTP 客户端配置
httpclient:
connect-timeout: 3000
response-timeout: 10s
pool:
type: elastic
max-connections: 500
acquire-timeout: 30000
# 全局跨域
globalcors:
cors-configurations:
'[/**]':
allowedOriginPatterns: "*"
allowedMethods: "*"
allowedHeaders: "*"
allowCredentials: true
maxAge: 3600
# 日志配置
logging:
level:
org.springframework.cloud.gateway: INFO
reactor.netty: INFO
常见问题
1. 404 Not Found
缘由:路由未匹配或服务未注册
排查步骤:
- 检查路由配置是否正确
- 访问 /actuator/gateway/routes 查看已注册的路由
- 检查服务是否正确注册到注册中心
2. 503 Service Unavailable
缘由:目标服务不可用
解决方案:
- 检查目标服务是否启动
- 检查服务名是否正确
- 配置熔断降级处理
3. 跨域问题
缘由:跨域配置不正确或重复配置
解决方案:
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_UNIQUE
4. 请求体丢失
缘由:在过滤器中多次读取请求体
解决方案:
@Bean
public GlobalFilter cacheRequestBodyFilter() {
return (exchange, chain) -> ServerWebExchangeUtils
.cacheRequestBody(exchange, (serverHttpRequest) ->
chain.filter(exchange.mutate().request(serverHttpRequest).build()));
}
5. 负载均衡不生效
缘由:缺少 LoadBalancer 依赖
解决方案:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
总结
Spring Cloud Gateway 作为 Spring Cloud 生态中的核心网关组件,提供了强劲而灵活的路由和过滤能力。通过本文的学习,你应该掌握了:
- 核心概念:Route、Predicate、Filter 的作用和关系
- 路由配置:YAML 和 Java 两种配置方式
- 断言工厂:Path、Method、Header、Query 等内置断言及自定义断言
- 过滤器:GatewayFilter 和 GlobalFilter 的使用及自定义
- 限流熔断:集成 Redis 限流和 Resilience4j/Sentinel 熔断
- 跨域处理:全局跨域配置和跨域冲突处理
- 安全鉴权:JWT 鉴权的完整实现
- 动态路由:基于 Nacos 的动态路由配置
- 最佳实践:生产环境配置和常见问题解决
在实际项目中,提议根据业务需求合理配置路由规则、限流策略和熔断机制,确保网关的高可用性和稳定性。





