Spring Cloud Gateway路由+断言+过滤

介绍


Spring Cloud Gateway 是 Spring Cloud 生态系统中的一个基于 Spring WebFlux 的 API 网关解决方案,旨在为微服务架构提供统一的入口点,支持路由、过滤、负载均衡等功能。它通过非阻塞的响应式编程模型,提供了高效、灵活的 API 网关能力。

核心功能


  • 动态路由: 支持基于路径、Header、Query参数等规则的动态路由。
  • 过滤器链: 通过全局过滤器 (Global Filter) 和局部过滤器 (Gateway Filter) 实现请求增强与响应修改。
  • 负载均衡: 集成Spring Cloud LoadBalancer,支持多实例流量分发。
  • 限流与熔断: 内置限流功能,支持 Redis 令牌桶算法,可结合 Resilience4j 实现熔断。
  • 安全与监控: 支持JWT、OAuth2等认证机制,保护后端服务。

三大核心


1. 路由 (Route)

路由是网关的核心,用于定义请求的转发规则。他由ID、目标URL、一系列的断言和过滤器组成,如果断言为true则路由匹配成功。一个路由包含以下属性:

  • ID: 路由的唯一标识。
  • URI: 目标服务的地址,支持 http、lb(负载均衡)等协议。
  • 断言: 定义路由匹配条件,如路径、Header、方法等。
  • 过滤器: 对请求或响应进行处理的逻辑。

2. 断言 (Predicate)

断言用于判断请求是否符合路由规则。常见的断言包括:

  • Path: 路径匹配。
  • Method: HTTP 方法匹配。
  • Header: 请求头匹配。
  • Query: 请求参数匹配。
  • After、Before、Between: 时间范围匹配。

3. 过滤 (Filter)

过滤器用于在请求转发前或响应返回后执行特定逻辑。常见的过滤器包括:

  • 全局过滤器: 对所有路由生效,如日志记录、认证校验。
  • 局部过滤器: 仅对特定路由生效,如路径重写、请求限流。

Route以服务名动态获取URL


网关服务 cloud-gateway9527 引入依赖

<!-- gateway -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 服务注册发现consul discovery,网关也要注册进服务注册中心统一管控 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 指标监控健康检查的actuator -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

