API 网关

摘要:位于微服务的前端、应用架构的边缘,充当系统的单一入口


目录

[TOC]

API 网关

网关Gateway)、网间连接器、协议转换器:是一个网络连接到另一个网络的“关口”。在传输层上以实现网络互连,是最复杂的网络互连设备,仅用于两个高层协议不同的网络互连。

API 网关:位于微服务的前端、应用架构的边缘,充当系统的单一入口。主要作用是接受来自客户端的 API 请求,(根据定义的策略处理请求),将请求路由(分配)给相应的后端服务处理,然后聚合结果并响应给请求者。

  • 为什么 API 网关要在 Nginx 反向代理后面:

服务网关:而服务网关主要用于内部服务间的交互,更多地嵌入在服务架构内部,处理微服务间的通信。

  • 将非功能性需求的实现卸载到基础设施层,并帮助开发人员专注于核心业务逻辑,从而加快应用程序的发布速度。

为什么需要网关?

传统的单体架构中只有一个服务开放给客户端调用,但是微服务架构中是将一个系统拆分成多个微服务,作为客户端去调用这些微服务,如果没有网关的存在,只能在本地记录每个微服务的调用地址。这种架构存在以下问题:

  1. 客户端多次请求不同的微服务:增加客户端代码或配置编写的复杂性。
  2. 认证复杂:每个服务都需要独立认证。
  3. 存在跨域请求:在一定场景下处理相对复杂。

基本功能

网关是所有微服务的门户,功能包括:统一入口,统一校验,统一分发

  1. 路由转发:接收到 Nginx 转发的 HTTP 请求。根据请求的路径和参数,将请求转发到相应的微服务实例。
  2. 请求统一认证、鉴权:统一在网关层面认证鉴权,微服务只专注于自身的业务,代码耦合性低。如,整合 Spring Cloud Gateway + Spring Cloud Security 统一认证鉴权。
  3. 请求校验、过滤 、拦截:网关对请求进行校验,如验证请求头、请求参数是否合法,是否包含必要的身份认证信息(如 Token )。过滤掉非法的、无资格的、低优先级的恶意流量(比如黑客攻击、恶意爬虫、黄牛、秒杀器等),不进行后面真正的业务逻辑处理,减轻系统的并发压力。
  4. 流量控制:为了保护后端微服务,网关可以配置 限流 和 熔断策略,防止请求过载导致服务崩溃。
  5. 协议转换、数据缓存
  6. 日志记录、监控:网关可以记录请求的日志信息,包括请求时间、请求路径、请求参数、响应状态等,以便于后续分析和监控。

认证和鉴权

大致的认证鉴权流程,架构如下图:

img

大致分为四个角色及对应服务,如下:

  1. 客户端:需要访问微服务资源
  2. 网关服务:负责转发、认证、鉴权,oauth2-cloud-gateway
  3. OAuth2.0 认证授权服务:负责认证授权颁发令牌oauth2-cloud-auth-server
  4. 微服务集合:提供资源的一系列服务。oauth2-cloud-order-service

大致流程如下:

1、客户端发出请求给网关,获取令牌

2、网关收到请求,直接转发给授权服务

3、授权服务验证用户名、密码等一系列身份,通过则颁发令牌给客户端

4、客户端携带令牌请求资源,请求直接到了网关层

5、网关对令牌进行校验(验签过期时间校验….)、鉴权(对当前令牌携带的权限)和访问资源所需的权限进行比对,如果权限有交集则通过校验,直接转发给微服务

6、微服务进行逻辑处理

针对上述架构需要新建三个服务,案例源码目录如下:

img

常见的网关系统

  1. Spring Cloud Gateway推荐
  2. Zuul:主要通过过滤器(类似于 AOP)来过滤请求。成熟、简单。
  3. Kong :基于 Nginx 与 Lua 的高性能 Web 平台,内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。高性能(能处理超高并发)、扩展性极高。
  4. Nginx:稳定可靠、低内存,偏运维、门槛高。
  5. 美团百亿规模API网关服务Shepherd的设计与实现

