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

简述

Spring Cloud Gateway可以

  • 反向代理

  • 鉴权

  • 流量控制

  • 熔断

  • 日志监控

其本身也是一个微服务,需要注册到服务注册中心


三大核心

其有三大核心概念

  • Route:路由,是构建网关的基本模块,由ID目标URI一系列的断言过滤器组成,如果断言为true则匹配该路由

  • Predicate:断言,java.util.function.Predicate,如果请求与断言相匹配则进行路由

  • Filter:过滤器,指GatewayFilter的实例,可在请求被路由前或后对请求进行修改


工作流程

  1. 客户端Gateway ClientSpring Cloud Gateway发起请求;

  2. Gateway Handler Mapping中找到与请求相匹配的路由并将其发送到Gateway Web Handler

  3. Gateway Web Handler再通过指定的过滤器链将请求发送到实际的服务Proxied Service

    • 这里的过滤器为pre(之前)类型;

    • 可以做参数校验权限校验流量控制日志输出协议转换等;

  4. 服务Proxied Service处理完,返回响应;

  5. 响应通过过滤器链,Gateway Web HandlerGateway Handler Mapping给到客户端Gateway Client

    • 这里的过滤器为post(之后)类型;

    • 可以做响应内容响应头的修改日志输出流量监控等;


路由映射

内部服务

接口控制器

package com.xlyo.springgatewaylearnservice.controller;
​
import jakarta.annotation.PostConstruct;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
​
import java.util.ArrayList;
import java.util.List;
​
@RestController
@RequestMapping("/pay")
public class PayController {
    private static final List<String> payList = new ArrayList<>();
​
    @PostConstruct
    public void init() {
        payList.addAll(List.of("Apple", "Banana", "Orange", "Pineapple", "Mango"));
    }
​
    @GetMapping("/list")
    public List<String> list() {
        return payList;
    }
​
    @GetMapping("/add")
    public String add(String fruit) {
        payList.add(fruit);
        return "Added " + fruit + " to the list.";
    }
}

网关

pom依赖

现在的spring cloud gateway是响应式的,不需要引入spring-boot-starter-web

<properties>
    <spring-cloud.version>2023.0.1</spring-cloud.version>
</properties>
​
<dependencies>
    <!-- Gateway -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
</dependencies>
​
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

配置文件

spring:
  application:
    name: spring-learn-gateway
  cloud:
    gateway:
      routes:
        - id: pay_route1                # 路由ID,需唯一
          uri: http://localhost:8080    # 匹配后面提供服务的路由地址
          predicates:
            - Path=/pay/list/**         # 断言,与路径相匹配的进行路由
​
        - id: pay_route2
          uri: http://localhost:8080
          predicates:
            - Path=/pay/add
​
server:
  port: 9527

测试

现在访问网关可以直接访问到内部的微服务

GET http://localhost:9527/pay/list


动态获取服务URI

提供微服务的名称动态获取服务的URI

使用负载均衡微服务名称进行动态获取lb://微服务名称

spring:
  cloud:
    gateway:
      routes:
        - id: pay_route1
          # uri: http://localhost:8080    # 采用动态获取方式
          uri: lb://spring-gateway-learn-service
          predicates:
            - Path=/pay/list/**
​
        - id: pay_route2
          uri: lb://spring-gateway-learn-service
          predicates:
            - Path=/pay/add

Predicate

简述

用来控制允不允许访问目标路由地址

其能断言的前缀(如上述Path)全都来自RoutePredicateFactory的实现类,默认提供如下

常用断言API

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

After

在某个时间之前禁止访问

使用ZonedDateTime获取时间字符串,然后填入

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

Before

在某个时间之后禁止访问

Between

在某个时间之间禁止访问


需要2个参数,一个是Cookie名称,另一个是正则表达式

其值匹配正则表达式才能允许访问

需要2个参数,一个是请求头属性名称,另一个是正则表达式

其值匹配正则表达式才能允许访问

Host

需要域名进行匹配,匹配成功允许访问

Method

对HTTP请求的方法进行匹配,匹配成功允许访问

Path

与路径匹配的允许访问


Query

需要2个参数,一个是请求URL中的属性名称,另一个是正则表达式

请求URL中的属性值匹配正则表达式才能允许访问


RemoteAddr

客户端(外部)的IP地址匹配网段的允许访问


自定义断言

  • 命名规范XxxxRoutePredicateFactory

  • 继承AbstractRoutePredicateFactory或实现RoutePredicateFactory接口


MyRoutePredicateFactory

package com.xlyo.springlearngateway.predicate;
​
import io.micrometer.common.util.StringUtils;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
​
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
​
/**
 * 这里实现通过配置用户的会员等级来判断是否可以访问
 *  会员等级:Normal、Advanced、VIP、SVIP
 */
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
    public MyRoutePredicateFactory() {
        super(MyRoutePredicateFactory.Config.class); // super传递断言规则内部类
    }
