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 |
重复代码多,维护成本高 |
基于以上痛点,一个通用、轻量、高性能的幂等中间件需求变得迫切。本文介绍如何利用 Spring Boot 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
public class IdempotentAspect {
private final IdempotentService idempotentService;
private final ExpressionParser parser = new SpelExpressionParser();
private final 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) {
throw new 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类吧
public class 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 key value EX expire NX 是原子操作,单节点 QPS > 5w+
- 一致性:基于 Redis 分布式锁语义,天然支持集群
- 安全性:Key 由业务参数通过 SpEL 动态生成,无注入风险(SpEL 在受控上下文中执行)
- 资源:Key 自动过期,无内存泄漏风险
六、快速集成
该工具的核心代码已完整开源在 GitHub 上。在你的 Spring Boot 项目中集成使用非常简单,只需引入依赖:
<!-- 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>
然后,在你需要保证幂等性、防止重复提交的接口方法上添加一行 @Idempotent 注解即可:
@Idempotent(key = "'order:' + #userId + ':' + #goodsId", expire = 300)
通过这种注解驱动的方式,开发者无需再编写繁琐的重复校验逻辑,显著提升了开发效率与代码的健壮性。希望这个基于 Spring Boot 和 Redis 的中间件方案能为你的项目带来帮助。更多技术实践与深度讨论,欢迎访问 云栈社区 与开发者们共同交流。
来源:https://blog.csdn.net/caijigskg/