GateWay

805 阅读19分钟

GateWay

GateWay概述:

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring6,Spring Boot 3和Project Reactor等技术。它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式,并为它们提供跨领域的关注点,例如:安全性、监控/度量和恢复能力。

GateWay是微服务分布式架构的入口程序,所有的请求通过GateWay才能访问后面的服务。

GateWay体系.jpg

GateWay作用:反向代理、鉴权、流量控制、熔断、日志监控

Spring Cloud Gateway组件的核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。 Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器,隐藏微服务结点IP端口信息,从而加强安全保护。

Spring Cloud Gateway本身也是一个微服务,需要注册进服务注册中心。

image-20241018201710734.png

GateWay3大核心:

  • Route 路由:路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
  • Predicate 断言:开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
  • Filter 过滤:对前两种功能的增强,使用过滤器,可以在请求被路由之前或者之后对请求进行修改(添加相应的功能)

Gateway网关工作流程:

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

核心逻辑:路由转发+断言判断+执行过滤器链

GateWay使用步骤:

Gateway作为服务也要注册到服务注册中心

在80和8001之间通过GateWay来进行隔离。

之前在80端可用直接通过OpenFeign直接访问8001,但是现在还要多一个Gateway

  1. 新建一个模块,eg:cloud-gateway-9527,来做网关

    • 建Model:eg:cloud-gateway-9527

    • 引入Gateway相应的坐标:

      <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>
      
    • yml文件:将Gateway注册进consul

      server:
        port: 9527
      
      spring:
        application:
          name: cloud-gateway #以微服务注册进consul或nacos服务列表内
        cloud:
          consul: #配置consul地址
            host: localhost
            port: 8500
            discovery:
              prefer-ip-address: true
              service-name: ${spring.application.name}
      
    • 主启动:

      @SpringBootApplication
      @EnableDiscoveryClient //服务注册和发现
      public class Main9527
      {
          public static void main(String[] args)
          {
              SpringApplication.run(Main9527.class,args);
          }
      }
      
    • 通用包中的FeignApi类上的@FeignClient中的服务名,使用GateWay对应的那个模块的服务名:

      @FeignClient(value ="cloud-gateway")//定义了一个Feign客户端,用于与名为cloud-gateway的微服务进行通信。!!!
      public interface PayFeignApi {
      
          /**
           * GateWay进行网关测试案例01
           * @param id
           * @return
           */
          @GetMapping(value = "/pay/gateway/get/{id}")
          public ResultData getById(@PathVariable("id") Integer id);
      
          /**
           * GateWay进行网关测试案例02
           * @return
           */
          @GetMapping(value = "/pay/gateway/info")
          public ResultData<String> getGatewayInfo();
      }
      
      
      
    • 业务类:

      不写任何代码,因为网关和业务无关。

  2. 将9527路由映射到相应的服务上(eg:8001服务提供者),就可以通过9527网关找到8001微服务

    • 将9527网关映射到8001微服务上,在网关的yml文件中配置:

      server:
        port: 9527
      
      spring:
        application:
          name: cloud-gateway #以微服务注册进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/get/**              # 断言,路径相匹配的进行路由
      
      
              - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合                                                      服务名
                uri: http://localhost:8001                #匹配后提供服务的"路由地址"
                predicates:
                  - Path=/pay/gateway/info/**             # 断言,路径相匹配的进行路由
                  
               "这里配置了两个路由/断言,对应8001微服务controller中的两个方法地址"
      
    • 在通用包中的FeignApi类上的@FeignClient中的服务名,使用GateWay对应的那个模块的服务名:

      @FeignClient(value ="cloud-gateway")
      //开启网关之后,80微服务先通过Feign找到GateWay网关微服务,然后在通过断言和路由的地址访问到8001微服务!!!
      public interface PayFeignApi {
      
          /**
           * GateWay进行网关测试案例01
           * @param id
           * @return
           */
          @GetMapping(value = "/pay/gateway/get/{id}")
          public ResultData getById(@PathVariable("id") Integer id);
      
          /**
           * GateWay进行网关测试案例02
           * @return
           */
          @GetMapping(value = "/pay/gateway/info")
          public ResultData<String> getGatewayInfo();
      }
      
    • 8001服务提供者模块上对应的controller

      @RestController
      public class PayGateWayController
      {
          @Resource
          PayService payService;
      
          @GetMapping(value = "/pay/gateway/get/{id}")
          public ResultData<Pay> getById(@PathVariable("id") Integer id)
          {
              Pay pay = payService.getById(id);
              return ResultData.success(pay);
          }
      
          @GetMapping(value = "/pay/gateway/info")
          public ResultData<String> getGatewayInfo()
          {
              return ResultData.success("gateway info test:"+ IdUtil.simpleUUID());//流水号
          }
      }
      
    • 客户端模块80controller

      
      @RestController
      public class OrderGateWayController
      {
          /**
           * GateWay进行网关测试案例01
           * @param id
           * @return
           */
      
          @Resource
          private PayFeignApi payFeignApi;
      
          @GetMapping(value = "/feign/pay/gateway/get/{id}")
          public ResultData getById(@PathVariable("id") Integer id)
          {
              return payFeignApi.getById(id);
          }
      
          @GetMapping(value = "/feign/pay/gateway/info")
          public ResultData<String> getGatewayInfo()
          {
              return payFeignApi.getGatewayInfo();
          }
      }
      
      "之后访问时:客户端之间通过http://localhost:9527/pay/getway/get/1(服务提供者的对应地址)来访问
      

      注意:为8001微服务添加网关之后,客户端80先通过Feign找到GateWay网关微服务,然后在通过其断言和路由的地址访问到8001微服务!!!

