SpringCloud Alibaba(三)

2022-11-29 09:00:29

1、基本環境搭建

1.1 依賴

<!--  Gatway 閘道器會和springMvc衝突,不能新增web依賴      -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

<!--   gateway 依賴     -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

1.2 設定

# 埠
server:
  port: 9606

# 服務名
spring:
  application:
    name: kgcmall-gatway

  cloud:
    #nacos 設定
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

    # 閘道器設定
    gateway:
      routes: # 路由,是list集合,可以設定多個路由
      	#product模組
        - id: kh96_route_first # 當前route路由的唯一標識,不能重複
          #uri: http://localhost:9602 # 路由轉發的目標資源地址,不支援多負載呼叫,不利於擴充套件,不推薦
          uri: lb://kgcmall96-prod # lb 從nacos註冊中心的服務列表中,根據指定的服務名,呼叫服務,推薦用法
          predicates: # 指定路由斷言設定,支援多個斷言,只要斷言成功(滿足路由轉發條件),才會執行轉發到目標資源地址存取
            - Path=/prod-gateway/** # 指定path路徑斷言,必須滿足請求地址是/prod-gateway開始,才會執行路由轉發
          filters: # 指定路由過濾設定,支援多個過濾器,在斷言成功,執行路由轉發時,對請求和響應資料進行過濾處理
            - StripPrefix=1 # 在請求斷言成功後,執行路由轉發時,自動去除第一層的存取路徑/prod-gateway
        #user模組
        - id: kh96_route_second
          uri: lb://kgcmall96-user
          predicates:
            - Path=/user-gateway/**
          filters:
            - StripPrefix=1

1.3 測試

1.3.1 nacos

1.3.2 請求測試

1.3.2.1 通過gateway閘道器呼叫prod模組

1.3.2.1 通過gateway閘道器呼叫user模組

2、路由Route

Route 主要由 路由id、目標uri、斷言集合和過濾器集合組成,那我們簡單看看這些屬性到底有什麼作用。

(1)id:路由標識,要求唯一,名稱任意(預設值 uuid,一般不用,需要自定義);

(2)uri:請求最終被轉發到的目標地址;

(3)order: 路由優先順序,數位越小,優先順序越高;

(4)predicates:斷言陣列,即判斷條件,如果返回值是boolean,則轉發請求到 uri 屬性指定的服務中;

(5)filters:過濾器陣列,在請求傳遞過程中,對請求做一些修改;

3、斷言 Predicate

Predicate(斷言, 謂詞) 用於進行條件判斷,只有斷言都返回真,才會真正的執行路由。

斷言就是說: 在什麼條件下 才能進行路由轉發

3.1 內建路由斷言工廠

  • 基於Datetime型別的斷言工廠

    • AfterRoutePredicateFactory: 接收一個日期引數,判斷請求日期是否晚於指定日期;
      • -After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
    • BeforeRoutePredicateFactory: 接收一個日期引數,判斷請求日期是否早於指定日期;
    • BetweenRoutePredicateFactory: 接收兩個日期引數,判斷請求日期是否在指定時間段內;
  • 基於遠端地址的斷言工廠RemoteAddrRoutePredicateFactory

    • -RemoteAddr=192.168.1.1/24
  • 基於Cookie的斷言工廠CookieRoutePredicateFactory(接收兩個引數,cookie 名字和一個正規表示式)

    • -Cookie=chocolate, ch
  • 基於Header的斷言工廠HeaderRoutePredicateFactory

    • -Header=X-Request-Id, \d+
  • 基於Host的斷言工廠HostRoutePredicateFactory

    • -Host=**.testhost.org
  • 基於Method請求方法的斷言工廠MethodRoutePredicateFactory

    • -Method=GET
  • 基於Path求路徑的斷言工廠

    • PathRoutePredicateFactory(接收一個引數,判斷請求的URI部分是否滿足路徑規則)
      • -Path=/foo/{segment}
    • `QueryRoutePredicateFactory·
      • -Query=baz, ba.
  • 基於路由權重的斷言工廠WeightRoutePredicateFactory(接收一個[組名,權重], 然後對於同一個組內的路由按照權重轉發)

    • routes:

      -id: weight_route1 uri: host1 predicates:

      -Path=/product/**

      -Weight=group3, 1

      -id: weight_route2 uri: host2 predicates:

      -Path=/product/**

      -Weight= group3, 9

3.2 自定義路由斷言工廠

3.2.1 自定義閘道器斷言工廠 - 許可權斷言
/**
 * Created On : 28/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: 自定義閘道器斷言工廠-許可權斷言
 */