比较分析:

  1. 性能:Spring Cloud Gateway基于Spring WebFlux实现,采用异步非阻塞模型,性能优于Zuul。
  2. 功能:Gateway提供了更丰富的路由规则和过滤器功能,如动态路由、权重路由等。
  3. 易用性:Zuul的配置相对简单,易于上手;而 Gateway 的配置更加灵活,但学习成本相对较高。
  4. 社区支持:Gateway作为Spring Cloud生态的一部分,得到了更广泛的社区支持。

综上所述,Spring Cloud Gateway在性能、功能和社区支持方面优于Zuul,但在易用性方面略逊一筹。在实际项目中,可以根据项目需求和团队技术栈选择合适的微服务网关方案。

Spring Cloud Zuul

Zuul提供基于配置的API表层。

作为网关,以便记录输入和输出日志,并可以根据各种参数来实现安全性或重定向请求。

  • 支持自动路由映射到在Eureka Server上注册的服务。使用注解@EnableZuulProxy来启用路由代理。

  • 允许重定向REST请求以执行各种类型的过滤器。
  • Spring Cloud Security通过Spring Cloud Zuul解决了许多安全问题。

Spring Cloud Gateway

Spring Cloud Gateway是Spring Cloud的新一代API网关,旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。

  • 作为Spring Cloud生态系统中的网关,目标是替代Netflix ZUUL,具有更好的性能、更强的扩展性、以及更丰富的功能特性,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,限流等。
  • 该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关。
    • 基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

功能

Spring Cloud Gateway 提供了丰富的功能,包括但不限于:

  1. 动态路由: 能够匹配任何请求属性,根据配置动态地将请求路由到不同的微服务实例
  2. 过滤器: 实现对请求和响应的各种操作,例如认证、授权、请求转发、限流等
  3. 集成Hystrix断路器: 处理微服务中的故障和延迟,防止故障扩散
  4. 集成Spring Cloud DiscoveryClient 服务发现功能
  5. 集成负载均衡: 将请求分发到多个服务实例,提高系统的性能和可用性
  6. 统一认证和授权: 通过集成 Spring Security 等机制,实现对微服务的统一认证和授权管理
  7. 监控和日志: 提供监控和日志功能,帮助理解网关的运行状况,分析请求流量

基于 Reactor 实现

Reactor 是一个运行在 Java8 之上满足 Reactice 规范的响应式框架,提供了一组响应式风格的 API。

Reactor 有两个核心类: Flux<T>Mono<T>,这两个类都实现 Publisher 接口。

  • Flux 类似 RxJava 的 Observable,它可以触发零到多个事件,并根据实际情况结束处理或触发错误。
  • Mono 最多只触发一个事件,所以可以把 Mono 用于在异步任务完成时发出通知。

preview

Flux 和 Mono 都是数据流的发布者,使用 Flux 和 Mono 都可以发出三种数据信号:元素值,错误信号,完成信号;

  • 错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者。

Mono

Mono 是一个发出(emit)0-1个元素的Publisher,可以被onComplete信号或者onError信号所终止。

mono 整体和Flux差不多,只不过这里只会发出0-1个元素。也就是说不是有就是没有。

演化过程

象Flux一样,我们来看看Mono的演化过程以帮助理解。

传统数据处理
1
2
3
public ClientUser currentUser () {
    return isAuthenticated ? new ClientUser("felord.cn", "reactive") : null;
}

直接返回符合条件的对象或者null

Optional的处理方式
1
2
3
4
public Optional<ClientUser> currentUser () {
    return isAuthenticated ? Optional.of(new ClientUser("felord.cn", "reactive"))
            : Optional.empty();
}

这个Optional我觉得就有反应式的那种味儿了,当然它并不是反应式。当我们不从返回值Optional取其中具体的对象时,我们不清楚里面到底有没有,但是Optional是一定客观存在的,不会出现NPE问题。