GateWay高级特性(!!!):

路由Route :

Route以微服务名动态获取服务的URL!!!,即想要访问哪个服务,就要在yml里面配置好其服务注册中心的微服务名称。

原来的URL是写死端口的eg:http:localhost:8001。如果有多个微服务,要依据服务名称,来匹配查找,要将uri写作 lb://服务名 的方式。

server:
  port: 9527

spring:
  application:
    name: cloud-gateway #以微服务注册进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: lb://cloud-payment-service          #匹配后提供服务的路由地址 ”9527代理的微服务的名字“
//想要访问哪个微服务就把它的名称配到这里!!!!
          predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由


        - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-payment-service                #匹配后提供服务的路由地址 ”9527代理的微服务的名字“
          predicates:
            - Path=/pay/gateway/info/**             # 断言,路径相匹配的进行路由

注意:之后比如微服务提供者有多个(不止8001)或者更换微服务提供者,我们可用通过使用:依据微服务名称,来匹配查找,之后无论端口如何变化,都可以找到对应的微服务。eg:上面我们配置了:uri: lb://cloud-payment-service,就是请求通过cloud-payment-service这个名字取找到对应的服务来进行访问,如果想要访问哪个服务要在这里配置好其服务注册中心的微服务名称。

断言Predicate:

路由:是否可以找到对应的服务

断言:相当于增加一些访问条件限制,通过布尔匹配值,相当于请求找到对应服务之后通过一系列访问条件(eg:访问时间是否在规定范围之内等条件)根据断言是否为tur,来进一步判断请求是否可以访问到对应服务**(!!!)**,需要哪些断言(限制条件)就在yml文件里面的 predicates下面配置,自定义也好,默认也好,可以参照下面:

两种配置语法方法
  1. Shortcut Configuration(简短的配置)

    •  predicates:
         - cookie=mycookie,mycookievalue
       等于号左边是过滤项名称,比如路径、cookie等等,等号右边是条件相应的值,如果是kv信息,key和value用逗号隔开,左边是    key,右边是value值。多个参数的信息也是各个参数之间使用逗号分隔。
      
  2. Fully Expanded Arguments(全面的配置)

    •           predicates:
                  - name: cookie
                    args: 
                      name: mycookie
                      regexp: mycookievalue
      

      一般使用简短配置,二选一。

常用内置断言api:

通过提供的RoutePredicateFactory来实现配置不同的路由规则,下面介绍种常用的Predicate

断言语法配置规则:

 #id:我们自定义的路由 ID,保持唯一
 #uri:目标服务的地址
 #predicates:路由条件,Predicate接受一个输入参数返回一个布尔值。
 ##           该属性包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)
1.After Route配置

After的配置项的值是一个ZoneDateTime类型的时间信息。此配置表示只有当前时间在这个配置的时间值之后,才能断言为true,才能成功访问对应服务,否则是false即访问失败。

predicates:
          - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
          - After=2024-10-19T21:07:02.975484400+08:00[Asia/Shanghai]
          eg:在2024-10-19 21:07:02之后的时间才能访问对应服务,否则报错。
2.Before Route

Before配置项同理,不过是当前时间要在配置的值的时间之前才能断言为true,才能成功访问对应服务。

predicates:
  - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
  - Before=2024-10-19T21:07:02.975484400+08:00[Asia/Shanghai]
  eg:在2024-10-19 21:07:02之前的才能访问对应服务,否则报错。
3.Between Route配置

时间信息同上,不过是开始时间和结束时间以:- Between=开始时间,结束时间 的方式配置,用逗号分隔即可。

predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
            - Between=2024-10-19T21:07:02.975484400+08:00[Asia/Shanghai],2024-10-19T21:08:02.975484400+08:00[Asia/Shanghai]
            eg:在这两个段时间之间才能访问对应服务,否则报错。
4.Cookie Route配置

Cookie Route Predicate需要两个参数,一个是 Cookie name ,一个是正则表达式。

请求必须包含cookie,之后路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上返回true就会执行路由,如果没有匹配上返回false则不执行

使用 - cookie=cookiename,正则表达式 的方式。

predicates:
  - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
  - After=2024-10-19T21:07:02.975484400+08:00[Asia/Shanghai] #必须在这个时间之后才能访问
  - cookie=username,cxk                  #必须含有cookie,且cookie的值和正则表达式的值要分别等于username,cxk

5.Header Route配置

通过请求头进行断言。两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配上才可执行。

predicates:
  - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
  - Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式
  
 之后客户端访问时,必须在请求头上加上X-Request-Id参数/属性,且值必须为整数才能访问成功。
6.Host Route配置

通过参数和主机地址进行匹配/断言。

Host Route Predicate 两个参数:一个是参数名,一个是匹配的域名列表,用.号作为分隔符。

predicates:
          - Host=**.atguigu.com
         
 之后客户端访问时,请求头上必须包含*.*.atguigu.com才能访问成功(eg:www.atguigu.com)
7.Path Route配置

请求路径相匹配的断言(为true)才会进行路由

predicates:
          - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
8.Query Route配置

带查询参数的断言

支持传入两个参数,一个是属性名,一个是属性值,属性值可以是正则表达式

predicates:
         - Query=username, \d+  # 要有参数名username并且值还要是整数才能路由
             
"客户端请求必须包含参数username,且他的值是整数才能路由/访问"
eg:http:localhost:9527/pay/getway/get/3?username=123
9.RemoteAddr Route配置

外部通过访问ip的限制进行断言,符合ip的才可以通过路由。

predicates:
            - RemoteAddr=192.168.124.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
                
之后远程能访问微服务的地址必须是192.168.124开头才可以访问成功
使用localhost访问就失效了,现在只支持192.168.124.(1-24)...
10Method Route配置

配置某个请求地址,只能用Get/Post访问,对方法进行限制

predicates:
     -Method=GET,POST
只能使用配置的请求方式进行微服务的访问,否则为false,无法访问。

总结:

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

自定义Predicate:

xxxRoutePredicateFactory

启动微服务之后,后台日志就会显示出官网上原生默认的Predicate,如果生产规则很复杂,默认的断言配置不满足,就需要子自定义。

步骤:

  1. 新建类,类名xxx必须以RoutePredicateFactory结尾,并继承AbstractRoutePredicateFactory类。

    @Component//所有自定义的东西都要加注解,被扫描到!
    public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
    }
    
  2. 重写apply方法

    @Override
     public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
        return null;
    }
    
  3. 新建apply所需要的静态内部类MyRoutePredicateFactory.Config,这个Config类就是我们路由断言规则重要

    @Validated
    public static class Config{
        @Setter
        @Getter
        @NotEmpty
        private String userType; //钻、金、银等会员等级
    }
    
  4. 空参构造方法,内部调用super。

    public MyRoutePredicateFactory()
    {
        super(MyRoutePredicateFactory.Config.class);
    }
    
  5. apply方法的具体重写

     @Override
        public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
            return new Predicate<ServerWebExchange>() {
                @Override
                public boolean test(ServerWebExchange serverWebExchange) {
           //查询requestq的参数里面是否带着参数会员等级userType。
                    String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
                    if(userType==null){ //如果没带着userType,断言就为false,就不可以访问
                        return false;
                    }
           //如果参数存在就和config中的数据即会员等级userType进行比较。
                    //如果userType的值等于上面userTpe的值(钻/金/银),断言为true,可以访问
                    if (userType.equalsIgnoreCase(config.getUserType())){
                        return true;
                    }
                    return false;//如果userType的值不等于上面userTpe的值(钻/金/银),断言为false,不可以访问
                }
            };
        }
    
  6. 加上支持短配置格式

      //支持Shortcut Configuration配置
        @Override
        public List<String> shortcutFieldOrder(){
            return Collections.singletonList("userType");
        }
    
  7. 之后在yml文件中配置上自定义断言即可使用:

    eg:我们这里设置自定义断言匹配值为:diamond,即请求中必须包含userType参数且参数值必须为diamond才能访问成功。

     predicates:
           - My=diamond
               
          "My:即自定义Predicate类名称RoutePredicateFactory前面的部分,即使用My来代表我们上面自定义的断言"
    

    注意:之后调用相关服务时,请求路径后面必须加上?userType=diamond才能成功访问到对应的服务。(断言要为true)

  8. 完整代码:

    
    /**
     * 自定义Predicate:自定义配置会员等级userType,按照钻石、金、银不同的会员等级进行断言判断,来访问对应的服务
     */
    @Component//所有自定义的东西都要加注解,被扫描到!
    public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {//1.新建类
    
        //4.
        public MyRoutePredicateFactory(){
            super(MyRoutePredicateFactory.Config.class);
        }
    
    
        //3.
        @Validated
        public static class Config
        {
            @Getter
            @Setter
            @NotEmpty
            private String  userType;//钻/金/银等会员等级
        }
        
        //6.
        @Override
        public List<String> shortcutFieldOrder(){
            return Collections.singletonList("userType");
        }
    
    
        //2.重写apply方法 //5.
        @Override
        public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
            return new Predicate<ServerWebExchange>() {
                @Override
                public boolean test(ServerWebExchange serverWebExchange) {
            //查询参数里面是否带着会员等级userType
                    String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
                    if(userType==null){ //如果没带着userType,断言就为false,就不可以访问
                        return false;
                    }
           //如果参数存在就和config中的数据即会员等级userType进行比较。
                    //如果userType的值等于上面userTpe的值(钻/金/银),断言为true,可以访问
                    if (userType.equalsIgnoreCase(config.getUserType())){
                        return true;
                    }
                    return false;//如果userType的值不等于上面userTpe的值(钻/金/银),断言为false,不可以访问
                }
            };
        }
    
    }
    
    

    **综上:**执行过程:即当请求根据网关上配置的路由即:uri: lb://cloud-payment-service找到对应的cloud-payment-servic e服务后,之后再会根据request请求里面是否有userType参数,且userType参数是否为diamond,进行自定义断言的判断,自定义断言MyRoutePredicateFactory为true,才能成功访问对应服务。

    访问时:可以直接使用网关的9527地址进行访问相应的服务(即网关yml文件中配置的对应服务)。

    eg:localhost:9527/paygateway/get/1.(9527即网关,paygateway/get/1即网关的yml文件配置的cloud-payment-service服务提供者中的相应服务,通过9527访问8001服务)

