09.Gateway新一代閘道器

长名06發表於2024-11-02

1.概述

1.1 是什麼

1.1.1 官網

https://docs.spring.io/spring-cloud-gateway/reference/

1.1.2 體系定位

Cloud全家桶中有個很重要的元件就是閘道器,在1.x版本中都是採用的Zuul閘道器;但在2.x版本中,zuul的升級一直跳票,SpringCloud最後自己研發了一個閘道器SpringCloud Gateway替代Zuul。一句話:gateway是原zuul1.x版的替代。

1.2 專案中的閘道器的定位

1.3 能幹嗎

反向代理,鑑權,流量控制,熔斷,日誌控制等。

1.4 總結

Spring Cloud Gateway元件的核心是一系列的過濾器,透過這些過濾器可以將客戶端傳送的請求轉發(路由)到對應的微服務。 Spring Cloud Gateway是加在整個微服務最前沿的防火牆和代理器,隱藏微服務結點IP埠資訊,從而加強安全保護。Spring Cloud Gateway本身也是一個微服務,需要註冊進服務註冊中心。

2.Gateway三大核心

2.1 概述

2.2 Route(路由)

路由是構建閘道器的基本模組,由ID,目標URI,一系列的斷言和過濾器組成,斷言為true則匹配該路由。

2.3 Predicate(斷言)

斷言參考的是Java8的java.util.function.Predicate開發人員可以匹配HTTP請求中的內容(請求頭或請求引數),請求符合斷言,進行路由。

2.4 Filter(過濾)

指的是Spring框架中的GatewayFilter的例項,使用過濾器,可以在請求被路由前或者之後對請求進行修改。

2.5 總結

http請求,到閘道器後,會先匹配是否有這個路由,有則進行斷言,成功則經過過濾,轉發到對應的微服務例項,進行後續的業務處理。不存在請求路由,或斷言失敗都無法,進行請求轉發。

3.Gateway工作流程

客戶端向 Spring Cloud Gateway 發出請求。然後在 Gateway Handler Mapping 中找到與請求相匹配的路由,將其傳送到 Gateway Web Handler。Handler 再透過指定的過濾器鏈來將請求傳送到我們實際的服務執行業務邏輯,然後返回。

過濾器之間用虛線分開是因為過濾器可能會在傳送代理請求之前(Pre)或之後(Post)執行業務邏輯。

在“pre”型別的過濾器可以做引數校驗、許可權校驗、流量監控、日誌輸出、協議轉換等;

在“post”型別的過濾器中可以做響應內容、響應頭的修改,日誌的輸出,流量監控等有著非常重要的作用。

核心邏輯,路由轉發 + 斷言判斷 + 執行過濾器鏈

4.入門配置

4.1 建module

cloud-gateway9527

4.2 改pom

一般Spring官網對於該元件的說明文件,都會有標識,該元件的maven座標。

To include Spring Cloud Gateway in your project, use the starter with a group ID of org.springframework.cloud and an artifact ID of spring-cloud-starter-gateway. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.

<!--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>

4.3 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}

4.4 主啟動

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class Main9527 {

    public static void main(String[] args) {
        SpringApplication.run(Main9527.class, args);
    }

}

4.5 業務類

無,閘道器無任何業務程式碼。

4.6 啟動

啟動consul和gateway,會發現,在Consul(http://localhost:8500)中有,cloud-gateway例項。

5.閘道器如何做路由對映

5.1 訴求

不希望暴露8001埠,希望在8001真正的支付微服務外面套一層9527閘道器。

5.2 8001服務,新建PayGateWayController

@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());
    }
}

5.3 啟動8001,測試

5.4 cloud-gateway新增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/**              # 斷言,路徑相匹配的進行路由

5.5 重啟9527

啟動consul,8001,Gateway9527。

訪問說明

新增閘道器前http://localhost:8001/pay/gateway/get/1

新增閘道器後http://localhost:9527/pay/gateway/get/1

隱真示假

    gateway:
      routes:
      #對映到真實的微服務的ip和埠
        - id: pay_routh1 #pay_routh1                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          uri: http://localhost:8001                #匹配後提供服務的路由地址
          predicates:
            - Path=/pay/gateway/get/**              # 斷言,路徑相匹配的進行路由

    	#對映到真實的微服務的ip和埠
        - id: pay_routh2 #pay_routh2                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          uri: http://localhost:8001                #匹配後提供服務的路由地址
          predicates:
            - Path=/pay/gateway/info/**              # 斷言,路徑相匹配的進行路由

證明,在8001支付微服務前在Gateway成功。但是注意這是,從外部訪問時會走閘道器。

5.6 啟動80訂單微服務測試

5.6.1 修改cloud-api-commons的PayFeignApi介面
    /**
     * GateWay進行閘道器測試案例01
     * @param id
     * @return
     */
    @GetMapping(value = "/pay/gateway/get/{id}")
    ResultData getById(@PathVariable("id") Integer id);

    /**
     * GateWay進行閘道器測試案例02
     * @return
     */
    @GetMapping(value = "/pay/gateway/info")
    ResultData<String> getGatewayInfo();
