Spring Cloud Gateway使用指南

内容分享18小时前发布
0 0 0

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

缘由:路由未匹配或服务未注册

排查步骤

  1. 检查路由配置是否正确
  2. 访问 /actuator/gateway/routes 查看已注册的路由
  3. 检查服务是否正确注册到注册中心

2. 503 Service Unavailable

缘由:目标服务不可用

解决方案

  1. 检查目标服务是否启动
  2. 检查服务名是否正确
  3. 配置熔断降级处理

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 生态中的核心网关组件,提供了强劲而灵活的路由和过滤能力。通过本文的学习,你应该掌握了:

  1. 核心概念:Route、Predicate、Filter 的作用和关系
  2. 路由配置:YAML 和 Java 两种配置方式
  3. 断言工厂:Path、Method、Header、Query 等内置断言及自定义断言
  4. 过滤器:GatewayFilter 和 GlobalFilter 的使用及自定义
  5. 限流熔断:集成 Redis 限流和 Resilience4j/Sentinel 熔断
  6. 跨域处理:全局跨域配置和跨域冲突处理
  7. 安全鉴权:JWT 鉴权的完整实现
  8. 动态路由:基于 Nacos 的动态路由配置
  9. 最佳实践:生产环境配置和常见问题解决

在实际项目中,提议根据业务需求合理配置路由规则、限流策略和熔断机制,确保网关的高可用性和稳定性。

Spring Cloud Gateway使用指南

© 版权声明

相关文章

暂无评论

none
暂无评论...