@Component //自定義斷言工廠,必須是一個元件放入容器才可以生效
public class MyAuthRoutePredicateFactory
        extends AbstractRoutePredicateFactory<MyAuthRoutePredicateFactory.Config> {

    /*
        設定項名MyAuth的設定引數值,對映到斷言工廠內部類的屬性名
     */
    public static final String MYAUTH_KEY = "myAuth";

    /*
        通過空參構造方法,指定靜態內部類,用於接收組態檔中的設定項的內容,即斷言(- myAuth=xxx)
     */
    public MyAuthRoutePredicateFactory() {
        super(MyAuthRoutePredicateFactory.Config.class);
    }

    /*
        價格核心組態檔中的自定義設定項的內容,對映到當前設定類的屬性中,即Collections.singletonList(DATETIME_KEY);指定的內部屬性
     */
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList(MYAUTH_KEY);
    }

    @Override
    public Predicate<ServerWebExchange> apply(MyAuthRoutePredicateFactory.Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                //自定義許可權斷言業務邏輯
                if (StringUtils.isNotBlank(config.getMyAuth())) {
                    //判斷自定義許可權設定引數值,是否和當前指定的值一直,如果一致則斷言成功,否則失敗
                    return config.getMyAuth().equals("KH96");
                }
                //斷言失敗
                return false;

            }
        };
    }

    @Data
    public static class Config {
        private String myAuth;
    }

}

可以設定多個引數,案例:https://blog.csdn.net/qq_31155349/article/details/108557969

3.2.2 設定

3.2.2.1 正確設定成功效果
predicates:
	- MyAuth=KH96  # 自定義 許可權斷言 設定 (注意首字母要大寫)

測試效果:

3.2.2.1 正確設定失敗效果

predicates:
	- MyAuth=KHxx  # 設定錯誤資訊

測試效果 :

4、過濾器 Filter

1 作用: 過濾器就是在請求的傳遞過程中,對請求和響應做一些手腳

2 生命週期: Pre Post

3 分類: 區域性過濾器(作用在某一個路由上) 全域性過濾器(作用全部路由上)

在Gateway中, Filter的生命週期只有兩個:「pre」 和 「post」。

PRE: 這種過濾器在請求被路由之前呼叫。我們可利用這種過濾器實現身份驗證、在叢集中選擇請求的微服務、記錄偵錯資訊等。
POST:這種過濾器在路由到微服務以後執行。這種過濾器可用來為響應新增標準的HTTP Header、收集統計資訊和指標、將響應從微服務傳送給使用者端等。

Gateway 的Filter從作用範圍可分為兩種: GatewayFilter與GlobalFilter。

  1. GatewayFilter:應用到單個路由或者一個分組的路由上。
  2. GlobalFilter:應用到所有的路由上。

4.1 內建的過濾器工廠

過濾器工廠 作用 引數
AddRequestHeader 為原始請求新增Header Header的名稱及值
AddRequestParameter 為原始請求新增請求引數 引數名稱及值
AddResponseHeader 為原始響應新增Header Header的名稱及值
DedupeResponseHeader 剔除響應頭中重複的值 需要去重的Header名稱及去重策略
Hystrix 為路由引入Hystrix的斷路器保護 HystrixCommand的名稱
FallbackHeaders 為fallbackUri的請求頭中新增具體的異常資訊 Header的名稱
PrefixPath 為原始請求路徑新增字首 字首路徑
PreserveHostHeader 為請求新增一個preserveHostHeader=true的屬性,路由過濾器會檢查該屬性以決定是否要傳送原始的Host
RequestRateLimiter 用於對請求限流,限流演演算法為令牌桶 keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus
RedirectTo 將原始請求重定向到指定的URL http狀態碼及重定向的url
RemoveHopByHopHeadersFilter 為原始請求刪除IETF組織規定的一系列Header 預設就會啟用,可以通過設定指定僅刪除哪些Header
RemoveRequestHeader 為原始請求刪除某個Header Header名稱
RemoveResponseHeader 為原始響應刪除某個Header Header名稱
RewritePath 重寫原始的請求路徑 原始路徑正規表示式以及重寫後路徑的正規表示式
RewriteResponseHeader 重寫原始響應中的某個Header Header名稱,值的正規表示式,重寫後的值
SaveSession 在轉發請求之前,強制執行WebSession::save操作
secureHeaders 為原始響應新增一系列起安全作用的響應頭 無,支援修改這些安全響應頭的值
SetPath 修改原始的請求路徑 修改後的路徑
SetResponseHeader 修改原始響應中某個Header的值 Header名稱,修改後的值
SetStatus 修改原始響應的狀態碼 HTTP 狀態碼,可以是數位,也可以是字串
StripPrefix 用於截斷原始請求的路徑 使用數位表示要截斷的路徑的數量
Retry 針對不同的響應進行重試 retries、statuses、methods、series
RequestSize 設定允許接收最大請求包的大小。如果請求包大小超過設定的值,則返回 413 Payload Too Large 請求包大小,單位為位元組,預設值為5M
ModifyRequestBody 在轉發請求之前修改原始請求體內容 修改後的請求體內容
ModifyResponseBody 修改原始響應體的內容 修改後的響應體內容
Default 為所有路由新增過濾器 過濾器工廠名稱及值

4.2 測試

簡單測試幾個;