反应式数据处理
1
2
3
4
public Mono<ClientUser> currentUser () {
    return isAuthenticated ? Mono.just(new ClientUser("felord.cn", "reactive"))
            : Mono.empty();
}

和Optional有点类似的机制,当然Mono不是为了解决NPE问题的,它是为了处理响应流中单个值(也可能是Void)而存在的。

工作流程

  1. 客户端向Spring Cloud Gateway发出请求,
  2. 然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler
  3. Handler再通过指定的过滤器链来对请求进行过滤处理,
  4. 最后发送到我们实际的服务执行业务逻辑,然后返回。

在这里插入图片描述

img

网关服务搭建

参考:

网关服务搭建过程如下:

代码逻辑如下:

  1. 检查是否是白名单,白名单直接放行
  2. 检验令牌是否存在
  3. 解析令牌中的用户信息
  4. 封装用户信息到JSON数据中
  5. 加密JSON数据
  6. 将加密后的JSON数据放入到请求头中

新建一个oauth2-cloud-gateway模块,目录如下图:

img

application.yml 配置

添加 nacos 与 网关的相关配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
server:
  port: 10001
spring:
  application:
    ## 指定服务名称,在nacos中的名字
    name: cloud-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848   # nacos的服务地址,nacos-server中IP地址:端口号
    gateway:
      routes: 	# 网关路由配置
        - id: user-service 		# 路由id,自定义,只要唯一即可
          # uri: http://localhost:8081 	# 路由的目标地址 http就是固定地址
          uri: lb://user-service 		# 路由的目标地址 lb就是负载均衡,后面跟服务名称,从注册中心获取uri
          predicates: 			# 路由断言,也就是判断请求是否符合路由规则的条件
            ## Path Route Predicate Factory断言,满足/user-service/**这个请求路径的都会被路由到http://localhost:8081这个uri中
            - Path=/user-service/** 	# 这个是按照路径匹配,只要以/user/开头就符合要求
          filters: ## 配置过滤器(局部)
            - AddResponseHeader=X-Response-Foo, Bar  # 浏览器请求响应头中已经有了`X-Response-Foo=Bar`这个键值对。
            ## AuthorizeGatewayFilterFactory自定义过滤器配置,值为true需要验证授权,false不需要。
            - Authorize=true
            # 使用 RewritePath 过滤器来去除请求路径中的 /user-service 前缀
            - RewritePath=/user-service/(?<path>.*), /$\{path}

        - id: order-service-1
          uri: lb://order-service
          predicates:
            - Path=/order/**
            ## Weight Route Predicate Factory,同一分组按照权重进行分配流量,这里分配了80%
            - Weight=group1, 8  # 第一个group1是分组名,第二个参数是权重

        - id: order-service-2
          uri: lb://order-service
          predicates:
            - Path=/order/**
            - Weight=group1, 2

三大核心概念

  1. routes(路由):路由是网关最基础的部分,路由信息由一个ID、一个目标URI、一组断言和过滤器组成。路由断言Predicate用于匹配请求,过滤器Filter用于修改请求和响应。如果断言为true,则说明请求URI和配置匹配,则执行路由。
  2. predicates(断言):满足断言的请求(则执行该路由)都会被路由到uri中。参考Java8中的断言Predicate,用于实现请求匹配逻辑,例如匹配路径、请求头、请求参数等。可以配置多个。
  3. filter(过滤器):可以在返回请求之前或之后修改请求和响应的内容。从作用范围可分为GatewayFilter(局部过滤器)和 GlobalFilter(全局过滤器)

routes(路由)

  • id:路由的唯一id,名称任意
  • uri:路由转发的目标地址,
    1. http://localhost:8081 就是固定地址
    2. lb://service-name
      • lb:指从nacos中按照名称获取微服务,并遵循负载均衡策略
      • service-name:nacos注册中心中的服务名称,这里并不是IP地址形式的
  • predicates
  • filter

predicates(断言)

断言(Predicate )属于 Java8 语言中的 Predicate 函数,参数为 ServerWebExchange 对象。可以让开发者(通过请求头、或请求参数)匹配到任意的 HTTP 请求。

  • 简单理解的话,就是一个条件判断工具。

  • 可以用于接口请求参数校验

    1. 如果符合当前配置的规则则通过验证,进而使得服务网关将该请求路由到微服务中。
    2. 如果不符合当前配置的规则,就无法通过验证,返回错误信息。

Predicate接受输入参数,返回一个布尔值结果。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)。

内置Predicate Factory

在Gateway中,有一些的内置Predicate Factory,有了这些,在运行时,Gateway会 自动根据需要创建其对应的Pridicate对象测试路由条件。

Path 路由断言 Factory: 根据请求路径匹配的路由条件工厂

1
2
3
predicates:
# 如果可以匹配的PathPattern有多个,则每个路径模式以,分开
- Path=/red/{segment},/blue/{segment}

Weight 路由断言 Factory: 请求按照权重分组到不同的路由

默认的路由转发如果路由到了两个,则是按照配置先后顺序转发,

  1. 如果没有配置权重,则肯定是转发到 uri。
  2. 如果配置了权重并且相同的分组,则按照权重比例进行分配流量。

datetime 路由断言 Factory: 在指定日期时间(After、Before、Between)发生的请求都将被匹配

1
2
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]

Cookie 路由断言 Factory: 有两个参数,cookie名称和正则表达式。请求包含此cookie名称且正则表达式为真的将会被匹配。

1
2
predicates:
- Cookie=chocolate, ch.p

Header 路由断言 Factory: 有两个参数,header名称和正则表达式。请求包含此header名称且正则表达式为真的将会被匹配。

1
2
predicates:
- Header=X-Request-Id, \d+

Host 路由断言 Factory: 包括一个参数:host name列表。使用Ant路径匹配规则, . 作为分隔符。

1
2
predicates:
- Host=**.somehost.org,**.anotherhost.org

Method 路由断言 Factory:只包含一个参数:需要匹配的HTTP请求方式

1
2
predicates:
- Method=GET

QueryParam 路由断言 Factory:

RemoteAddr 路由断言 Factory:

image-20250902161217293

自定义Predicate

可以自定义Predicate来实现复杂的路由匹配规则。

Spring Cloud Gateway中的断言命名都是有规范的,格式:xxxRoutePredicateFactory

  • 比如权重的断言:WeightRoutePredicateFactory,那么配置时直接取前面的Weight
1
2
3
4
5
6
// 实现自定义 Predicate 工厂
// 通过HostRoutePredicateFactory创建Predicate进行路由判断
@Component
public class MyHostRoutePredicateFactory extends AbstractRoutePredicateFactory<MyHostRoutePredicateFactory.Config> {

}

filter(过滤器)

在这里插入图片描述

名称只需要写前缀,过滤器命名必须是xxxGatewayFilterFactory(包括自定义),如AddResponseHeaderGatewayFilterFactory,为原始响应添加Header。

从生命周期可分为:

  • PRE 过滤器:在请求被路由之前调用。可实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • POST 过滤器:在路由到微服务以后执行。可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

从作用范围可分为两种:

  • GatewayFilter(局部过滤器):应用到单个路由或者一个分组的路由上。需要在指定路由配置才能生效。
  • GlobalFilter(全局过滤器):应用到所有的路由上。无需配置,全局生效。
GatewayFilter(局部)

Spring Cloud Gateway内置了多种过滤器,例如:

  • AddRequestHeader GatewayFilter:在请求头中添加参数
  • PrefixPath GatewayFilter:请求路径前缀
  • Hystrix GatewayFilter:断路器
  • RateLimit GatewayFilter:限流
  • Retry GatewayFilter:重试

Spring Cloud Gateway中内置了许多的局部过滤器,如下图:

模拟一个授权验证的过程,如果请求头或者请求参数中携带token则放行,否则直接拦截返回401

  • AuthorizeGatewayFilterFactory只是涉及到了过滤器的前置处理,后置处理是在chain.filter().then()中的then()方法中完成的,具体可以看下项目源码中的TimeGatewayFilterFactory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/**
 * 名称必须是xxxGatewayFilterFactory形式
 * todo:模拟授权的验证,具体逻辑根据业务完善
 */
