5. SpringCloudAlibab 集成 gateway

一、什么是 Spring Cloud Gateway

1、网关简介

网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等等。

SpringCloud Gateway是 Spring Cloud 官方推出的第二代网关框架,定位取代 Netflix Zuul。相对Zuul来说,SpringCloud Gateway 提供更优秀的性能,更强大的功能。

Spring Cloud Gateway是由 WebFlux + Netty + Reactor实现的响应式API网关。

它不能在传统的 servlet容器中工作,也不能构建成war包。

SpringCloud Gateway 旨在为微服务架构提供一种简单且有效的API路由的管理方式,并基于Filter的方式提供网关的基本功能,例如 安全认证、监控、限流等等。

Gateway网关的官方文档地址

2、核心概念

  • 路由(route)
    路由是网关中最基础的部分,路由信息包括一个ID,一个目的URI、一组 谓词工厂、一组Filter组成,如果谓词为真,则说明请求的URL和配置的路由匹配。
  • 谓词(predicates)
    既 java.util.function.Predicate,Spring Cloud Gateway使用 Predicate实现路由的匹配条件
  • 过滤器(Filter)
    Spring Coud Gateway中的 filter分为 Gateway Filter和Global Filter,Filter可以对请求和响应进行处理。
    【路由就是转发规则,谓词就是 是否走这个路径的条件,过滤器可以为路由添加业务逻辑,修改请求以及响应】

例如下面这组代码:

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
          filters:
            - StripPrefix=1

上面这段routes 配置的就是 只要路径是 product开头的路径就会路由到 cloud-product 这个服务上面去。

3、工作原理

Spring Cloud Gateway的工作原理和Zuul的差不多,最大的区别就是 Gateway的 Filter只有 pre和post两种。

5. SpringCloudAlibab 集成 gateway_第1张图片
客户端向Spring Cloud Gateway 发出请求,如果请求与网关程序定义的路由匹配,该请求就会被发送到网关WEB处理,此时处理程序运行特定的请求过滤器链。

过滤器之间用虚线分开的原因是:

过滤器可能在发送请求的前后执行逻辑,所有的pre过滤器逻辑先执行,然后执行代理请求,代理请求完成后,执行post过滤器逻辑。

二、搭建SpringCloud Gateway

2.1 pom文件添加 gateway依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- 如果父项目里面引入了 web 那么这里一定要给排除掉 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!-- 声明依赖的作用范围,依赖的作用范围是 test,表示该依赖仅在测试代码中使用,不会包含在实际的应用程序中。这通常用于引入在测试过程中需要的额外库或工具 -->
            <scope>test</scope>
        </dependency>   

2.2 配置文件中的配置如下

server:
  port: 8999