yml配置

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  # Spring Cloud Consul for Service Discovery
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
    gateway:
      routes:
        - id: pay_routh1  # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
          uri: lb://cloud-payment-service  # 服务名
          predicates:
            - Path=/pay/gateway/get/**  # 断言,路径匹配的进行路由

订单服务 cloud-feign-order9002:OrderGatewayController

@RestController
public class OrderGatewayController {

    @Resource
    private PayFeignApi payFeignApi;

    @GetMapping("/feign/pay/gateway/get/{id}")
    public Result getById(@PathVariable("id") Integer id) {
        return payFeignApi.getById(id);
    }
}

Feign接口 cloud-common-api:PayFeignApi

@FeignClient("cloud-gateway")
public interface PayFeignApi {

    @GetMapping("/pay/gateway/get/{id}")
    public Result getById(@PathVariable("id") Integer id);
}

支付服务 cloud-payment8001:PayGatewayController

@RestController
public class PayGatewayController {

    @Resource
    private PayService payService;

    @GetMapping(value = "/pay/gateway/get/{id}")
    public Result<Pay> getById(@PathVariable("id") Integer id) {
        Pay pay = payService.getById(id);
        return Result.success(pay);
    }
}

测试结果

启动支付服务8001,启动订单服务9002,访问 http://localhost:9002/feign/pay/gateway/get/1 返回异常。再启动网关服务9527,访问 http://localhost:9002/feign/pay/gateway/get/1 返回成功。

Predicate常用断言


Path Route Predicate

spring:
  cloud:
    gateway:
      routes:
        - id: pay_routh1  # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
          uri: lb://cloud-payment-service  # 服务名
          predicates:
            - Path=/pay/gateway/get/**  # 断言,路径匹配的进行路由

测试结果

访问 http://localhost:9527/pay/gateway/get/1 返回成功。

After Route Predicate

# 获取当前时间串
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
spring:
  cloud:
    gateway:
      routes:
        - id: pay_routh1  # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
          uri: lb://cloud-payment-service  # 服务名
          predicates:
            - Path=/pay/gateway/get/**  # 断言,路径匹配的进行路由
            - After=2025-04-19T15:51:15.516781+08:00[Asia/Shanghai]  # 在某个时间之后可以访问

测试结果

2025-04-19 15:51:15 之前访问 http://localhost:9527/pay/gateway/get/1 返回失败。
2025-04-19 15:51:15 之后访问 http://localhost:9527/pay/gateway/get/1 返回成功。

Before Route Predicate

spring:
  cloud:
    gateway:
      routes:
        - id: pay_routh1  # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
          uri: lb://cloud-payment-service  # 服务名
          predicates:
            - Path=/pay/gateway/get/**  # 断言,路径匹配的进行路由
            - Before=2025-04-19T15:51:15.516781+08:00[Asia/Shanghai]  # 在某个时间之前可以访问

测试结果

2025-04-19 15:51:15 之前访问 http://localhost:9527/pay/gateway/get/1 返回成功。
2025-04-19 15:51:15 之后访问 http://localhost:9527/pay/gateway/get/1 返回失败。

Between Route Predicate

spring:
  cloud:
    gateway:
      routes:
        - id: pay_routh1  # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
          uri: lb://cloud-payment-service  # 服务名
          predicates:
            - Path=/pay/gateway/get/**  # 断言,路径匹配的进行路由
            - Between=2025-04-19T16:11:15.516781+08:00[Asia/Shanghai],2025-04-19T16:12:15.516781+08:00[Asia/Shanghai]  # 在某个时间段之间可以访问

测试结果

2025-04-19 16:11:15 到 2025-04-19 16:12:15 时间段之间访问 http://localhost:9527/pay/gateway/get/1 返回成功。

Cookie Route Predicate

spring:
  cloud:
    gateway:
      routes:
        - id: pay_routh1  # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
          uri: lb://cloud-payment-service  # 服务名
          predicates:
            - Path=/pay/gateway/get/**  # 断言,路径匹配的进行路由
            - Cookie=username,zzyy  # cookie中含有username=zzyy可以访问

测试结果

  • 原生命令测试
    curl http://localhost:9527/pay/gateway/get/1 --cookie “username=zzyy” 返回成功。
  • postman测试
    请求头中添加 cookie:username=zzyy,访问 http://localhost:9527/pay/gateway/get/1 返回成功。

Header Route Predicate

spring:
  cloud:
    gateway:
      routes:
        - id: pay_routh1  # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
          uri: lb://cloud-payment-service  # 服务名
          predicates:
            - Path=/pay/gateway/get/**  # 断言,路径匹配的进行路由
            - Header=X-Request-Id,\d+  # 请求头中含有X-Request-Id且值为整数的正则表达式可以访问

测试结果

  • 原生命令测试
    curl http://localhost:9527/pay/gateway/get/1 -H “X-Request-Id:123” 返回成功。
  • postman测试
    请求头中添加 X-Request-Id:123,访问 http://localhost:9527/pay/gateway/get/1 返回成功。

Host Route Predicate

spring:
  cloud:
    gateway:
      routes:
        - id: pay_routh1  # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
          uri: lb://cloud-payment-service  # 服务名
          predicates:
            - Path=/pay/gateway/get/**  # 断言,路径匹配的进行路由
            - Host=**.zzyy.com  # 主机地址后必须含有.zzyy.com可以访问

测试结果

  • 原生命令测试
    curl http://localhost:9527/pay/gateway/get/1 -H “Host:www.zzyy.com” 返回成功。
  • postman测试
    请求头中添加 Host:www.zzyy.com,访问 http://localhost:9527/pay/gateway/get/1 返回成功。

Query Route Predicate

spring:
  cloud:
    gateway:
      routes:
        - id: pay_routh1  # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
          uri: lb://cloud-payment-service  # 服务名
          predicates:
            - Path=/pay/gateway/get/**  # 断言,路径匹配的进行路由
            - Query=uid,\d+  # 请求参数必须含有uid且值为整数的正则表达式可以访问

测试结果

访问 http://localhost:9527/pay/gateway/get/1?uid=123 返回成功。

RemoteAddr Route Predicate

spring:
  cloud:
    gateway:
      routes:
        - id: pay_routh1  # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
          uri: lb://cloud-payment-service  # 服务名
          predicates:
            - Path=/pay/gateway/get/**  # 断言,路径匹配的进行路由
            - RemoteAddr=192.168.42.1/24  # 远程访问地址必须是192.168.42.xx才能访问

测试结果

当前电脑IP为 192.168.42.3,访问 http://192.168.42.3:9527/pay/gateway/get/1 返回成功。

Method Route Predicate

spring:
  cloud:
    gateway:
      routes:
        - id: pay_routh1  # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
          uri: lb://cloud-payment-service  # 服务名
          predicates:
            - Path=/pay/gateway/get/**  # 断言,路径匹配的进行路由
            - Method=GET,POST  # get/post请求可以访问

测试结果

get 请求访问 http://localhost:9527/pay/gateway/get/1 返回成功。

自定义Predicate


网关服务 cloud-gateway9527 中新建 MyRoutePredicateFactory

// 自定义配置会员等级,按照 铂、金、银和yml配置的会员等级,才可以访问
// 继承 AbstractRoutePredicateFactory
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {

    // 无参构造方法
    public MyRoutePredicateFactory() {
        super(MyRoutePredicateFactory.Config.class);
    }

    // 短格式
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("userType");
    }

    // 重写apply方法
    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new Predicate<ServerWebExchange>() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                // 检查request参数中是否存在userType,且值和config中的相同,则可以访问
                String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
                return userType != null && userType.equals(config.getUserType());
            }
        };
    }

    // 这个Config类就是路由断言规则
    public static class Config {
    
        private @NotNull String userType;  // 铂、金、银和yml配置的会员等级

        public Config() {}

        public String getUserType() {
            return userType;
        }

        public void setUserType(String userType) {
            this.userType = userType;
        }
    }
}

yml配置

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  # Spring Cloud Consul for Service Discovery
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
    gateway:
      routes:
        - id: pay_routh1  # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
          uri: lb://cloud-payment-service  # 服务名
          predicates:
            - Path=/pay/gateway/get/**  # 断言,路径匹配的进行路由
            - My=gold  # 自定义断言

测试结果

访问 http://localhost:9527/pay/gateway/get/1?userType=gold 返回成功。

自定义Filter


自定义全局Filter统计接口调用耗时情况

网关服务 cloud-gateway9527 中新建 MyGlobalFilter

@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered {

    private static final String START_TIME = "start_time";  // 开始调用方法的时间

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 记录开始时间
        exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            Long startTime = exchange.getAttribute(START_TIME);
            if (startTime != null) {
                URI uri = exchange.getRequest().getURI();
                log.info("访问接口主机:" + uri.getHost() + ",端口:" + uri.getPort() +
                         ",URL:" + uri.getPath() + ",参数:" + uri.getRawQuery() +
                         ",时长:" + (System.currentTimeMillis() - startTime) + "毫秒");
            }
        }));
    }

    // 值越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}

测试结果

访问 http://localhost:9527/pay/gateway/get/1 日志输出:访问接口主机:localhost,端口:9527,URL:/pay/gateway/get/1,参数:null,时长:6毫秒

自定义条件Filter

// 继承 AbstractGatewayFilterFactory
@Component
@Slf4j
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config>  {

    // 无参构造方法
    public MyGatewayFilterFactory() {
        super(MyGatewayFilterFactory.Config.class);
    }
    
    // 短格式
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("state");
    }

    // 重写apply方法
    @Override
    public GatewayFilter apply(MyGatewayFilterFactory.Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                ServerHttpRequest request = exchange.getRequest();
                log.info("进入自定义条件过滤器MyGatewayFilterFactory, state=" + config.getState());
                if (request.getQueryParams().containsKey("zzyy")) {
                    return chain.filter(exchange);
                }
                exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
                return exchange.getResponse().setComplete();
            }
        };
    }

    public static class Config {
    
        private String state;

        public Config() {}

        public String getState() {
            return state;
        }

        public void setState(String state) {
            this.state = state;
        }
    }
}

yml配置

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  # Spring Cloud Consul for Service Discovery
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
    gateway:
      routes:
        - id: pay_routh1  # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
          uri: lb://cloud-payment-service  # 服务名
          predicates:
            - Path=/pay/gateway/get/**  # 断言,路径匹配的进行路由
          filters:
            - My=zzyy  # 自定义条件过滤器

测试结果

访问 http://localhost:9527/pay/gateway/get/1?zzyy=16 返回成功。

总结


以上主要介绍了 Spring Cloud Gateway 路由、断言、过滤的相关知识,以及自定义 Predicate 和 Filter,想了解更多 Spring Cloud Gateway 知识的小伙伴请参考 Spring Cloud Gateway 官网 进行学习,学习更多 Spring Cloud 实战实用技巧的小伙伴,请关注后期发布的文章,认真看完一定能让你有所收获。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值