5.6.2 修改cloud-comsumer-feign-order80模組

新增OrderGateWayController

package com.atguigu.cloud.controller;

import com.atguigu.cloud.apis.PayFeignApi;
import com.atguigu.cloud.resp.ResultData;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderGateWayController
{
    @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();
    }
}

5.6.3 啟動cloud-comsumer-feign-order80

訪問http://localhost:8080/feign/pay/gateway/get/1

測試發現,gateway啟動與否,都能獲取到資料。

原因, 是因為PayFeignApi,配置的直接訪問的8001的例項。不會走閘道器。因為80模組,是在服務系統的內部呼叫內部的微服務,不需要再走閘道器,多次一舉,閘道器是對外部請求的一種分發和限制。也可以將其改成cloud-gateway服務,來測試閘道器效果,略。

@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi {
    ...
}

5.7 存在問題

上述yml配置,路由的地址,是寫死的,其他服務一改埠號,配置失效了。

6.Gateway高階特性

6.1 Route以微服務名動態獲取服務URI

6.1.1 存在問題

見上述5.7。

6.1.2 解決方式
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                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**              # 斷言,路徑相匹配的進行路由


        - id: pay_routh2 #pay_routh2                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 斷言,路徑相匹配的進行路由
6.1.3測試

測試,cloud-payment-service服務,修改埠號,都可以訪問成功。

6.2 Predicate(斷言)

6.2.1 是什麼

官網

https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/request-predicates-factories.html


Spring Cloud Gateway將路由匹配作為Spring WebFlux HandlerMapping基礎架構的一部分。

Spring Cloud Gateway內建許多Route Predicate工廠。所有這些Predicate都與HTTP請求的不同屬性匹配。多個Route Predicate工廠可以進行組合。

Spring Cloud Gateway建立Route物件時,使用RouterPredicateFactory建立Predicate物件,該物件賦給Route。Spring Cloud Gateway包含許多內建的Route Predicate Factories。所有這些謂詞都匹配HTTP請求的不同屬性,多種謂詞工廠可以組合,並透過邏輯and

6.3 啟動gateway檢視日誌

6.4 整體架構

6.5 常用的內建Route Predicate

6.5.1 配置語法總體概述

6.5.2 Shortcut Configuration

6.5.3 Fully Expanded Arguments

6.5.4 測試地址

http://localhost:9527/pay/gateway/get/1

6.5.5 AfterRoutePredicate使用

官網例子

注意,After=後面跟的是ZonedDateTime類生成的時間字串,需要ZonedDateTime生成。

public class ZonedDateTimeDemo{
    public static void main(String[] args){
    	ZonedDateTime zdt = ZonedDateTime.now(); // 預設時區
		System.out.println(zdt);
    }
}

實際配置

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                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**              # 斷言,路徑相匹配的進行路由
            - After=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai] #在指定時間之後

        - id: pay_routh2 #pay_routh2                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 斷言,路徑相匹配的進行路由

測試

6.5.6 BeforeRoutePredicate

和AfterRoutePredicate類似,只是作用在指定時間之前,略。

6.5.7 BetweenRoutePredicateFactory

和之前類似,略

6.5.8 CookieRoutePredicate

官網例子

cookie的值,支援使用正規表示式。

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                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**              # 斷言,路徑相匹配的進行路由
            #- After=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,再之後
            #- Before=2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,之前
            #- Between=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai],2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai]
            - Cookie=username,br.+x 	#Cookie斷言,,前是cookie鍵名 ,後是值,可以使用正規表示式
        - id: pay_routh2 #pay_routh2                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 斷言,路徑相匹配的進行路由

測試

6.5.9 HeaderRoutePredicate

官網示例

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                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**              # 斷言,路徑相匹配的進行路由
            #- After=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,再之後
            #- Before=2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,之前
            #- Between=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai],2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai]
            #- Cookie=username,br.+x #Cookie斷言,,前是cookie鍵名 ,後是值,可以使用正規表示式
            - Header=X-Request-Id, \d+  # 請求頭要有X-Request-Id屬性,並且值為整數的正規表示式
        - id: pay_routh2 #pay_routh2                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 斷言,路徑相匹配的進行路由

測試

6.5.10 HostRoutePredicate

