news 2026/4/18 5:42:06

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

AI系统架构评审中的成本优化:5个技巧帮你降低算力开支

AI系统架构评审中的成本优化:5个技巧帮你降低算力开支 副标题:从架构设计到落地的全流程成本控制实践 摘要/引言 在AI项目中,算力成本往往是仅次于人力的第二大开支——据Gartner统计,2023年全球企业AI算力支出同比增长41%,其中超过30%的成本因架构设计不合理而被浪费。…

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

大数据诊断性分析全攻略:工具、方法与最佳实践

大数据诊断性分析全攻略&#xff1a;工具、方法与最佳实践 摘要/引言 在当今数字化时代&#xff0c;数据量以惊人的速度增长&#xff0c;大数据已成为企业和组织获取竞争优势的关键资产。然而&#xff0c;仅仅拥有大量数据是远远不够的&#xff0c;如何从这些数据中提取有价值…

作者头像 李华
网站建设 2026/4/18 0:35:44

cudnn尝试vgg

自己用c写vgg&#xff0c;写了两个版本才成功&#xff0c;其实就是3*3卷积&#xff0c;padding等于1. 没想cudnn实现起来很方便&#xff1a;一个是参考程序类封装的好&#xff0c;一个是cudnn写好了调用函数&#xff0c;这里只展示vgg改成功的代码&#xff0c;详细代码见&…

作者头像 李华
网站建设 2026/4/17 0:48:14

一键部署Qwen3-ASR-0.6B:打造你的私人语音助手

一键部署Qwen3-ASR-0.6B&#xff1a;打造你的私人语音助手 1. 为什么你需要一个轻量又聪明的语音识别助手&#xff1f; 你有没有过这些时刻&#xff1a; 开会时录音记了半小时&#xff0c;回听整理却花了两小时&#xff1b; 客户发来一段方言口音浓重的语音&#xff0c;转文字…

作者头像 李华
网站建设 2026/4/16 19:52:05

深求·墨鉴使用技巧:提升手写笔记识别准确率

深求墨鉴使用技巧&#xff1a;提升手写笔记识别准确率 1. 为什么手写笔记识别总是“差一点”&#xff1f; 你有没有过这样的经历&#xff1a; 拍下一页密密麻麻的课堂笔记&#xff0c;满怀期待地点击「研墨启笔」&#xff0c;结果生成的文字里—— “微积分”变成了“微积风”…

作者头像 李华
网站建设 2026/4/16 19:05:02

文脉定序应用实践:客服工单知识匹配中重排序模块降低误判率42%

文脉定序应用实践&#xff1a;客服工单知识匹配中重排序模块降低误判率42% 1. 项目背景与挑战 在客服工单处理场景中&#xff0c;知识匹配的准确性直接关系到问题解决效率和客户满意度。传统的关键词匹配和基础向量检索虽然能够快速找到相关文档&#xff0c;但经常出现"…

作者头像 李华