news 2026/4/3 1:25:20

通用幂等与防重就该这么实现!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/3/25 10:19:40

一键启动Qwen3-ForcedAligner:语音处理从未如此简单

一键启动Qwen3-ForcedAligner&#xff1a;语音处理从未如此简单 1. 引言&#xff1a;语音对齐的痛点与解决方案 你是否曾经遇到过这样的场景&#xff1a;需要为视频添加字幕&#xff0c;但手动对齐音频和文字耗时耗力&#xff1f;或者想要分析语音内容&#xff0c;却苦于没有…

作者头像 李华
网站建设 2026/3/25 10:56:30

LLaVA-v1.6-7B多模态模型5分钟快速部署指南:Ollama一键安装

LLaVA-v1.6-7B多模态模型5分钟快速部署指南&#xff1a;Ollama一键安装 想体验让AI看懂图片并和你聊天吗&#xff1f;LLaVA-v1.6-7B就是这样一个神奇的多模态模型。它能像人一样“看”图片&#xff0c;理解图片里的内容&#xff0c;然后和你进行智能对话。无论是识别物体、描述…

作者头像 李华
网站建设 2026/3/31 18:16:14

Spring Boot日期格式注解@DateTimeFormat和@JsonFormat的区别与应用

DateTimeFormat 和 JsonFormat 是 Spring Boot 中处理日期时间格式化的两个常用注解&#xff0c;但它们的用途和工作场景不同。DateTimeFormat用途主要用于 Spring MVC 参数绑定&#xff0c;处理表单提交、URL参数、请求参数中的日期时间字符串转换。使用场景javaController pu…

作者头像 李华
网站建设 2026/3/29 9:20:46

影墨·今颜FLUX.1-dev实战:LoRA权重融合+风格迁移+局部重绘技巧

影墨今颜FLUX.1-dev实战&#xff1a;LoRA权重融合风格迁移局部重绘技巧 1. 认识影墨今颜&#xff1a;重新定义AI人像生成 「影墨今颜」是一款基于FLUX.1-dev引擎的高端AI影像创作系统&#xff0c;专门针对时尚人像生成进行了深度优化。这个系统最大的特点是能够生成极其真实、…

作者头像 李华
网站建设 2026/3/27 12:42:17

Qwen2.5-7B与ChatGLM3-6B性能对比:推理速度实测部署教程

Qwen2.5-7B与ChatGLM3-6B性能对比&#xff1a;推理速度实测部署教程 1. 两款主力7B模型的核心定位与差异 在当前轻量级大模型落地实践中&#xff0c;Qwen2.5-7B-Instruct 和 ChatGLM3-6B 是开发者最常选用的两个开源指令模型。它们参数量相近&#xff08;70亿 vs 62亿&#x…

作者头像 李华
网站建设 2026/4/1 19:40:33

一键部署:Moondream2轻量级视觉问答系统体验

一键部署&#xff1a;Moondream2轻量级视觉问答系统体验 1. 引言&#xff1a;给你的电脑装上“眼睛” 你有没有想过&#xff0c;让电脑像人一样“看懂”图片&#xff0c;并且能回答关于图片的任何问题&#xff1f;比如&#xff0c;你随手拍了一张办公桌的照片&#xff0c;电脑…

作者头像 李华