官網示例

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                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**              # 斷言,路徑相匹配的進行路由
            #- After=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,再之後
            #- Before=2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,之前
            #- Between=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai],2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai]
            #- Cookie=username,br.+x #Cookie斷言,,前是cookie鍵名 ,後是值,可以使用正規表示式
            #- Header=X-Request-Id, \d+  # 請求頭要有X-Request-Id屬性,並且值為整數的正規表示式
            - Host=**.atguigu.com
        - id: pay_routh2 #pay_routh2                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 斷言,路徑相匹配的進行路由

測試

6.5.11 PathRoutePredicate

一直就有該配置,略

- Path=/pay/gateway/get/** 
6.5.12 QueryRoutePredicate

官網示例

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                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**              # 斷言,路徑相匹配的進行路由
            #- After=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,再之後
            #- Before=2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,之前
            #- Between=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai],2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai]
            #- Cookie=username,br.+x #Cookie斷言,,前是cookie鍵名 ,後是值,可以使用正規表示式
            #- Header=X-Request-Id, \d+  # 請求頭要有X-Request-Id屬性,並且值為整數的正規表示式
            #- Host=**.atguigu.com
            - Query=username,\d+  # 要有引數名username並且值還要是整數才能路由
        - id: pay_routh2 #pay_routh2                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 斷言,路徑相匹配的進行路由

測試

6.5.13 RemoteAddrRoutePredicate

官網示例

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                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**              # 斷言,路徑相匹配的進行路由
            #- After=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,再之後
            #- Before=2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,之前
            #- Between=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai],2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai]
            #- Cookie=username,br.+x #Cookie斷言,,前是cookie鍵名 ,後是值,可以使用正規表示式
            #- Header=X-Request-Id, \d+  # 請求頭要有X-Request-Id屬性,並且值為整數的正規表示式
            #- Host=**.atguigu.com
            #- Query=username,\d+  # 要有引數名username並且值還要是整數才能路由
            - RemoteAddr=192.168.1.1/24 # 外部訪問我的IP限制,最大跨度不超過32,目前是1~24它們是CIDR表示法。
        - id: pay_routh2 #pay_routh2                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 斷言,路徑相匹配的進行路由

CIDR表示法

CIDR(Classless Inter-Domain Routing),無類別域間路由是一個用於給使用者分配IP地址以及在網際網路上有效地路由IP資料包的對IP地址進行歸類的方法。

簡略說,給一個舉例的ip/count,客戶端請求的ip地址,從左到右,count個要和舉例的ip相同,才成功。

測試

6.5.14 MethodRoutePredicate

官網示例

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                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**              # 斷言,路徑相匹配的進行路由
            #- After=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,再之後
            #- Before=2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,之前
            #- Between=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai],2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai]
            #- Cookie=username,br.+x #Cookie斷言,,前是cookie鍵名 ,後是值,可以使用正規表示式
            #- Header=X-Request-Id, \d+  # 請求頭要有X-Request-Id屬性,並且值為整數的正規表示式
            #- Host=**.atguigu.com
            #- Query=username,\d+  # 要有引數名username並且值還要是整數才能路由
            #- RemoteAddr=192.168.1.7/24 # 外部訪問我的IP限制,最大跨度不超過32,目前是1~24它們是 CIDR 表示法。
            - Method=GET,POST
        - id: pay_routh2 #pay_routh2                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 斷言,路徑相匹配的進行路由

測試

6.5.15 總結

上述內建的Predicate,就是對請求,進行限制,滿足限制透過,不滿足,返回404。

6.6 自定義斷言

類名,和自定義的保持一致XxxRoutePredicateFactory

6.6.1 擴充套件

擴充套件某些介面,或實現某些抽象類時,最簡單,也最有效的方式,就是借鑑別人寫好的示例。

這裡,直接根據AfterRoutePredicateFactory類,作為模板,進行自定義。

6.6.2 完整程式碼
package com.atguigu.cloud.mygateway;

import jakarta.validation.constraints.NotNull;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;

@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {

    public static final String USER_TYPE = "userType";

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

    /** 支援Shortcut Configuration配置的方法 */
    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList(USER_TYPE);
    }

    public static class Config {

        @NotNull
        private String userType;

        public String getUserType() {
            return userType;
        }

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

    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
                if (userType == null) return false;
                return userType.equals(config.getUserType());
            }

            @Override
            public Object getConfig() {
                return config;
            }

            @Override
            public String toString() {
                return String.format("UserType: %s", config.getUserType());
            }
        };
    }
}
6.6.3 YML

這也是,為什麼要類名要求是XxxRoutePredicateFactory

- My=gold
6.6.4 測試

