news 2026/5/16 11:22:47

通用幂等与防重就该这么实现!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 23:53:48

适用于LiblibLiblibTV跨项目的商业化体系重构实践

背景 1. 业务动机&#xff1a;为什么需要跨项目复用 本 monorepo 中有两个面向用户的产品&#xff1a; 主站项目&#xff1a;社区型 Web 应用&#xff0c;提供模型浏览、AI 生图/生视频、训练等核心功能&#xff0c;是最早的产品形态TV 项目&#xff1a;面向大屏/新场景的独立应…

作者头像 李华
网站建设 2026/5/9 18:54:36

App 的消亡与 Agent 的崛起:OpenClaw 启示录与本地化 AI 的反叛

在 GitHub 上一夜之间斩获 16 万颗星并非偶然,OpenClaw 的爆发式增长揭示了 AI 领域正在发生的一场静悄悄的变革。当整个行业还在卷大模型的参数量与云端算力时,OpenClaw 以一种反直觉的姿态——本地化运行、全权限掌控、去中心化数据——撕开了通往 2026 年的缝隙。这不仅是…

作者头像 李华
网站建设 2026/5/11 23:51:29

ollama部署Phi-4-mini-reasoning:轻量级推理模型5分钟快速上手

ollama部署Phi-4-mini-reasoning&#xff1a;轻量级推理模型5分钟快速上手 1. 引言&#xff1a;当推理能力遇上轻量级部署 在AI模型日益庞大的今天&#xff0c;一个有趣的问题出现了&#xff1a;我们是否能在资源受限的设备上&#xff0c;运行一个真正擅长“思考”的模型&…

作者头像 李华
网站建设 2026/5/9 16:17:30

AI绘画从入门到精通:Z-Image Turbo全功能解析

AI绘画从入门到精通&#xff1a;Z-Image Turbo全功能解析 如果你对AI绘画感兴趣&#xff0c;但又觉得那些复杂的模型和参数让人望而却步&#xff0c;那么今天这篇文章就是为你准备的。我们将深入解析一个名为“Z-Image Turbo”的本地极速画板&#xff0c;它能让AI绘画变得像使…

作者头像 李华