Skip to main content

概述

API 网关是微服务架构的入口,负责请求路由、负载均衡、鉴权、限流等功能。

网关的作用

                     ┌────────────────────────────────────┐
     客户端请求 ───►  │          API 网关 (Gateway)         │
                     │                                    │
                     │  ✓ 路由转发    ✓ 负载均衡          │
                     │  ✓ 身份认证    ✓ 权限校验          │
                     │  ✓ 限流熔断    ✓ 日志记录          │
                     │  ✓ 请求改写    ✓ 响应改写          │
                     └──────────────┬─────────────────────┘

           ┌────────────────────────┼────────────────────────┐
           │                        │                        │
           ▼                        ▼                        ▼
    ┌─────────────┐          ┌─────────────┐          ┌─────────────┐
    │  用户服务   │          │  订单服务   │          │  商品服务   │
    └─────────────┘          └─────────────┘          └─────────────┘

Spring Cloud Gateway

Spring Cloud Gateway 是 Spring Cloud 官方推出的网关组件,基于 WebFlux 响应式编程。

添加依赖

<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>
Spring Cloud Gateway 基于 WebFlux,不能和 spring-boot-starter-web 一起使用。

基本配置

server:
  port: 8080

spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      routes:
        - id: user-service           # 路由ID,唯一
          uri: lb://user-service     # 目标地址,lb:// 表示负载均衡
          predicates:                # 断言(路由条件)
            - Path=/api/users/**
          filters:                   # 过滤器
            - StripPrefix=1          # 去掉前缀
            
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1

路由配置方式

方式一:配置文件
spring:
  cloud:
    gateway:
      routes:
        - id: user-route
          uri: lb://user-service
          predicates:
            - Path=/users/**
方式二:Java 代码
@Configuration
public class GatewayConfig {
    
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("user-route", r -> r
                .path("/users/**")
                .uri("lb://user-service"))
            .route("order-route", r -> r
                .path("/orders/**")
                .filters(f -> f.stripPrefix(1))
                .uri("lb://order-service"))
            .build();
    }
}

断言工厂(Predicate)

断言用于匹配请求,满足条件的请求才会被路由。

常用断言

断言说明示例
Path路径匹配Path=/api/**
Method请求方法Method=GET,POST
Header请求头Header=X-Token, \d+
Query查询参数Query=name
Host主机名Host=**.example.com
CookieCookieCookie=token, \d+
After时间之后After=2024-01-01T00:00:00+08:00
Before时间之前Before=2024-12-31T23:59:59+08:00
Between时间区间Between=时间1,时间2
RemoteAddr客户端 IPRemoteAddr=192.168.1.0/24

示例

spring:
  cloud:
    gateway:
      routes:
        - id: complex-route
          uri: lb://service
          predicates:
            - Path=/api/**
            - Method=GET,POST
            - Header=Authorization
            - Query=token
            - After=2024-01-01T00:00:00+08:00[Asia/Shanghai]

过滤器(Filter)

过滤器可以在请求被路由前后进行处理。

内置过滤器

过滤器说明
StripPrefix去掉路径前缀
AddRequestHeader添加请求头
AddResponseHeader添加响应头
SetPath设置路径
RewritePath重写路径
SetStatus设置响应状态码
Retry重试
RequestRateLimiter限流

过滤器示例

spring:
  cloud:
    gateway:
      routes:
        - id: filter-route
          uri: lb://service
          predicates:
            - Path=/api/**
          filters:
            - StripPrefix=1
            - AddRequestHeader=X-Request-Id, ${random.uuid}
            - AddResponseHeader=X-Response-Time, ${timestamp}
            - RewritePath=/api/(?<segment>.*), /$\{segment}

全局过滤器

spring:
  cloud:
    gateway:
      default-filters:
        - AddRequestHeader=X-Gateway, true

自定义过滤器

自定义 GatewayFilter

@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {
    
    public AuthGatewayFilterFactory() {
        super(Config.class);
    }
    
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            
            // 获取 token
            String token = request.getHeaders().getFirst("Authorization");
            
            if (token == null || !validateToken(token)) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }
            
            return chain.filter(exchange);
        };
    }
    
    private boolean validateToken(String token) {
        // 验证 token 逻辑
        return true;
    }
    
    @Data
    public static class Config {
        private boolean enabled = true;
    }
}

自定义全局过滤器

@Component
@Order(-1)  // 数值越小,优先级越高
public class AuthGlobalFilter implements GlobalFilter {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().value();
        
        // 白名单路径
        if (isWhitePath(path)) {
            return chain.filter(exchange);
        }
        
        // 验证 token
        String token = request.getHeaders().getFirst("Authorization");
        if (token == null || !validateToken(token)) {
            return unauthorized(exchange);
        }
        
        return chain.filter(exchange);
    }
    
    private boolean isWhitePath(String path) {
        return path.contains("/login") || path.contains("/register");
    }
    
    private boolean validateToken(String token) {
        // 验证逻辑
        return true;
    }
    
    private Mono<Void> unauthorized(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        
        String body = "{\"code\":401,\"message\":\"未授权\"}";
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
        
        return response.writeWith(Mono.just(buffer));
    }
}

跨域配置

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            allowedHeaders: "*"
            maxAge: 3600

限流配置

基于 Redis 的限流

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
@Configuration
public class RateLimiterConfig {
    
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
        );
    }
    
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getHeaders().getFirst("X-User-Id")
        );
    }
}
spring:
  cloud:
    gateway:
      routes:
        - id: rate-limit-route
          uri: lb://service
          predicates:
            - Path=/api/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10   # 每秒允许请求数
                redis-rate-limiter.burstCapacity: 20   # 令牌桶容量
                key-resolver: "#{@ipKeyResolver}"

整合 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>
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080