6.7 Filter(過濾)

6.7.1 官網

https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/gatewayfilter-factories.html

6.7.2 介紹

類似於SpringMVC框架中的Interceptor,和Servlet體系中的Filter。

"pre"和"post"分別會在請求被執行前和被執行後呼叫,用來修改請求和響應資訊。

6.7.3 作用

請求鑑權、異常處理、統計介面呼叫時長等。

6.7.4 型別

有三類

全域性預設過濾器Global Filters

全域性官網地址

gateway出廠預設已有的,直接用即可,作用在所有的路由。不需要在配置檔案中配置,作用在所有的路由上,實現GlobalFilter介面即可。

單一內建過濾器 Gateway Filter

單一官網介紹地址

此型別過濾器,也被稱為閘道器過濾器,這種過濾器主要是作用於單一路由或者某個路由分組。

自定義過濾器

後續有案例。

6.8 Gateway內建的過濾器

官網

也即是上述分類中的單一內建過濾器GatewayFilter。


路由過濾器允許以某種方式修改傳入的HTTP請求或傳出的HTTP響應。路由過濾器的作用域為特定路由。Spring Cloud Gateway包括許多內建的閘道器過濾器工廠。

從官網,可以看到,有30多個單一過濾器。課程中,只選擇了一部分進行講解。其他未講到的內建過濾器,可以見Spring官網,都有配置案例,和解釋

6.9 常用的內建過濾器

按照在HTTP請求中,所屬的模組(請求頭,請求引數,響應頭,路徑,其他),不同分為以下幾組

6.9.1 請求頭(RequestHeader)相關組

1.AddRequestHeader GatewayFilter Factory

新增請求頭內容ByName

8001微服務,PayGateWayController新增介面

程式碼簡單,就是獲取請求頭,列印,判斷是否有特殊的請求頭。

@GetMapping(value = "/pay/gateway/filter")
public ResultData<String> getGatewayFilter(HttpServletRequest request)
{
    String result = "";
    Enumeration<String> headers = request.getHeaderNames();
    while(headers.hasMoreElements())
    {
        String headName = headers.nextElement();
        String headValue = request.getHeader(headName);
        System.out.println("請求頭名: " + headName +"\t\t\t"+"請求頭值: " + headValue);
        if(headName.equalsIgnoreCase("X-Request-atguigu1")
                || headName.equalsIgnoreCase("X-Request-atguigu2")) {
            result = result+headName + "\t " + headValue +" ";
        }
    }
    return ResultData.success("getGatewayFilter 過濾器 test: "+result+" \t "+ DateUtil.now());
}

9527閘道器YML新增過濾內容

注意以下為新增配置

spring:
  cloud:
    gateway:
      routes:
        - id: pay_routh3 #pay_routh3                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/filter/**              # 斷言,路徑相匹配的進行路由
          filters:
            - AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 請求頭kv,若新增多個請求頭,則多寫一行設定
            - AddRequestHeader=X-Request-atguigu2,atguiguValue2

重啟9527和8001服務,請求 http://localhost:9527/pay/gateway/filter,會發現8001服務,列印的請求頭中,有以下內容,只列舉一部分。

請求頭名: sec-fetch-site			請求頭值: none
請求頭名: sec-fetch-mode			請求頭值: navigate
請求頭名: x-request-atguigu1			請求頭值: atguiguValue1
請求頭名: x-request-atguigu2			請求頭值: atguiguValue2

2.RemoveRequestHeader GatewayFilter Factory

刪除請求頭ByName

修改Gateway配置前,從上述結果看,是有該請求頭的。

修改YML

        - id: pay_routh3 #pay_routh3                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/filter/**              # 斷言,路徑相匹配的進行路由
          filters:
            - AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 請求頭kv,若新增多個請求頭,則多寫一行設定
            - AddRequestHeader=X-Request-atguigu2,atguiguValue2
            - RemoveRequestHeader=sec-fetch-site # 新增 刪除請求頭sec-fetch-site

重啟9527和8001,測試,請求 http://localhost:9527/pay/gateway/filter。

結果中,已經不包括sec-fetch-site請求頭。

3.SetRequestHeader GatewayFilter Factory

修改請求頭ByName

修改GatewayYML前,從AddRequestHeader結果看

請求頭名: sec-fetch-mode			請求頭值: navigate

修改YML

- SetRequestHeader=sec-fetch-mode,Blue #修改請求頭sec-fetch-mode

重啟9527和8001,測試,請求http://localhost:9527/pay/gateway/filter。

結果

請求頭名: sec-fetch-mode			請求頭值: Blue
6.9.2 請求引數(RequestParameter)相關組

1.AddRequestParameter GatewayFilter Factory