​
    /**
     * 根据配置的会员等级来判断是否可以访问
     */
    @Override
    public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
        return serverWebExchange -> {
            // 获取用户的会员等级
            String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
            if (StringUtils.isBlank(userType)) return false; // 如果用户没有会员等级,则不允许访问
​
            // 判断用户的会员等级是否符合要求
            return userType.equals(config.getUserType());
        };
    }
​
    /**
     * 支持Shortcut风格的配置
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("userType");
    }
​
​
    /**
     * 断言规则配置内部类
     *    在配置文件中配置
     */
    @Data
    @Validated
    public static class Config {
        @NotEmpty
        private String userType; // 会员等级:Normal、Advanced、VIP、SVIP
    }
}

配置文件

spring:
  cloud:
    gateway:
      routes:
        - id: pay_route1
          uri: http://localhost:8080
          predicates:
            - Path=/pay/list/**
            - My=VIP         # 自定义断言

测试

现在请求参数带userType并且值为配置文件中的值才能允许访问

GET http://localhost:9527/pay/list?userType=VIP


Filter

  • 全局默认过滤器Clobal Filter

  • 单一内置过滤器Gateway Filter

  • 自定义过滤器

单一内置过滤器

可以https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/gatewayfilter-factories.html来获得详细的使用方式

PrefixPathSetPathRedirectTo这三个与路由相关比较重要


自定义全局过滤器

以下以统计接口调用耗时为目标设计自定义全局过滤器

package com.xlyo.springlearngateway.filter;
​
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.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
​
import java.net.URI;
import java.time.Instant;
​
​
/**
 * 全局过滤器,用于记录请求的调用时间
 */
@Slf4j
@Component
public class DisplayCallTimeGlobalFilter implements GlobalFilter, Ordered {
    // 开始调用方法的时间
    private static final String BEGIN_CALL_TIME = "begin_call_time";
​
​
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 记录开始调用的时间到请求的attribute中
        exchange.getAttributes().put(BEGIN_CALL_TIME, Instant.now().toEpochMilli());
​
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            Long startTime = exchange.getAttribute(BEGIN_CALL_TIME);
            if (startTime!= null) {
                URI requestUri = exchange.getRequest().getURI();
                Long endTime = Instant.now().toEpochMilli();
                log.info("请求[{}]耗时:{}ms", requestUri, endTime - startTime);
            }
        }));
    }
​
​
    /**
     * 设置过滤器的顺序(数字越小,优先级越高)
     */
    @Override
    public int getOrder() {
        return 0;
    }
}


自定义条件过滤器

单一内置过滤器的用法相同

package com.xlyo.springlearngateway.filter;
​
import lombok.Data;
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 org.springframework.stereotype.Component;
​
import java.util.List;
​
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
    public MyGatewayFilterFactory() {
        super(MyGatewayFilterFactory.Config.class);
    }
​
​
    @Override
    public GatewayFilter apply(MyGatewayFilterFactory.Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String status = request.getQueryParams().getFirst("status");
            if (status!= null && status.equals(config.getStatus())) {
                return chain.filter(exchange);
            }
​
            // 若状态参数不匹配,则返回400
            exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
            return exchange.getResponse().setComplete();
        };
    }
​
​
    @Override
    public List<String> shortcutFieldOrder() {
        return List.of("status");
    }
​
    /**
     * 自定义配置内部类
     */
    @Data
    public static class Config {
        private String status;  // 设定一个状态参数,用于对应值匹配才能访问
    }
}
spring:
  application:
    name: spring-learn-gateway
  cloud:
    gateway:
      routes:
        - id: pay_route1
          uri: http://localhost:8080
          predicates:
            - Path=/pay/list/**
          filters:
            - My=123        # 自定义条件过滤器

规则,就是用来打破的( ̄へ ̄)!