过滤Filter:

能干嘛:请求鉴权、异常处理

在请求被执行前和调用过滤和被执行后调用过滤,用来修改请求和响应信息。

断言主要功能是根据条件是否为true进行路由的转发,而过滤器则是进入了路由,在请求到达接口之前和接口响应之后做一些操作

GatewayFilter内置过滤器(单一):
1.请求头相关组
  1. 以AddRequestHeader GatewayFilter

    当断言为true后就添加请求头

     route: 
              - id: pay_routh3 #pay_routh3
              uri: lb://cloud-payment-service                #匹配后提供服务的路由地址
              predicates:
                - Path=/pay/gateway/filter/**              # 断言,路径相匹配的进行路由
              filters:
                - AddRequestHeader=X-Request-atguigu1,atguiguValue1  # 请求头kv,若一头含有多参则重写一行设置
                - AddRequestHeader=X-Request-atguigu2,atguiguValue2    
    

    当断言为true通过路由,就会在请求上加上请求头,请求头的名称为:X-Request-atguigu1和X-Request-atguigu2,其值分别为atguiguValue1和atguigu2。

  2. RemoveRequestHeader同理,当断言为true时,就删除指定请求头

    - RemoveRequestHeader=sec-fetch-site      # 删除请求头sec-fetch-site
    
  3. SetRequestHeader是当断言为true时更新请求头的值,将指定的请求头的值更新为指定的值。

    - SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
    
2.请求参数(RequestParameter)相关组
  1. AddRequestParameter

    在请求上地址上面新增相应的请求参数

     filters:
                - AddRequestParameter=customerId,9586998     
                 //在请求路径上新增参数:?customerId=9586998
    
  2. RemoveRequestParameter

    删除请求路径上相应的请求参数

    filters:
                - RemoveRequestParameter=customerId,9586998 
                 //在请求路径上删除参数:?customerId=9586998
    
3.回应头(ResponseHeader)相关组

回应头相关组也有add,remove、set

- AddResponseHeader=X-Response-atguigu, BlueResponse # 在回应头新增参数X-Response-atguigu并设值为BlueResponse
- SetResponseHeader=Date,2099-11-11 # 更新设置回应头Date值为2099-11-11
- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除

回应头过滤组.png

4.前缀和路径相关组(!!!)

网关对于安全加密:通过对地址进行前缀和路径编排之后,隐藏真实地址,起到加密,安全的作用。

  1. 自动添加路径前缀PrefixPath:

    eg:通过路由的请求会被加上前缀/pay

    //- Path=/pay/gateway/filter/**    被分拆为: PrefixPath + Path
     
    predicates:
                - Path=/gateway/filter/**              # 断言,为配合PrefixPath测试过滤,暂时注释掉/pay
           filters:
                - PrefixPath=/pay
                
    之后在执行的时候就会在/gateway/filter/**之前加上 "/pay"即现在完整的组合地址为:  http:localhost:9527/pay/gateway/filter/**
        
    而实际调用的地址:http:localhost:9527/gateway/filter
    

    相当于前缀被过滤器统一管理了,之后在请求地址上都会加上/pay。

  2. SetPath

    就是将路径中的指定的部分修改

前缀路径相关组修改地址.png

​ 即使用/XYZ/abc代替/pay/gateway起到加密的作用

3.RedirectTo重定向

即重定向到某个页面/地址

          predicates:
            - Path=/pay/gateway/filter/** # 真实地址
          filters:
            - RedirectTo=302, <http://www.atguigu.com/> # 访问http://localhost:9527/pay/gateway/filter就会跳到http://www.atguigu.com/
5.DefaultFiters

添加过滤,应用到所有的路由(配置是全局通用)。

不推荐使用。

一般根据id,针对路由(某个微服务),里面某个具体的访问路径做具体/细粒度的过滤配置。

GateWay自定义过滤器
  1. 自定义全局Filter

    案例:统计接口调用的耗时情况

    步骤:

    • 创建类eg:MyGlobalFilter实现两个接口并实现其方法

      @Component//注意别忘了!!!
      @Slf4j
      public class MyGlobalFilter implements GlobalFilter, Ordered {
      
          public static final String BEGIN_VISIT_TIME="begin_visit_time";//开始调用方法的时间
      
      
      
          //在 HTTP 协议中,请求头和请求体是请求的重要组成部分,ServerWebExchange 提供了丰富的方法来对其进行处理。
          @Override
          public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
      
              //1.先记录下访问接口的开始时间,k-v形式
              exchange.getAttributes().put(BEGIN_VISIT_TIME,System.currentTimeMillis());
      
              //2.返回统计的各个结果给后台
              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().getRawAuthority());
                      log.info("访问接口时长:"+(System.currentTimeMillis() - beginVisitTime+"毫秒"));
                      log.info("================分割线===================");
                      System.out.println();
                  }
              }));
          }
      
          /**
           * 数字越小,优先级越高
           * @return
           */
          @Override
          public int getOrder() {
              return 0;
          }
      }
      
      
    • yml

      在yml文件中写好:要架加载哪个服务即url、predicate等("要获得哪个服务的哪些方法的调用情况直接在yml文件中配置好即可")
      这样,只要在gateway的yml文件里面配置上的的相应请求,即能匹配到路由并断言通过的请求,都会经过此过滤器。!!!
      

      之后访问对应的服务时,在后台就会出现我们自定义全局过滤器里面输出的对应日志。

      这样就可以统计每个接口的访问时间等调用情况。