2.RemoveRequestParameter GatewayFilter Factory

上述兩個,一起配置,測試

YML修改

            - AddRequestParameter=breeze,001 #新增請求引數k,v
            - RemoveRequestParameter=name #刪除url請求引數name

PayGatewayController#getGatewayFilter方法修改

        System.out.println("=============================================");
        String breeze = request.getParameter("breeze");
        System.out.println("request Parameter breeze: "+breeze);

        String name = request.getParameter("name");
        System.out.println("request Parameter name: "+name);
        System.out.println("=============================================");

重啟測試

1.訪問 http://localhost:9527/pay/gateway/filter

=============================================
request Parameter breeze: 001
request Parameter name: null
=============================================

2.訪問http://localhost:9527/pay/gateway/filter?breeze=002&name=breeze

注意,AddRequestParameter只是一個兜底的,如果請求的引數中有其新增相同的引數,則此配置無效,使用請求中的自帶的。

還有,由於HTTP協議本身對於URL編碼的要求,任何非ASCII字元(包括中文字元)都需要經過適當的編碼才能正確傳輸,也就是說,如果想配置請求引數的value,為中文字元,則需要使用URL編碼後的值,例如,風對應UTF-8 URL編碼的結果為%E9%A3%8E

=============================================
request Parameter breeze: 002 
request Parameter name: null
=============================================
6.9.3 響應頭(ResponseHeader)相關組

開啟配置前,看一下,http://localhost:9527/pay/gateway/filter 響應的響應頭資訊。


1.AddResponseHeader GatewayFilter Factory
2.SetResponseHeader GatewayFilter Factory
3.RemoveResponseHeader GatewayFilter Factory

新增YML配置

            - AddResponseHeader=X-Response-atguigu,BlueResponse # 新增請求引數X-Response-atguigu並設值為BlueResponse
            - SetResponseHeader=Date,2099-11-11 # 設定回應頭Date值為2099-11-11
            - RemoveResponseHeader=Content-Type # 將預設自帶Content-Type回應屬性刪除

重啟,出現中文字元程式設計亂碼的原因,移除了Content-Type 響應頭,該響應頭用於指示資源的媒體型別,即伺服器傳送的資料格式。這個頭部欄位非常重要,因為它告訴客戶端(如瀏覽器)應該如何解析接收到的資料。

6.9.4 字首和路徑相關組

1.PrefixPath GatewayFilter Factory

功能,自動新增路徑字首

之前正確的地址 http://localhost:9527/pay/gateway/filter

