magic-spring-boot-starter-protection 技术组件,由它的 idempotent 包,提供声明式的幂等特性,可防止重复请求。

// UserController.java
@Idempotent(timeout = 10, timeUnit = TimeUnit.SECONDS, message = "正在添加用户中,请勿重复提交")
@PostMapping("/user/create")
public String createUser(User user){
    userService.createUser(user);
    return "添加成功";
}
1
2
3
4
5
6
7

添加了注解 Idempotent 后, 表示:每 10 秒钟,只能执行一次【创建用户】操作。

疑问:如果想按照每个用户,或者每个 IP,限制请求呢?

可设置该注解的 keyResolver 属性,可选择的有:

  • DefaultIdempotentKeyResolver:全局级别
  • UserIdempotentKeyResolver:用户级别
  • ExpressionIdempotentKeyResolver:自定义级别,通过 keyArg 属性指定 Spring EL 表达式

# 1 基本原理

核心逻辑:相同参数的请求,在指定时间窗口内仅允许执行一次

# 执行流程

  1. 请求拦截
    • 根据方法名 + 参数生成唯一 Redis Key(默认 MD5 压缩,避免过长)。
    • 若 Key 已存在 → 直接拒绝请求(防重)。
    • 若不存在 → 写入 Redis 并设置过期时间(标记为“执行中”)。
  2. 执行控制
    • 成功:保留 Key 至自动过期(默认不删除)。
    • 异常:自动删除 Key,允许重试。
    • 超时:若执行时间超过 Key 的过期时间,Redis 自动清理,需合理设置过期时间。

# 2 源码解析

如图,IdempotentAspect 拦截器会拦截带有注解 Idempotent 的方法 。

在执行业务逻辑前,有两个步骤:

1、通过 keyResolvers 解析参数,并组装成 Key 。

/**
 * 默认(全局级别)幂等 Key 解析器,使用方法名 + 方法参数,组装成一个 Key
 *
 * 为了避免 Key 过长,使用 MD5 进行“压缩”
 **/
public class DefaultIdempotentKeyResolver implements IdempotentKeyResolver {

    @Override
    public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
        String methodName = joinPoint.getSignature().toString();
        String argsStr = StrUtil.join(",", joinPoint.getArgs());
        return SecureUtil.md5(methodName + argsStr);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

默认(全局级别)幂等 Key 解析器,使用方法名 + 方法参数,组装成一个 Key, 为了避免 Key 过长,使用 MD5 进行“压缩” 。

2、通过 Redis 的 setNx 命令创建占位

// 1. 尝试获取幂等锁
boolean success = idempotentRedisDAO.setIfAbsent(
    appName, 
    key, 
    idempotent.timeout(), 
    idempotent.timeUnit()
);

// 2. 检查是否获取锁成功
if (!success) {
    log.info("[幂等拦截][方法({}) 参数({}) 存在重复请求]", 
        joinPoint.getSignature().toString(), 
        joinPoint.getArgs());
    throw new ServiceException(
        GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), 
        idempotent.message()
    );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

setIfAbsent 本质是调用 Redis 的 SETNX 命令。

SETNX 是 Redis 中一个用于设置键值对的命令,全称是 "SET if Not eXists",意思是只有在键不存在的情况下,才会设置该键的值。

如果键已存在,则SETNX 不会执行任何操作,也不会覆盖现有值。

SETNX 常被用来实现分布式锁。当一个进程需要访问一个共享资源时,它可以尝试使用SETNX 来获取锁。如果SETNX 返回1,则表示该进程成功获取了锁;如果返回0,则表示锁已被其他进程持有 。

上次更新: 2026/6/28