前言事情是这样的前段时间在公司项目里又写了一遍防重复提交的逻辑——Redis 加锁、拼 Key、设过期时间、处理异常释放锁……写到一半我就烦了这套东西每个项目都要来一遍而且每次写法还不太一样维护起来头大。限流也是要么上 Sentinel 搞一套要么自己写个拦截器糊一个代码散得到处都是。想了想干脆自己封一个 Starter。搞着搞着就把防重复提交和接口限流都做了打包发到 Maven Central 开源了。项目叫 Guardian一个轻量级的 Spring Boot API 请求层防护框架目前 v1.3.0。两个功能完全独立用哪个引哪个互不依赖。GitHubhttps://github.com/BigGG-Guardian/guardianMaven Centralio.github.biggg-guardian一、防重复提交先看效果三步搞定第一步引依赖dependency groupIdio.github.biggg-guardian/groupId artifactIdguardian-repeat-submit-spring-boot-starter/artifactId version1.3.0/version /dependency第二步加注解PostMapping(/submit) RepeatSubmit(interval 10, message 订单正在处理请勿重复提交) public Result submitOrder(RequestBody OrderDTO order){ return orderService.submit(order); }第三步没了。启动项目就生效了。10 秒内同一个用户、同一个接口、同样的请求参数第二次请求会被直接拦截。为什么不直接用 Redis 加个锁你肯定想说这不就是 Redis setnx 嘛我自己写也行。确实能写但你想想实际项目里会遇到的问题1. Key 怎么拼userId url够不够如果同一个用户对同一个接口传了不同的参数呢比如下单接口买商品 A 和买商品 B 应该算两次不同的请求不能拦截。所以防重 Key 要把请求参数也算进去。但 POST 请求的 body 是个流读了一次就没了你还得处理HttpServletRequestWrapper的问题。Guardian 内置了RepeatableRequestFilter自动缓存请求体Key 生成时会把请求参数做 JSON 序列化 Base64 编码拼进去。2. 用户没登录怎么办很多防重方案直接用 userId 作为 Key 的一部分但用户没登录的时候 userId 是 nullKey 就乱了。Guardian 的处理是已登录用 userId → 没登录用 sessionId → 没 session 用客户端 IP。三级降级永远不会出现 null。3. 业务异常了锁不释放怎么办比如用户提交订单业务代码报了个异常但防重锁已经设了 10 秒。结果用户修正数据重新提交被告知请勿重复提交——这体验就很差了。Guardian 在拦截器的afterCompletion里做了处理如果请求抛了异常自动释放锁。正常完成的请求才让锁自然过期。4. 有些接口不需要防重怎么办全局配了防重之后健康检查接口、公开查询接口这些也被拦了。你要么给每个接口单独控制要么维护一个白名单。Guardian 支持exclude-urls白名单AntPath 通配符匹配优先级最高。命中直接放行不走任何防重逻辑。注解不够用试试 YAML 批量配置单个接口用注解挺方便但如果你有 50 个接口都要配防重一个一个加注解就有点累了。Guardian 支持在 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全局比如短信发送接口配key-scope: ip同一个 IP 60 秒内只能发一次不管登没登录、哪个用户——这比按用户维度更合理。拦截了之后怎么响应这是我纠结了挺久的一个设计点。一开始只做了抛异常的方式——拦截后抛RepeatSubmitException让业务端的全局异常处理器去处理。但后来想到有些项目可能就想开箱即用不想为了一个防重还得写个异常处理器。所以做了两种模式guardian: repeat-submit: response-mode:exception # 默认抛异常 # response-mode: json # 直接返回 JSONexception 模式默认抛RepeatSubmitException你在全局异常处理器里接一下RestControllerAdvice publicclassGlobalExceptionHandler{ ExceptionHandler(RepeatSubmitException.class) publicResulthandleRepeatSubmit(RepeatSubmitExceptione) { return Result.fail(e.getMessage()); } }json 模式拦截器直接写 JSON 响应默认格式是{code:500,msg:...,timestamp:...}。格式不满意注册一个RepeatSubmitResponseHandlerBean 就能覆盖Bean public RepeatSubmitResponseHandler repeatSubmitResponseHandler(){ return (request, response, message) - { response.setContentType(application/json;charsetUTF-8); response.getWriter().write(JSONUtil.toJsonStr(R.fail(message))); }; }不用 Redis 也能跑不是每个项目都有 Redis 的。本地开发环境、小型单体应用可能就没有 Redis。guardian: repeat-submit: storage:local # 用本地缓存切成local就行了底层用ConcurrentHashMap实现带定时过期清理。当然生产环境还是推荐 Redis支持分布式。关于 context-path 的坑这个坑我自己踩过。项目配了server.servlet.context-path: /admin-api然后 YAML 里配的 URL 规则死活匹配不上。排查了一下发现request.getRequestURI()返回的是带 context-path 的完整路径比如/admin-api/order/submit但 YAML 里配的可能是/order/submit。Guardian 的处理是匹配时同时尝试完整 URI 和去掉 context-path 后的路径两者有一个匹配上就算命中。所以不管你 YAML 里写的是/order/submit还是/admin-api/order/submit都能正确匹配。防重的内部流程简单画一下请求的处理流程请求进入 │ ▼ RepeatableRequestFilter ← 缓存请求体支持重复读取 │ ▼ RepeatSubmitInterceptor ├─ 1. 匹配白名单 → 命中直接放行 ├─ 2. 匹配 YAML 规则 ├─ 3. 检查 RepeatSubmit 注解 │ 均未命中 → 放行 ▼ KeyGenerator ← 按维度user/ip/global生成防重 Key │ ▼ KeyEncrypt ← 可选 MD5 加密 │ ▼ Storage.tryAcquire() ├─ 成功 → 放行写入存储 设置 TTL └─ 失败 → 根据 response-mode 响应 ├─ exception → 抛 RepeatSubmitException └─ json → 直接写 JSON 响应 │ ▼ 业务执行 ├─ 正常 → Key 自然过期 └─ 异常 → afterCompletion 自动释放二、接口限流为什么要做限流防重复提交解决的是同一个请求短时间内被提交多次的问题但还有另一类问题它管不了恶意刷接口。比如有人写个脚本一秒钟请求你的搜索接口 1000 次防重拦不住因为每次参数可能不一样这时候就需要限流了。市面上的限流方案不少但要么是网关级别的Sentinel、Spring Cloud Gateway要么得写一堆配置。如果你就是个普通的 Spring Boot 单体应用想给几个接口加个限流没必要引那么重的东西。Guardian 的限流就是冲着这个场景来的轻量、注解 YAML 双模式、两种算法可选。先看效果dependency groupIdio.github.biggg-guardian/groupId artifactIdguardian-rate-limit-spring-boot-starter/artifactId version1.3.0/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/**和防重一样注解 YAML 双模式YAML 优先级高于注解白名单优先级最高。滑动窗口 vs 令牌桶这是限流最常用的两种算法Guardian 都支持。滑动窗口统计时间窗口内的请求次数超了就拒绝。比如配了qps10, window1s就是每秒最多 10 次多了直接打回。RateLimit(qps 10)特点是严格。窗口内绝对不会超过阈值。适合短信发送、登录尝试这种需要精确控制频率的场景。令牌桶桶里装令牌按固定速率往里放请求来了取一个桶空了就拒绝。桶满时可以一口气把令牌全用完。RateLimit(qps 5, capacity 20, algorithm RateLimitAlgorithm.TOKEN_BUCKET)这个配置的意思是每秒补 5 个令牌桶最多攒 20 个。平时空闲的时候令牌慢慢攒突然来一波流量瞬间可以放过 20 个请求打完之后回到每秒 5 个的稳态。特点是允许突发。适合秒杀、抢购这种平时没啥流量偶尔来一波高峰的场景。举个直观的例子都是qps10突然来了 20 个请求滑动窗口令牌桶capacity20第 1-10 个通过通过第 11-20 个全部拒绝全部通过之后每秒最多 10 个最多 10 个补充速率怎么控制令牌桶的补充速率通过qps和window两个参数控制qps10, window1s→ 每秒补 10 个qps10, window1min→ 每分钟补 10 个约 6 秒补 1 个// 每分钟补 10 个令牌桶容量 10 RateLimit(qps 10, window 1, windowUnit TimeUnit.MINUTES, capacity 10, algorithm RateLimitAlgorithm.TOKEN_BUCKET)这样就能实现慢速补充的场景。限流维度和防重一样限流也支持三种维度维度效果典型场景GLOBAL默认整个接口共用一个计数器全站搜索接口IP每个 IP 独立计数短信发送、验证码USER每个用户独立计数用户操作频率限制RateLimit(qps 1, rateLimitScope RateLimitKeyScope.IP, message 短信发送过于频繁)限流的响应处理和防重一样两种模式guardian: rate-limit: response-mode:exception # 默认抛 RateLimitException # response-mode: json # 直接返回 JSON也支持自定义响应处理器注册一个RateLimitResponseHandlerBean 就行。三、一些设计细节并发安全限流对并发安全的要求比防重高。你想10 个请求同时进来限流阈值是 5如果并发控制没做好可能 10 个都放过去了。Guardian 的处理Redis滑动窗口和令牌桶都用Lua 脚本Redis 单线程执行 Lua 是天然原子的本地缓存synchronized锁到 Key 粒度不同 Key 之间互不阻塞防重那边也是一样Redis 用SET NX EX原子操作本地缓存用ConcurrentHashMap的原子方法。本地缓存的内存管理用ConcurrentHashMap做本地存储有个容易忽略的问题Key 只进不出长时间运行内存会一直涨。Guardian 在防重和限流的本地存储里都加了守护线程每 5 分钟扫一次清理过期的 Key。线程是 daemon 的不会阻止 JVM 关闭。可插拔架构两个模块的核心组件都是面向接口编程的框架内部用ConditionalOnMissingBean做的你不注册就用默认的注册了就用你的组件防重复提交接口限流Key 生成RepeatSubmitKeyGeneratorRateLimitKeyGeneratorKey 加密AbstractKeyEncryptAbstractKeyEncrypt存储RepeatSubmitStorageRateLimitStorage响应处理RepeatSubmitResponseHandlerRateLimitResponseHandler用户上下文UserContext共享UserContext共享可观测性两个模块都内置了监控能力拦截日志log-enabled: true开启后拦截/放行都有日志输出。Actuator 端点GET /actuator/guardian-repeat-submit → 防重统计 GET /actuator/guardian-rate-limit → 限流统计限流的统计数据长这样{ totalRequestCount: 5560, totalPassCount: 5432, totalBlockCount: 128, blockRate: 2.30%, topBlockedApis: { /api/sms/send: 56 }, topRequestApis: { /api/search: 3200 } }项目结构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-storage-redis/ # Redis 存储多模块共享 └── guardian-example/ # 示例工程模块拆分是为了灵活组合。比如你只需要防重就引guardian-repeat-submit-spring-boot-starter只需要限流就引guardian-rate-limit-spring-boot-starter都需要就两个都引互不影响。guardian-core放的是两个模块都用到的公共类比如UserContext、GuardianResponseHandler。guardian-storage-redis是 Redis 存储的共享实现两个模块的 Redis 存储都在这里面。总结Guardian 做了两件事防重复提交拦截短时间内的重复请求支持注解 / YAML、用户 / IP / 全局维度、异常自动释放、context-path 兼容接口限流控制接口访问频率支持滑动窗口 / 令牌桶、突发流量处理、三种维度两个功能独立 Starter核心组件全部可插拔注册 Bean 就能替换默认实现。Redis 和本地缓存一键切换。如果你的 Spring Boot 项目里需要这些能力但又不想引 Sentinel 那么重的东西可以试试。Maven Central 坐标!-- 防重复提交 -- dependency groupIdio.github.biggg-guardian/groupId artifactIdguardian-repeat-submit-spring-boot-starter/artifactId version1.3.0/version /dependency !-- 接口限流 -- dependency groupIdio.github.biggg-guardian/groupId artifactIdguardian-rate-limit-spring-boot-starter/artifactId version1.3.0/version /dependency
SpringBoot接口防护神器(防重+限流)
前言事情是这样的前段时间在公司项目里又写了一遍防重复提交的逻辑——Redis 加锁、拼 Key、设过期时间、处理异常释放锁……写到一半我就烦了这套东西每个项目都要来一遍而且每次写法还不太一样维护起来头大。限流也是要么上 Sentinel 搞一套要么自己写个拦截器糊一个代码散得到处都是。想了想干脆自己封一个 Starter。搞着搞着就把防重复提交和接口限流都做了打包发到 Maven Central 开源了。项目叫 Guardian一个轻量级的 Spring Boot API 请求层防护框架目前 v1.3.0。两个功能完全独立用哪个引哪个互不依赖。GitHubhttps://github.com/BigGG-Guardian/guardianMaven Centralio.github.biggg-guardian一、防重复提交先看效果三步搞定第一步引依赖dependency groupIdio.github.biggg-guardian/groupId artifactIdguardian-repeat-submit-spring-boot-starter/artifactId version1.3.0/version /dependency第二步加注解PostMapping(/submit) RepeatSubmit(interval 10, message 订单正在处理请勿重复提交) public Result submitOrder(RequestBody OrderDTO order){ return orderService.submit(order); }第三步没了。启动项目就生效了。10 秒内同一个用户、同一个接口、同样的请求参数第二次请求会被直接拦截。为什么不直接用 Redis 加个锁你肯定想说这不就是 Redis setnx 嘛我自己写也行。确实能写但你想想实际项目里会遇到的问题1. Key 怎么拼userId url够不够如果同一个用户对同一个接口传了不同的参数呢比如下单接口买商品 A 和买商品 B 应该算两次不同的请求不能拦截。所以防重 Key 要把请求参数也算进去。但 POST 请求的 body 是个流读了一次就没了你还得处理HttpServletRequestWrapper的问题。Guardian 内置了RepeatableRequestFilter自动缓存请求体Key 生成时会把请求参数做 JSON 序列化 Base64 编码拼进去。2. 用户没登录怎么办很多防重方案直接用 userId 作为 Key 的一部分但用户没登录的时候 userId 是 nullKey 就乱了。Guardian 的处理是已登录用 userId → 没登录用 sessionId → 没 session 用客户端 IP。三级降级永远不会出现 null。3. 业务异常了锁不释放怎么办比如用户提交订单业务代码报了个异常但防重锁已经设了 10 秒。结果用户修正数据重新提交被告知请勿重复提交——这体验就很差了。Guardian 在拦截器的afterCompletion里做了处理如果请求抛了异常自动释放锁。正常完成的请求才让锁自然过期。4. 有些接口不需要防重怎么办全局配了防重之后健康检查接口、公开查询接口这些也被拦了。你要么给每个接口单独控制要么维护一个白名单。Guardian 支持exclude-urls白名单AntPath 通配符匹配优先级最高。命中直接放行不走任何防重逻辑。注解不够用试试 YAML 批量配置单个接口用注解挺方便但如果你有 50 个接口都要配防重一个一个加注解就有点累了。Guardian 支持在 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全局比如短信发送接口配key-scope: ip同一个 IP 60 秒内只能发一次不管登没登录、哪个用户——这比按用户维度更合理。拦截了之后怎么响应这是我纠结了挺久的一个设计点。一开始只做了抛异常的方式——拦截后抛RepeatSubmitException让业务端的全局异常处理器去处理。但后来想到有些项目可能就想开箱即用不想为了一个防重还得写个异常处理器。所以做了两种模式guardian: repeat-submit: response-mode:exception # 默认抛异常 # response-mode: json # 直接返回 JSONexception 模式默认抛RepeatSubmitException你在全局异常处理器里接一下RestControllerAdvice publicclassGlobalExceptionHandler{ ExceptionHandler(RepeatSubmitException.class) publicResulthandleRepeatSubmit(RepeatSubmitExceptione) { return Result.fail(e.getMessage()); } }json 模式拦截器直接写 JSON 响应默认格式是{code:500,msg:...,timestamp:...}。格式不满意注册一个RepeatSubmitResponseHandlerBean 就能覆盖Bean public RepeatSubmitResponseHandler repeatSubmitResponseHandler(){ return (request, response, message) - { response.setContentType(application/json;charsetUTF-8); response.getWriter().write(JSONUtil.toJsonStr(R.fail(message))); }; }不用 Redis 也能跑不是每个项目都有 Redis 的。本地开发环境、小型单体应用可能就没有 Redis。guardian: repeat-submit: storage:local # 用本地缓存切成local就行了底层用ConcurrentHashMap实现带定时过期清理。当然生产环境还是推荐 Redis支持分布式。关于 context-path 的坑这个坑我自己踩过。项目配了server.servlet.context-path: /admin-api然后 YAML 里配的 URL 规则死活匹配不上。排查了一下发现request.getRequestURI()返回的是带 context-path 的完整路径比如/admin-api/order/submit但 YAML 里配的可能是/order/submit。Guardian 的处理是匹配时同时尝试完整 URI 和去掉 context-path 后的路径两者有一个匹配上就算命中。所以不管你 YAML 里写的是/order/submit还是/admin-api/order/submit都能正确匹配。防重的内部流程简单画一下请求的处理流程请求进入 │ ▼ RepeatableRequestFilter ← 缓存请求体支持重复读取 │ ▼ RepeatSubmitInterceptor ├─ 1. 匹配白名单 → 命中直接放行 ├─ 2. 匹配 YAML 规则 ├─ 3. 检查 RepeatSubmit 注解 │ 均未命中 → 放行 ▼ KeyGenerator ← 按维度user/ip/global生成防重 Key │ ▼ KeyEncrypt ← 可选 MD5 加密 │ ▼ Storage.tryAcquire() ├─ 成功 → 放行写入存储 设置 TTL └─ 失败 → 根据 response-mode 响应 ├─ exception → 抛 RepeatSubmitException └─ json → 直接写 JSON 响应 │ ▼ 业务执行 ├─ 正常 → Key 自然过期 └─ 异常 → afterCompletion 自动释放二、接口限流为什么要做限流防重复提交解决的是同一个请求短时间内被提交多次的问题但还有另一类问题它管不了恶意刷接口。比如有人写个脚本一秒钟请求你的搜索接口 1000 次防重拦不住因为每次参数可能不一样这时候就需要限流了。市面上的限流方案不少但要么是网关级别的Sentinel、Spring Cloud Gateway要么得写一堆配置。如果你就是个普通的 Spring Boot 单体应用想给几个接口加个限流没必要引那么重的东西。Guardian 的限流就是冲着这个场景来的轻量、注解 YAML 双模式、两种算法可选。先看效果dependency groupIdio.github.biggg-guardian/groupId artifactIdguardian-rate-limit-spring-boot-starter/artifactId version1.3.0/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/**和防重一样注解 YAML 双模式YAML 优先级高于注解白名单优先级最高。滑动窗口 vs 令牌桶这是限流最常用的两种算法Guardian 都支持。滑动窗口统计时间窗口内的请求次数超了就拒绝。比如配了qps10, window1s就是每秒最多 10 次多了直接打回。RateLimit(qps 10)特点是严格。窗口内绝对不会超过阈值。适合短信发送、登录尝试这种需要精确控制频率的场景。令牌桶桶里装令牌按固定速率往里放请求来了取一个桶空了就拒绝。桶满时可以一口气把令牌全用完。RateLimit(qps 5, capacity 20, algorithm RateLimitAlgorithm.TOKEN_BUCKET)这个配置的意思是每秒补 5 个令牌桶最多攒 20 个。平时空闲的时候令牌慢慢攒突然来一波流量瞬间可以放过 20 个请求打完之后回到每秒 5 个的稳态。特点是允许突发。适合秒杀、抢购这种平时没啥流量偶尔来一波高峰的场景。举个直观的例子都是qps10突然来了 20 个请求滑动窗口令牌桶capacity20第 1-10 个通过通过第 11-20 个全部拒绝全部通过之后每秒最多 10 个最多 10 个补充速率怎么控制令牌桶的补充速率通过qps和window两个参数控制qps10, window1s→ 每秒补 10 个qps10, window1min→ 每分钟补 10 个约 6 秒补 1 个// 每分钟补 10 个令牌桶容量 10 RateLimit(qps 10, window 1, windowUnit TimeUnit.MINUTES, capacity 10, algorithm RateLimitAlgorithm.TOKEN_BUCKET)这样就能实现慢速补充的场景。限流维度和防重一样限流也支持三种维度维度效果典型场景GLOBAL默认整个接口共用一个计数器全站搜索接口IP每个 IP 独立计数短信发送、验证码USER每个用户独立计数用户操作频率限制RateLimit(qps 1, rateLimitScope RateLimitKeyScope.IP, message 短信发送过于频繁)限流的响应处理和防重一样两种模式guardian: rate-limit: response-mode:exception # 默认抛 RateLimitException # response-mode: json # 直接返回 JSON也支持自定义响应处理器注册一个RateLimitResponseHandlerBean 就行。三、一些设计细节并发安全限流对并发安全的要求比防重高。你想10 个请求同时进来限流阈值是 5如果并发控制没做好可能 10 个都放过去了。Guardian 的处理Redis滑动窗口和令牌桶都用Lua 脚本Redis 单线程执行 Lua 是天然原子的本地缓存synchronized锁到 Key 粒度不同 Key 之间互不阻塞防重那边也是一样Redis 用SET NX EX原子操作本地缓存用ConcurrentHashMap的原子方法。本地缓存的内存管理用ConcurrentHashMap做本地存储有个容易忽略的问题Key 只进不出长时间运行内存会一直涨。Guardian 在防重和限流的本地存储里都加了守护线程每 5 分钟扫一次清理过期的 Key。线程是 daemon 的不会阻止 JVM 关闭。可插拔架构两个模块的核心组件都是面向接口编程的框架内部用ConditionalOnMissingBean做的你不注册就用默认的注册了就用你的组件防重复提交接口限流Key 生成RepeatSubmitKeyGeneratorRateLimitKeyGeneratorKey 加密AbstractKeyEncryptAbstractKeyEncrypt存储RepeatSubmitStorageRateLimitStorage响应处理RepeatSubmitResponseHandlerRateLimitResponseHandler用户上下文UserContext共享UserContext共享可观测性两个模块都内置了监控能力拦截日志log-enabled: true开启后拦截/放行都有日志输出。Actuator 端点GET /actuator/guardian-repeat-submit → 防重统计 GET /actuator/guardian-rate-limit → 限流统计限流的统计数据长这样{ totalRequestCount: 5560, totalPassCount: 5432, totalBlockCount: 128, blockRate: 2.30%, topBlockedApis: { /api/sms/send: 56 }, topRequestApis: { /api/search: 3200 } }项目结构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-storage-redis/ # Redis 存储多模块共享 └── guardian-example/ # 示例工程模块拆分是为了灵活组合。比如你只需要防重就引guardian-repeat-submit-spring-boot-starter只需要限流就引guardian-rate-limit-spring-boot-starter都需要就两个都引互不影响。guardian-core放的是两个模块都用到的公共类比如UserContext、GuardianResponseHandler。guardian-storage-redis是 Redis 存储的共享实现两个模块的 Redis 存储都在这里面。总结Guardian 做了两件事防重复提交拦截短时间内的重复请求支持注解 / YAML、用户 / IP / 全局维度、异常自动释放、context-path 兼容接口限流控制接口访问频率支持滑动窗口 / 令牌桶、突发流量处理、三种维度两个功能独立 Starter核心组件全部可插拔注册 Bean 就能替换默认实现。Redis 和本地缓存一键切换。如果你的 Spring Boot 项目里需要这些能力但又不想引 Sentinel 那么重的东西可以试试。Maven Central 坐标!-- 防重复提交 -- dependency groupIdio.github.biggg-guardian/groupId artifactIdguardian-repeat-submit-spring-boot-starter/artifactId version1.3.0/version /dependency !-- 接口限流 -- dependency groupIdio.github.biggg-guardian/groupId artifactIdguardian-rate-limit-spring-boot-starter/artifactId version1.3.0/version /dependency