YML修改

        - id: pay_routh3 #pay_routh3                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            #- Path=/pay/gateway/filter/**              # 斷言,路徑相匹配的進行路由
            - Path=/gateway/filter/**              # 斷言,為配合PrefixPath測試過濾,暫時註釋掉/pay
          filters:
            - AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 請求頭kv,若新增多個請求頭,則多寫一行設定
#            - AddRequestHeader=X-Request-atguigu2,atguiguValue2
#            - RemoveRequestHeader=sec-fetch-site # 刪除請求頭sec-fetch-site
#            - SetRequestHeader=sec-fetch-mode,Blue #修改請求頭sec-fetch-mode
#            - AddRequestParameter=breeze,001   #新增請求引數k,v
#            - RemoveRequestParameter=name #刪除url請求引數name
#            - AddResponseHeader=X-Response-atguigu,BlueResponse # 新增請求引數X-Response-atguigu並設值為BlueResponse
#            - SetResponseHeader=Date,2099-11-11 # 設定回應頭Date值為2099-11-11
#            - RemoveResponseHeader=Content-Type # 將預設自帶Content-Type回應屬性刪除
            - PrefixPath=/pay # http://localhost:9527/pay/gateway/filter

分拆,說明

之前完整正確地址: http://localhost:9527/pay/gateway/filter
現在完整組合地址: PrefixPath + Path
實際呼叫地址: http://localhost:9527/gateway/filter相當於說字首被過濾器統一管理了。


還有StripPrefix GatewayFilter Factory,用以移除,字首。

2.SetPath GatewayFilter Factory

訪問路徑修改

配置

        - id: pay_routh3 #pay_routh3                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            #- Path=/pay/gateway/filter/**              # 斷言,路徑相匹配的進行路由
            #- Path=/gateway/filter/**              # 斷言,為配合PrefixPath測試過濾,暫時註釋掉/pay
            - Path=/XYZ/abc/{segment}           # 斷言,為配合SetPath測試,{segment}的內容最後被SetPath取代
          filters:
            - AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 請求頭kv,若新增多個請求頭,則多寫一行設定
#            - AddRequestHeader=X-Request-atguigu2,atguiguValue2
#            - RemoveRequestHeader=sec-fetch-site # 刪除請求頭sec-fetch-site
#            - SetRequestHeader=sec-fetch-mode,Blue #修改請求頭sec-fetch-mode
#            - AddRequestParameter=breeze,001   #新增請求引數k,v
#            - RemoveRequestParameter=name #刪除url請求引數name
#            - AddResponseHeader=X-Response-atguigu,BlueResponse # 新增請求引數X-Response-atguigu並設值為BlueResponse
#            - SetResponseHeader=Date,2099-11-11 # 設定回應頭Date值為2099-11-11
#            - RemoveResponseHeader=Content-Type # 將預設自帶Content-Type回應屬性刪除
#            - PrefixPath=/pay # http://localhost:9527/pay/gateway/filter
            - SetPath=/pay/gateway/{segment}  # {segment}表示佔位符,你寫abc也行但要上下一致

配置說明

/XYZ/abc/ {segment}就是個佔位符,等價於SetPath後面指定的{segment}內容

瀏覽器訪問地址: http://localhost:9527/XYZ/abc/filter

實際微服務地址:http://localhost:9527/pay/gateway/filter

結果,瀏覽器,顯示請求 url:http://localhost:9527/XYZ/abc/filter 但是,最終請求的地址 http://localhost:9527/pay/gateway/filter

3.RedirectTo GatewayFilter Factory

重定向到某個頁面

        - id: pay_routh3 #pay_routh3                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/filter/**              # 斷言,路徑相匹配的進行路由
            #- Path=/gateway/filter/**              # 斷言,為配合PrefixPath測試過濾,暫時註釋掉/pay
            #- Path=/XYZ/abc/{segment}           # 斷言,為配合SetPath測試,{segment}的內容最後被SetPath取代
          filters:
            - AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 請求頭kv,若新增多個請求頭,則多寫一行設定
#            - AddRequestHeader=X-Request-atguigu2,atguiguValue2
#            - RemoveRequestHeader=sec-fetch-site # 刪除請求頭sec-fetch-site
#            - SetRequestHeader=sec-fetch-mode,Blue #修改請求頭sec-fetch-mode
#            - AddRequestParameter=breeze,001   #新增請求引數k,v
#            - RemoveRequestParameter=name #刪除url請求引數name
#            - AddResponseHeader=X-Response-atguigu,BlueResponse # 新增請求引數X-Response-atguigu並設值為BlueResponse
#            - SetResponseHeader=Date,2099-11-11 # 設定回應頭Date值為2099-11-11
#            - RemoveResponseHeader=Content-Type # 將預設自帶Content-Type回應屬性刪除
#            - PrefixPath=/pay # http://localhost:9527/pay/gateway/filter
#            - SetPath=/pay/gateway/{segment}  # {segment}表示佔位符,你寫abc也行但要上下一致
            - RedirectTo=302, https://www.bilibili.com/video/BV1gW421P7RD # 訪問http://localhost:9527/pay/gateway/filter重定向到https://www.bilibili.com/video/BV1gW421P7RD

重啟9527,測試,略。

6.9.5 其他

Default Filters


這樣配置的,單一條件過濾器,就變成了全域性過濾器,適用於所有斷言匹配的router。

本次案例,全部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                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**              # 斷言,路徑相匹配的進行路由
            #- After=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,再之後
            #- Before=2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,之前
            #- Between=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai],2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai]
            #- Cookie=username,br.+x #Cookie斷言,,前是cookie鍵名 ,後是值,可以使用正規表示式
            #- Header=X-Request-Id, \d+  # 請求頭要有X-Request-Id屬性,並且值為整數的正規表示式
            #- Host=**.atguigu.com
            #- Query=username,\d+  # 要有引數名username並且值還要是整數才能路由
            #- RemoteAddr=192.168.1.7/24 # 外部訪問我的IP限制,最大跨度不超過32,目前是1~24它們是 CIDR 表示法。
            #- Method=GET,POST
            - My=gold
        - id: pay_routh2 #pay_routh2                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 斷言,路徑相匹配的進行路由
        - id: pay_routh3 #pay_routh3                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/filter/**              # 斷言,路徑相匹配的進行路由
            #- Path=/gateway/filter/**              # 斷言,為配合PrefixPath測試過濾,暫時註釋掉/pay
            #- Path=/XYZ/abc/{segment}           # 斷言,為配合SetPath測試,{segment}的內容最後被SetPath取代
          filters:
            - AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 請求頭kv,若新增多個請求頭,則多寫一行設定
#            - AddRequestHeader=X-Request-atguigu2,atguiguValue2
#            - RemoveRequestHeader=sec-fetch-site # 刪除請求頭sec-fetch-site
#            - SetRequestHeader=sec-fetch-mode,Blue #修改請求頭sec-fetch-mode
#            - AddRequestParameter=breeze,001   #新增請求引數k,v
#            - RemoveRequestParameter=name #刪除url請求引數name
#            - AddResponseHeader=X-Response-atguigu,BlueResponse # 新增請求引數X-Response-atguigu並設值為BlueResponse
#            - SetResponseHeader=Date,2099-11-11 # 設定回應頭Date值為2099-11-11
#            - RemoveResponseHeader=Content-Type # 將預設自帶Content-Type回應屬性刪除
#            - PrefixPath=/pay # http://localhost:9527/pay/gateway/filter
#            - SetPath=/pay/gateway/{segment}  # {segment}表示佔位符,你寫abc也行但要上下一致
            - RedirectTo=302, https://www.bilibili.com/video/BV1gW421P7RD # 訪問http://localhost:9527/pay/gateway/filter重定向到https://www.bilibili.com/video/BV1gW421P7RD

6.10 Gateway自定義過濾器

6.10.1 自定義全域性Filter

面試題,統計介面呼叫耗時情況

透過自定義全域性過濾器搞定上述情況。

自定義全域性過濾器,官網

https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/global-filters.html

該頁面有案例

編碼

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 記錄訪問日誌的Gateway全域性過濾器
 */
