闲来无事利用AI写了一个通用的“幂等 限流”Spring Boot Startercommon-guard想起来以前平时做接口开发最容易被忽视的两个点一个是“重复提交”另一个是“流量打爆”。前者让你出现重复扣款/重复写库/重复发券后者让你短信接口一天把预算烧光或者把系统拖到雪崩。我一直不太喜欢在每个业务里重复写一套“幂等校验 限流计数 Redis 脚本 异常兜底”也不喜欢大家各自写一份风格、策略、监控都不一致。于是就把这块抽成了一个通用 startercommon-guard。这篇文章介绍为什么做、怎么设计、怎么用、以及一些踩坑点。1. 为什么要做这个大多数项目里幂等和限流通常是“被动补齐”的线上出现了重复提交才去补一段“查 redis / 查数据库 / 加锁”的代码某个接口被刷才临时上 nginx 限速、或者在 controller 里加个计数器代码写完后没有统一的监控指标出了问题只能翻日志。common-guard 的目标是把这些事情“前置”和“标准化”业务侧接入简单注解一贴就生效策略可控key 怎么生成、异常怎么处理、存储故障怎么兜底存储可插拔单机/开发环境可以本地缓存线上集群用 Redis可观测知道重复提交多少、拒绝多少、存储异常多少2. 这个项目提供了什么核心能力两件事幂等Idempotent原子抢占一个 key带 TTL抢占失败说明重复请求走 handler 或直接抛异常业务异常时可选释放 key允许重试限流RateLimit固定窗口或令牌桶支持 fallback 方法降级额外能力SpEL Key 解析但做了安全限制预置安全变量token/header/param/ip/app写 key 更省事Micrometer 指标接 Prometheus/Grafana 很方便可选 key 日志排障时输出 key默认关闭支持脱敏3. 设计思路别让 SpEL 变成安全坑很多人用 SpEL 生成 key 的第一反应是user: #request.getHeader(Authorization)问题是一旦放开方法调用、类型引用、构造器SpEL 很容易从“拼 key”变成“执行表达式”安全风险非常大。common-guard 的做法是禁用类型引用 / 构造器 / 方法调用只允许“读变量/属性”把常用的请求信息提前采集成安全快照作为变量注入给表达式用所以你可以直接写cfg: token或者order: header[x-user-id] : #req.orderNo不用在表达式里写复杂逻辑也不用担心“表达式能干出你预料之外的事”。4. 使用方式推荐只引一个 starter如果你是“下载源码后直接用”或者你们公司网络环境无法直接拉取 GitHub Packages/私服可以先把本项目安装到你本机的~/.m2/repository再在业务项目里按普通 Maven 依赖引入。在本项目根目录执行mvn cleaninstall-DskipTests安装成功后本地仓库会出现例如~/.m2/repository/com/yourcompany/common-guard-spring-boot-starter/1.0.0-SNAPSHOT/~/.m2/repository/com/yourcompany/common-guard-autoconfigure/1.0.0-SNAPSHOT/4.1 引入依赖dependencygroupIdcom.yourcompany/groupIdartifactIdcommon-guard-spring-boot-starter/artifactIdversion1.0.0-SNAPSHOT/version/dependency然后根据你用的 store 再补一条依赖本地模式CaffeinedependencygroupIdcom.github.ben-manes.caffeine/groupIdartifactIdcaffeine/artifactId/dependency分布式模式Redisson / Redis建议只引入 redisson 核心库并自己提供RedissonClient避免跟现有 redis 配置冲突dependencygroupIdorg.redisson/groupIdartifactIdredisson/artifactId/dependency4.2 基础配置common:guard:enabled:truestore:auto# auto/local/redisson5. 写几个最常用的例子5.1 防重复提交基于 tokenIdempotent(keycfg: token,expire3,timeUnitTimeUnit.SECONDS,message请勿重复提交)PostMapping(/config)publicStringsaveConfig(){returnOK;}5.2 幂等基于 header 业务单号publicrecordOrderReq(StringorderNo){}Idempotent(keyorder: header[x-user-id] : #req.orderNo,bizNo#req.orderNo,expire30)PostMapping(/order)publicStringcreate(RequestBodyOrderReqreq){returnOK;}这里的bizNo只是为了日志/排障定位不影响幂等判定。5.3 短信限流固定窗口RateLimit(keysms: ip,limit5,window60,timeUnitTimeUnit.SECONDS)GetMapping(/send-sms)publicStringsendSms(){returnOK;}含义同一个 key 在 60 秒窗口内最多 5 次。5.4 限流 fallback 降级RateLimit(keyquery: ip,limit3,window10,fallbackqueryFallback)GetMapping(/query)publicStringquery(){returnOK;}publicStringqueryFallback(){returnTOO_MANY_REQUESTS;}6. 一些实际使用建议key 的设计尽量稳定且可解释推荐userId 业务单号。不要用随机值也不要让 key “可能为空”。集群部署优先 Redisson本地缓存适合开发环境或单机服务多实例要一致性就用 Redis。存储异常的兜底策略要想清楚默认是“存储异常时放行”避免 Redis 抖一下就把业务全拒了。key 日志默认别开排障时开一会儿可以但不要长期输出原始 key尤其 key 里拼了 token 的场景。7. 写在最后common-guard 不是为了“炫技”就是想把幂等与限流这两件事做成能复用可观测不踩安全坑业务侧接入成本低如果你也遇到过重复提交/接口被刷/线上限流策略不统一的问题这类 starter 能省掉不少“每个项目重复造轮子”的成本。项目更细的说明与更多场景示例见GITHUB这样以后就不用重复造了。直接用就很爽
gurad幂等工具
发布时间:2026/5/22 1:49:54
闲来无事利用AI写了一个通用的“幂等 限流”Spring Boot Startercommon-guard想起来以前平时做接口开发最容易被忽视的两个点一个是“重复提交”另一个是“流量打爆”。前者让你出现重复扣款/重复写库/重复发券后者让你短信接口一天把预算烧光或者把系统拖到雪崩。我一直不太喜欢在每个业务里重复写一套“幂等校验 限流计数 Redis 脚本 异常兜底”也不喜欢大家各自写一份风格、策略、监控都不一致。于是就把这块抽成了一个通用 startercommon-guard。这篇文章介绍为什么做、怎么设计、怎么用、以及一些踩坑点。1. 为什么要做这个大多数项目里幂等和限流通常是“被动补齐”的线上出现了重复提交才去补一段“查 redis / 查数据库 / 加锁”的代码某个接口被刷才临时上 nginx 限速、或者在 controller 里加个计数器代码写完后没有统一的监控指标出了问题只能翻日志。common-guard 的目标是把这些事情“前置”和“标准化”业务侧接入简单注解一贴就生效策略可控key 怎么生成、异常怎么处理、存储故障怎么兜底存储可插拔单机/开发环境可以本地缓存线上集群用 Redis可观测知道重复提交多少、拒绝多少、存储异常多少2. 这个项目提供了什么核心能力两件事幂等Idempotent原子抢占一个 key带 TTL抢占失败说明重复请求走 handler 或直接抛异常业务异常时可选释放 key允许重试限流RateLimit固定窗口或令牌桶支持 fallback 方法降级额外能力SpEL Key 解析但做了安全限制预置安全变量token/header/param/ip/app写 key 更省事Micrometer 指标接 Prometheus/Grafana 很方便可选 key 日志排障时输出 key默认关闭支持脱敏3. 设计思路别让 SpEL 变成安全坑很多人用 SpEL 生成 key 的第一反应是user: #request.getHeader(Authorization)问题是一旦放开方法调用、类型引用、构造器SpEL 很容易从“拼 key”变成“执行表达式”安全风险非常大。common-guard 的做法是禁用类型引用 / 构造器 / 方法调用只允许“读变量/属性”把常用的请求信息提前采集成安全快照作为变量注入给表达式用所以你可以直接写cfg: token或者order: header[x-user-id] : #req.orderNo不用在表达式里写复杂逻辑也不用担心“表达式能干出你预料之外的事”。4. 使用方式推荐只引一个 starter如果你是“下载源码后直接用”或者你们公司网络环境无法直接拉取 GitHub Packages/私服可以先把本项目安装到你本机的~/.m2/repository再在业务项目里按普通 Maven 依赖引入。在本项目根目录执行mvn cleaninstall-DskipTests安装成功后本地仓库会出现例如~/.m2/repository/com/yourcompany/common-guard-spring-boot-starter/1.0.0-SNAPSHOT/~/.m2/repository/com/yourcompany/common-guard-autoconfigure/1.0.0-SNAPSHOT/4.1 引入依赖dependencygroupIdcom.yourcompany/groupIdartifactIdcommon-guard-spring-boot-starter/artifactIdversion1.0.0-SNAPSHOT/version/dependency然后根据你用的 store 再补一条依赖本地模式CaffeinedependencygroupIdcom.github.ben-manes.caffeine/groupIdartifactIdcaffeine/artifactId/dependency分布式模式Redisson / Redis建议只引入 redisson 核心库并自己提供RedissonClient避免跟现有 redis 配置冲突dependencygroupIdorg.redisson/groupIdartifactIdredisson/artifactId/dependency4.2 基础配置common:guard:enabled:truestore:auto# auto/local/redisson5. 写几个最常用的例子5.1 防重复提交基于 tokenIdempotent(keycfg: token,expire3,timeUnitTimeUnit.SECONDS,message请勿重复提交)PostMapping(/config)publicStringsaveConfig(){returnOK;}5.2 幂等基于 header 业务单号publicrecordOrderReq(StringorderNo){}Idempotent(keyorder: header[x-user-id] : #req.orderNo,bizNo#req.orderNo,expire30)PostMapping(/order)publicStringcreate(RequestBodyOrderReqreq){returnOK;}这里的bizNo只是为了日志/排障定位不影响幂等判定。5.3 短信限流固定窗口RateLimit(keysms: ip,limit5,window60,timeUnitTimeUnit.SECONDS)GetMapping(/send-sms)publicStringsendSms(){returnOK;}含义同一个 key 在 60 秒窗口内最多 5 次。5.4 限流 fallback 降级RateLimit(keyquery: ip,limit3,window10,fallbackqueryFallback)GetMapping(/query)publicStringquery(){returnOK;}publicStringqueryFallback(){returnTOO_MANY_REQUESTS;}6. 一些实际使用建议key 的设计尽量稳定且可解释推荐userId 业务单号。不要用随机值也不要让 key “可能为空”。集群部署优先 Redisson本地缓存适合开发环境或单机服务多实例要一致性就用 Redis。存储异常的兜底策略要想清楚默认是“存储异常时放行”避免 Redis 抖一下就把业务全拒了。key 日志默认别开排障时开一会儿可以但不要长期输出原始 key尤其 key 里拼了 token 的场景。7. 写在最后common-guard 不是为了“炫技”就是想把幂等与限流这两件事做成能复用可观测不踩安全坑业务侧接入成本低如果你也遇到过重复提交/接口被刷/线上限流策略不统一的问题这类 starter 能省掉不少“每个项目重复造轮子”的成本。项目更细的说明与更多场景示例见GITHUB这样以后就不用重复造了。直接用就很爽