news 2026/5/19 4:25:16

通用幂等与防重就该这么实现!SpringBoot + Redis 打造一个生产级中间件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
通用幂等与防重就该这么实现!SpringBoot + Redis 打造一个生产级中间件

GitHub:https://github.com/songrongzhen/OnceKit

技术栈:Spring Boot 3.0 + JDK 17 + Spring AOP + Redis + Lua +SpEL

目标:开箱即用、生产就绪、注解驱动、支持高并发防重场景

一、为什么要做这个中间件?

1.1 痛点场景
  • 用户点击“提交订单”按钮多次→ 生成多笔订单

  • 网络超时重试→ 后端重复处理支付回调

  • MQ 消息重复投递→ 账户余额被多次扣减

  • 考生重复提交报名信息→ 数据库出现多条相同身份证记录

这些都违反了 幂等性(Idempotency)原则:同一操作无论执行多少次,结果应一致。

1.2 现有方案的问题

方案

缺点

数据库唯一索引

仅适用于写入场景,无法防“并发穿透”

前端按钮禁用

不可靠(可绕过)

Token 机制

需前后端配合,增加复杂度

手动写 Redis

重复代码多,维护成本高

于是,我决定:用 AOP + 注解 + Redis,打造一个通用、轻量、高性能的幂等中间件。

二、整体架构设计

2.1 系统架构图

整个过程在毫秒级完成,且无数据库压力

2.2 核心组件

组件

职责

@Idempotent

自定义注解,声明幂等规则

IdempotentAspect

AOP 切面,拦截带注解的方法

SpelKeyGenerator

使用 Spring SpEL 动态生成唯一 Key

RedisIdempotentStore

基于 Redis 实现原子校验

IdempotentFailureHandler

自定义重复请求处理策略

三、核心代码实现

