从 0 到 1 用 Spring Boot 3 + Redis 打造一个生产级通用幂等与防重中间件(含图解 + 代码 + 案例)
·
我们来一步步实现这个生产级通用的幂等与防重中间件。核心思路是利用 Redis 的原子性操作(如 SETNX、INCR)和 唯一请求令牌(Token) 机制。
1. 核心概念与设计图
(1) 幂等性 (Idempotency)
- 定义: 用户对同一操作发起多次请求,其产生的效果与一次请求相同。
- 场景: 订单支付、库存扣减、表单提交等。
- 关键: 识别重复请求。
(2) 防重放 (Anti-Replay)
- 定义: 防止恶意用户捕获并重复发送有效的请求。
- 场景: 短信验证码获取、优惠券领取等。
- 关键: 限制请求频率或次数。
(3) 设计图 (简化版)
+----------------+ 1. 生成Token +----------+
| 客户端 (前端) | ---------------------> | 应用层 |
+----------------+ +----------+
| |
| 2. 携带Token请求业务 | 3. 校验Token (Redis)
| |
v v
+----------------+ +----------+
| 业务API (后端) | <---------------------- | Redis |
+----------------+ 4. 业务处理 & 响应 +----------+
2. 核心实现 - 幂等性Token机制
(1) Token生成服务 (IdempotentService)
@Service
public class IdempotentService {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 生成幂等性Token (存储到Redis,有效期默认30分钟)
* @return 唯一Token字符串
*/
public String generateToken() {
String token = UUID.randomUUID().toString().replace("-", "");
redisTemplate.opsForValue().set(token, "0", 30, TimeUnit.MINUTES); // 初始状态
return token;
}
/**
* 校验Token有效性 (原子操作)
* @param token 客户端传递的Token
* @return true-有效且首次使用; false-无效或已使用
*/
public boolean validateToken(String token) {
// Lua脚本保证原子性: 检查是否存在 && 标记为已使用
String script = "if redis.call('get', KEYS[1]) == '0' then " +
"redis.call('set', KEYS[1], '1') " +
"return true " +
"else return false end";
DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(script, Boolean.class);
return Boolean.TRUE.equals(redisTemplate.execute(redisScript, Collections.singletonList(token)));
}
}
3. 防重放机制 - 基于时间窗口
(1) 防重服务 (AntiReplayService)
@Service
public class AntiReplayService {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 检查是否允许请求 (例如:1分钟内同一用户最多3次)
* @param key 防重Key (如: user:123:send_sms)
* @param timeWindow 时间窗口 (秒)
* @param maxCount 最大允许次数
* @return true-允许; false-超过限制
*/
public boolean allowRequest(String key, long timeWindow, int maxCount) {
Long count = redisTemplate.opsForValue().increment(key, 1);
if (count == 1) {
redisTemplate.expire(key, timeWindow, TimeUnit.SECONDS); // 设置过期
}
return count != null && count <= maxCount;
}
}
4. 集成Spring Boot - 自定义注解与AOP
(1) 幂等注解 (@Idempotent)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
String tokenParam() default "token"; // 客户端传递Token的参数名
}
(2) 防重注解 (@AntiReplay)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AntiReplay {
String key() default ""; // 防重Key (支持SpEL表达式)
long window() default 60; // 时间窗口 (秒)
int maxCount() default 1; // 最大请求次数
}
(3) AOP切面处理 (IdempotentAspect & AntiReplayAspect)
@Aspect
@Component
public class IdempotentAspect {
@Autowired
private IdempotentService idempotentService;
@Around("@annotation(idempotent)")
public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
// 1. 获取请求中的Token
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String token = request.getParameter(idempotent.tokenParam());
// 2. 校验Token
if (!idempotentService.validateToken(token)) {
throw new IdempotentException("重复请求或Token无效");
}
// 3. 执行原方法
return joinPoint.proceed();
}
}
5. 使用案例
(1) 订单创建 (幂等)
@RestController
@RequestMapping("/order")
public class OrderController {
@Idempotent // 启用幂等校验
@PostMapping("/create")
public ResponseEntity<String> createOrder(@RequestParam String token, @RequestBody OrderDTO order) {
// 业务逻辑...
return ResponseEntity.ok("订单创建成功");
}
}
(2) 短信发送 (防重)
@RestController
@RequestMapping("/sms")
public class SmsController {
@AntiReplay(key = "'sms:' + #phone", window = 60, maxCount = 1)
@GetMapping("/send")
public ResponseEntity<String> sendSms(@RequestParam String phone) {
// 发送短信逻辑...
return ResponseEntity.ok("验证码已发送");
}
}
6. 生产级优化
- Redis集群: 使用Redisson实现分布式锁增强一致性。
- Token存储: 结合JWT携带用户信息。
- 监控告警: 记录异常日志,对接监控系统。
- 压测: 使用JMeter模拟高并发场景。
总结
通过Redis的原子操作 + 唯一Token + AOP注解,我们实现了一个轻量级、高可用的幂等与防重中间件。代码已适配Spring Boot 3,可直接集成到生产环境!
更多推荐

所有评论(0)