@Component
@Slf4j
public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizeGatewayFilterFactory.Config> {

    private static final String AUTHORIZE_TOKEN = "token";

    //构造函数,加载Config
    public AuthorizeGatewayFilterFactory() {
        //固定写法
        super(AuthorizeGatewayFilterFactory.Config.class);
        log.info("Loaded GatewayFilterFactory [Authorize]");
    }

    //读取配置文件中的参数 赋值到 配置类中
    @Override
    public List<String> shortcutFieldOrder() {
        //Config.enabled
        return Arrays.asList("enabled");
    }

    @Override
    public GatewayFilter apply(AuthorizeGatewayFilterFactory.Config config) {
        return (exchange, chain) -> {
            //判断是否开启授权验证
            if (!config.isEnabled()) {
                return chain.filter(exchange);
            }

            ServerHttpRequest request = exchange.getRequest();
            //
            HttpHeaders headers = request.getHeaders();
            //从请求头中获取token
            String token = headers.getFirst(AUTHORIZE_TOKEN);
            if (token == null) {
                //从请求头参数中获取token
                token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
            }

            ServerHttpResponse response = exchange.getResponse();
            //如果token为空,直接返回401,未授权
            if (StringUtils.isEmpty(token)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                //处理完成,直接拦截,不再进行下去
                return response.setComplete();
            }
            /**
             * todo chain.filter(exchange) 之前的都是过滤器的前置处理
             *
             * chain.filter().then(
             *  过滤器的后置处理...........
             * )
             */
            //授权正常,继续下一个过滤器链的调用
            return chain.filter(exchange);
        };
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Config {
        // 控制是否开启认证
        private boolean enabled;
    }
}
GlobalFilter(全局)

全局过滤器应用到全部路由上,不必在路由上配置,注入到IOC容器中即可全局生效。

功能其实和GatewayFilter是相同的,只是作用域是所有的路由配置,而不是绑定在指定的路由配置上。

  • 多个GlobalFilter可以通过@Order或者getOrder()方法指定执行顺序,order值越小,执行的优先级越高。
  • 注意:由于过滤器有pre和post两种类型,
    1. pre类型过滤器如果order值越小,那么它就应该在pre过滤器链的顶层,最先执行。
    2. post类型过滤器如果order值越小,那么它就应该在pre过滤器链的底层。最后执行。

Spring Cloud Gateway也内置了一些全局过滤器,如下图:

图片

自定义过滤器

场景:模拟Nginx的Access Log 功能,记录每次请求的相关信息。

集成注册中心、动态路由

网关接收外部请求,按照一定的规则,将请求转发给其他服务或者应用。如果站在服务调用的角度,网关就扮演着服务消费者的角色。

此时,如果再来看看服务调用的目标URI配置,就会很自然的发现一个问题,服务提供者调用的地址是写死的(每次路由配置都是指定固定的服务uri),即网关没有动态的发现服务,缺点:

  • 服务的IP的地址一旦修改了,路由配置中的uri必须修改
  • 服务集群中无法实现负载均衡

这就涉及到了服务的自动发现问题,以及发现服务后,所涉及到的服务调用的负载均衡的问题。

此时就需要集成注册中心(如 Nacos),使得网关能够从注册中心自动获取uri(负载均衡)。

  • 可以通过Nacos或者Eureka注册中心动态发现服务,通过Ribbon进行服务调用的负载均衡。
  • 同样,Gateway也可以整合Nacos或者Eureka,Ribbon从而实现动态路由的功能。

想要使用动态路由的功能,首先要整合注册中心,这里以Nacos为例

例如 Eureka:

在 Spring Boot 主类上添加 @EnableDiscoveryClient 注解,以启用服务发现功能。

集成配置中心

将网关的一系列配置写到项目的配置文件中,一旦路由发生改变必须要重新配置项目,这样维护成本很高。

其实可以将网关的配置存放到配置中心中,这样由配置中心统一管理,一旦路由发生改变,只需要在配置中心修改,这样便能达到一处修改,多出生效的目的。

这里当然要使用Nacos作为配置中心了。

bootstrap.yml文件中指定Nacos作为配置中心的一些相关配置:

1
2
3
4
5
6
7
8
9
10
11
spring:
  application:
    ## 指定服务名称,在nacos中的名字
    name: cloud-gateway
  cloud:
    nacos:
      ## todo 此处作为演示,仅仅配置了后缀,其他分组,命名空间根据需要自己配置
      config:
        server-addr: 127.0.0.1:8848
        ## 指定文件后缀为yaml
        file-extension: yaml

在nacos中的public命名空间中创建dataIdcloud-gateway.yaml的配置(未指定环境),配置内容如下:

自定义全局异常处理

通过前面的测试可以看到一个现象:一旦路由的微服务下线或者失联了,Spring Cloud Gateway直接返回了一个错误页面,Whitelabel Error Page。

  • 显然这种异常信息不友好,前后端分离架构中必须定制返回的异常信息。

传统的Spring Boot 服务中都是使用@ControllerAdvice来包装全局异常处理的,但是由于服务下线,请求并没有到达。

因此必须在网关中也要定制一层全局异常处理,这样才能更加友好的和客户端交互。

Spring Cloud Gateway提供了多种全局处理的方式,今天只介绍其中一种方式,实现还算比较优雅。

直接创建一个类GlobalErrorExceptionHandler,实现ErrorWebExceptionHandler,重写其中的handle方法,返回JSON数据。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
 * 用于网关的全局异常处理
 * @Order(-1):优先级一定要比ResponseStatusExceptionHandler低
 */
@Slf4j
@Order(-1)
@Component
@RequiredArgsConstructor
public class GlobalErrorExceptionHandler implements ErrorWebExceptionHandler {

     private final ObjectMapper objectMapper;

     @SuppressWarnings({"rawtypes", "unchecked", "NullableProblems"})
     @Override
     public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse response = exchange.getResponse();
        if (response.isCommitted()) {
            return Mono.error(ex);
        }

         // JOSN格式返回
         response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
         if (ex instanceof ResponseStatusException) {
             response.setStatusCode(((ResponseStatusException) ex).getStatus());
         }

        return response.writeWith(Mono.fromSupplier(() -> {
            DataBufferFactory bufferFactory = response.bufferFactory();
            try {
                //todo 返回响应结果,根据业务需求,自己定制
                CommonResponse resultMsg = new CommonResponse("500",ex.getMessage(),null);
                return bufferFactory.wrap(objectMapper.writeValueAsBytes(resultMsg));
            }
            catch (JsonProcessingException e) {
                log.error("Error writing response", ex);
                return bufferFactory.wrap(new byte[0]);
            }
        }));
 	}
}

OAuth2.0 认证和授权服务搭建

很多企业是将认证授权服务直接集成到网关中,这么做耦合性太高了,这里直接将认证授权服务抽离出来。

认证服务的搭建这里就不再细说了,上一篇文章中已经介绍的很清楚了:OAuth2.0实战!使用JWT令牌认证!

接口测试

img

Knife4j API 文档生成工具

请求过滤

请求过滤的方法:

  1. 封禁攻击者来源 IP
  2. 拒绝带有非法参数的请求
  3. 按来源 IP 限流
  4. 按用户 ID 限流等
0%