4.2.1 AddRequestHeader

4.2.1.1 新增設定
filters: 
	- AddRequestHeader=X-Request-token,token_kh96  # 新增請求頭引數,兩個引數,第一個是新增請求頭引數名,第二個引數值
4.2.1.2 通過路由呼叫的方法
@GetMapping("/mallProduct")
public KgcMallProduct mallProduct(@RequestParam Integer pid,
                                  @RequestHeader(value = "X-Request-token", required = false) String gateWayFilterToken) {
    log.info("------ 根據商品編號:{}, 查詢商品詳情 ------", pid);

    //通過GateWay閘道器濾器,增加請求頭引數
    log.info("------ 通過GateWay閘道器濾器,增加請求頭引數 X-Request-token:{} ------", gateWayFilterToken);
    // 呼叫業務介面,查詢商品詳情
    return kgcMallProductService.getMallProductById(pid);
}
4.2.1.3 測試

4.2.2 AddRequestParameter

4.2.2.1 新增設定
filters: 
	- AddRequestParameter=queryName,param_kh96 # 新增普通 請求引數 ,兩個引數,第一個是新增請求頭引數名,第二個引數值
4.2.2.2 通過路由呼叫的方法
@GetMapping("/mallProduct")
public KgcMallProduct mallProduct(@RequestParam Integer pid
                                  @RequestParam(value = "queryName", required = false) String gateWayFilterParamQueryName) {
    log.info("------ 根據商品編號:{}, 查詢商品詳情 ------", pid);
    
    //通過GateWay閘道器濾器,增加請求引數
    log.info("------ 通過GateWay閘道器濾器,增加請求頭引數 queryName:{} ------", gateWayFilterParamQueryName);

    // 呼叫業務介面,查詢商品詳情
    return kgcMallProductService.getMallProductById(pid);
}
4.2.2.3 測試

4.3 自定義閘道器過濾器

如果帶checkParam引數進行引數校驗,如果沒有攜帶直接放行;

4.3.1 自定義閘道器過濾器

/**
 * Created On : 28/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: 自定義閘道器過濾工廠 -校驗過濾器
 */
@Component //自定義閘道器過濾工廠,必須是一個元件放入容器才可以生效
public class MyCheckGatewayFilterFactory extends AbstractGatewayFilterFactory<MyCheckGatewayFilterFactory.Config> {
    /*
        設定項名MyAuth的設定引數值,對映到斷言工廠內部類的屬性名
     */
    public static final String MYCHECK_KEY = "myCheck";

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

    public List<String> shortcutFieldOrder() {
        return Arrays.asList(MYCHECK_KEY);
    }

    public GatewayFilter apply(MyCheckGatewayFilterFactory.Config config) {
        return new GatewayFilter() {
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                //自定義閘道器過濾器實現過濾業務邏輯
                //模擬傳送到閘道器的請求,如果請求攜帶引數和閘道器過濾器設定引數一致,放行,如果不一致,直接404
                String checkParam = exchange.getRequest().getQueryParams().getFirst("checkParam");

                //如果攜帶了checkParam引數  判斷獲取的請求引數是否和閘道器過濾器中設定的引數一致
                if (StringUtils.isNotBlank(checkParam)) {
                    //判斷是否一致
                    if (checkParam.equals(config.getMyCheck())) {
                        //過濾器放行到目標請求
                        return chain.filter(exchange);
                    }

                    //返回404
                    exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);

                    return exchange.getResponse().setComplete();

                }
                //沒有攜帶引數,直接放行
                return chain.filter(exchange);
            }
        };
    }

    @Data
    public static class Config {
        private String myCheck;
    }

}

4.3.2 帶正確的引數

4.3.3 帶錯誤的引數

4.3.4 不帶引數

4.4 自定義閘道器全域性過濾(token)

4.4.1 自定義閘道器全域性過濾(token)

攜帶token放行,不攜帶,跳轉到登入頁面(通過跳轉到百度模擬)

/**
 * Created On : 28/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: 自定義閘道器全域性過濾,實現token鑑權,實現GlobalFilter 和 Ordered介面
 */
@Component  //必須是spring元件
public class MyTokenGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //自定義全域性過濾邏輯,判斷請求頭中們是否攜帶了token引數,如果帶了就放行,非歐洲就就拒絕
        if (StringUtils.isBlank(exchange.getRequest().getHeaders().getFirst("token"))) {
            //沒有攜帶token,直接重定向到登入頁
            //模擬重定向到單點登入網址,臨時用百度
            String redirectUrl = "https://www.baidu.com";

            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.SEE_OTHER); //303 錯誤,重定向
            response.getHeaders().set(HttpHeaders.LOCATION, redirectUrl);

            //結束響應
            return response.setComplete();
        }
        //代了token,
        //TODO token校驗
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        //指定全域性過濾器的優先順序,值越小,優先順序越高
        return 0;
    }
}

4.4.2 攜帶token

4.4.2 不攜帶token