#测试环境
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          #默认值是false,如果设为true开启通过微服务创建路由的功能,即可以通过微服务名访问服务
          #比如:http://localhost:13001/product-service/product/create
          #不建议打开,因为这样暴露了我们的服务名称:product-service 了
          enabled: false
      #是否开启网关
      enabled: true
      # 路由配置
      routes:
        - id: product-service
          # 这里的uri 你也可以写成 精准的地址  比如下面这里
          # uri: http://localhost:8001/
          uri: lb://product-service
          predicates:
            - Path=/product/**
          filters:
          	# 将路由的前缀去掉1个 也就是去掉 /product
            - StripPrefix=1

表示访问 GATEWAY_URL/product/ 会转发到 product-service 微服务的 /**

三、路由谓词工厂(Route Predicate Factories)配置

谓词工厂官网地址

3.1 谓词工厂的分类

5. SpringCloudAlibab 集成 gateway_第2张图片

3.2 时间相关路由

1.After路由断言工厂

规则:

该 断言工厂的参数 是一个UTC格式的时间,将请求访问到Gateway的时间与该时间进行对比,若请求的时间,在参数的范围内则请求放行,否则返回404

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
             # 当请求的时间 After 配置的时间时,才会转发到用户微服务
			 # 否则不会进该路由配置,所以返回404
			 # 将时间改成 < now的时间,则访问 cloud-product/**
            - After=2030-01-20T17:42:47.789-07:00[America/Denver]

时间可使用

System.out.println(ZonedDateTime.now());

打印,然后就可以看到时区。

2023-06-13T15:55:54.027+08:00[Asia/Shanghai]

2.Before路由断言工厂

这个断言工厂和 After路由断言 类似,after是在设置时间的之后,而before是在设置时间的之前。用法也一样,下面是示例

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
             # 当请求的时间 Before 配置的时间时,才会转发到用户微服务
			 # 否则不会进该路由配置,所以返回404
			 # 将时间改成 > now的时间,则访问 cloud-product/**
            - Before=2030-01-20T17:42:47.789-07:00[America/Denver]

3.Between路由断言工厂

规则:

工厂从参数是一个开始时间,一个结束时间,判断请求的时间是否在配置这两个时间的范围内,如果在就通过,否则就拒绝。

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
             # 当且仅当请求时的时间Between配置的时间时,才会转发到用户微服务
			 # 否则不会进该路由配置,所以返回404
            - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2027-01-21T17:42:47.789-07:00[America/Denver]

3.3 Cookie相关路由

1.Cookie路由断言工厂

规则:

工厂包括两个参数,分别是 cookie的key 和 value,判断请求中 是否携带了指定的key 和 value值,如果匹配就通过,否则就拒绝。 值可以设置成正则表达式

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
             # 当请求带有 bigcookie,并且值是 product-oo时,才会转发到微服务
			 # 否则不会进该路由配置,所以返回404
            - Cookie=bigcookie,product-oo

3.4 Header相关路由

1.header路由断言工厂

规则:

包含两个参数,分别是请求头 header里面的 key和 value,请求携带了指定的key和value通过,否则拒绝。值可以设置成正则表达式

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
             # 当请求的请求头中 有名为 X-Request-area,并且值是hb,才会转发到服务
			 # 否则不会进该路由配置,所以返回404
			 # 
            - Header=X-Request-area,hb

3.5 请求相关路由

1. Host路由断言工厂

规则:

请求头中的 Host属性,是否是配置文件中指定的Host属性值,如果是,通过。否则失败。

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
             # 判断请求头中的Host 是否符合 **.666.org 或 **.abcd.org,如果符合转发到相关服务中
			 # 否则不会进该路由配置,所以返回404
            - Host=**.666.org,**.abcd.org

2. Method路由断言工厂

规则:

判断请求是否使用了指定方法,POST还是GET或者其他类型的请求。匹配就通过,否则就拒绝。

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
             # 当HTTP请求是GET时,才会转发到微服务中
			 # 否则不会进该路由配置,所以返回404
            - Method=GET

3. Path路由断言工厂

规则:

判断请求路径是否包含指定的uri,若包含,则通过,否则拒绝。

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
             # 当访问路径是 /product/* 或者/sim-product/**,才会转发请求到微服务中
			 # 否则不会进该路由配置,所以返回404
            - Path=/product/{segment},/sim-product/**

4. Query路由断言工厂

规则:

在请求中查找指定的请求参数,可以只设置参数名称,也可以同时设置参数的名称和值。匹配就通过,否则就拒绝。

参数:
param 请求参数的key值
regexp 请求参数的值,配置的值是 JAVA中的 正则表达式

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
             # 当请求带有 bba的参数时,才会转发到微服务中
			 # 否则不会进该路由配置,所以返回404
			 # 例如 localhost:8999/product/flash/killing?bba=111 这样才会转发到对应的服务中
            - Query=bba
            # 当且仅当请求带有名为bba的参数,且参数值符合正则ba.,才会转发到微服务 例如 localhost:8999/product/flash/killing?bba=bca
            - Query=bba, bc.

5. Weight路由断言工厂

规则:

包含两个参数,分别是用于表示组 group,权重 weight,对于同一组多个uri地址,会根据权重的值来进行请求的指向。

spring:
  cloud:
    nacos:
      routes:
        - id:  weight_high
          uri: http://localhost:8081/
          predicates:
          	- Path=/order/**
            - Weight=group1, 8
        - id:  weight_low
          uri: http://localhost:8082/
          predicates:
          	- Path=/order/**
            - Weight=group1, 2

比如上面 weight_high的权重是8,那么请求会优先给到 weight_high,然后部分请求会给到weight_low。

3.6 自定义谓词工厂实战

比如我们现在有个秒杀场景,秒杀的开始时间是 晚上的9点到 9点10分。限制 21:00 - 21:10 才能访问我们的 product/flash/killing 接口。

假如我们自定义个 FlashKillingTime= 下午09:00,下午09:10

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
            # 只有在 下午9点 到 下午的 9点10分才能进行访问
            - FlashKillingTime= 下午09:00,下午09:10

自定义路由断言工厂需要继承 AbstractRoutePredicateFactory类,重写apply方法的逻辑。

自定义路由工厂的命名 必须以 RoutePredicateFactory 结尾。

1.创建参数配置文件 FlashKillingTimeConfig

import lombok.Data;

import java.time.LocalTime;

@Data
public class FlashKillingTimeConfig {
    /**
     * 接收开始时间
     */
    private LocalTime start;

    /**
     * 接收结束时间
     */
    private LocalTime end;
}

2.创建 FlashKillingTimeRoutePredicateFactory文件

import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import java.time.LocalTime;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/**
 * 时间判断断言工厂类
 */
