跳至主要內容

Gateway

sixkey大约 9 分钟后端SpringCloud微服务Gateway

Gateway

Cloud之Gateway

官网总述

三大核心

  • Route:路由是构建网关的基本模块,它由ID,目标URL,一系列的断言和过滤器组成,如果断言为true则匹配该路由
  • Predicate(断言):参考的是Java8的java.util.function.predicate 开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
  • Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
screenshot-1712888226159

总结:

web前端请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。predicate就是我们的匹配条件;filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了

工作流程

screenshot-1712888789839

客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。

过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(Pre)或之后(Post)执行业务逻辑。

在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等;

在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

核心逻辑

路由转发 + 断言判断 + 执行过滤器链

创建gateway项目

创建

  • 建Model
cloud-gateway9527
  • 改Pom
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.cloud</groupId>
        <artifactId>cloud2024</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>cloud-gateway9527</artifactId>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!--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>
        </dependency>
        <!-- 指标监控健康检查的actuator,网关是响应式编程删除掉spring-boot-starter-web dependency-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  • 写Yaml
server:
  port: 9527

spring:
  application:
    name: cloud-gateway9527 #以微服务注册进consul或nacos服务列表内
  cloud:
    consul: #配置consul地址
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true
        service-name: ${spring.application.name}
  • 主启动类
@SpringBootApplication
@EnableDiscoveryClient //服务注册和发现
public class GatewayApp
{
    public static void main(String[] args)
    {
        SpringApplication.run(GatewayApp.class,args);
    }
}

添加网关前测试

在cloud-payment-service服务提供者中新增测试网关的两个接口

/**
     * 支付新增接口
     * @param payDto
     * @return
     */
    @PostMapping("/gateway/add")
    @Operation(summary = "新增",description = "支付接口")
    public ResultData gateWayAddPayment(@RequestBody PayDto payDto){
        payService.addPayment(payDto);
        return ResultData.success();
    }

    /**
     * 查询支付接口
     * @param orderNo
     * @return
     */
    @GetMapping("/gateway/query/{orderNo}")
    @Operation(summary = "查询",description = "查询支付接口")
    public ResultData gateWayQueryOrder(@PathVariable("orderNo") String orderNo){
        List<PayDto> payDto = payService.queryOrder(orderNo);
        return ResultData.success(payDto);
    }

进行测试,查看是否可以请求到数据

测试地址:localhost:8001/pay/gateway/query/9798464swxopen in new window

自测通过,不过这是没有添加网关之前的请求,因为本身已经知道了内部请求地址,所以能通过很正常。

添加网关后测试

  • cloud-gateway9527的yaml配置修改
server:
  port: 9527

spring:
  application:
    name: cloud-gateway9527 #以微服务注册进consul或nacos服务列表内
  cloud:
    consul: #配置consul地址
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true
        service-name: ${spring.application.name}
    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001                #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/query/**              # 断言,路径相匹配的进行路由


        - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001                #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/add/**              # 断言,路径相匹配的进行路由

进行测试,查看是否可以请求到数据

测试地址:localhost:9527/pay/gateway/query/9798464swxopen in new window

自测通过,说明网关配置没有问题。

引出问题

看看网关9527的Yml配置,映射写死问题

screenshot-1712890841286

高级特性

Route动态获取服务URL

以微服务名称,动态获取服务URL,解决URL地址写死问题

  • 修改9527网关的yaml配置
    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#          uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service            #以微服务名称动态获取URL
          predicates:
            - Path=/pay/gateway/query/**              # 断言,路径相匹配的进行路由


        - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#          uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service            #以微服务名称动态获取URL
          predicates:
            - Path=/pay/gateway/add/**              # 断言,路径相匹配的进行路由

Predicate断言配置

是什么?
screenshot-1712898946998
screenshot-1712898946998
架构概述
screenshot-1713000128791
screenshot-1713000128791

内置断言

screenshot-1712899012868
screenshot-1712899012868
常用内置断言

配置语法总体概述:有两种

  • Shortcut Configuration(常用)

    screenshot-1712899333697

  • Fully Expanded Arguments

screenshot-1712899517397

示例

常用断言api包括以上内置的十几种,这里以After断言举例,其他的配置举一反三

作用:配置了After断言意味着只有在这个时间段后才可以通过访问请求

screenshot-1712899698076

我们的问题是:上述这个After好懂,这个时间串串???对应的格式如何获得?

获得ZonedDateTime

/**
 * @auther wenxueshi
 * @create 2024-4-12 17:37
 */