自定义过滤器后台结果展示.png

  1. 自定义单一内置过滤器:

    写法类似于自定义断言

    • 新建的自定义条件过滤器的类名要以GatewayFilterFactory结尾,并继承AbstractGatewayFilterFactory

      @Component  //注解别忘了
      public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config>
      {
       
      }
      
    • 新建一个Config内部类:

          public static class Config {
              @Setter @Getter
              private String status;//设定一个状态值/标志位,它等于多少,匹配之后才可以访问
          }
      
    • 重写apply方法

      @Component
      public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
      
          public MyGatewayFilterFactory()
          {
              super(MyGatewayFilterFactory.Config.class);
          }
          
        
          @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("atguigu")){//如果请求参数中有"atguigu"参数
                          return chain.filter(exchange);//代表合法请求,放行
                  }else {//如果请求中没有这个"atguigu"参数代表请求不合法,就返回异常
                          exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
                      }
                   return exchange.getResponse().setComplete();
              }
          };
      
          }
      
    • 重写shortcutFieldOrder方法

      @Override
      public List<String> shortcutFieldOrder() {
          List<String> list = new ArrayList<String>();
          list.add("status");
          return list;
      }
      
    • 新建空参构造方法,内部调用super

      public MyGatewayFilterFactory() {
          super(MyGatewayFilterFactory.Config.class);
      }
      
    • 完整代码

      
      /**
       * 自定义单一内置过滤器
       */
      @Component
      public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
      
      
          public MyGatewayFilterFactory()
          {
              super(MyGatewayFilterFactory.Config.class);
          }
      
      
          @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("atguigu")){//如果请求参数中有"atguigu"参数
                          return chain.filter(exchange);//代表合法请求,放行
                  }else {//如果请求中没有这个"atguigu"参数代表请求不合法,就返回异常
                          exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
                      }
                   return exchange.getResponse().setComplete();
              }
          };
      
          }
      
          @Override
          public List<String> shortcutFieldOrder() {
              return Arrays.asList("status");
          }
      
          public  static  class Config //内部类
          {
              @Getter
              @Setter
              private String status;//设置一个状态值/标志位,它等于多少,匹配后才可以访问
          }
      }
      
      
    • yml文件中配置

      filters:
           - My=atguigu //自定义过滤器“名称”即:GatewayFilterFactory前面部分也就是 "My",值为:"atguigu"
      

      **注意:**根据我们上面自定义配置的过滤器:就是规定相应请求上面必须包含?atguigu参数,其值无所谓,过滤器才能让请求通过,否则就报错(BAD_REQUEST)。