@Component
public class FlashKillingTimeRoutePredicateFactory extends AbstractRoutePredicateFactory<FlashKillingTimeConfig> {


    public FlashKillingTimeRoutePredicateFactory() {
        super(FlashKillingTimeConfig.class);
    }

    /**
     * 进行判断逻辑代码
     * @param config
     * @return
     */
    @Override
    public Predicate<ServerWebExchange> apply(FlashKillingTimeConfig config) {
        LocalTime start = config.getStart();
        LocalTime end = config.getEnd();
        return new Predicate<ServerWebExchange>() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                String uri = serverWebExchange.getRequest().getURI().getPath();
                if (uri.equals("/product/flash/killing")) {
                    LocalTime now = LocalTime.now();
                    return now.isAfter(start) && now.isBefore(end);
                }
                return true;
            }
        };
    }

    /**
     * 配置文件中的参数 下午09:00,下午09:10  相当于
     *                   start   , end
     * 如果需要多个参数 那么后面还可以添加就好了
     * 这里面的名称 也一定要和 我们实体类里面 定义的名称一样
     * 
     * 用来映射参数,asList("配置文件的第1个参数","配置文件的第2个参数","配置文件的第3个参
     * 数","配置文件的第4个参数"....),接给配置类。
     * @return
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("start", "end");
    }


}

四、过滤器工厂( GatewayFilter Factories)配置

SpringCloudGateway 内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑。比如添加、剔除响应头,添加去除参数等等。

SpringCloudGateway官网
官方内置的过滤器很多,如果需要其他的请去官方看下,这里列举以下平常使用的较多的。

1.内置过滤工厂类

1. AddRequestHeader 过滤工厂

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
		  filters:
 			- AddRequestHeader=X-Request-Foo, Bar

为原始请求添加名为 X-Request-Foo ,值为 Bar 的请求头。

2. AddRequestParameter 过滤工厂

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
		  filters:
 			- AddRequestParameter=foo, bar

为原始请求添加请求参数 foo=bar

3. AddResponseHeader 过滤工厂

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
		  filters:
 			- AddResponseHeader=X-Response-Foo, Bar

添加名为 X-Request-Foo ,值为 Bar 的响应头。

4.PrefixPath 过滤工厂

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
		  filters:
 			- PrefixPath=/mypath

为匹配的路由添加前缀。
例如:访问 ${GATEWAY_URL}/product 会转发到 https://example.org/mypath/product

5. RequestRateLimiter 过滤工厂

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
          filters:
            - name: RequestRateLimiter
            args:
              redis-rate-limiter.replenishRate: 10
              redis-rate-limiter.burstCapacity: 20

设置请求的限流,这里的限流是使用的redis

6.StripPrefix 过滤工厂

spring:
  cloud:
    nacos:
      routes:
        - id: cloud-product
          uri: lb://cloud-product
          predicates:
            - Path=/product/*
          filters:
			- StripPrefix=2

数字表示要截断的路径的数量。
如上配置,如果请求的路径为 /product/bar/foo ,则路径会修改为 /foo ,也就是会截断2个路径。

2.自定义过滤器实战

自定义过滤器分为两种:

  1. 局部过滤器:AbstractNameValueGatewayFilterFactory子类则需要在使用路由配置的时候,明确指定需要使用的过滤器名称和相关参数。
  2. 全局过滤器:实现GlobalFilter的过滤器会被应用到所有的路由中

2.1 局部过滤器实现:

2.2 全局过滤器实现:

1.创建 IPAddressStatisticsFilter 记录客户端ip地址的访问次数:

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 记录当前ip访问的次数
 */
@Component
@Slf4j
public class IPAddressStatisticsFilter implements GlobalFilter, Ordered {

    public static final Map<String, AtomicInteger> CACHE = new ConcurrentHashMap<>();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取客户端的ip地址
        InetSocketAddress host = exchange.getRequest().getHeaders().getHost();
        //如果获取不到 返回 400 错误
        if (host == null || host.getHostName() == null) {
            exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
            return exchange.getResponse().setComplete();
        }
        //否则的话 记录当前ip访问次数
        String hostName = host.getHostName();
        AtomicInteger count = CACHE.getOrDefault(hostName, new AtomicInteger(0));
        count.incrementAndGet();
        CACHE.put(hostName, count);
        log.info("IP地址:" + hostName + ",访问次数:" + count.intValue());
        return chain.filter(exchange);

    }

    /**
     * order的值越小,当前过滤器加载的顺序 就越先被执行
     * 越小越靠前  越大越靠后
     *
     * @return
     */
    @Override
    public int getOrder() {
        return -1;
    }
}

你可能感兴趣的:(gateway,gateway,java,spring)