@Component
public class RecordVisitLogGlobalFilter implements GlobalFilter, Ordered {

    public static final Logger logger = LoggerFactory.getLogger(RecordVisitLogGlobalFilter.class);

    public static final String BEGIN_VISIT_TIME = "beginVisitTime";//開始訪問時間

    @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) {
                logger.info("訪問介面主機: " + exchange.getRequest().getURI().getHost());
                logger.info("訪問介面埠: " + exchange.getRequest().getURI().getPort());
                logger.info("訪問介面URL: " + exchange.getRequest().getURI().getPath());
                logger.info("訪問介面URL引數: " + exchange.getRequest().getURI().getRawQuery());
                logger.info("訪問介面時長: " + (System.currentTimeMillis() - beginVisitTime) + "ms");
                logger.info("###################################################");
                System.out.println();
            }
        }));
    }

    @Override
    public int getOrder() {
        return -1;//設定優先順序,值越小優先順序越高
    }
}

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                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**              # 斷言,路徑相匹配的進行路由
            #- After=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,再之後
            #- Before=2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,之前
            #- Between=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai],2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai]
            #- Cookie=username,br.+x #Cookie斷言,,前是cookie鍵名 ,後是值,可以使用正規表示式
            #- Header=X-Request-Id, \d+  # 請求頭要有X-Request-Id屬性,並且值為整數的正規表示式
            #- Host=**.atguigu.com
            #- Query=username,\d+  # 要有引數名username並且值還要是整數才能路由
            #- RemoteAddr=192.168.1.7/24 # 外部訪問我的IP限制,最大跨度不超過32,目前是1~24它們是 CIDR 表示法。
            #- Method=GET,POST
            #- My=gold
        - id: pay_routh2 #pay_routh2                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 斷言,路徑相匹配的進行路由
        - id: pay_routh3 #pay_routh3                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/filter/**              # 斷言,路徑相匹配的進行路由
            #- Path=/gateway/filter/**              # 斷言,為配合PrefixPath測試過濾,暫時註釋掉/pay
            #- Path=/XYZ/abc/{segment}           # 斷言,為配合SetPath測試,{segment}的內容最後被SetPath取代
          filters:
            - AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 請求頭kv,若新增多個請求頭,則多寫一行設定
#            - AddRequestHeader=X-Request-atguigu2,atguiguValue2
#            - RemoveRequestHeader=sec-fetch-site # 刪除請求頭sec-fetch-site
#            - SetRequestHeader=sec-fetch-mode,Blue #修改請求頭sec-fetch-mode
#            - AddRequestParameter=breeze,001   #新增請求引數k,v
#            - RemoveRequestParameter=name #刪除url請求引數name
#            - AddResponseHeader=X-Response-atguigu,BlueResponse # 新增請求引數X-Response-atguigu並設值為BlueResponse
#            - SetResponseHeader=Date,2099-11-11 # 設定回應頭Date值為2099-11-11
#            - RemoveResponseHeader=Content-Type # 將預設自帶Content-Type回應屬性刪除
#            - PrefixPath=/pay # http://localhost:9527/pay/gateway/filter
#            - SetPath=/pay/gateway/{segment}  # {segment}表示佔位符,你寫abc也行但要上下一致
#            - RedirectTo=302, https://www.bilibili.com/video/BV1gW421P7RD # 訪問http://localhost:9527/pay/gateway/filter重定向到https://www.bilibili.com/video/BV1gW421P7RD

