09.Gateway新一代网关

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章节。

只是为了记录自己的学习历程,且本人水平有限,不对之处,请指正。

posted @ 2024-11-02 20:00  长名06  阅读(60)  评论(0编辑  收藏  举报