欢迎光临
我们一直在努力

七合一 Spring Boot API 防护框架:防重、限流、幂等、自动Trim、慢接口检测、链路追踪、IP黑白名单,一个 Starter 搞定

前言

之前写了个轻量级的 Spring Boot 接口防护框架 Guardian,陆续做了防重复提交、接口限流、接口幂等、参数自动 Trim、慢接口检测、请求链路追踪六个功能,发到了 Maven Central 开源。

最近根据社区反馈又做了不少更新:IP 黑白名单(全局黑名单 + URL 绑定白名单)、MQ 消息链路追踪(RabbitMQ / Kafka / RocketMQ)、跨线程 TraceId 传递、消息国际化、配置中心动态刷新、慢接口 SPI 扩展。现在 Guardian v1.6.2 一共七个模块,覆盖了 API 请求层最常见的防护需求。

每个模块独立 Starter,用哪个引哪个,互不依赖。最快只需要引个依赖就能用,零配置。所有模块的 YAML 配置均支持配置中心动态刷新(Nacos / Apollo 等),无需重启即可生效。

项目地址(源码 + 示例 + 文档全在里面):

  • GitHub:github.com/BigGG-Guardian/guardian ← 顺手点个 Star,不迷路
  • Gitee(镜像同步):gitee.com/BigGG-Guardian/guardian

功能一览

功能Starter注解YAML说明
防重复提交 guardian-repeat-submit-spring-boot-starter @RepeatSubmit 防止用户重复提交表单/请求
接口限流 guardian-rate-limit-spring-boot-starter @RateLimit 滑动窗口 + 令牌桶,双算法可选
接口幂等 guardian-idempotent-spring-boot-starter @Idempotent Token 机制保证接口幂等性,支持结果缓存
参数自动Trim guardian-auto-trim-spring-boot-starter 自动去除请求参数首尾空格 + 不可见字符替换
慢接口检测 guardian-slow-api-spring-boot-starter @SlowApiThreshold 慢接口自动告警 + Top N 统计 + Actuator 端点 + SPI 扩展
请求链路追踪 guardian-trace-spring-boot-starter 自动生成/透传 TraceId,MDC 日志串联,支持跨线程传递、MQ 链路追踪
IP黑白名单 guardian-ip-filter-spring-boot-starter 全局黑名单 + URL 绑定白名单,支持精确/通配符/CIDR

下面一个一个说。


一、防重复提交

什么场景需要?

用户点了提交按钮,前端没做防抖,或者网络慢用户多点了几下。后端收到三个一模一样的请求,创建了三个订单。

防重复提交就是解决这个问题:同一个请求短时间内别让它提交两次。

先看效果

三步搞定。

第一步,引依赖:

<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-repeat-submit-spring-boot-starter</artifactId>
<version>1.6.2</version>
</dependency>

第二步,加注解:

@PostMapping("/submit")
@RepeatSubmit(interval = 10, message = "订单正在处理,请勿重复提交")
public Result submitOrder(@RequestBody OrderDTO order) {
return orderService.submit(order);
}

第三步,没了。启动项目就生效了。

10 秒内同一个用户、同一个接口、同样的请求参数,第二次请求会被直接拦截。

完整可运行的示例代码在仓库的 guardian-example 模块里,各种场景都有,clone 下来直接跑。

YAML 批量配置

单个接口用注解挺方便,但如果有 50 个接口都要配防重,一个一个加注解就有点累了。支持在 YAML 里用 AntPath 通配符批量配置:

guardian:
repeat-submit:
storage: redis
key-encrypt: md5
urls:
pattern: /api/order/**
interval: 10
key-scope: user
message: "订单正在处理,请勿重复提交"
pattern: /api/sms/send
interval: 60
key-scope: ip
exclude-urls:
/api/public/**
/api/health

几个要点:

  • YAML 规则的优先级高于注解,同一个接口两边都配了以 YAML 为准
  • 白名单(exclude-urls)优先级最高,命中直接放行
  • key-scope 控制防重维度:user(按用户)、ip(按 IP)、global(全局)

全量配置

guardian:
repeatable-filter-order: -100 # 请求体缓存过滤器排序(全局共享,仅需配置一次)
repeat-submit:
storage: redis # redis / local
key-encrypt: md5 # none / md5
response-mode: exception # exception / json
log-enabled: false
interceptor-order: 2000 # 拦截器排序(值越小越先执行)
exclude-urls:
/api/public/**
urls:
pattern: /api/order/submit
interval: 10
time-unit: seconds
key-scope: user # user / ip / global
message: "请勿重复提交"

防重维度

维度YAML 值注解值效果
用户级 user KeyScope.USER 同一用户 + 同一接口 + 同一参数(默认)
IP 级 ip KeyScope.IP 同一 IP + 同一接口 + 同一参数
全局级 global KeyScope.GLOBAL 同一接口 + 同一参数

响应模式

模式配置值行为
异常模式 exception(默认) 抛出 RepeatSubmitException,由全局异常处理器捕获
JSON 模式 json 拦截器直接写入 JSON 响应

一些设计细节

Key 怎么拼?

userId + url 够不够?如果同一个用户对同一个接口传了不同的参数呢?比如下单接口,买商品 A 和买商品 B 应该算两次不同的请求,不能拦截。

所以防重 Key 把请求参数也算了进去。但 POST 请求的 body 是个流,读了一次就没了,框架内置了 RepeatableRequestFilter 自动缓存请求体,Key 生成时会把请求参数做 JSON 序列化 + Base64 编码拼进去。

用户没登录怎么办?

已登录用 userId → 没登录用 sessionId → 没 session 用客户端 IP。三级降级,永远不会出现 null。

业务异常了锁不释放怎么办?

拦截器的 afterCompletion 里做了处理:如果请求抛了异常,自动释放锁。正常完成的请求才让锁自然过期。

context-path 的坑:

匹配时同时尝试完整 URI 和去掉 context-path 后的路径,两者有一个匹配上就算命中。所以不管 YAML 里写的是 /order/submit 还是 /admin-api/order/submit,都能正确匹配。

可观测性

  • 拦截日志:log-enabled: true,前缀 [Guardian-Repeat-Submit]
  • Actuator:GET /actuator/guardianRepeatSubmit

{
"totalBlockCount": 128,
"totalPassCount": 5432,
"topBlockedApis": {
"/api/order/submit": 56,
"/api/sms/send": 42
}
}

扩展点

核心组件均可替换,注册同类型 Bean 即可覆盖默认实现。

自定义用户上下文(所有模块共享):

@Bean
public UserContext userContext() {
return () -> SecurityUtils.getCurrentUserId();
}

不实现也能用,框架会自动以 SessionId / IP 作为用户标识。

自定义 Key 生成策略:

public class MyKeyGenerator extends AbstractKeyGenerator {
public MyKeyGenerator(UserContext userContext, AbstractKeyEncrypt keyEncrypt) {
super(userContext, keyEncrypt);
}
@Override
protected String buildKey(RepeatSubmitKey key) {
return key.getServletUri() + ":" + key.getUserId();
}
}

@Bean
public MyKeyGenerator myKeyGenerator(UserContext userContext, AbstractKeyEncrypt keyEncrypt) {
return new MyKeyGenerator(userContext, keyEncrypt);
}

想看防重的完整实现?拦截器源码在 RepeatSubmitInterceptor.java,Redis 存储在 guardian-storage-redis,本地存储在 RepeatSubmitLocalStorage.java,代码不多,感兴趣可以看看。


二、接口限流

什么场景需要?

有人写个脚本一秒钟请求你的搜索接口 1000 次,防重拦不住(因为每次参数可能不一样),这时候就需要限流了。

Guardian 的限流就是冲着轻量场景来的:注解 + YAML 双模式、滑动窗口 + 令牌桶双算法可选。不需要引 Sentinel 那么重的东西。

先看效果

<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-rate-limit-spring-boot-starter</artifactId>
<version>1.6.2</version>
</dependency>

// 滑动窗口:每秒最多 10 次
@RateLimit(qps = 10)

// 令牌桶:每秒补 5 个令牌,桶容量 20,允许瞬间突发 20 次
@RateLimit(qps = 5, capacity = 20, algorithm = RateLimitAlgorithm.TOKEN_BUCKET)

同样支持 YAML 批量配置:

guardian:
rate-limit:
urls:
pattern: /api/sms/send
qps: 1
rate-limit-scope: ip
pattern: /api/seckill/**
qps: 10
capacity: 50
algorithm: token_bucket
rate-limit-scope: global
exclude-urls:
/api/public/**

滑动窗口 vs 令牌桶

滑动窗口(默认)令牌桶
算法 统计窗口内请求数,超过阈值拒绝 按速率补充令牌,有令牌放行,无令牌拒绝
突发流量 不允许,窗口内严格限制 允许,桶满时可瞬间消耗所有令牌
适合场景 精确控速(短信、登录尝试) 允许突发(秒杀、抢购)
数据结构 Local: Deque / Redis: ZSET Local: double + synchronized / Redis: HASH

举个直观的例子,都是 qps=10,突然来了 20 个请求:

滑动窗口令牌桶(capacity=20)
第 1-10 个 通过 通过
第 11-20 个 全部拒绝 全部通过
之后每秒 最多 10 个 最多 10 个

并发安全

限流对并发安全的要求很高。Guardian 的处理:

  • Redis:滑动窗口和令牌桶都用 Lua 脚本,Redis 单线程执行 Lua 天然原子
  • 本地缓存:synchronized 锁到 Key 粒度,不同 Key 之间互不阻塞

可观测性

  • 拦截日志:log-enabled: true,前缀 [Guardian-Rate-Limit]
  • Actuator:GET /actuator/guardianRateLimit

{
"totalRequestCount": 5560,
"totalPassCount": 5432,
"totalBlockCount": 128,
"blockRate": "2.30%",
"topBlockedApis": { "/api/sms/send": 56 },
"topRequestApis": { "/api/search": 3200 },
"apiDetails": {
"/api/sms/send": { "requests": 200, "passes": 144, "blocks": 56, "blockRate": "28.00%" }
}
}

限流拦截器源码在 RateLimitInterceptor.java,Redis Lua 脚本在 guardian-storage-redis,clone 下来看看实现不到 200 行。


三、接口幂等

什么场景需要?

防重和幂等经常被搞混,但它们解决的是不同的问题:

  • 防重复提交:同一个请求短时间内别提交两次(锁一段时间就行)
  • 接口幂等:同一个操作不管执行几次,结果都一样(比如支付,扣一次钱就行)

防重是"不让你提交",幂等是"提交了也没事"。

先看效果

<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-idempotent-spring-boot-starter</artifactId>
<version>1.6.2</version>
</dependency>

1. 获取 Token:

GET /guardian/idempotent/token?key=order-submit

返回:

{
"code": 200,
"data": {
"token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"expireIn": 300,
"expireUnit": "SECONDS"
}
}

2. 业务接口携带 Token:

@Idempotent("order-submit")
@PostMapping("/order/submit")
public Result submitOrder(@RequestBody OrderDTO order) {
return orderService.submit(order);
}

请求头带上 X-Idempotent-Token: {token},首次请求正常处理,重复请求直接拒绝。Token 是一次性的,用完就没了。

Token 机制是怎么工作的?

1. 客户端请求 Token
GET /guardian/idempotent/token?key=order-submit


2. 服务端生成 UUID Token,存入 Redis/本地缓存,设置 TTL
Key: guardian:idempotent:order-submit:{uuid}


3. 客户端携带 Token 发起业务请求
Header: X-Idempotent-Token: {uuid}


4. 拦截器校验
├─ Token 存在 → 删除 Token(原子操作)→ 放行业务执行
└─ Token 不存在或已消费 → 拒绝请求

关键在第 4 步的删除操作是原子的。Redis 用 DEL 命令,返回 1 表示删除成功(首次消费),返回 0 表示 Key 不存在(重复请求)。本地缓存用 ConcurrentHashMap.remove(),也是原子的。

结果缓存

默认行为是重复请求直接拒绝。但有些场景下需要返回首次执行的结果,比如支付回调平台重发通知时期望收到正常的成功响应。

guardian:
idempotent:
result-cache: true

开启后,首次请求的返回值自动缓存。后续拿同一个 Token 再请求时,直接返回缓存的结果而非报错。


四、参数自动Trim

什么场景需要?

用户注册时用户名输了个 " zhangsan "(前后带空格),存进了数据库。后来登录输 "zhangsan" 死活登不上。运维排查半天,发现数据库里的用户名前面多了个空格。

更隐蔽的是不可见字符。用户从某些网页复制粘贴内容,看起来一模一样,但实际上带了零宽空格(\\u200B)或 BOM(\\uFEFF)。

先看效果

<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-auto-trim-spring-boot-starter</artifactId>
<version>1.6.2</version>
</dependency>

引完就能用了,零配置。所有请求参数(表单参数 + JSON Body)自动去除首尾空格。

不可见字符替换

guardian:
auto-trim:
character-replacements:
from: "\\\\r" # 回车符
to: ""
from: "\\\\u200B" # 零宽空格
to: ""
from: "\\\\uFEFF" # BOM
to: ""

执行顺序:先执行字符替换,再执行 trim。

排除字段

密码、签名等不应被 trim 的字段可以排除:

guardian:
auto-trim:
exclude-fields:
password
signature

底层通过 OncePerRequestFilter + HttpServletRequestWrapper 实现,对业务代码完全透明,Controller 拿到的参数已经是 trim 过的。

核心源码在 AutoTrimFilter.java,总共不到 200 行,实现很清晰。


五、慢接口检测

什么场景需要?

线上一个接口平时响应 200ms,某天突然变成 5 秒。如果没有监控,你可能要等到用户投诉了才知道。

Sentinel 能做,但太重了。APM(SkyWalking 之类的)也能做,但不是每个项目都上了 APM。

Guardian 的慢接口检测就是一个轻量方案:超过阈值自动打 WARN 日志 + 记录统计 + Actuator 端点查看排行。

先看效果

<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-slow-api-spring-boot-starter</artifactId>
<version>1.6.2</version>
</dependency>

零配置即可使用,默认阈值 3000ms。接口响应超过 3 秒就会自动打印 WARN 日志:

WARN [Guardian-Slow-Api] @SlowApiThreshold 慢接口检测 | Method=GET | URI=/api/detail | 耗时=3521ms | 阈值=3000ms

注解自定义阈值

@SlowApiThreshold(1000) // 这个接口超过 1 秒就算慢
@GetMapping("/detail")
public Result getDetail(@RequestParam Long id) {
return detailService.query(id);
}

注解优先级高于全局配置。

SlowApiRecorder SPI 扩展(v1.6.2 新增)

默认慢接口记录存在内存中。如果你想把慢接口数据写到数据库或 ES 做持久化分析,实现 SlowApiRecorder 接口并注册为 Bean 即可替换:

@Bean
public SlowApiRecorder mySlowApiRecorder() {
return (uri, method, duration, threshold) -> {
// 写入数据库 / ES / 告警系统…
slowApiDao.save(uri, method, duration, threshold, LocalDateTime.now());
};
}

框架检测到自定义实现后会自动使用你的 Recorder 替代默认内存实现。

Actuator 端点

GET /actuator/guardianSlowApi

{
"totalSlowCount": 15,
"topSlowApis": {
"/api/detail": { "count": 8, "maxDuration": 5230 },
"/api/export": { "count": 7, "maxDuration": 12500 }
}
}

拦截器源码在 SlowApiInterceptor.java,两个文件加起来不到 100 行,非常适合学习 Spring Boot 拦截器的封装思路。


六、请求链路追踪

什么场景需要?

线上出了问题,你打开日志搜索,发现几十个接口的日志混在一起,根本分不清哪些日志属于同一个请求。

TraceId 就是解决这个问题:给每个请求分配一个唯一 ID,同一个请求的所有日志都带上这个 ID,搜索时按 ID 过滤就能串联起来。

先看效果

<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-trace-spring-boot-starter</artifactId>
<version>1.6.2</version>
</dependency>

零配置即可使用。只需要在 Logback 的日志格式里加 %X{traceId}:

<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%thread] %-5level %logger{36} – %msg%n</pattern>

效果:

2026-02-20 14:30:01.123 [143025wkz8dxqn] [http-nio-8080-exec-1] INFO c.s.g.e.t.TraceController – [Controller] 接收请求
2026-02-20 14:30:01.145 [143025wkz8dxqn] [http-nio-8080-exec-1] INFO c.s.g.e.t.TraceController – [Service] 执行业务逻辑
2026-02-20 14:30:01.167 [143025wkz8dxqn] [http-nio-8080-exec-1] INFO c.s.g.e.t.TraceController – [Dao] 执行数据库操作

同一个请求的三条日志都带了 143025wkz8dxqn,搜索这个 ID 就能把整个请求链路串联起来。

跨服务链路串联

请求进来时,如果请求头有 X-Trace-Id,直接复用;没有则自动生成。TraceId 同时写入响应头。

服务 A 收到请求 → 生成 traceId=abc123 → 调用服务 B 时带上 X-Trace-Id: abc123
服务 B 收到请求 → 从请求头取出 abc123 → 复用同一个 traceId

跨线程传递(v1.5.1 新增)

MDC 是线程绑定的,一旦开了子线程或用了线程池,traceId 就丢了。Guardian 提供了几种方案:

// 方式 1:TraceUtils.wrap — 包一层就行
executor.submit(TraceUtils.wrap(() -> {
log.info("子线程也能拿到 traceId");
}));

// 方式 2:CompletableFuture
CompletableFuture.runAsync(() -> {
log.info("异步也有 traceId");
}, TraceUtils.wrap(executor));

// 方式 3:@Async 线程池
@Bean
public ThreadPoolTaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(new TraceTaskDecorator());
return executor;
}

工具场景用法
TraceRunnable / TraceCallable 手动提交 Runnable/Callable TraceUtils.wrap(task)
TraceTaskDecorator @Async 线程池 设置到 ThreadPoolTaskExecutor.setTaskDecorator()
TraceUtils.wrap(Executor) CompletableFuture CompletableFuture.runAsync(task, TraceUtils.wrap(executor))
TraceUtils.getTraceId() 获取当前 traceId 需要传给外部系统时使用

MQ 消息链路追踪(v1.6.2 新增)

HTTP 请求有了 TraceId,但消息发到 MQ 之后就断了?Guardian 支持 RabbitMQ、Kafka、RocketMQ 三种消息队列的 TraceId 自动传递。

核心设计:发送端自动注入 traceId 到消息 Header,消费端通过 AOP 切面自动提取并写入 MDC。消费端采用 AOP 而非框架原生拦截器,不占用拦截器位置,你可以自由注册自己的拦截器。

RabbitMQ:

<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-trace-rabbitmq</artifactId>
<version>1.6.2</version>
</dependency>

引依赖就完事了。发送端通过 MessagePostProcessor 自动注入,消费端 AOP 自动提取。

Kafka:

<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-trace-kafka</artifactId>
<version>1.6.2</version>
</dependency>

发送端需额外注册拦截器(Kafka 的机制要求):

spring:
kafka:
producer:
properties:
interceptor.classes: com.sun.guardian.trace.kafka.interceptor.TraceKafkaProducerInterceptor

RocketMQ:

<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-trace-rocketmq</artifactId>
<version>1.6.2</version>
</dependency>

引依赖就完事了。发送端通过 SendMessageHook 自动注入。

注意:消费端 Listener 方法的参数类型必须使用消息原始类型(Message / ConsumerRecord / MessageExt),不能用 String,否则 AOP 切面无法从消息中提取 traceId。

批量消费场景

AOP 切面自动设置第一条消息的 traceId。批量消费中逐条切换 traceId,各模块提供了专用工具类,一行代码搞定:

// RabbitMQ 批量消费
@RabbitListener(queues = "myQueue", containerFactory = "batchContainerFactory")
public void handleBatch(List<Message> messages) {
for (Message msg : messages) {
TraceRabbitUtils.switchTraceId(msg);
// 业务逻辑…每条消息有各自的 traceId
}
}

// Kafka 批量消费
@KafkaListener(topics = "myTopic", batch = "true")
public void handleBatch(List<ConsumerRecord<String, String>> records) {
for (ConsumerRecord<String, String> record : records) {
TraceKafkaUtils.switchTraceId(record);
// 业务逻辑…
}
}

模块工具类用法
RabbitMQ TraceRabbitUtils TraceRabbitUtils.switchTraceId(message)
Kafka TraceKafkaUtils TraceKafkaUtils.switchTraceId(record)
RocketMQ TraceRocketMQUtils TraceRocketMQUtils.switchTraceId(messageExt)

工具类内部通过 TraceConfig 动态获取 headerName,支持配置中心热更新。

RocketMQ 说明:RocketMQListener 本身是逐条回调,AOP 切面自动为每次调用注入/清理 traceId,通常无需手动切换。

全量配置

guardian:
trace:
enabled: true # 总开关(默认 true)
filter-order: -30000 # Filter 排序(最先执行,覆盖全链路)
header-name: XTraceId # 请求头/响应头名称

整个 TraceId 的核心实现就一个文件:TraceIdFilter.java,不到 60 行,MDC + Filter 的经典用法。MQ 链路追踪的 AOP 切面也很精简,clone 下来看看挺有收获的。


七、IP 黑白名单(v1.6.0 新增)

什么场景需要?

运营后台只允许公司内网 IP 访问、某个爬虫 IP 需要封禁、特定接口只开放给合作方 IP —— 这些都是 IP 黑白名单的典型场景。

Nginx 可以做,但每次改了要 reload 配置。Gateway 也可以做,但不是所有项目都有网关。

Guardian 的 IP 黑白名单直接在应用层做,YAML 配置 + 配置中心动态刷新,不用重启、不用 reload,改了立即生效。

先看效果

<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-ip-filter-spring-boot-starter</artifactId>
<version>1.6.2</version>
</dependency>

guardian:
ip-filter:
enabled: true
black-list:
192.168.100.100 # 精确匹配
10.0.0.0/8 # CIDR 网段
urls:
pattern: /admin/**
white-list:
127.0.0.1 # 管理后台仅允许本机访问
192.168.1.0/24 # 或内网网段

两种模式:

  • 全局黑名单:匹配的 IP 拒绝访问所有接口
  • URL 绑定白名单:指定接口仅允许白名单 IP 访问,其余 IP 拒绝

匹配优先级:全局黑名单 > URL 绑定白名单 > 放行

IP 规则格式

格式示例说明
精确匹配 192.168.1.100 精确匹配单个 IP
通配符 192.168.1.* 匹配整个网段
CIDR 10.0.0.0/8 CIDR 网段匹配

全量配置

guardian:
ip-filter:
enabled: true # 总开关(默认 false,需显式开启)
response-mode: json # exception / json
log-enabled: true # 拦截日志
message: "IP 访问被拒绝" # 支持 i18n Key
filter-order: -20000 # Filter 排序
black-list:
192.168.100.100
10.0.0.0/8
urls:
pattern: /admin/**
white-list:
127.0.0.1

可观测性

  • 拦截日志:log-enabled: true,前缀 [Guardian-Ip-Filter]
  • Actuator:GET /actuator/guardianIpFilter

{
"totalBlackListBlockCount": 5,
"totalWhiteListBlockCount": 2,
"topBlackListBlocked": [
{ "ip": "192.168.100.100", "count": 3 }
],
"topWhiteListBlocked": [
{ "ip": "10.0.0.5", "uri": "/admin/dashboard", "count": 2 }
]
}


八、亮点特性

消息国际化(i18n)

防重、限流、幂等、IP 黑白名单的拒绝提示信息都支持 Spring MessageSource 国际化。message 字段可配置 i18n Key,根据 Accept-Language 请求头自动匹配语言:

guardian:
rate-limit:
urls:
pattern: /api/sms/send
message: guardian.ratelimit.rejected # i18n Key

# messages_zh_CN.properties
guardian.rate-limit.rejected=请求过于频繁,请稍后再试

# messages_en.properties
guardian.rate-limit.rejected=Rate limit exceeded, please try again later

不使用国际化的项目零感知——message 字段直接写中文也完全没问题。

配置中心动态刷新

所有模块的 YAML 配置均支持配置中心(Nacos / Apollo 等)动态刷新。修改配置后无需重启应用即可生效。

这意味着你可以在运行时动态调整限流 QPS、防重间隔、慢接口阈值、IP 黑白名单等——对于线上突发状况非常有用。


九、拦截器执行顺序

如果你同时用了多个模块,它们的执行顺序是确定的,通过各自的 order 配置控制,值越小越先执行:

顺序模块类型默认 order为什么这样排
1 链路追踪 Filter -30000 最先执行,确保全链路日志带 TraceId
2 IP 黑白名单 Filter -20000 尽早拦截恶意 IP
3 参数Trim Filter -10000 在业务 Filter 之前处理参数
4 请求体缓存 Filter -100 缓存 body,供防重和幂等读取
5 慢接口检测 Interceptor -1000 记录开始时间
6 限流 Interceptor 1000 先拦截超限流量
7 防重 Interceptor 2000 再判断是否重复请求
8 幂等 Interceptor 3000 最后消费 Token(不可逆)

幂等放最后是关键——Token 一旦消费就没了,如果先消费 Token 再被限流拒绝,这个 Token 就浪费了。

每个模块的 order 都可以通过 YAML 自定义。


十、存储方式

防重、限流、幂等三个模块支持两种存储:

RedisLocal
分布式 支持 仅单机
持久性 Redis 持久化 重启丢失
推荐场景 生产环境 开发/单体应用
额外依赖 需要 Redis

切换方式:

guardian:
repeat-submit:
storage: local # 或 redis
rate-limit:
storage: local
idempotent:
storage: local

本地缓存底层用 ConcurrentHashMap,带守护线程定期清理过期 Key,不会内存泄漏。


项目结构

guardian-parent
├── guardian-core # 公共基础(共享类)
├── guardian-repeat-submit/ # 防重复提交
│ ├── guardian-repeat-submit-core/
│ └── guardian-repeat-submit-spring-boot-starter/
├── guardian-rate-limit/ # 接口限流
│ ├── guardian-rate-limit-core/
│ └── guardian-rate-limit-spring-boot-starter/
├── guardian-idempotent/ # 接口幂等
│ ├── guardian-idempotent-core/
│ └── guardian-idempotent-spring-boot-starter/
├── guardian-auto-trim/ # 参数自动Trim
│ ├── guardian-auto-trim-core/
│ └── guardian-auto-trim-spring-boot-starter/
├── guardian-slow-api/ # 慢接口检测
│ ├── guardian-slow-api-core/
│ └── guardian-slow-api-spring-boot-starter/
├── guardian-trace/ # 请求链路追踪
│ ├── guardian-trace-core/
│ ├── guardian-trace-spring-boot-starter/
│ ├── guardian-trace-rabbitmq/ # RabbitMQ 链路追踪
│ ├── guardian-trace-kafka/ # Kafka 链路追踪
│ └── guardian-trace-rocketmq/ # RocketMQ 链路追踪
├── guardian-ip-filter/ # IP黑白名单
│ ├── guardian-ip-filter-core/
│ └── guardian-ip-filter-spring-boot-starter/
├── guardian-storage-redis/ # Redis 存储(多模块共享)
└── guardian-example/ # 示例工程

七个模块完全独立,用哪个引哪个,互不依赖。guardian-core 放公共类(UserContext、GuardianResponseHandler 等),guardian-storage-redis 是 Redis 存储的共享实现。

完整可运行的示例代码在 guardian-example 模块里,七个模块的各种场景都有,clone 下来直接跑。示例配置在 application.yml,里面每个配置项都有注释。


总结

Guardian v1.6.2 现在覆盖了七种 API 请求层防护场景:

功能解决什么问题Starter
防重复提交 用户手抖连点、表单重复提交 guardian-repeat-submit-spring-boot-starter
接口限流 恶意刷接口、突发流量 guardian-rate-limit-spring-boot-starter
接口幂等 支付回调重试、MQ 重复消费 guardian-idempotent-spring-boot-starter
参数自动Trim 前后空格、不可见字符导致数据异常 guardian-auto-trim-spring-boot-starter
慢接口检测 接口变慢无感知、缺少轻量监控 guardian-slow-api-spring-boot-starter
请求链路追踪 日志散乱无法串联、MQ 链路断裂 guardian-trace-spring-boot-starter
IP黑白名单 恶意 IP 封禁、管理后台访问控制 guardian-ip-filter-spring-boot-starter

如果你的 Spring Boot 项目需要这些能力,但又不想引 Sentinel 那么重的东西,可以试试。


Maven Central 坐标(最新 v1.6.2):

<!– 防重复提交 –>
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-repeat-submit-spring-boot-starter</artifactId>
<version>1.6.2</version>
</dependency>

<!– 接口限流 –>
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-rate-limit-spring-boot-starter</artifactId>
<version>1.6.2</version>
</dependency>

<!– 接口幂等 –>
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-idempotent-spring-boot-starter</artifactId>
<version>1.6.2</version>
</dependency>

<!– 参数自动Trim –>
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-auto-trim-spring-boot-starter</artifactId>
<version>1.6.2</version>
</dependency>

<!– 慢接口检测 –>
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-slow-api-spring-boot-starter</artifactId>
<version>1.6.2</version>
</dependency>

<!– 请求链路追踪 –>
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-trace-spring-boot-starter</artifactId>
<version>1.6.2</version>
</dependency>

<!– IP黑白名单 –>
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-ip-filter-spring-boot-starter</artifactId>
<version>1.6.2</version>
</dependency>

<!– MQ 链路追踪(按需选择) –>
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-trace-rabbitmq</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-trace-kafka</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-trace-rocketmq</artifactId>
<version>1.6.2</version>
</dependency>


项目地址(README 里有完整配置文档和更新日志):

  • GitHub:github.com/BigGG-Guardian/guardian
  • Gitee:gitee.com/BigGG-Guardian/guardian

整个项目源码不多,没有复杂的抽象层,每个模块核心代码就几个文件。如果你正在学 Spring Boot Starter 的封装思路,或者想看看 Filter / Interceptor / AOP / Lua 脚本 / MDC 这些东西在实际项目中怎么用的,clone 下来翻翻挺有收获的。

有问题直接提 Issue,我基本都会回。也欢迎 PR,一起把这个轮子打磨得更好。

觉得有用的话,去 GitHub 点个 Star 支持一下,这对开源作者真的很重要。

赞(0)
未经允许不得转载:171主机测评 » 七合一 Spring Boot API 防护框架:防重、限流、幂等、自动Trim、慢接口检测、链路追踪、IP黑白名单,一个 Starter 搞定
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址