作者为冰河忠实粉丝,可以加入【冰河技术】知识星球获取源码,也可以关注【冰河技术】公众号,加入社群学习,学习氛围好,干货满满。
1 网关概述
当采用分布式、微服务的架构模式开发系统中,服务网关是整个系统中必不可少的一部分。
1.1 没有网关的弊端
当一个系统使用分布式、微服务架构后,系统会被拆分为一个个小的微服务,每个微服务专注一个小的业务。那么,客户端如何调用这么多微服务的接口呢?如果不做任何处理,没有服务网关,就只能在客户端记录下每个微服务的每个接口地址,然后根据实际需要去调用相应的接口。
这种直接使用客户端记录并管理每个微服务的每个接口的方式,存在着太多的问题,列举几个常见的问题。
- 由客户端记录并管理所有的接口缺乏安全性。
- 由客户端直接请求不同的微服务,会增加客户端程序编写的复杂性。
- 涉及到服务认证与鉴权规则时,需要在每个微服务中实现这些逻辑,增加了代码的冗余性。
- 客户端调用多个微服务,由于每个微服务可能部署的服务器和域名不同,存在跨域的风险。
- 当客户端比较多时,每个客户端上都管理和配置所有的接口,维护起来相对比较复杂。
1.2 引入API网关
API网关,其实就是整个系统的统一入口。网关会封装微服务的内部结构,为客户端提供统一的入口服务,同时,一些与具体业务逻辑无关的通用逻辑可以在网关中实现,比如认证、授权、路由转发、限流、监控等。引入API网关后,如下所示。
引入API网关后,客户端只需要连接API网关,由API网关根据实际情况进行路由转发,将请求转发到具体的微服务,同时,API网关会提供认证、授权、限流和监控等功能。
2 主流的API网关
当系统采用分布式、微服务的架构模式后,API网关就成了整个系统不可分割的一部分。业界通过不断的探索与创新,实现了多种API网关的解决方案。目前,比较主流的API网关有:Nginx+Lua、Kong官网、Zuul网关、Apache Shenyu网关、SpringCloud Gateway网关。
2.1 Nginx+Lua
Nginx的一些插件本身就实现了限流、缓存、黑白名单和灰度发布,再加上Nginx的反向代理和负载均衡,能够实现对服务接口的负载均衡和高可用。而Lua语言可以实现一些简单的业务逻辑,Nginx又支持Lua语言。所以,可以基于Nginx+Lua脚本实现网关。
2.2 Kong网关
Kong网关基于Nginx与Lua脚本开发,性能高,比较稳定,提供多个限流、鉴权等插件,这些插件支持热插拔,开箱即用。Kong网关提供了管理页面,但是,目前基于Kong网关二次开发比较困难。
2.3 Zuul网关
Zuul网关是Netflix开源的网关,功能比较丰富,主要基于Java语言开发,便于在Zuul网关的基础上进行二次开发。但是Zuul网关无法实现动态配置规则,依赖的组件相对来说也比较多,在性能上不如Nginx。
2.4 Apache Shenyu网关
Dromara社区开发的网关框架,其是一个异步的,高性能的,跨语言的,响应式的API网关,并在此基础上提供了非常丰富的扩展功能:
- 支持各种语言(http协议),支持Dubbo, Spring-Cloud, Grpc, Motan, Sofa, Tars等协议。
- 插件化设计思想,插件热插拔,易扩展。
- 灵活的流量筛选,能满足各种流量控制。
- 内置丰富的插件支持,鉴权,限流,熔断,防火墙等等。
- 流量配置动态化,性能极高。
- 支持集群部署,支持 A/B Test,蓝绿发布。
2.6 SpringCloud Gateway网关
Spring为了替换Zuul而开发的网关,SpringCloud Alibaba技术栈中,并没有单独实现网关的组件。在后续的案例实现中,我们会使用SpringCloud Gateway实现网关功能。
3 SpringCloud Gateway网关
Spring Cloud Gateway是Spring公司基于Spring 5.0, Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流、重试等。
3.1 SpringCloud Gateway概述
Spring Cloud Gateway是Spring Cloud的一个全新项目,基于Spring 5.0 + Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式。Spring Cloud Geteway作为Spring Cloud生态系统中的网关,目标是替代Zuul,在Spring Cloud2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关性能,Spring Cloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
Spring Cloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
总结一句话:Spring Cloud Gateway使用的Webflux中的reactor-netty响应式编程组件,底层使用Netty通讯框架。
3.2 SpringCloud Gateway核心架构
客户端请求到 Gateway 网关,会先经过 Gateway Handler Mapping 进行请求和路由匹配。匹配成功后再发送到 Gateway Web Handler 处理,然后会经过特定的过滤器链,经过所有前置过滤后,会发送代理请求。请求结果返回后,最后会执行所有的后置过滤器。
SpringCloud Gateway的主要流程为:客户端请求会先打到Gateway,具体的讲应该是DispacherHandler(因为Gateway引入了WebFlux,作用可以类比MVC的DispacherServlet),Gateway根据用户的请求找到相应的HandlerMapping,请求和具体的handler之间有一个映射关系,网关会对请求进行路由,handler会匹配到RoutePredicateHandlerMapping,匹配请求对应的Route,然后到达Web处理器,WebHandler代理了一系列网关过滤器和全局过滤器的实例,这些过滤器可以对请求和响应进行修改,最后由代理服务完成用户请求,并将结果返回。
注:SpringCloud Gateway部分参考链接:
https://www.csdn.net/tags/NtTagg0sMTk1OTItYmxvZwO0O0OO0O0O.html
https://baijiahao.baidu.com/s?id=1685753662803832483
3.3 项目整合SpringCloud Gateway网关
在项目中整合SpringCloud Gateway来为项目增加API网关,同时,会将SpringCloud Gateway与Sentinel进行整合实现网关的限流能力。
3.3.1 项目整合网关
在项目中增加一个服务网关模块shop-gateway,在服务网关模块中实现网关的能力。此时,我们的项目中就会有用户微服务、商品微服务、订单微服务、服务网关,除此之外,作者将nacos集成到项目中,还有一个Nacos服务(通过jar启动的不影响)。
3.3.2 新建网关模块
在项目中,新建shop-gateway模块,新增网关模块后项目结构图如下所示。
3.3.3 初步整合SpringCloud Gateway
- 在服务网关shop-gateway模块的pom.xml文件中添加如下依赖。
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
- 在服务网关shop-gateway模块的resources目录下新建application.yml文件,并在文件中添加如下配置信息。
server:
port: 10001
spring:
application:
name: server-gateway
cloud:
gateway:
# 解决SpringCloud Gateway跨域的问题
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
allowCredentials: true
allowedHeaders: "*"
# 表示一个路由数组,可以在此节点下配置多个路由信息。
routes:
- id: user-gateway # 当前路由的唯一标识。
uri: http://localhost:8060
order: 1 # 路由的优先级,数字越小表示优先级越高。
predicates: # 网关断言,也就是路由转发的条件,也是一个数组,可以配置多个路由转发条件。
- Path=/server-user/** # 当客户端请求的路径满足Path的规则时,进行路由转发操作。
filters: # 网关过滤器,在过滤器中可以修改请求的参数和header信息,以及响应的结果和header信息,网关过滤器也是一个数组,可以配置多个过滤规则。
- StripPrefix=1 # 网关在进行路由转发之前,会去掉1层访问路径。
- id: product-gateway
uri: http://localhost:8070
order: 1
predicates:
- Path=/server-product/**
filters:
- StripPrefix=1
- id: order-gateway
uri: http://localhost:8080
order: 1
predicates:
- Path=/server-order/**
filters:
- StripPrefix=1
此处注意,如果springboot版本为2.4.0以上,需要将cors-configurations下allowedOrigins替换为allowedOriginPatterns,否则会报错IllegalArgumentException,提示跨域配置问题。
- 在网关服务shop-gateway模块下的 cn.mawenda.shop 包下新建ShopGatewayApplication类,表示服务网关的启动类,源码如下所示。
/**
* 服务网关启动类
* @Author: Ma.wenda
* @Date: 2024-01-31 17:19
* @Version: 1.0
**/
@SpringBootApplication
public class ShopGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ShopGatewayApplication.class, args);
}
}
- 由于之前项目中整合了Nacos和Sentinel,所以,在启动项目前,要分别启动Nacos和Sentinel。
- Nacos jar方式启动,进入Nacos的bin目录下,输入如下命令启动Nacos。
startup.cmd -m standalone
- 进入Sentinel jar包所在目录,输入如下命令启动 Sentinel。
java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.4.jar
通过自己的方式将Nacos 和 Sentinel启动成功即可。
- 分别启动用户微服务、商品微服务、订单微服务和服务网关。
- 通过服务网关访问用户微服务,在浏览器中输入http://localhost:10001/server-user/user/get/1001,如下所示。
可以看到,通过服务网关能够正确访问到用户微服务。
- 通过服务网关访问产品微服务,在浏览器中输入http://localhost:10001/server-product/product/get/1001,如下所示。
- 通过服务网关访问订单微服务,在浏览器中输入http://localhost:10001/server-order/order/test_sentinel,如下所示。
访问各微服务正常。
3.3.4 网关整合Nacos
初步整合SpringCloud Gateway,我们在服务网关模块的application.yml文件中硬编码配置了服务转发的地址,如下所示。
# 用户微服务地址
uri: http://localhost:8060
......
# 产品微服务地址
uri: http://localhost:8070
......
# 订单微服务地址
uri: http://localhost:8080
......
这里,我们将网关整合Nacos实现从nacos注册中心获取转发的服务地址。
- 在服务网关shop-gateway模块的pom.xml文件中继续添加如下依赖。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 在服务网关shop-gateway模块的启动类cn.mawenda.shop.ShopGatewayApplication上添加@EnableDiscoveryClient注解,如下所示。
/**
* 服务网关启动类
* @Author: Ma.wenda
* @Date: 2024-01-31 17:19
* @Version: 1.0
**/
@SpringBootApplication
@EnableDiscoveryClient
public class ShopGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ShopGatewayApplication.class, args);
}
}
- 将application.yml备份一份,命名为application-simple.yml,并修改application.yml配置文件,修改后的文件如下所示。
server:
port: 10001
spring:
application:
name: server-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
# 解决SpringCloud Gateway跨域的问题
globalcors:
cors-configurations:
'[/**]':
allowedOriginPatterns: "*"
allowedMethods: "*"
allowCredentials: true
allowedHeaders: "*"
discovery:
locator:
enabled: true
# 表示一个路由数组,可以在此节点下配置多个路由信息。
routes:
- id: user-gateway # 当前路由的唯一标识。
uri: lb://server-user
order: 1 # 路由的优先级,数字越小表示优先级越高。
predicates: # 网关断言,也就是路由转发的条件,也是一个数组,可以配置多个路由转发条件。
- Path=/server-user/** # 当客户端请求的路径满足Path的规则时,进行路由转发操作。
filters: # 网关过滤器,在过滤器中可以修改请求的参数和header信息,以及响应的结果和header信息,网关过滤器也是一个数组,可以配置多个过滤规则。
- StripPrefix=1 # 网关在进行路由转发之前,会去掉1层访问路径。
- id: product-gateway
uri: lb://server-product
order: 1
predicates:
- Path=/server-product/**
filters:
- StripPrefix=1
- id: order-gateway
uri: lb://server-order
order: 1
predicates:
- Path=/server-order/**
filters:
- StripPrefix=1
在上述配置中增加了Nacos相关的配置,如下所示。
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
新增了让SpringCloud Gateway可以发现Nacos中的服务配置,如下所示。
Spring:
cloud:
gateway:
discovery:
locator:
enabled: true
另外,将硬编码的服务转发地址修改成从Nacos中按照名称获取微服务地址,并按照负载均衡策略分发。
# 从Nacos中获取微服务地址
uri: lb://server-user
......
uri: lb://server-product
......
uri: lb://server-order
......
lb指的是从Nacos中按照微服务的名称获取微服务地址,并按照负载均衡的策略分发。使用lb从Nacos中获取微服务时,遵循如下的格式。
lb://微服务名称
微服务的名称就是各个微服务在application.yml文件中配置的服务名称。
spring:
application:
name: 服务名称
- 分别启动用户微服务、商品微服务、订单微服务和服务网关。
- 分别通过服务网关,访问用户微服务、产品微服务、订单微服务的接口,如下所示。
配置 lb://微服务名称后,通过服务网关请求各服务均正常。
3.4 网关整合Sentinel限流
Sentinel从1.6.0版本开始,提供了SpringCloud Gateway的适配模块,并且可以提供两种资源维度的限流,一种是route维度;另一种是自定义API分组维度。
- route维度:对application.yml文件中配置的spring.cloud.gateway.routes.id限流,并且资源名为spring.cloud.gateway.routes.id对应的值。
- 自定义API分组维度:利用Sentinel提供的API接口来自定义API分组,并且对这些API分组进行限流。
3.4.1 实现route维度限流
- 在服务网关shop-gateway模块的pom.xml文件中添加如下依赖。
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
</dependencies>
- 在服务网关shop-gateway模块中新建cn.mawenda.shop.gateway.config包,并在包下新建GatewayConfig类。基于Sentinel 的Gateway限流是通过其提供的Filter来完成的,使用时只需注入对应的SentinelGatewayFilter实例以及 SentinelGatewayBlockExceptionHandler 实例即可。
GatewayConfig类的源代码如下所示。
/**
* 网关配置类
* @Author: Ma.wenda
* @Date: 2024-02-01 14:20
* @Version: 1.0
**/
@Configuration
public class GatewayConfig {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
@Value("${spring.cloud.gateway.discovery.locator.route-id-prefix}")
private String routeIdPrefix;
public GatewayConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* 初始化一个限流的过滤器
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
@PostConstruct
public void init() {
this.initGatewayRules();
this.initBlockHandlers();
}
/**
* 配置初始化的限流参数
*/
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
/**
* Sentinel整合SpringCloud Gateway使用的API类型为Route ID类型,也就是基于route维度时,
* 由于Sentinel为SpringCloud Gateway网关生成的API名称规则如下:
* 生成的规则为:${spring.cloud.gateway.discovery.locator.route-id-prefix}后面直接加上目标微服务的名称,如下所示。
* ${spring.cloud.gateway.discovery.locator.route-id-prefix}目标微服务的名称
* 其中,${spring.cloud.gateway.discovery.locator.route-id-prefix}是在yml文件中配置的访问前缀
*
* 为了让通过服务网关访问目标微服务链接后,请求链路中生成的API名称与流控规则中生成的API名称一致,以达到启动项目即可实现访问链接的限流效果,
* 而无需登录Setinel管理界面手动配置限流规则,可以将
* resource参数设置为${spring.cloud.gateway.discovery.locator.route-id-prefix}目标微服务的名称
*
* 当然,如果不按照上述配置,也可以在项目启动后,通过服务网关访问目标微服务链接后,在Sentinel管理界面的请求链路中找到对应的API名称所代表的请求链路,
* 然后手动配置限流规则。
**/
// //用户微服务网关
// rules.add(this.getGatewayFlowRule("user-gateway"));
// //商品微服务网关
// rules.add(this.getGatewayFlowRule("product-gateway"));
// //订单微服务网关
// rules.add(this.getGatewayFlowRule("order-gateway"));
//用户微服务网关
rules.add(this.getGatewayFlowRule(getResource("server-user")));
//商品微服务网关
rules.add(this.getGatewayFlowRule(getResource("server-product")));
//订单微服务网关
rules.add(this.getGatewayFlowRule(getResource("server-order")));
//加载规则
GatewayRuleManager.loadRules(rules);
}
private String getResource(String targetServiceName){
if (routeIdPrefix == null){
routeIdPrefix = "";
}
return routeIdPrefix.concat(targetServiceName);
}
private GatewayFlowRule getGatewayFlowRule(String resource){
//传入资源名称生成GatewayFlowRule
GatewayFlowRule gatewayFlowRule = new GatewayFlowRule(resource);
//限流阈值
gatewayFlowRule.setCount(1);
//统计的时间窗口,单位为
gatewayFlowRule.setIntervalSec(1);
return gatewayFlowRule;
}
/**
* 配置限流的异常处理器
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
/**
* 自定义限流异常页面
*/
private void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap<>();
map.put("code", 1001);
map.put("codeMsg", "接口被限流了");
return ServerResponse.status(HttpStatus.OK).
contentType(MediaType.APPLICATION_JSON_UTF8).
body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
注意:
Sentinel1.8.4整合SpringCloud Gateway使用的API类型为Route ID类型时,也就是基于route维度时,由于Sentinel为SpringCloud Gateway网关生成的API名称规则如下:
生成的规则为:${spring.cloud.gateway.discovery.locator.route-id-prefix}后面直接加上目标微服务的名称,如下所示。
s
p
r
i
n
g
.
c
l
o
u
d
.
g
a
t
e
w
a
y
.
d
i
s
c
o
v
e
r
y
.
l
o
c
a
t
o
r
.
r
o
u
t
e
−
i
d
−
p
r
e
f
i
x
目标微服务的名称。其中,
{spring.cloud.gateway.discovery.locator.route-id-prefix}目标微服务的名称。其中,
spring.cloud.gateway.discovery.locator.route−id−prefix目标微服务的名称。其中,{spring.cloud.gateway.discovery.locator.route-id-prefix}是在yml文件中配置的访问前缀。
为了让通过服务网关访问目标微服务链接后,请求链路中生成的API名称与流控规则中生成的API名称一致,以达到启动项目即可实现访问链接的限流效果,而无需登录Setinel管理界面手动配置限流规则,可以将生成GatewayFlowRule对象的resource参数设置为${spring.cloud.gateway.discovery.locator.route-id-prefix}目标微服务的名称
当然,如果不按照上述配置,也可以在项目启动后,通过服务网关访问目标微服务链接后,在Sentinel管理界面的请求链路中找到对应的API名称所代表的请求链路,然后手动配置限流规则。
- 将服务网关shop-gateway模块的application.yml文件备份一份名称为application-nacos-simple.yml的文件,并将application.yml文件的内容修改成如下所示。
server:
port: 10001
spring:
application:
name: server-gateway
main:
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
server-addr: ${NACOS_HOST:shop-nacos}:${NACOS_PORT:8848}
sentinel:
transport:
port: 7777
dashboard: 127.0.0.1:8888
web-context-unify: false
eager: true
gateway:
globalcors:
cors-configurations:
'[/**]':
# allowedOrigins springboot版本为2.4.0以上替换为allowedOriginPatterns
allowedOrigins: "*"
allowedMethods: "*"
allowCredentials: true
allowedHeaders: "*"
discovery:
locator:
enabled: true
route-id-prefix: gateway-
- spring.cloud.sentinel.eager表示程序启动时,流控规则是否立即注册到Sentinel,配置为true表示立即注册到Sentinel。
- spring.cloud.gateway.discovery.locator.route-id-prefix:生成流控规则API名称的前缀。
- 在IDEA中配置启动服务网关shop-gateway模块的参数-Dcsp.sentinel.app.type=1,如下所示。
或在启动类io.binghe.shop.ShopGatewayApplication的main()方法中添加一行System.setProperty(“csp.sentinel.app.type”, “1”);代码,如下所示。
- 分别启动用户微服务、商品微服务、订单微服务和服务网关,启动后会在Sentinel管理界面左侧菜单栏中看到server-gateway菜单,并且在流控规则中可以看到各服务的流控规则已经注册到Sentinel中,如下所示。
- 通过服务网关访问各微服务,不断刷新各微服务的请求,如下所示。
可以看到,通过不断刷新请求,各服务均触发了服务限流,并返回了自定义的限流结果数据。
3.4.2 实现自定义API分组维度限流
- 在服务网关shop-gateway模块的cn.mawenda.shop.gateway.config.GatewayConfig配置类中新增initCustomizedApis()方法,初始化API管理的信息,源码如下所示。
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("user_api1")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
// 以/server-user/user/api1 开头的请求
add(new ApiPathPredicateItem().setPattern("/server-user/user/api1/**").
setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("user_api2")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
// 以/server-user/user/api2/demo1 完成的url路径匹配
add(new ApiPathPredicateItem().setPattern("/server-user/user/api2/demo1"));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
上述代码中,配置了两个API分组,每个API分组的规则如下。
- user_api1分组:匹配以/product-serv/product/api1开头的所有请求。
- user_api2分组:精确匹配/server-user/user/api2/demo1。
- 在服务网关shop-gateway模块的cn.mawenda.shop.gateway.config.GatewayConfig配置类中init()方法中调用initCustomizedApis()方法,为了避免route维度的限流对自定义API分组维度的限流产生影响,这里,同时在init()方法中注释掉调用initGatewayRules()方法,修改后的init()方法的代码如下所示。
@PostConstruct
public void init() {
//this.initGatewayRules();
this.initBlockHandlers();
this.initCustomizedApis();
}
- 在用户微服务shop-user的cn.mawenda.shop.user.controller.UserController类中新增四个测试接口,源码如下所示。
@GetMapping(value = "/api1/demo1")
public String api1Demo1(){
log.info("访问了api1Demo1接口");
return "api1Demo1";
}
@GetMapping(value = "/api1/demo2")
public String api1Demo2(){
log.info("访问了api1Demo2接口");
return "api1Demo2";
}
@GetMapping(value = "/api2/demo1")
public String api2Demo1(){
log.info("访问了api2Demo1接口");
return "api2Demo1";
}
@GetMapping(value = "/api2/demo2")
public String api2Demo2(){
log.info("访问了api2Demo2接口");
return "api2Demo2";
}
- 分别启动用户微服务、商品微服务、订单微服务和服务网关,启动后会在Sentinel管理界面左侧菜单栏中看到server-gateway菜单,在Api管理中会发现我们自定义的API分组已经注册到Sentinel中了,如下所示。
- 在Sentinel管理界面的流控规则中,由于我们注释了以route维度限流的方法,所以,在流控规则里的限流规则为空,那么我们新增网关流控规则,如下所示。
- 点击新增网关流控规则后,会弹出新增网关流控规则配置框,按照如下方式为user_api1分组配置限流规则。
- 点击新增按钮后,按照同样方式为user_api2分组配置限流规则。
- 配置完毕后,在流控规则中的限流规则如下所示。
- 配置的预期测试结果如下。
- 当频繁访问http://localhost:10001/server-user/user/api1/demo1时会被限流。
- 当频繁访问http://localhost:10001/server-user/user/api1/demo2时会被限流。
- 当频繁访问http://localhost:10001/server-user/user/api2/demo1时会被限流。
- 当频繁访问http://localhost:10001/server-user/user/api2/demo2时不会被限流。
注意,只有最后一个不会被限流。
- 使用浏览器频繁访问如下几个接口,分别进行测试,如下所示。
- 在浏览器上频繁访问http://localhost:10001/server-user/user/api1/demo1,如下所示。
- 在浏览器上频繁访问http://localhost:10001/server-user/user/api1/demo2,如下所示。
- 在浏览器上频繁访问http://localhost:10001/server-user/user/api2/demo1,如下所示。
- 在浏览器上频繁访问http://localhost:10001/server-user/user/api2/demo2,如下所示。
经过以上的测试,可以发现api1/demo1、api1/demo2、api2/demo1都触发了限流,并且返回了限流结果数据,而api2/demo2无论访问的多频繁,都不会触发Sentinel限流,至此,我们成功的在项目中整合了SpringCloud Gateway网关,并实现了网关限流操作。
4 SpringCloud Gateway核心技术
SpringCloud Gateway能够实现多种网关功能,比如路由转发、断言、过滤器、熔断、限流、降级、自定义谓词配置、自定义过滤器等等多种功能。
4.1 网关断言
网关断言是用于在 API 网关中进行路由规则匹配和过滤的一种机制。它的作用是根据请求的特征(比如请求路径、请求参数、请求头等)来进行条件判断,从而实现请求的路由、过滤或转发。以下是网关断言的主要作用:
- 请求路由:网关断言可以根据请求的特征将请求路由到不同的目标服务。比如,根据请求路径来将请求路由到不同的微服务实例,或者根据请求头信息将请求路由到不同的处理逻辑。
- 请求过滤:网关断言可以根据特定的条件对请求进行过滤。比如,根据请求参数或请求头信息来拦截请求、修改请求、添加额外的信息等。
- 请求转发:网关断言可以根据条件将请求转发到其他的网关或服务实例,实现请求的转发功能。
- 请求重定向:根据条件将请求重定向到指定的 URL 地址。
- 请求限流:通过断言来对请求进行限流,控制请求的流量,防止过载。
4.1.1 SpringCloud Gateway内置断言
SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配。
4.1.1.1 基于日期时间类型的断言
基于日期时间类型的断言根据时间做判断,主要有三个:
- AfterRoutePredicateFactory:接收一个日期参数,判断请求日期是否晚于指定日期
- BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期
- BetweenRoutePredicateFactory:接收两个日期参数,判断请求日期是否在指定时间段内
使用示例
- After=2022-05-10T23:59:59.256+08:00[Asia/Shanghai]
4.1.1.2 基于远程地址的断言
RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机缔造者是否在地址段中。
使用示例
- RemoteAddr=192.168.0.1/24
4.1.1.3 基于Cookie的断言
CookieRoutePredicateFactory:接收两个参数, cookie 名字和一个正则表达式。 判断请求cookie是否具有给定名称且值与正则表达式匹配。
使用示例
- Cookie=name, binghe.
4.1.1.4 基于Header的断言
HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式,判断请求的Header是否具有给定名称且值与正则表达式匹配。
使用示例
- Header=X-Request-Id, \d+
4.1.1.5 基于Host的断言
HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。
使用示例
- Host=**.mawenda.cn
4.1.1.6 基于Method请求方法的断言
MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。
使用示例
- Method=GET
4.1.1.17 基于Path请求路径的断言
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。
使用示例
- Path=/binghe/{segment}
4.1.1.18 基于Query请求参数的断言
QueryRoutePredicateFactory :接收两个参数,请求参数和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。
使用示例
- Query=name, binghe.
4.1.1.19 基于路由权重的断言
WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发。
使用示例
- id: weight1
uri: http://localhost:8080
predicates:
- Path=/api/**
- Weight=group1,2
filters:
- StripPrefix=1
- id: weight2
uri: http://localhost:8081
predicates:
- Path=/api/**
- Weight=group1,8
filters:
- StripPrefix=1
4.1.2 演示内置断言
我们基于Path请求路径的断言判断请求路径是否符合规则,基于远程地址的断言判断请求主机地址是否在地址段中,并且限制请求的方式为GET方式。整个演示的过程以访问用户微服务的接口为例。
- 在开发项目时,所有的服务都是在本地启动的,首先查看本机的IP地址,如下所示。
# mac
ipconfig getifaddr en1
# win
ipconfig
- 在服务网关模块shop-gateway中,将application.yml文件备份成application-sentinel.yml文件,并将application.yml文件中的内容修改成application-simple.yml文件中的内容。接下来,在application.yml文件中的spring.cloud.gateway.routes节点下的- id: user-gateway下面进行断言配置,配置后的结果如下所示。
# 此处展示的非完整配置,在原配置基础上只修改了routes下配置
spring:
cloud:
gateway:
routes:
- id: user-gateway
uri: http://localhost:8060
order: 1
predicates:
- Path=/user/**
- RemoteAddr=192.168.3.1/24
- Method=GET
filters:
- StripPrefix=1
- 配置完成后启动用户微服务和网关服务,通过网关服务访问用户微服务,在浏览器中输入http://localhost:10001/server-user/user/get/1001,如下所示。
可以看到,访问 http://localhost:10001/server-user/user/get/1001 链接不能正确的请求不能正确获取到用户信息。
接下来,在浏览器中输入 http://192.168.3.45:10001/user/user/get/1001 ,能正确的获取到用户信息,如下所示。
- 停止网关微服务,将基于远程地址的断言配置成- RemoteAddr=192.168.1.1/24,也就是将基于远程地址的断言配置成与我本机IP地址不在同一个网段,这样就能演示请求主机地址不在地址段中的情况,修改后的基于远程地址的断言配置如下所示。
# 此处展示的非完整配置,在原配置基础上只修改了routes下配置
spring:
cloud:
gateway:
routes:
- id: user-gateway
uri: http://localhost:8060
order: 1
predicates:
- Path=/user/**
- RemoteAddr=192.168.1.1/24
- Method=GET
filters:
- StripPrefix=1
- 重启网关,浏览器重新访问http://localhost:10001/server-user/user/get/1001,如下所示。
可以看到,访问http://localhost:10001/server-user/user/get/1001链接依旧不能访问。
在使用 http://192.168.3.45:10001/user/user/get/1001 访问该链接,也不能正确访问了。
4.1.3 自定义断言
SpringCloud Gateway支持自定义断言功能,我们可以在具体业务中,基于SpringCloud Gateway自定义特定的断言功能。
4.1.3.1 自定义断言概述
SpringCloud Gateway虽然提供了多种内置的断言功能,但是在某些场景下无法满足业务的需要,此时,我们就可以基于SpringCloud Gateway自定义断言功能,以此来满足我们的业务场景。
4.1.3.2 实现自定义断言
基于SpringCloud Gateway实现断言功能,实现后的效果是在服务网关的application.yml文件中的spring.cloud.gateway.routes节点下的- id: user-gateway下面进行如下配置。
spring:
cloud:
gateway:
routes:
- id: user-gateway
uri: http://localhost:8060
order: 1
predicates:
- Path=/user/**
- Name=binghe
filters:
- StripPrefix=1
通过服务网关访问用户微服务时,只有在访问的链接后面添加?name=binghe参数时才能正确访问用户微服务。
- 在网关服务shop-gateway中新建cn.mawenda.shop.gateway.predicate包,在包下新建NameRoutePredicateConfig类,主要定义一个Spring类型的name成员变量,用来接收配置文件中的参数,源码如下所示。
/**
* 接收配置文件中的参数
* @Author: Ma.wenda
* @Date: 2024-05-10 15:45
* @Version: 1.0
**/
@Data
public class NameRoutePredicateConfig implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
}
- 在网关服务shop-gateway中cn.mawenda.shop.gateway.predicate包下,创建NameRoutePredicateFactory类,并继承AbstractRoutePredicateFactory类,并覆盖相关方法,如下所示。
@Component
public class NameRoutePredicateFactory extends AbstractRoutePredicateFactory<NameRoutePredicateConfig> {
public NameRoutePredicateFactory() {
super(NameRoutePredicateConfig.class);
}
@Override
public Predicate<ServerWebExchange> apply(NameRoutePredicateConfig config) {
return (serverWebExchange)->{
String name = serverWebExchange.getRequest().getQueryParams().getFirst("name");
if (StringUtils.isEmpty(name)){
name = "";
}
return name.equals(config.getName());
};
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("name");
}
}
- 在服务网关的application.yml文件中的spring.cloud.gateway.routes节点下的- id: user-gateway下面进行如下配置。
spring:
cloud:
gateway:
routes:
- id: user-gateway
uri: http://localhost:8060
order: 1
predicates:
- Path=/user/**
- Name=binghe
filters:
- StripPrefix=1
- 分别启动用户微服务与网关服务,在浏览器中输入http://localhost:10001/server-user/user/get/1001,如下所示。
可以看到,在访问链接后添加?name=binghe参数后,能够正确访问并获取用户信息,
至此,我们就实现了自定义断言功能。
4.2 网关过滤器
过滤器可以在请求过程中,修改请求的参数和响应的结果等信息。在生命周期的角度总体上可以分为前置过滤器(Pre)和后置过滤器(Post)。在实现的过滤范围角度可以分为局部过滤器(GatewayFilter)和全局过滤器(GlobalFilter)。局部过滤器作用的范围是某一个路由,全局过滤器作用的范围是全部路由。
- Pre前置过滤器:在请求被网关路由之前调用,可以利用这种过滤器实现认证、鉴权、路由等功能,也可以记录访问时间等信息。
- Post后置过滤器:在请求被网关路由到微服务之后执行。可以利用这种过滤器修改HTTP的响应Header信息,修改返回的结果数据(例如对于一些敏感的数据,可以在此过滤器中统一处理后返回),收集一些统计信息等。
- 局部过滤器(GatewayFilter):也可以称为网关过滤器,这种过滤器主要是作用于单一路由或者某个路由分组。
- 全局过滤器(GlobalFilter):这种过滤器主要作用于所有的路由。
4.2.1 局部过滤器
局部过滤器又称为网关过滤器,这种过滤器主要是作用于单一路由或者某个路由分组。
4.2.1.1 局部过滤器概述
在SpringCloud Gateway中内置了很多不同类型的局部过滤器,主要如下所示。
过滤器 | 作用 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称及值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
AddResponseHeader | 为原始响应添加Header | Header的名称及值 |
DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名 称及去重策略 |
Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand的名 称 |
FallbackHeaders | 为fallbackUri的请求头中添加具 体的异常信息 | Header的名称 |
PrefixPath | 为原始请求路径添加前缀 | 前缀路径 |
PreserveHostHeader | 为请求添加一个 preserveHostHeader=true的属 性, 路由过滤器会检查该属性以 决定是否要发送原始的Host | 无 |
RequestRateLimiter | 用于对请求限流, 限流算法为令牌桶 | keyResolver、 rateLimiter、 statusCode、 denyEmptyKey、 emptyKeyStatus |
RedirectTo | 将原始请求重定向到指定的URL | http状态码及重定向的 url |
RemoveHopByHopHeadersFilter | 为原始请求删除IETF组织规定的 一系列Header | 默认就会启用, 可以通 过配置指定仅删除哪些 Header |
RemoveRequestHeader | 为原始请求删除某个Header | Header名称 |
RemoveResponseHeader | 为原始响应删除某个Header | Header名称 |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以 及重写后路径的正则表 达式 |
RewriteResponseHeader | 重写原始响应中的某个Header | Header名称, 值的正 则表达式, 重写后的值 |
SaveSession | 在转发请求之前, 强制执行 WebSession::save操作 | 无 |
secureHeaders | 为原始响应添加一系列起安全作 用的响应头 | 无, 支持修改这些安全 响应头的值 |
SetPath | 修改原始的请求路径 | 修改后的路径 |
SetResponseHeader | 修改原始响应中某个Header的值 | Header名称, 修改后 的值 |
SetStatus | 修改原始响应的状态码 | HTTP 状态码, 可以是 数字, 也可以是字符串 |
StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的 路径的数量 |
Retry | 针对不同的响应进行重试 | retries、 statuses、 methods、 series |
RequestSize | 设置允许接收最大请求包的大 小。 如果请求包大小超过设置的 值, 则返回 413 Payload Too Large | 请求包大小, 单位为字 节, 默认值为5M |
ModifyRequestBody | 在转发请求之前修改原始请求体 内容 | 修改后的请求体内容 |
ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 |
4.2.1.2 演示内部过滤器
演示内部过滤器时,我们为原始请求添加一个名称为IP的Header,值为localhost,并添加一个名称为name的参数,参数值为binghe。同时修改响应的结果状态,将结果状态修改为1001。
- 在服务网关的application.yml文件中的spring.cloud.gateway.routes节点下的- id: user-gateway下面进行如下配置。
spring:
cloud:
gateway:
routes:
- id: user-gateway
uri: http://localhost:8060
order: 1
predicates:
- Path=/user/**
filters:
- StripPrefix=1
- AddRequestHeader=IP,localhost
- AddRequestParameter=name,binghe
- SetStatus=1001
- 在用户微服务的cn.mawenda.shop.user.controller.UserController类中新增apiFilter1()方法,如下所示。
@GetMapping(value = "/api/filter1")
public String apiFilter1(HttpServletRequest request, HttpServletResponse response){
log.info("访问了apiFilter1接口");
String ip = request.getHeader("IP");
String name = request.getParameter("name");
log.info("ip = " + ip + ", name = " + name);
return "apiFilter1";
}
- 分别启动用户微服务与网关服务,在浏览器访问http://localhost:10001/server-user/user/api/filter1,如下所示。
可以看到,接口请求成功,并将状态码修改为1001。
用户微服务控制台输出如下内容。
访问了apiFilter1接口
ip = localhost, name = binghe
说明使用SpringCloud Gateway的内置过滤器成功为原始请求添加了一个名称为IP的Header,值为localhost,并添加了一个名称为name的参数,参数值为binghe。同时修改了响应的结果状态,将结果状态修改为1001,符合预期效果。
4.2.1.3 自定义局部过滤器
基于SpringCloud Gateway自定义局部过滤器实现是否开启灰度发布的功能,整个实现过程如下所示。
- 在服务网关的application.yml文件中的spring.cloud.gateway.routes节点下的- id: user-gateway下面进行如下配置。
spring:
cloud:
gateway:
routes:
- id: user-gateway
uri: http://localhost:8060
order: 1
predicates:
- Path=/user/**
filters:
- StripPrefix=1
- Grayscale=true
- 在网关服务模块shop-gateway中新建cn.mawenda.shop.gateway.filter包,在包下新建GrayscaleGatewayFilterConfig类,用于接收配置中的参数,如下所示。
/**
* 接收配置参数
* @Author: Ma.wenda
* @Date: 2024-05-10 16:56
* @Version: 1.0
**/
@Data
public class GrayscaleGatewayFilterConfig implements Serializable {
private static final long serialVersionUID = 1L;
private boolean grayscale;
}
- 在cn.mawenda.shop.gateway.filter包下创建GrayscaleGatewayFilterFactory类,继承org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory类,主要是实现自定义过滤器,模拟实现灰度发布。代码如下所示。
/**
* @Author: Ma.wenda
* @Date: 2024-05-10 16:59
* @Version: 1.0
**/
@Component
public class GrayscaleGatewayFilterFactory extends AbstractGatewayFilterFactory<GrayscaleGatewayFilterConfig> {
public GrayscaleGatewayFilterFactory(){
super(GrayscaleGatewayFilterConfig.class);
}
@Override
public GatewayFilter apply(GrayscaleGatewayFilterConfig config) {
return (exchange, chain) -> {
if (config.isGrayscale()){
System.out.println("开启了灰度发布功能...");
}else{
System.out.println("关闭了灰度发布功能...");
}
return chain.filter(exchange);
};
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("grayscale");
}
}
- 分别启动用户微服务和服务网关,在浏览器中输入http://localhost:10001/server-user/user/get/1001,如下所示。
可以看到,通过服务网关正确访问到了用户微服务,并正确获取到了用户信息。
4.2.2 全局过滤器
全局过滤器是一系列特殊的过滤器,会根据条件应用到所有路由中。
4.2.2.1 全局过滤器概述
在SpringCloud Gateway中内置了多种不同的全局过滤器,如下所示。
过滤器 | 作用 |
---|---|
ForwardRoutingFilter | 用于本地forward,也就是将请求在Gateway服务内进行转发,而不是转发到下游服务。 |
LoadBalancerClientFilter | 整合Ribbon实现负载均衡。 |
NettyRoutingFilter | 使用Netty的HttpClient 转发http、https请求。 |
NettyWriteResponseFilter | 将代理响应写回网关的客户端侧。 |
RouteToRequestUrlFilter | 将从request里获取的原始url转换成Gateway进行请求转发时所使用的url。 |
WebsocketRoutingFilter | 使用Spring Web Socket将转发 Websocket 请求。 |
GatewayMetricsFilter | 整合监控相关,提供监控指标。 |
ForwardPathFilter | 解析路径,并转发路径。 |
WebClientHttpRoutingFilter | 通过WebClient客户端转发请求真实的URL。 |
WebClientWriteResponseFilter | 将响应信息写入到当前的请求响应中。 |
4.2.2.2 演示全局过滤器
- 在服务网关模块shop-gateway模块下的cn.mawenda.shop.gateway.config包下新建GatewayFilterConfig类,并在类中配置几个全局过滤器,如下所示。
/**
* 网关过滤器配置
* @Author: Ma.wenda
* @Date: 2024-05-10 17:32
* @Version: 1.0
**/
@Configuration
@Slf4j
public class GatewayFilterConfig {
@Bean
@Order(-1)
public GlobalFilter globalFilter() {
return (exchange, chain) -> {
log.info("执行前置过滤器逻辑");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("执行后置过滤器逻辑");
}));
};
}
}
注意:@Order注解中的数字越小,执行的优先级越高。
- 启动用户微服务与服务网关,在浏览器中访问http://localhost:10001/server-user/user/get/1001,如下所示。
在网关服务终端输出如下信息。
说明全局过滤器生效。
4.2.2.3 自定义全局过滤器
SpringCloud Gateway内置了很多全局过滤器,一般情况下能够满足实际开发需要,但是对于某些特殊的业务场景,还是需要我们自己实现自定义全局过滤器。
这里,我们就模拟实现一个获取客户端访问信息,并统计访问接口时长的全局过滤器。
- 在网关服务模块cn.mawenda.shop.order.filter包下,新建GlobalGatewayLogFilter类,实现org.springframework.cloud.gateway.filter.GlobalFilter接口和org.springframework.core.Ordered接口,代码如下所示。
/**
* 自定义全局过滤器,模拟实现获取客户端信息并统计接口访问时长
* @Author: Ma.wenda
* @Date: 2024-05-10 17:38
* @Version: 1.0
**/
@Slf4j
@Component
public class GlobalGatewayLogFilter 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");
}
}));
}
@Override
public int getOrder() {
return 0;
}
}
- 启动用户微服务与网关服务,在浏览器中输入http://localhost:10001/server-user/user/api/filter1?name=binghe,如下所示。
/**
* 自定义全局过滤器,模拟实现获取客户端信息并统计接口访问时长
* @Author: Ma.wenda
* @Date: 2024-05-10 17:38
* @Version: 1.0
**/
@Slf4j
@Component
public class GlobalGatewayLogFilter 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");
}
}));
}
@Override
public int getOrder() {
return 0;
}
}
- 启动用户微服务与网关服务,在浏览器中输入http://localhost:10001/server-user/user/api/filter1?name=binghe,如下所示。
自定义全局过滤器生效。