3.1 注解定义
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Idempotent { String key(); int expire() default 300; String value() default "1"; }
3.2 AOP 切面逻辑
@Aspect publicclass IdempotentAspect { privatefinal IdempotentService idempotentService; privatefinal ExpressionParser parser = new SpelExpressionParser(); privatefinal StandardReflectionParameterNameDiscoverer discoverer = new StandardReflectionParameterNameDiscoverer(); public IdempotentAspect(IdempotentService idempotentService) { this.idempotentService = idempotentService; } @Around("@annotation(idempotent)") public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String[] paramNames = discoverer.getParameterNames(signature.getMethod()); Object[] args = joinPoint.getArgs(); // 解析 SpEL key StandardEvaluationContext context = new StandardEvaluationContext(); for (int i = 0; i < args.length; i++) { context.setVariable(paramNames[i], args[i]); } String key = parser.parseExpression(idempotent.key()).getValue(context, String.class); if (!idempotentService.tryLock(key, idempotent.expire())) { if (idempotent.mode() == Idempotent.Mode.REJECT) { thrownew IllegalStateException("重复请求,请勿重复提交"); } // TODO: RETURN_CACHE 模式(需结果缓存) } return joinPoint.proceed(); } }
3.3 自定义失败处理器(可扩展)
public interface IdempotentFailureHandler { void handle(String key, Method method); } @Component public class DefaultIdempotentFailureHandler implements IdempotentFailureHandler { @Override public void handle(String key, Method method) { // 默认什么都不做,由 AOP 抛出异常 } }

四、使用案例

案例 1:下单(防重复下单)
@PostMapping("/order") @Idempotent(key = "'order:' + #userId + ':' + #goodsId", expire = 300) public Result<String> createOrder( @RequestParam String userId, @RequestParam String goodsId) { // 模拟下单逻辑 orderService.create(userId, goodsId); return Result.success("下单成功"); }

若同一用户对同一商品在 5 分钟内重复下单,后续请求将被拒绝。

案例 2:考生报名(防身份证重复)
@PostMapping("/enroll") @Idempotent(key = "'enroll:' + #candidate.idCard", expire = 300) public Result<Void> enroll(@RequestBody Candidate candidate) { // 防止同一身份证重复报名 enrollmentService.save(candidate); return Result.OK(); } // 简写一个dto类吧 publicclass Candidate { private String name; private String idCard; private String phone; }

key 为enroll:11010119900307XXXX,5分钟内无法重复提交。

案例 3:秒杀场景(用户 + 商品维度)
@PostMapping("/seckill") @Idempotent(key = "'seckill:' + #userId + ':' + #goodsId", expire = 60) public Result<String> seckill(@RequestParam String userId, @RequestParam Long goodsId) { return seckillService.execute(userId, goodsId); }

即使用户疯狂点击,1 分钟内只允许一次有效请求。

五、性能与可靠性

  • 性能:Redis SET NX EX 是原子操作,单节点 QPS > 5w+

  • 一致性:基于 Redis 分布式锁语义,天然支持集群

  • 安全性:Key 由业务生成,无注入风险(SpEL 在受控上下文中执行)

  • 资源:Key 自动过期,无内存泄漏风险

工具代码已经完整的放到GitHub上,使用超级简单,你的项目中引用依赖

<!-- https://mvnrepository.com/artifact/io.github.songrongzhen/once-kit-spring-boot-starter --> <dependency> <groupId>io.github.songrongzhen</groupId> <artifactId>once-kit-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency>

然后在你的需要幂等和防止重复提交的接口上加上一行注解就OK

@Idempotent(key = "'order:' + #userId + ':' + #goodsId", expire = 300)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/15 18:26:49

AI供应链惊魂!OpenClaw 341个恶意技能,正在收割全球用户数据

当AI代理工具从“便捷辅助”升级为“日常刚需”&#xff0c;其生态供应链已成为黑产团伙的新猎场。近期OpenClaw平台爆发的ClawHavoc大规模投毒事件&#xff0c;341个恶意技能潜伏于官方市场&#xff0c;恶意占比超11.9%&#xff0c;刷新了AI插件生态供应链攻击的规模纪录。这不…

作者头像 李华
网站建设 2026/5/19 0:04:27

云计算如何助力企业实现安全高效的规模化发展!

云计算是企业实现安全高效规模化的核心引擎&#xff0c;它通过提供可扩展、灵活且智能的基础设施与服务&#xff0c;从根本上改变了企业增长的方式。以下是其关键作用的详细解析&#xff1a;一、实现“高效规模化”的核心能力弹性伸缩&#xff0c;告别资源瓶颈按需供应&#xf…

作者头像 李华
网站建设 2026/5/5 2:29:00

什么是机器学习?—— 用 “买西瓜” 讲透核心逻辑

什么是机器学习&#xff1f;—— 用 “买西瓜” 讲透核心逻辑 快速导读 难度&#xff1a;入门位置&#xff1a;第 1 篇读完可接&#xff1a;第 2 篇《机器学习基本术语大拆解 —— 用西瓜数据集逐个对应》 文章目录什么是机器学习&#xff1f;—— 用 “买西瓜” 讲透核心逻…

作者头像 李华
网站建设 2026/5/14 17:20:19

Cosmos-Reason1-7B快速部署:Docker镜像免配置启动本地推理服务

Cosmos-Reason1-7B快速部署&#xff1a;Docker镜像免配置启动本地推理服务 一句话总结&#xff1a;无需复杂配置&#xff0c;一条命令启动专业级本地推理服务&#xff0c;让AI帮你解决逻辑推理、数学计算和编程问题。 1. 为什么选择Cosmos-Reason1-7B&#xff1f; 如果你经常需…

作者头像 李华
网站建设 2026/5/14 8:06:05

UI-TARS-desktop入门必看:零基础搭建AI开发环境

UI-TARS-desktop入门必看&#xff1a;零基础搭建AI开发环境 1. UI-TARS-desktop是什么&#xff1f;为什么选择它&#xff1f; 如果你正在寻找一个能在自己电脑上运行的AI助手&#xff0c;既能理解你的指令&#xff0c;又能帮你完成各种实际任务&#xff0c;那么UI-TARS-deskt…

作者头像 李华
网站建设 2026/5/12 14:21:37

校验日期格式:正则表达式

// 不允许空字符串&#xff0c;使用分支(|) Pattern(regexp "^\\d{4}-\\d{2}-\\d{2}$", message "日期格式必须为yyyy-MM-dd") // 允许空字符串&#xff0c;使用分支(|) Pattern(regexp "^\\d{4}-\\d{2}-\\d{2}$|^$", message "日期格式…

作者头像 李华