測試結果

6.10.2 自定義條件過濾器

自定義單一過濾器GatewayFilter

參考Gateway內建的過濾器,比如AddResponseHeaderGatewayFilterFactory,SetResponseHeaderGatewayFilterFactory等。

自定義閘道器過濾器規則,和自定義斷言類似

新建類名要以XxxGatewayFilterFactory,並繼承AbstractGatewayFilterFactory類,新建XxxGatewayFilterFactory.Config內部類,重寫apply方法,重寫shortcutFieldOrder方法等,具體見編碼。

編碼

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;

import java.util.Arrays;
import java.util.List;

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

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

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String configStatus = config.getStatus();//配置的值
            System.out.println("進入了自定義閘道器過濾器MyGatewayFilterFactory,status:" + configStatus);
            if (request.getQueryParams().containsKey(configStatus)) {
                return chain.filter(exchange);
            } else {
                exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
                return exchange.getResponse().setComplete();
            }
        };
    }

    public static class Config {
        private String status;//設定一個狀態值,等於多少,才可以訪問

        public String getStatus() {
            return status;
        }

        public void setStatus(String status) {
            this.status = status;
        }
    }

    /**
     * 支援Shortcut Configuration配置的方法
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("status");
    }
}

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                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**              # 斷言,路徑相匹配的進行路由
            #- After=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,再之後
            #- Before=2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai] #時間斷言,之前
            #- Between=2024-10-29T21:06:17.219789300+08:00[Asia/Shanghai],2024-10-30T21:06:17.219789300+08:00[Asia/Shanghai]
            #- Cookie=username,br.+x #Cookie斷言,,前是cookie鍵名 ,後是值,可以使用正規表示式
            #- Header=X-Request-Id, \d+  # 請求頭要有X-Request-Id屬性,並且值為整數的正規表示式
            #- Host=**.atguigu.com
            #- Query=username,\d+  # 要有引數名username並且值還要是整數才能路由
            #- RemoteAddr=192.168.1.7/24 # 外部訪問我的IP限制,最大跨度不超過32,目前是1~24它們是 CIDR 表示法。
            #- Method=GET,POST
            #- My=gold
        - id: pay_routh2 #pay_routh2                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 斷言,路徑相匹配的進行路由
        - id: pay_routh3 #pay_routh3                #路由的ID(類似mysql主鍵ID),沒有固定規則但要求唯一,建議配合服務名
          #uri: http://localhost:8001                #匹配後提供服務的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/filter/**              # 斷言,路徑相匹配的進行路由
            #- Path=/gateway/filter/**              # 斷言,為配合PrefixPath測試過濾,暫時註釋掉/pay
            #- Path=/XYZ/abc/{segment}           # 斷言,為配合SetPath測試,{segment}的內容最後被SetPath取代
          filters:
            - My=atguigu #這也是為什麼自定義的條件過濾器的型別要滿足XxxGatewayFilterFactory的原因
            - AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 請求頭kv,若新增多個請求頭,則多寫一行設定
#            - AddRequestHeader=X-Request-atguigu2,atguiguValue2
#            - RemoveRequestHeader=sec-fetch-site # 刪除請求頭sec-fetch-site
#            - SetRequestHeader=sec-fetch-mode,Blue #修改請求頭sec-fetch-mode
#            - AddRequestParameter=breeze,001   #新增請求引數k,v
#            - RemoveRequestParameter=name #刪除url請求引數name
#            - AddResponseHeader=X-Response-atguigu,BlueResponse # 新增請求引數X-Response-atguigu並設值為BlueResponse
#            - SetResponseHeader=Date,2099-11-11 # 設定回應頭Date值為2099-11-11
#            - RemoveResponseHeader=Content-Type # 將預設自帶Content-Type回應屬性刪除
#            - PrefixPath=/pay # http://localhost:9527/pay/gateway/filter
#            - SetPath=/pay/gateway/{segment}  # {segment}表示佔位符,你寫abc也行但要上下一致
#            - RedirectTo=302, https://www.bilibili.com/video/BV1gW421P7RD # 訪問http://localhost:9527/pay/gateway/filter重定向到https://www.bilibili.com/video/BV1gW421P7RD

測試結果

http://localhost:9527/pay/gateway/filter 報錯狀態碼400

http://localhost:9527/pay/gateway/filter?atguigu=x 訪問成功

7.Gateway整合Sentinel實現容錯

見SpringCloud Alibaba章節。

只是為了記錄自己的學習歷程,且本人水平有限,不對之處,請指正。

相關文章