Gateway
Gateway
Cloud之Gateway
官网总述
- 路由:网关的基本构建块。 它由 ID、目标 URI、谓词集合和筛选器集合定义。如果聚合谓词为 true,则匹配路由。
- 谓词:这是一个 Java 8 函数谓词。输入类型是 Spring Framework
ServerWebExchange
。 这使您可以匹配 HTTP 请求中的任何内容,例如标头或参数。 - 筛选器:这些是使用特定工厂构建的
GatewayFilter
实例。 在这里,您可以在发送下游请求之前或之后修改请求和响应。
三大核心
- Route:路由是构建网关的基本模块,它由ID,目标URL,一系列的断言和过滤器组成,如果断言为true则匹配该路由
- Predicate(断言):参考的是Java8的java.util.function.predicate 开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
- Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

总结:
web前端请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。predicate就是我们的匹配条件;filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了
工作流程

客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(Pre)或之后(Post)执行业务逻辑。
在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等;
在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
核心逻辑
路由转发 + 断言判断 + 执行过滤器链
创建gateway项目
创建
- 建Model
cloud-gateway9527
- 改Pom
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.cloud</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cloud-gateway9527</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 写Yaml
server:
port: 9527
spring:
application:
name: cloud-gateway9527 #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
- 主启动类
@SpringBootApplication
@EnableDiscoveryClient //服务注册和发现
public class GatewayApp
{
public static void main(String[] args)
{
SpringApplication.run(GatewayApp.class,args);
}
}
添加网关前测试
在cloud-payment-service服务提供者中新增测试网关的两个接口
/**
* 支付新增接口
* @param payDto
* @return
*/
@PostMapping("/gateway/add")
@Operation(summary = "新增",description = "支付接口")
public ResultData gateWayAddPayment(@RequestBody PayDto payDto){
payService.addPayment(payDto);
return ResultData.success();
}
/**
* 查询支付接口
* @param orderNo
* @return
*/
@GetMapping("/gateway/query/{orderNo}")
@Operation(summary = "查询",description = "查询支付接口")
public ResultData gateWayQueryOrder(@PathVariable("orderNo") String orderNo){
List<PayDto> payDto = payService.queryOrder(orderNo);
return ResultData.success(payDto);
}
进行测试,查看是否可以请求到数据
自测通过,不过这是没有添加网关之前的请求,因为本身已经知道了内部请求地址,所以能通过很正常。
添加网关后测试
- cloud-gateway9527的yaml配置修改
server:
port: 9527
spring:
application:
name: cloud-gateway9527 #以微服务注册进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/query/** # 断言,路径相匹配的进行路由
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/add/** # 断言,路径相匹配的进行路由
进行测试,查看是否可以请求到数据
自测通过,说明网关配置没有问题。
引出问题
看看网关9527的Yml配置,映射写死问题

高级特性
Route动态获取服务URL
以微服务名称,动态获取服务URL,解决URL地址写死问题
- 修改9527网关的yaml配置
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #以微服务名称动态获取URL
predicates:
- Path=/pay/gateway/query/** # 断言,路径相匹配的进行路由
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #以微服务名称动态获取URL
predicates:
- Path=/pay/gateway/add/** # 断言,路径相匹配的进行路由
测试2通过:即使访问提供者的应用端口8001变更也可以请求到数据
Predicate断言配置
是什么?

架构概述

内置断言

常用内置断言
配置语法总体概述:有两种
Shortcut Configuration(常用)
Fully Expanded Arguments
示例
常用断言api包括以上内置的十几种,这里以After断言举例,其他的配置举一反三
作用:配置了After断言意味着只有在这个时间段后才可以通过访问请求

我们的问题是:上述这个After好懂,这个时间串串???对应的格式如何获得?
获得ZonedDateTime
/**
* @auther wenxueshi
* @create 2024-4-12 17:37
*/
public class ZonedDateTimeDemo
{
public static void main(String[] args)
{
ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
System.out.println(zbj);
}
}
Yaml配置
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=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
自定义断言
官方内置的断言不够用时,可以看视频解决自定义断言。
总结
Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。
Filter过滤器链
常用的内置过滤器讲解
1、请求头(RequestHeader)相关组
具体讲解,其他的举一反三
网关服务添加yaml
filters:
- AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 请求头kv,若一头含有多参则重写一行设置
- AddRequestHeader=X-Request-atguigu2,atguiguValue2
2、请求参数(RequestParameter)相关组
自定义过滤器
全局过滤器
案例分析:统计接口调用耗时情况,通过自定义全局过滤器解决上述要求
步骤
新建类MyGlobalFilter并实现GlobalFilter和Ordered两个接口
code
package org.cloud.config; /** * ClassName: MyGlobalFilter * Package: org.cloud.config * Description: * * @Author: @weixueshi * @Create: 2024/4/12 - 15:50 * @Version: v1.0 */ import jakarta.validation.constraints.Size; 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.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * 自定义全局网关过滤器 */ @Slf4j @Component public class MyGlobalFilter implements GlobalFilter, Ordered { /** * 开始访问时间 */ private static final String BEGIN_VISIT_TIME = "begin_visit_time"; /** *第2版,各种统计 * @param exchange * @param chain * @return */ @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){ log.info("访问接口主机: " + exchange.getRequest().getURI().getHost()); log.info("访问接口端口: " + exchange.getRequest().getURI().getPort()); log.info("访问接口URL: " + exchange.getRequest().getURI().getPath()); log.info("访问接口URL参数: " + exchange.getRequest().getURI().getRawQuery()); log.info("访问接口时长: " + (System.currentTimeMillis() - beginVisitTime) + "ms"); log.info("我是美丽分割线: ###################################################"); System.out.println(); } })); } /** * 过滤器执行优先级,数字越小优先级越大 * @return */ @Override public int getOrder() { return 0; } }
单一内置过滤器
是什么?
**区别:**以上的全局过滤器是对网关中的所有请求路径下的所有方法都有效,单一内置过滤器是针对某一个请求路径下的所有方法有效。范围比全局过滤器要小。
案例分析:我们内置的这个过滤器的作用是进行请求时需要携带一个statusTest参数并且值为任意才可以通过过滤器
定义步骤
1、新建类名xxx需要以GatewayFilterFactory结尾并继承AbstractGatewayFilterFact0ry类
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config>
{
}
2、新建xxxGatewayFilterFactory.Config内部类
public static class Config {
@Setter @Getter
private String status;
}
3、重写apply方法
@Override
public GatewayFilter apply(MyGatewayFilterFactory.Config config)
{
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
System.out.println("进入自定义网关过滤器MyGatewayFilterFactory,status===="+config.getStatus());
if(request.getQueryParams().containsKey("statusTest")) {
return chain.filter(exchange);
}else {
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
}
};
}
4、重写shortcutFieldOrder
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("status");
}
5、空参构造方法,内部调用super()
public MyGatewayFilterFactory() {
super(MyGatewayFilterFactory.Config.class);
}
如何在网关配置
先看出厂配置
screenshot-1712911648995 自己定制My配置
screenshot-1712911965900
测试
由上图可知,我们只配置了id为pay_routh3下的请求路径
请求路径:
localhost:9527/pay/gateway/get/info
- 请求失败,因为不符合过滤要求
localhost:9527/pay/gateway/get/info?statusTest=test
- 请求成功,符合过滤要求