public class ZonedDateTimeDemo
{
    public static void main(String[] args)
    {
        ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
              System.out.println(zbj);
    }
}

Yaml配置

gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service          #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
            - After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
自定义断言

官方内置的断言不够用时,可以看视频解决自定义断言。

总结

Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。

Filter过滤器链

常用的内置过滤器讲解

1、请求头(RequestHeader)相关组

具体讲解,其他的举一反三

网关服务添加yaml

 filters:
            - AddRequestHeader=X-Request-atguigu1,atguiguValue1  # 请求头kv,若一头含有多参则重写一行设置
            - AddRequestHeader=X-Request-atguigu2,atguiguValue2

2、请求参数(RequestParameter)相关组

自定义过滤器
全局过滤器

案例分析:统计接口调用耗时情况,通过自定义全局过滤器解决上述要求

  • 步骤

    • 新建类MyGlobalFilter并实现GlobalFilter和Ordered两个接口

    • code

      package org.cloud.config;
      
      /**
       * ClassName: MyGlobalFilter
       * Package: org.cloud.config
       * Description:
       *
       * @Author: @weixueshi
       * @Create: 2024/4/12 - 15:50
       * @Version: v1.0
       */
      
      import jakarta.validation.constraints.Size;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.cloud.gateway.filter.GatewayFilterChain;
      import org.springframework.cloud.gateway.filter.GlobalFilter;
      import org.springframework.core.Ordered;
      import org.springframework.web.server.ServerWebExchange;
      import reactor.core.publisher.Mono;
      
      /**
       * 自定义全局网关过滤器
       */
      @Slf4j
      @Component
      public class MyGlobalFilter implements GlobalFilter, Ordered {
      
          /**
           * 开始访问时间
           */
          private static final String BEGIN_VISIT_TIME = "begin_visit_time";
          /**
           *第2版,各种统计
           * @param exchange
           * @param chain
           * @return
           */
          @Override
          public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
              //先记录下访问接口的开始时间
              exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());
      
              return chain.filter(exchange).then(Mono.fromRunnable(()->{
                  Long beginVisitTime = exchange.getAttribute(BEGIN_VISIT_TIME);
                  if (beginVisitTime != null){
                      log.info("访问接口主机: " + exchange.getRequest().getURI().getHost());
                      log.info("访问接口端口: " + exchange.getRequest().getURI().getPort());
                      log.info("访问接口URL: " + exchange.getRequest().getURI().getPath());
                      log.info("访问接口URL参数: " + exchange.getRequest().getURI().getRawQuery());
                      log.info("访问接口时长: " + (System.currentTimeMillis() - beginVisitTime) + "ms");
                      log.info("我是美丽分割线: ###################################################");
                      System.out.println();
                  }
              }));
          }
      
          /**
           * 过滤器执行优先级,数字越小优先级越大
           * @return
           */
          @Override
          public int getOrder() {
              return 0;
          }
      }
      
单一内置过滤器

是什么?

**区别:**以上的全局过滤器是对网关中的所有请求路径下的所有方法都有效,单一内置过滤器是针对某一个请求路径下的所有方法有效。范围比全局过滤器要小。

案例分析:我们内置的这个过滤器的作用是进行请求时需要携带一个statusTest参数并且值为任意才可以通过过滤器

定义步骤

1、新建类名xxx需要以GatewayFilterFactory结尾并继承AbstractGatewayFilterFact0ry类

@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config>
{
}

2、新建xxxGatewayFilterFactory.Config内部类

public static class Config {
        @Setter @Getter
        private String status;
}

3、重写apply方法

@Override
    public GatewayFilter apply(MyGatewayFilterFactory.Config config)
    {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                ServerHttpRequest request =  exchange.getRequest();
                System.out.println("进入自定义网关过滤器MyGatewayFilterFactory,status===="+config.getStatus());
                if(request.getQueryParams().containsKey("statusTest")) {
                    return chain.filter(exchange);
                }else {
                    exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
                    return exchange.getResponse().setComplete();
                }
            }
        };
    }

4、重写shortcutFieldOrder

@Override
public List<String> shortcutFieldOrder() {
     return Arrays.asList("status");
}

5、空参构造方法,内部调用super()

public MyGatewayFilterFactory() {
        super(MyGatewayFilterFactory.Config.class);
    }

如何在网关配置

  • 先看出厂配置

    screenshot-1712911648995
    screenshot-1712911648995
  • 自己定制My配置

    screenshot-1712911965900
    screenshot-1712911965900

测试

由上图可知,我们只配置了id为pay_routh3下的请求路径

请求路径: