当前位置: 首页 > news >正文

【PmHub后端篇】PmHub Gateway全局过滤器:接口调用耗时统计及黑白名单配置技术深度解析

在微服务架构日益成为现代应用开发主流模式的背景下,网关作为微服务架构前端的关键组件,肩负着路由请求、负载均衡、安全认证、流量控制、监控和日志记录等多项重要任务。本文将围绕PmHub项目中Gateway全局过滤器实现接口调用耗时统计的相关技术进行深入剖析,帮助开发者更好地理解和应用这些技术。

1 网关基础理论

在微服务架构中,单体应用被拆分为多个小型微服务,每个微服务可独立部署、扩展和维护。然而,这也带来了服务间通信管理的挑战,网关因此成为不可或缺的组件。网关是一个位于微服务架构前端的组件,它充当了所有微服务的入口。网关负责路由请求、负载均衡、安全认证、流量控制、监控和日志记录等任务。网关还可以将多个微服务组合成一个统一的 API,从而简化客户端与微服务之间的通信

在这里插入图片描述

常见的微服务网关包括Spring Cloud NetflixZuulSpring Cloud Gateway以及KongKong基于OpenResty + Lua开发,具有高性能、丰富的插件生态、多语言支持、跨平台支持和企业版等优势。但由于其学习成本较高,且对Java开发者不太友好,在实际项目中使用并不普遍。SpringCloud Gateway则是原zuul1.x版的替代品,更适合新项目使用。

以下是Spring Cloud GatewayZuul的详细对比:

对比点Spring Cloud GatewayZuul
架构与设计基于Spring 5Spring Boot 2Project Reactor,采用响应式编程模型基于Servlet,为阻塞模型
性能具备高吞吐量、低延迟的特点,适用于高并发场景性能相对较差,在高并发场景下可能出现瓶颈
功能特性支持动态路由、WebSocket,拥有丰富的过滤器工厂,可与Spring Security集成,具备限流、重试、熔断等功能仅具备基本的路由和过滤功能,支持前置和后置过滤器
易用性能够与Spring生态无缝集成,开发体验一致,拥有良好的文档和社区支持配置和扩展相对简单,但功能有限
维护与社区支持VMware积极维护,更新频繁,社区活跃,文档丰富Zuul 1已被归档,Zuul 2社区支持较少
插件与扩展性提供丰富的内置功能和插件,扩展性良好插件生态相对薄弱,功能扩展性较差
学习曲线需要学习响应式编程模型,对不熟悉响应式编程的开发者有一定挑战相对简单,适合小型和中型项目
典型使用场景适用于高性能、高并发且需要丰富功能和扩展性的企业级应用适用于小型和中型项目,无需处理高并发场景

2 Spring Cloud Gateway的核心组件

Spring Cloud Gateway主要包含三大核心组件:路由(Route)、断言(Predicate)、过滤器(Filter)
在这里插入图片描述

2.1 路由(Route)

路由是构建网关的基础模块,由ID、目标URI、一系列断言和过滤器组成。当断言为true时,请求将被匹配到相应的路由。

spring:redis:host: localhostport: 6379password:cloud:gateway:discovery:locator:lowerCaseServiceId: trueenabled: trueroutes:# 认证中心- id: pmhub-authuri: lb://pmhub-authpredicates:- Path=/auth/**filters:# 验证码处理- CacheRequestFilter# - ValidateCodeFilter- StripPrefix=1

以PmHub项目中的认证服务pmhub-auth为例,通过配置,请求网关的URL中带有「 /auth/** 」的请求会被转发到认证中心服务。

Gateway中,URI 有三种方式,包括:

  • Websocket配置方式
spring:cloud:gateway:routes:- id: pmhub-apiuri: ws://localhost:9090/predicates:- Path=/api/**
  • http地址配置方式
spring:cloud:gateway:routes:- id: pmhub-apiuri: http://localhost:9090/predicates:- Path=/api/**
  • Nacos注册中心配置方式
spring:cloud:gateway:routes:- id: pmhub-apiuri: lb://pmhub-authpredicates:- Path=/api/**

PmHub 采用的是第三种。

2.2 断言(Predicate)

断言可理解为匹配规则,用于确定请求是否符合特定条件,从而找到对应的Route进行处理。

Spring Cloud Gateway包含多种内置的Route Predicate Factories,例如根据日期时间、请求的远端地址、路由权重、请求头、Host地址、请求方法、请求路径和请求参数等进行断言。多个断言可以通过逻辑与(and)组合使用。

以下是一些常用的断言

  • Weight-匹配权重
spring: application:name: pmhub-gatewaycloud:gateway:routes:- id: pmhub-system-auri: http://localhost:9201/predicates:- Weight=group1, 8- id: pmhub-system-buri: http://localhost:9201/predicates:- Weight=group1, 2
  • Datetime-匹配日期时间之后发生的请求
spring: application:name: pmhub-gatewaycloud:gateway:routes:- id: pmhub-systemuri: http://localhost:9201/predicates:- After=2021-02-23T14:20:00.000+08:00[Asia/Shanghai]
  • Query-匹配查询参数
spring: application:name: pmhub-gatewaycloud:gateway:routes:- id: pmhub-systemuri: http://localhost:9201/predicates:- Query=username, abc.
  • Path-匹配请求路径
spring: application:name: pmhub-gatewaycloud:gateway:routes:- id: pmhub-systemuri: http://localhost:9201/predicates:- Path=/system/**
  • Header-匹配具有指定名称的请求头,\d+值匹配正则表达式
spring: application:name: pmhub-gatewaycloud:gateway:routes:- id: pmhub-systemuri: http://localhost:9201/predicates:- Header=X-Request-Id, \d+

如果内置断言无法满足需求,开发者还可以通过继承AbstractRoutePredicateFactory抽象类或实现lRoutePredicateFactory接口来自定义断言规则,自定义类需以RoutePredicateFactory后缀结尾。

  • 继承 AbstractRoutePredicateFactory 抽象类
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>
{public MyRoutePredicateFactory(){super(MyRoutePredicateFactory.Config.class);}@Validatedpublic static class Config{@Setter@Getter@NotEmptyprivate String userType; //钻、金、银等用户等级}@Overridepublic Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config){return new Predicate<ServerWebExchange>(){@Overridepublic 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;}};}
}

2.3 过滤器(Filter)

网关中的过滤器类似于SpringMVC中的拦截器InterceptorServlet过滤器,分为全局默认过滤器、单一内置过滤器和自定义过滤器。
在这里插入图片描述

  • 全局过滤器:作用于所有路由,无需单独配置,可实现权限认证、IP访问限制等统一化处理的业务需求。在PmHub项目中,AuthFilter.java就是通过实现GlobalFilterOrdered接口来实现的全局过滤器。
  • 单一内置过滤器:主要作用于单一路由或某个路由。常见的单一过滤器功能包括过滤指定请求头的路径、过滤特定请求参数、添加响应头信息、对前缀和路径进行过滤以及路径重定向等。
  • 自定义过滤器:例如,在统计接口调用耗时时,可以创建一个全局Filter,实现GlobalFilterOrdered接口,并在filter方法中进行接口访问耗时统计。具体实现步骤为:记录接口访问的开始时间;在请求处理完成后,执行异步任务记录日志。
//先记录下访问接口的开始时间
exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());// Mono.fromRunnable 是非阻塞的,适合在 then 中处理后续的日志逻辑。
return chain.filter(exchange).then(Mono.fromRunnable(() -> {try {// 记录接口访问日志Long beginVisitTime = exchange.getAttribute(BEGIN_VISIT_TIME);if (beginVisitTime != null) {URI uri = exchange.getRequest().getURI();Map<String, Object> logData = new HashMap<>();logData.put("host", uri.getHost());logData.put("port", uri.getPort());logData.put("path", uri.getPath());logData.put("query", uri.getRawQuery());logData.put("duration", (System.currentTimeMillis() - beginVisitTime) + "ms");log.info("访问接口信息: {}", logData);log.info("我是美丽分割线: ###################################################");}} catch (Exception e) {log.error("记录日志时发生异常: ", e);}
}));

3 Gateway的相关配置

3.1 Gateway限流配置

限流是为了对流量进行限制,保护系统不被过高的流量冲击。

常见的限流算法包括:计数器算法、漏桶算法(Leaky Bucket)、以及令牌桶算法(Token Bucket)

Spring Cloud Gateway中,通过结合RedisLua脚本,利用RequestRateLimiterGatewayFilterFactory过滤器工厂实现基于令牌桶的限流方式。

  • 添加依赖
<!-- spring data redis reactive 依赖 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
  • 限流规则,根据URI限流
spring:redis:host: localhostport: 6379password: cloud:gateway:routes:# 系统模块- id: pmhub-systemuri: lb://pmhub-systempredicates:- Path=/system/**filters:- StripPrefix=1- name: RequestRateLimiterargs:redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充速率redis-rate-limiter.burstCapacity: 2 # 令牌桶总容量key-resolver: "#{@pathKeyResolver}" # 使用 SpEL 表达式按名称引用 bean

在配置时,需要注意StripPrefix=1表示网关转发到业务模块时会自动截取前缀,可根据实际情况进行配置。

  • 限流规则配置类
/*** 限流规则配置类*/
@Configuration
public class KeyResolverConfiguration
{@Beanpublic KeyResolver pathKeyResolver(){return exchange -> Mono.just(exchange.getRequest().getURI().getPath());}
}

同时,启动网关服务PmHubGatewayApplication和系统服务PmHubSystemApplication进行验证,因为网关服务有认证鉴权,可以在 gateway 配置中增加一下白名单/system/**再进行测试,多次请求可能会返回HTTP ERROR 429,并且在Redis中会存在两个key,表明限流成功。

request_rate_limiter.{xxx}.timestamp
{xxx}.tokens

也可以根据其他限流规则来配置,如参数限流,IP限流:

//参数限流
@Bean
public KeyResolver parameterKeyResolver()
{return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}// ip限流
@Bean
public KeyResolver ipKeyResolver()
{return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}

3.2 Gateway黑名单配置

黑名单是一个禁止访问的URL列表。通过创建自定义过滤器BlackListUrlFilter,并配置黑名单列表blacklistUrl,可以实现对特定URL的访问限制。如有其他特殊需求,还可以实现自定义规则的过滤器来满足特定的过滤要求。

  • 过滤器核心逻辑 (apply方法)
public GatewayFilter apply(Config config) {return (exchange, chain) -> {// 获取请求路径String url = exchange.getRequest().getURI().getPath();// 检查是否命中黑名单if (config.matchBlacklist(url)) {// 返回拒绝访问响应return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求地址不允许访问");}// 放行合法请求return chain.filter(exchange);};
}
  • 配置处理逻辑 (Config内部类)
public static class Config {// 存储配置的黑名单路径(支持**通配符)private List<String> blacklistUrl;// 存储编译后的正则表达式模式private List<Pattern> blacklistUrlPattern = new ArrayList<>();// 路径匹配逻辑public boolean matchBlacklist(String url) {return !blacklistUrlPattern.isEmpty() && blacklistUrlPattern.stream().anyMatch(p -> p.matcher(url).find());}// 配置注入时自动转换通配符为正则public void setBlacklistUrl(List<String> blacklistUrl) {this.blacklistUrl = blacklistUrl;this.blacklistUrlPattern.clear();this.blacklistUrl.forEach(url -> {this.blacklistUrlPattern.add(Pattern.compile(url.replaceAll("\\*\\*", "(.*?)"),  // 将**转换为正则表达式Pattern.CASE_INSENSITIVE  // 忽略大小写匹配));});}
}
  • 工作流程

    • 初始化:通过构造函数注册配置类
    • 配置加载:Spring Boot将application.yml中的blacklistUrl配置注入到Config对象
    • 模式转换:setBlacklistUrl()将通配符路径转换为正则表达式(如/auth/** ^/auth/(.*?)$
    • 请求过滤:每个请求经过网关时检查URL是否匹配黑名单模式
  • 典型配置示例

spring:cloud:gateway:routes:# 系统模块- id: pmhub-systemuri: lb://pmhub-systempredicates:- Path=/system/**filters:- StripPrefix=0- name: BlackListUrlFilterargs:blacklistUrl:- /user/list

3.3 Gateway白名单配置

白名单是允许访问的URL地址列表,例如登录、注册接口等无需登录即可访问的接口可以放在白名单中。在全局过滤器中添加相应逻辑,在ignore中设置whites,实现对匿名访问的支持。

  • 在全局过滤器中添加以下逻辑
// 跳过不需要验证的路径
if (StringUtils.matches(url, ignoreWhite.getWhites())) {return chain.filter(exchange);
}
  • 白名单配置
# 不校验白名单
ignore:whites:- /auth/logout- /auth/login

4 在PmHub中整合Gateway实战

4.1 编写全局过滤器

  • 新建AuthFilter
    在网关服务pmhub-gateway的filter下新建AuthFilter类,实现GlobalFilterOrdered接口。
/*** 网关鉴权*/
@Component
public class AuthFilter implements GlobalFilter, Ordered {private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);private static final String BEGIN_VISIT_TIME = "begin_visit_time";//开始访问时间// 排除过滤的 uri 地址,nacos自行添加@Autowiredprivate IgnoreWhiteProperties ignoreWhite;@Autowiredprivate RedisService redisService;...@Overridepublic int getOrder() {return -200; // 设置过滤器优先级(数值越小优先级越高)}}
  • 在filter接口的实现中,主要完成以下几个方面的操作
    • 白名单过滤,即过滤掉不需要验证的请求路径;
    • 进行token鉴权,确保令牌不能为空且未过期,并将用户信息放在请求头中,方便服务调用传递;
    • 记录访问接口的开始时间,用于统计接口调用的耗时情况。
    @Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();ServerHttpRequest.Builder mutate = request.mutate();// 1. 请求路径检查String url = request.getURI().getPath();// 跳过白名单路径if (StringUtils.matches(url, ignoreWhite.getWhites())) {return chain.filter(exchange);}// 2. Token校验流程String token = getToken(request);if (StringUtils.isEmpty(token)) { // 令牌空校验return unauthorizedResponse(exchange, "令牌不能为空");}Claims claims = JwtUtils.parseToken(token); // JWT解析if (claims == null) { // 令牌有效性校验return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");}// 3. Redis登录状态验证String userkey = JwtUtils.getUserKey(claims); boolean islogin = redisService.hasKey(getTokenKey(userkey));if (!islogin) { // 检查是否已登录return unauthorizedResponse(exchange, "登录状态已过期");}// 4. 用户信息提取与注入String userid = JwtUtils.getUserId(claims);String username = JwtUtils.getUserName(claims);if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) {return unauthorizedResponse(exchange, "令牌验证失败");}// 5. 请求头信息处理addHeader(mutate, SecurityConstants.USER_KEY, userkey); // 注入用户标识addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid); // 注入用户IDaddHeader(mutate, SecurityConstants.DETAILS_USERNAME, username); // 注入用户名// 移除内部请求标识(防止网关携带内部请求标识,造成系统安全风险)removeHeader(mutate, SecurityConstants.FROM_SOURCE);// 6. 接口访问日志记录exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());// Mono.fromRunnable 是非阻塞的,适合在 then 中处理后续的日志逻辑。return chain.filter(exchange).then(Mono.fromRunnable(() -> {try {// 记录接口耗时和访问信息Long beginVisitTime = exchange.getAttribute(BEGIN_VISIT_TIME);if (beginVisitTime != null) {URI uri = exchange.getRequest().getURI();Map<String, Object> logData = new HashMap<>();logData.put("host", uri.getHost());logData.put("port", uri.getPort());logData.put("path", uri.getPath());logData.put("query", uri.getRawQuery());logData.put("duration", (System.currentTimeMillis() - beginVisitTime) + "ms");log.info("访问接口信息: {}", logData);log.info("我是美丽分割线: ###################################################");}} catch (Exception e) {log.error("记录日志时发生异常: ", e);}}));}
  • bootstrap.yml配置
    完成上述操作后,通过bootstrap.yml配置文件对应用名称、环境等信息进行配置,实现PmHub网关的自定义过滤器。
