(学习笔记)
1、概述
官网:Spring Cloud Gateway
Gateway是在Spring生态系统之上构建的API网关服务,基于Spring6,Spring Boot 3和Project Reactor等技术。它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式,并为它们提供跨领域的关注点,例如:安全性、监控/度量和恢复能力。
2、体系定位
Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关SpringCloud Gateway替代Zuul,那就是SpringCloud Gateway一句话:Gateway是原zuul1.x版的替代
3、作用
反向代理
鉴权
流量控制
熔断
日志监控
总结:
Spring Cloud Gateway组件的核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。
Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器,隐藏微服务结点IP端口信息,从而加强安全保护。
Spring Cloud Gateway本身也是一个微服务,需要注册进服务注册中心。
4、Gateway三大核心
4.1、Route(路由)
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
4.2、Predicate(断言)
参考的是Java8的java.util.function.Predicate;开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
4.3、Filter(过滤)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
4.4、总结
Web前端请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
predicate就是我们的匹配条件;
filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了
5、工作流程
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(Pre)或之后(Post)执行业务逻辑。
在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等;
在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
6、入门配置
(1)创建Module,改pom
<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>
(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}
(3)主启动类(^(* ̄(oo) ̄)^ 不写任务业务代码)
@SpringBootApplication
@EnableDiscoveryClient
public class Main9527 {
public static void main(String[] args) {
SpringApplication.run(Main9527.class, args);
}
}
(4)测试
7、路由映射
7.1:测试一
诉求:不想暴露8001端口,希望在8001真正的支付微服务外面套一层9527网关
(1)cloud-payment8001 添加测试接口
@RestController
public class PayGateWayController {
@Resource
private PayService payService;
@GetMapping(value = "/pay/gateway/get/{id}")
public ResultData<Pay> getById(@PathVariable("id") Integer id) {
Pay pay = payService.getPayById(id);
return ResultData.success(pay);
}
@GetMapping(value = "/pay/gateway/info")
public ResultData<String> getGatewayInfo() {
return ResultData.success("gateway info test:"+ IdUtil.simpleUUID());
}
}
(2)cloud-gateway9527 新增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/** # 断言,路径相匹配的进行路由
(3)测试
7.2、测试二
我们启动80订单微服务,它从Consul注册中心通过微服务名称找到8001支付微服务进行调用,
80 → 9527 → 8001
要求访问9527网关后才能访问8001,如果我们此时启动80订单,可以做到吗?
(1)修改cloud-api-commons 的 PayFeignApi
@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi {
/**
* GateWay进行网关测试案例01
* @param id
* @return
*/
@GetMapping(value = "/pay/gateway/get/{id}")
public ResultData getById(@PathVariable("id") Integer id);
/**
* GateWay进行网关测试案例02
* @return
*/
@GetMapping(value = "/pay/gateway/info")
public ResultData<String> getGatewayInfo();
}
(2)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();
}
}
(3)测试
cloud-gateway9527 网关开启和关闭都访问通过
结论:
9527网关是否启动,毫无影响,o(╥﹏╥)o
目前的配置来看,网关被绕开了......
7.3、正确做法
(1)同一家公司自己人,系统内环境,直接找微服务
@FeignClient(value = "cloud-payment-service")//自己人内部,自己访问自己,写微服务名字OK
public interface PayFeignApi
{
/**
* GateWay进行网关测试案例01
* @param id
* @return
*/
@GetMapping(value = "/pay/gateway/get/{id}")
public ResultData getById(@PathVariable("id") Integer id);
/**
* GateWay进行网关测试案例02
* @return
*/
@GetMapping(value = "/pay/gateway/info")
public ResultData<String> getGatewayInfo();
}
(2) 不同家公司有外人,系统外访问,先找网关再服务
8、Gateway高级特性
8.1、Route以微服务名-动态获取服务URI
(1)痛点:
(2)解决uri地址写死问题
将url地址改成服务名,9527服务修改后的yml
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/** # 断言,路径相匹配的进行路由
8.2、Predicate断言(谓词)
8.2.1、介绍
官网:Spring Cloud Gateway
IDEA启动控制台打印:
整体架构概述:
8.2.2、常用的内置Route Predicate
官网:Spring Cloud Gateway
Most examples below use the shortcut way
Shortcut Configuration
Fully Expanded Arguments
8.2.2.1、The After Route Predicate Factory (用于秒杀、抢购之类的场景)
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- After=2024-09-09T16:07:32.204+08:00[Asia/Shanghai]
如何获得ZonedDateTime
public class ZonedDateTimeDemo{
public static void main(String[] args){
ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
System.out.println(zbj);
}
}
8.2.2.2、The Before Route Predicate Factory
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- Before=2024-09-09T16:10:32.204+08:00[Asia/Shanghai]
8.2.2.3、The Between Route Predicate Factory
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- Between=2024-09-09T16:10:32.204+08:00[Asia/Shanghai], 2024-09-09T16:13:32.204+08:00[Asia/Shanghai]
8.2.2.4、The Cookie Route Predicate Factory
Cookie Route Predicate需要两个参数,一个是 Cookie name ,一个是正则表达式。
路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- Cookie=chocolate, ch.p
测试
不带cookie
curl http://localhost:9527/pay/gateway/get/1
带cookie
curl http://localhost:9527/pay/gateway/get/1 --cookie "chocolate=ch.p"
8.2.2.5、The Header Route Predicate Factory
两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
测试
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"
8.2.2.6、The Host Route Predicate Factory
Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.号作为分隔符。
它通过参数中的主机地址作为匹配规则。
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- Host=**.test.com
测试
正确: curl http://localhost:9527/pay/gateway/get/1 -H "Host:www.test.com"
正确: curl http://localhost:9527/pay/gateway/get/1 -H "Host:java.test.com"
错误: curl http://localhost:9527/pay/gateway/get/1 -H "Host:java.test.net"
8.2.2.7、The Method Route Predicate Factory
8.2.2.8、The Path Route Predicate Factory
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
8.2.2.9、The Query Route Predicate Factory
支持传入两个参数,一个是属性名,一个为属性值,属性值可以是正则表达式。
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
测试:
http://localhost:9527/pay/gateway/get/1?username=123
http://localhost:9527/pay/gateway/get/1?username=abc 要有参数名username并且值还要是整数才能路由
8.2.2.10、The RemoteAddr Route Predicate Factory
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- RemoteAddr=192.168.18.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
测试
8.3、自定义断言,XXXRoutePredicateFactory规则
8.3.1、源代码:
public abstract class AbstractRoutePredicateFactory<C> extends AbstractConfigurable<C> implements RoutePredicateFactory<C>
{
public AbstractRoutePredicateFactory(Class<C> configClass)
{
super(configClass);
}
}
8.3.2、架构概述
8.3.3、模版套路
要么继承AbstractRoutePredicateFactory抽象类
要么实现RoutePredicateFactory接口
开头任意取名,但是必须以RoutePredicateFactory后缀结尾
8.3.4、编写步骤
(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;
}
};
}
完整代码:
package com.atguigu.cloud.mygateway;
import jakarta.validation.constraints.NotEmpty;
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;
/**
* 需求说明,自定义会员等级userType,按照钻/金/银和yml配置的会员等级,以适配是否可以访问
*/
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
//这个Config类就是我们的路由断言规则,重要
@Validated
public static class Config{
@NotEmpty
private String userType; //钻/金/银等级
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
}
public MyRoutePredicateFactory() {
super(MyRoutePredicateFactory.Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
if (userType == null) {
return false;
}
return userType.equals(config.getUserType());
}
};
}
}
8.3.5、测试一
yml
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- My=diamond
启动出现错误:
导致原因:为什么Shortcut Configuration不生效?
解决方案:Fully Expanded Arguments
yml
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- name: My
args:
userType: diamond
测试结果
8.3.6、测试二
上述bug分析:
缺少shortcutFieldOrder方法的实现,所以不支持短格式
完整代码
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
//这个Config类就是我们的路由断言规则,重要
@Validated
public static class Config{
@NotEmpty
private String userType; //钻/金/银等级
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
}
public MyRoutePredicateFactory() {
super(MyRoutePredicateFactory.Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
return new Predicate<ServerWebExchange>() {
@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 List<String> shortcutFieldOrder() {
return Collections.singletonList("userType");
}
}
yml
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- My=diamond
8.4、Filter过滤
8.4.1、概念
官网:Spring Cloud Gateway
作用:请求鉴权:异常处理等
记录接口调用时长统计(大厂面试题)
类型
①全局默认过滤器Global Filters
官网:Spring Cloud Gateway
Gateway出厂默认已有的,直接用即可,主要作用于所有的路由
不需要在配置文件中配置,作用在所有的路由上,实现GlobalFilter接口即可
②单一内置过滤器GatewayFilter
也可以称为网关过滤器,这种过滤器主要是作用于单一路由或者某个路由分组
官网:Spring Cloud Gateway
③自定义过滤器
8.4.2、内置过滤器
服务提供者cloud-provider-payment8001 添加接口
@RestController
public class 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());
}
}
8.4.2.1、请求头(RequestHeader)相关组
8.4.2.1.1、The AddRequestHeader GatewayFilter Factory (6.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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- My=diamond
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
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/** # 断言,路径相匹配的进行路由
filters:
- AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 请求头kv,若一头含有多参则重写一行设置
- AddRequestHeader=X-Request-atguigu2,atguiguValue2
测试
curl http://localhost:9527/pay/gateway/filter
8.4.2.1.2、The RemoveRequestHeader GatewayFilter Factory (6.18)
删除请求头
请求之前:
yml
- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- My=diamond
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
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/** # 断言,路径相匹配的进行路由
filters:
- AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 请求头kv,若一头含有多参则重写一行设置
- AddRequestHeader=X-Request-atguigu2,atguiguValue2
- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
测试
8.4.2.1.3、The SetRequestHeader GatewayFilter Factory(6.29)
修改请求头
修改之前
yml
- SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- My=diamond
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
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/** # 断言,路径相匹配的进行路由
filters:
- AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 请求头kv,若一头含有多参则重写一行设置
- AddRequestHeader=X-Request-atguigu2,atguiguValue2
- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
- SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
测试结果:
8.4.2.2、请求参数(RequestParameter)相关组
The AddRequestParameter GatewayFilter Factory(6.3)
The RemoveRequestParameter GatewayFilter Factory(6.19)
- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v
- RemoveRequestParameter=customerName # 删除url请求参数customerName,你传递过来也是null
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- My=diamond
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
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/** # 断言,路径相匹配的进行路由
filters:
- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v
- RemoveRequestParameter=customerName # 删除url请求参数customerName,你传递过来也是null
服务提供者cloud-provider-payment8001的接口修改
@RestController
public class 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 +" ";
}
}
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
8.4.2.3、回应头(ResponseHeader)相关组
The AddResponseHeader GatewayFilter Factory(6.4)
The SetResponseHeader GatewayFilter Factory(6.30)
The RemoveResponseHeader GatewayFilter Factory(6.20)
- AddResponseHeader=X-Response-atguigu, BlueResponse # 新增请求参数X-Response-atguigu并设值为BlueResponse
- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11
- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- My=diamond
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
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/** # 断言,路径相匹配的进行路由
filters:
- AddResponseHeader=X-Response-atguigu, BlueResponse # 新增请求参数X-Response-atguigu并设值为BlueResponse
- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11
- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除
测试之前,请求url:http://localhost:9527/pay/gateway/filter
添加过滤器测试
8.4.2.4、前缀和路径相关组
8.4.2.4.1、The PrefixPath GatewayFilter Factory (6.14)
自动添加路径前缀
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- My=diamond
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
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
filters:
- PrefixPath=/pay # http://localhost:9527/pay/gateway/filter
之前完整正确地址:
|
http://localhost:9527/pay/gateway/filter
|
现在完整组合地址:
|
PrefixPath + Path
|
实际调用地址:
| http://localhost:9527/gateway/filter
相当于说前缀被过滤器统一管理了。
|
测试
8.4.2.4.2、The SetPath GatewayFilter Factory(6.29)
访问路径修改
yml
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- My=diamond
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
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=/XYZ/abc/{segment} # 断言,为配合SetPath测试,{segment}的内容最后被SetPath取代
filters:
- SetPath=/pay/gateway/{segment} # {segment}表示占位符,你写abc也行但要上下一致
测试
8.4.2.4.3、The RedirectTo GatewayFilter Factory(6.16)
重定向到某个页面
yml
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- My=diamond
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
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/** # 断言,路径相匹配的进行路由
filters:
- RedirectTo=302, http://www.atguigu.com/ # 访问http://localhost:9527/pay/gateway/filter跳转到http://www.atguigu.com/
8.4.2.5、其它
配置在此处相当于全局通用,自定义秒变Global
8.4.3、自定义过滤器
8.4.3.1、自定义全局Filter
官网:Spring Cloud Gateway
案例需求:统计接口调用耗时情况,如何落地,谈谈设计思路
解决方案:通过自定义全局过滤器搞定上述需求,自定义接口调用耗时统计的全局过滤器
步骤:
(1)新建类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;
}
}
(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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
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/** # 断言,路径相匹配的进行路由,默认正确地址
(3)全局过滤器完整代码
@Slf4j
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
private static final String BEGIN_VISIT_TIME = "begin_visit_time";//开始访问时间
@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;
}
}
测试结果:
8.4.3.2、自定义条件Filter
自定义单一内置过滤器GatewayFilter
(1)先参考GateWay内置出厂默认的 :SetStatusGatewayFilterFactory、SetPathGatewayFilterFactory、AddResponseHeaderGatewayFilterFactory 等等
(2)自定义网关过滤器规则步骤套路
①新建类名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);
}
⑥完整代码
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
public MyGatewayFilterFactory() {
super(MyGatewayFilterFactory.Config.class);
}
@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);
}
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
};
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("status");
}
@Setter
@Getter
public static class Config {
private String status;
}
}
⑦yml
参照:
配置
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: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai]
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
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/** # 断言,路径相匹配的进行路由,默认正确地址
filters:
- My=atguigu
测试结果: