这里写目录标题
- 1 概述
- 2 Gateway三大核心
- 3 GateWay工作流程
- 4 入门配置
- 5 9527网关如何做路由映射
- 6 Gateway高级特性
- 6.1 Route以微服务名-动态获取服务URI
- 6.2 Predicate断言(谓词) (需要看完)
- 6.2.1 是什么
- 6.2.2 启动微服务gateway9527,看看IDEA后台的输出
- 6.2.3 整体架构概述
- 6.2.4 常用的内置Route Predicate
- 6.2.4.1 配置语法总体概述
- 6.2.4.2 测试地址
- 6.2.4.3 常用断言api
- 6.2.4.3.1 After Route Predicate
- 6.2.4.3.2 Before Route Predicate
- 6.2.4.3.3 Between Route Predicate
- 6.2.4.3.4 Cookie Route Predicate
- 6.2.4.3.5 Header Route Predicate
- 6.2.4.3.6 Host Route Predicate
- 6.2.4.3.7 Path Route Predicate
- 6.2.4.3.8 Query Route Predicate
- 6.2.4.3.9 RemoteAddr route predicate
- 6.2.4.3.10 Method Route Predicate
- 6.2.4.4 上述配置小总结
- 6.2.5 自定义断言,XXXRoutePredicateFactory规则
- 6.3 Filter过滤
- 7 Gateway整合阿里巴巴Sentinel实现容错
通过百度网盘分享的文件:springcloud
链接:https://pan.baidu.com/s/1lTChHsKgJpvvFRnq7WQImQ
提取码:msr3
1 概述
1.1是什么
官网:https://docs.spring.io/spring-cloud-gateway/docs/4.0.4/reference/html/
Gateway是在Spring生态系统之上构建的API网关服务,基于Spring6,Spring Boot 3和Project Reactor等技术。它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式,并为它们提供跨领域的关注点,例如:安全性、监控/度量和恢复能力。
体系定位:
Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;
但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关SpringCloud Gateway替代Zuul,
那就是SpringCloud Gateway一句话: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则匹配该路由 - Predicate(断言)
参考的是Java8的java.util.function.Predicate
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由 - Filter(过滤)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
2.3 总结
web前端请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
predicate就是我们的匹配条件;
filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了
3 GateWay工作流程
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(Pre)或之后(Post)执行业务逻辑。
在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等;
在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
核心逻辑: 路由转发+断言判断+执行过滤器链
4 入门配置
- 建Module
cloud-gateway9527 - 改POM
<?xml version="1.0" encoding="UTF-8"?>
<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>com.msr.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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 写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}
- 主启动类
@SpringBootApplication
@EnableDiscoveryClient //服务注册和发现
public class Main9527
{
public static void main(String[] args)
{
SpringApplication.run(Main9527.class,args);
}
}
- 业务类
无,不写任何业务代码,网关和业务无关 - 测试
先启动8500服务中心Consul
再启动9527网关入驻
5 9527网关如何做路由映射
5.1 9527网关如何做路由映射那???
- 诉求
我们目前不想暴露8001端口,希望在8001真正的支付微服务外面套一层9527网关 - 8001新建PayGateWayController
package com.atguigu.cloud.controller;
import cn.hutool.core.util.IdUtil;
import com.atguigu.cloud.entities.Pay;
import com.atguigu.cloud.resp.ResultData;
import com.atguigu.cloud.service.PayService;
import io.swagger.v3.oas.annotations.Operation;
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;
import java.util.concurrent.TimeUnit;
/**
* @auther zzyy
* @create 2023-11-20 13:09
*/
@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());
}
}
- 启动8001支付
- 8001自测通过
http://localhost:8001/pay/gateway/get/1
http://localhost:8001/pay/gateway/info
5.2 9527网关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.3 测试1
- 启动Consul8500服务
- 启动8001支付
- 启动9527网关
- 访问说明
1. 添加网关前
http://localhost:8001/pay/gateway/get/1
http://localhost:8001/pay/gateway/info
2. 隐真示假,映射说明
3. 添加网关后
http://localhost:9527/pay/gateway/get/1
http://localhost:9527/pay/gateway/info - 目前8001支付微服务前面添加GateWay成功
GateWay9527 → Pay8001
5.4 测试2
-
启动订单微服务测试,看看是否通过网关?
- 修改cloud-api-commons
PayFeignApi接口 添加方法
/** * GateWay进行网关测试案例01 */ @GetMapping(value = "/pay/gateway/get/{id}") public ResultData getById(@PathVariable("id") Integer id); /** * GateWay进行网关测试案例02 */ @GetMapping(value = "/pay/gateway/info") public ResultData<String> getGatewayInfo();
- 修改cloud-consumer-feign-order80
新建OrderGateWayController
@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(); } }
- 网关开启
http://localhost/feign/pay/gateway/get/1
http://localhost/feign/pay/gateway/info - 网关关闭
http://localhost/feign/pay/gateway/get/1
http://localhost/feign/pay/gateway/info - 结论
9527网关是否启动,毫无影响
目前的配置来看,网关被绕开了…
- 修改cloud-api-commons
-
正确做法
-
同一家公司自己人,系统内环境,直接找微服务
-
不同家公司有外人,系统外访问,先找网关在服务
1刷新feign接口jar包
2重启80订单微服务
3有网关正常success
4无网管异常
-
5.5 还有问题
请看看网关9527的yml配置,映射写死问题,_
6 Gateway高级特性
6.1 Route以微服务名-动态获取服务URI
6.1.1 痛点
6.1.2 是什么
6.1.3 解决uri地址写死问题
9527修改前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/** # 断言,路径相匹配的进行路由
9527修改后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/** # 断言,路径相匹配的进行路由
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由
6.1.4. 测试1
重启网关9527,80/8001保持不变
http://localhost/feign/pay/gateway/get/1
6.1.5. 测试2
如果将8001微服务yaml文件端口修改为8007,按照访问我实际启动的程序是main8001但是端口名改为8007
我们依据微服务名字,匹配查找即可
uri: lb://cloud-payment-service
6.2 Predicate断言(谓词) (需要看完)
6.2.1 是什么
https://docs.spring.io/spring-cloud-gateway/docs/4.0.4/reference/html/#gateway-request-predicates-factories
Route Predicate Factories这个是什么东东?
6.2.2 启动微服务gateway9527,看看IDEA后台的输出
6.2.3 整体架构概述
6.2.4 常用的内置Route Predicate
6.2.4.1 配置语法总体概述
https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/configuring-route-predicate-factories-and-filter-factories.html
两种配置,二选一
-
Shortcut Configuration
-
Fully Expanded Arguments
6.2.4.2 测试地址
如需自己测试,每次修改完记得重启项目
测试地址:
http://localhost:9527/pay/gateway/get/1
6.2.4.3 常用断言api
6.2.4.3.1 After Route Predicate
- 如何获得ZonedDateTime
public static void main(String[] args){
ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
System.out.println(zbj);
}
spring:
cloud:
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]
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由
6.2.4.3.2 Before Route Predicate
- Before=2024-09-04T14:56:21.971050300+08:00[Asia/Shanghai]
6.2.4.3.3 Between Route Predicate
- Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
6.2.4.3.4 Cookie Route Predicate
Cookie Route Predicate需要两个参数,一个是 Cookie name ,一个是正则表达式。
路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行
- Cookie=username,zzyy
测试方法1: 原生命令
不带cookie参数 curl http://localhost:9527/pay/gateway/get/1
自带cookie参数 curl http://localhost:9527/pay/gateway/get/1 --cookie “username=zzyy”
测试方法2:postman
测试方法3:chrome浏览器
6.2.4.3.5 Header Route Predicate
两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。
- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
测试方法1:原生命令
curl http://localhost:9527/pay/gateway/get/1 -H “X-Request-Id:123456”
curl http://localhost:9527/pay/gateway/get/1 -H “X-Request-Id:abcd”
测试方法1:postman
上图正确,下图错误
6.2.4.3.6 Host Route Predicate
Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.号作为分隔符。
它通过参数中的主机地址作为匹配规则。
- Host=**.msr.com
测试方法1:原生命令
正确: curl http://localhost:9527/pay/gateway/get/3 -H “Host:www.msr.com”
正确: curl http://localhost:9527/pay/gateway/get/3 -H “Host:java.msr.com”
错误: curl http://localhost:9527/pay/gateway/get/3 -H “Host:java.msr.net”
测试方法2:postman
6.2.4.3.7 Path Route Predicate
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
6.2.4.3.8 Query Route Predicate
- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
测试:
http://localhost:9527/pay/gateway/get/3?username=123
http://localhost:9527/pay/gateway/get/3?username=abc 要有参数名username并且值还要是整数才能路由
6.2.4.3.9 RemoteAddr route predicate
- RemoteAddr=192.168.124.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。 192.168.124.1 - 192.168.124.255
CIDR网路IP划分(无类别域间路由Classless Inter-Domain Routing缩写)
6.2.4.3.10 Method Route Predicate
配置某个请求地址,只能用Get/Post方法访问,方法限制
6.2.4.4 上述配置小总结
Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。
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-09-04T14:21:47.216094800+08:00[Asia/Shanghai]
# - Before=2024-09-04T14:56:21.971050300+08:00[Asia/Shanghai]
# - Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
# - Cookie=username,zzyy
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
# - Host=**.msr.com
# - Query=username, \d+
# - RemoteAddr=192.168.0.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
# - Method=GET
- 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
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由 默认正确地址
6.2.5 自定义断言,XXXRoutePredicateFactory规则
6.2.5.1 痛点
1.原有的断言配置不满足业务怎么办?
2.看看AfterRoutePredicateFactory
3.架构概述
4.模板套路
要么继承AbstractRoutePredicateFactory抽象类
要么实现RoutePredicateFactory接口
开头任意取名,但是必须以RoutePredicateFactory后缀结尾
6.2.5.2 自定义路由断言规则步骤套路
6.2.5.2.1 编写步骤
1.新建类名XXX需要以RoutePredicateFactory结尾并继承AbstractRoutePredicateFactory类
@Component标注不可忘
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>{
}
2.重写apply方法
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config)
{
return null;
}
3.新建apply方法所需要的静态内部类MyRoutePredicateFactory.Config,这个Config类就是我们的路由断言规则,重要
@Validated
public static class Config{
@Setter
@Getter
@NotEmpty
private String userType; //钻、金、银等用户等级
}
4.空参构造方法,内部调用super
public MyRoutePredicateFactory(){
super(MyRoutePredicateFactory.Config.class);
}
5.重写apply方法第二版
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config){
return new Predicate<ServerWebExchange>(){
@Override
public boolean test(ServerWebExchange serverWebExchange){
//检查request的参数里面,userType是否为指定的值,符合配置就通过
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
if (userType == null) return false;
//如果说参数存在,就和config的数据进行比较
if(userType.equals(config.getUserType())) {
return true;
}
return false;
}
};
}
6.2.5.2.2 完整代码01
package com.msr.cloud.mygateway;
import jakarta.validation.constraints.NotEmpty;
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.function.Predicate;
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>{
public MyRoutePredicateFactory(){
super(MyRoutePredicateFactory.Config.class);
}
@Validated
public static class Config{
@Setter
@Getter
@NotEmpty
private String userType; //钻、金、银等用户等级
}
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config){
return new Predicate<ServerWebExchange>(){
@Override
public boolean test(ServerWebExchange serverWebExchange){
//检查request的参数里面,userType是否为指定的值,符合配置就通过
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
if (userType == null) return false;
//如果说参数存在,就和config的数据进行比较
if(userType.equals(config.getUserType())) {
return true;
}
return false;
}
};
}
}
6.2.5.3 测试1
6.2.5.3.1
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-09-04T14:21:47.216094800+08:00[Asia/Shanghai]
# - Before=2024-09-04T14:56:21.971050300+08:00[Asia/Shanghai]
# - Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
# - Cookie=username,zzyy
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
# - Host=**.msr.com
# - Query=username, \d+
# - RemoteAddr=192.168.0.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
# - Method=GET
# - My=diamond
- 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
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由 默认正确地址
6.2.5.3.2 启动后???
6.2.5.3.2.1 故障现象
6.2.5.3.2.2 导致原因
为什么Shortcut Configuration不生效?
6.2.5.3.2.3 解决方案
先解决问题,让我们自定义的能用
Fully Expanded Arguments
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-09-04T14:21:47.216094800+08:00[Asia/Shanghai]
# - Before=2024-09-04T14:56:21.971050300+08:00[Asia/Shanghai]
# - Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
# - Cookie=username,zzyy
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
# - Host=**.msr.com
# - Query=username, \d+
# - RemoteAddr=192.168.0.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
# - Method=GET
# - My=diamond
- name: My
args:
userType: diamond
- 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
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由 默认正确地址
http://localhost:9527/pay/gateway/get/1?userType=diamond
6.2.5.4 bug分析
缺少shortcutFieldOrder方法的实现,所以不支持短格式
6.2.5.5 测试2
6.2.5.5.1 完整代码02
package com.msr.cloud.mygateway;
import jakarta.validation.constraints.NotEmpty;
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.ArrayList;
import java.util.List;
import java.util.function.Predicate;
/**
* @auther zzyy
* @create 2023-04-23 18:30
*/
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>{
public MyRoutePredicateFactory(){
super(MyRoutePredicateFactory.Config.class);
}
@Validated
public static class Config{
@Setter
@Getter
@NotEmpty
private String userType; //钻、金、银等用户等级
}
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config){
return new Predicate<ServerWebExchange>(){
@Override
public boolean test(ServerWebExchange serverWebExchange){
//检查request的参数里面,userType是否为指定的值,符合配置就通过
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
if (userType == null) return false;
//如果说参数存在,就和config的数据进行比较
if(userType.equals(config.getUserType())) {
return true;
}
return false;
}
};
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("userType");
}
}
6.2.5.5.2 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-09-04T14:21:47.216094800+08:00[Asia/Shanghai]
# - Before=2024-09-04T14:56:21.971050300+08:00[Asia/Shanghai]
# - Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
# - Cookie=username,zzyy
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
# - Host=**.msr.com
# - Query=username, \d+
# - RemoteAddr=192.168.0.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
# - Method=GET
- My=diamond
# - name: My
# args:
# userType: diamond
- 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
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由 默认正确地址
6.2.5.5.2 重启9527并测试
http://localhost:9527/pay/gateway/get/1?userType=diamond
6.3 Filter过滤
6.3.1 概述
6.3.1.1 官网
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
6.3.1.2 一句话
SpringMVC里面的的拦截器Interceptor,Servlet的过滤器
“pre”和 “post” 分别会在请求被执行前调用和被执行后调用,用来修改请求和响应信息
6.3.1.3 能干嘛
请求鉴权
异常处理
记录接口调用时长统计,重点,大厂面试设计题
6.3.1.4 类型
1.全局默认过滤器Global Filters
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#global-filters
gateway出厂默认已有的,直接用即可,主要作用于所有的路由
不需要在配置文件中配置,作用在所有的路由上,实现GlobalFilter接口即可
2.单一内置过滤器GatewayFilter
https://docs.spring.io/spring-cloud-gateway/docs/4.0.4/reference/html/#gatewayfilter-factories
也可以称为网关过滤器,这种过滤器主要是作用于单一路由或者某个路由分组
3.自定义过滤器
6.3.2 Gateway内置的过滤器
6.3.2.1 是什么
官网:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
6.3.2.2 常用的内置过滤器
6.3.2.2.1 请求头(RequestHeader)相关组
6.3.2.2.1.1 The 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-msr1")
|| headName.equalsIgnoreCase("X-Request-msr2")) {
result = result+headName + "\t " + headValue +" ";
}
}
return ResultData.success("getGatewayFilter 过滤器 test: "+result+" \t "+ DateUtil.now());
}
- 9527网关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-09-04T14:21:47.216094800+08:00[Asia/Shanghai]
# - Before=2024-09-04T14:56:21.971050300+08:00[Asia/Shanghai]
# - Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
# - Cookie=username,zzyy
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
# - Host=**.msr.com
# - Query=username, \d+
# - RemoteAddr=192.168.0.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
# - Method=GET
# - My=diamond
# - name: My
# args:
# userType: diamond
- 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
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-msr1,msrValue1 # 请求头kv,若一头含有多参则重写一行设置
# - AddRequestHeader=X-Request-msr2,msrValue2
- 重启9527和8001并再次调用地址
http://localhost:9527/pay/gateway/filter
6.3.2.2.1.2 The RemoveRequestHeader GatewayFilter Factory
-
删除请求头ByName
-
修改前
-
YML
- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
- 重启9527和8001并再次调用地址
http://localhost:9527/pay/gateway/filter - 修改后
6.3.2.2.1.3 The SetRequestHeader GatewayFilter Factory
- 修改请求头ByName
- 修改前(sec-fetch-mode)
- YML
- SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
- 重启9527和8001并再次调用地址
http://localhost:9527/pay/gateway/filter - 修改后
6.3.2.2.2 请求参数(RequestParameter)相关组
6.3.2.2.2.1 The AddRequestParameter GatewayFilter Factory
6.3.2.2.2.2 The RemoveRequestParameter GatewayFilter Factory
6.3.2.2.2.3 上述两个合一块
- YML
- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v
- RemoveRequestParameter=customerName # 删除url请求参数customerName,你传递过来也是null
- 修改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("request headName:" + headName +"---"+"request headValue:" + headValue);
if(headName.equalsIgnoreCase("X-Request-atguigu1")
|| headName.equalsIgnoreCase("X-Request-atguigu2")) {
result = result+headName + "\t " + headValue +" ";
}
}
System.out.println("=============================================");
String customerId = request.getParameter("customerId");
System.out.println("request Parameter customerId: "+customerId);
String customerName = request.getParameter("customerName");
System.out.println("request Parameter customerName: "+customerName);
System.out.println("=============================================");
return ResultData.success("getGatewayFilter 过滤器 test: "+result+" \t "+ DateUtil.now());
}
- 测试
1: http://localhost:9527/pay/gateway/filter
2: http://localhost:9527/pay/gateway/filter?customerId=9999&customerName=z3
6.3.2.2.3 回应头(ResponseHeader)相关组
6.3.2.2.3.1 开启配置前,按照地址chrome查看一下
http://localhost:9527/pay/gateway/filter
6.3.2.2.3.2 The AddResponseHeader GatewayFilter Factory
- AddResponseHeader=X-Response-atguigu, BlueResponse # 新增请求参数X-Response-msr并设值为BlueResponse
6.3.2.2.3.3 The SetResponseHeader GatewayFilter Factory
- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11
6.3.2.2.3.4 The RemoveResponseHeader GatewayFilter Factory
- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除
6.3.2.2.3.5 开启配置后,上面三个配置打包一块上
6.3.2.2.4 前缀和路径相关组
6.3.2.2.4.1 The PrefixPath GatewayFilter Factory
- 自动添加路径前缀
- 之前的正确地址
http://localhost:9527/pay/gateway/filter - YML
分拆说明
- Chrome测试
6.3.2.2.4.2 The SetPath GatewayFilter Factory
访问路径修改
- YML
说明
带占位符的地址替换
- http://localhost:9527/XYZ/abc/filter
- 结果
6.3.2.2.4.3 The RedirectTo GatewayFilter Factory
- 重定向到某个页面
- http://localhost:9527/pay/gateway/filter
- YML
6.3.2.2.5 其它
6.3.2.2.5.1 Default Filters
6.3.2.2.5.2 配置在此处相当于全局通用,自定义秒变Global
6.3.2.2.5.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}
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-09-04T14:21:47.216094800+08:00[Asia/Shanghai]
# - Before=2024-09-04T14:56:21.971050300+08:00[Asia/Shanghai]
# - Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
# - Cookie=username,zzyy
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
# - Host=**.msr.com
# - Query=username, \d+
# - RemoteAddr=192.168.0.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
# - Method=GET
# - My=diamond
# - name: My
# args:
# userType: diamond
- 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
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-msr1,msrValue1 # 请求头kv,若一头含有多参则重写一行设置
# - AddRequestHeader=X-Request-msr2,msrValue2
# - RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
# - SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
# - AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v,此处新增是默认值,如果手动进行传值,会选用你传过来的值
# - RemoveRequestParameter=customerName # 删除url请求参数customerName,你传递过来也是null
# - AddResponseHeader=X-Response-msr, 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 被分拆为: PrefixPath + Path
# - SetPath=/pay/gateway/{segment} # {segment}表示占位符,你写abc也行但要上下一致
# - RedirectTo=302, https://www.baidu.com/ # 访问http://localhost:9527/pay/gateway/filter跳转到https://www.baidu.com/
6.3.3 Gateway自定义过滤器
6.3.3.1 自定义全局Filter
面试题
统计接口调用耗时情况,如何落地,谈谈设计思路
通过自定义全局过滤器搞定上述需求
6.3.3.1.1 案例
自定义接口调用耗时统计的全局过滤器
6.3.3.1.1.1 知识出处
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-combined-global-filter-and-gatewayfilter-ordering
6.3.3.1.1.2 步骤
- 新建类MyGlobalFilter并实现GlobalFilter,Ordered两个接口
@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
return null;
}
@Override
public int getOrder(){
return 0;
}
}
- YML
- code
@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered{
/**
* 数字越小优先级越高
* @return
*/
@Override
public int getOrder(){
return 0;
}
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();
}
}));
}
}
6.3.3.2.1.3 测试
http://localhost:9527/pay/gateway/info
http://localhost:9527/pay/gateway/get/1
http://localhost:9527/pay/gateway/filter
6.3.3.2 自定义条件Filter
6.3.3.2.1 自定义,单一内置过滤器GatewayFilter
6.3.3.2.2 先参考GateWay内置出厂默认的
SetStatusGatewayFilterFactory
SetPathGatewayFilterFactory
AddResponseHeaderGatewayFilterFactory
6.3.3.2.3 自定义网关过滤器规则步骤套路
- 新建类名XXX需要以GatewayFilterFactory结尾并继承AbstractGatewayFilterFactory类
@Component标注不可忘
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config>{
}
- 新建XXXGatewayFilterFactory.Config内部类
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config>{
public static class Config {
@Setter @Getter
private String status;
}
}
- 重写apply方法
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config>{
@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("atguigu")) {
return chain.filter(exchange);
}else {
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
}
};
}
public static class Config {
@Setter @Getter
private String status;
}
}
- 重写shortcutFieldOrder
@Override
public List<String> shortcutFieldOrder() {
List<String> list = new ArrayList<String>();
list.add("status");
return list;
}
- 空参构造方法,内部调用super
public MyGatewayFilterFactory() {
super(MyGatewayFilterFactory.Config.class);
}
- 完整代码01
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
public MyGatewayFilterFactory() {
super(MyGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(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("msr")) {
return chain.filter(exchange);
}else {
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
}
};
}
@Override
public List<String> shortcutFieldOrder() {
List<String> list = new ArrayList<String>();
list.add("status");
return list;
}
public static class Config {
@Setter
@Getter
private String status;//设定一个状态值/标定位,它等于多少,匹配和才可以访问
}
}
6.3.3.2.4 YML
- 默认
- 自己定制my
6.3.3.2.5 测试
错误: http://localhost:9527/pay/gateway/filter
正确: http://localhost:9527/pay/gateway/filter?msr=java
7 Gateway整合阿里巴巴Sentinel实现容错
见后续springcloud alibaba篇章