# Spring
spring: application:# 应用名称name: pmhub-gatewayprofiles:# 环境配置active: dev

5 总结

本文围绕 PmHub 项目,介绍微服务架构下网关的重要任务,对比 Spring Cloud Gateway 与 Zuul 的特点,阐述其核心组件,如路由、断言、过滤器,还说明了限流、黑白名单等配置及在 PmHub 中编写全局过滤器的实践,利于提升系统性能和安全性。

6 参考链接

  1. PmHub Gateway全局过滤器统计接口调用耗时
  2. 项目仓库(GitHub)
  3. 项目仓库(码云)

相关文章:

  • 【se-res模块学习】结合CIFAR-10分类任务学习
  • 机器学习笔记——特征工程
  • HOW - 在 Mac 上的 Chrome 浏览器中调试 Windows 场景下的前端页面
  • 第五十四篇 AI与数据分析
  • Windows ABBYY FineReader 16 Corporate 文档转换、PDF编辑和文档比较
  • 非对称加密:为什么RSA让“公开传密”成为可能
  • 汽车为什么需要以太网?带宽?实时?
  • 如何在 C# 和 .NET 中打印 DataGrid
  • 2025年APP安全攻防指南:抵御DDoS与CC攻击的实战策略
  • 基于智能家居项目 RGB彩灯(P9813)
  • 一个用C#开发的记事本Notepads开源编辑器
  • 57.[前端开发-前端工程化]Day04-webpack插件模式-搭建本地服务器
  • Matter协议,智能家居生态平台的“共生契约”
  • 系统重装之后,通过ssh无法登录
  • java 破解aspose.words 18.6 使用
  • SEMI E40标准的加工任务状态模型(Process Job State Model)解析
  • 【Python 日期和时间】
  • DLMS协议 —— System title 详解(作用及结构一览)
  • C# NX二次开发:判断两个体是否干涉和获取系统日志的UFUN函数
  • QT 文件选择对话框 QFileDialog
  • 七大交响乐团在沪“神仙斗法”,时代交响奏出何等时代新声
  • 汪明荃,今生不负你
  • “三德子”赵亮直播间卖“德子土鸡”,外包装商标实为“德子土”
  • 广州下调个人住房公积金贷款利率
  • 美联储宣布维持联邦基金利率目标区间不变
  • 太空摄影的发展