核心是「在方法执行前拦截并校验请求是否已存在」,通过自定义注解标记需防护接口、AOP切面生成用户+操作维度唯一key(如"repeat_submit:1001:POST:/order")、Redis setIfAbsent原子校验,存在则拒绝执行并抛异常。

用自定义注解 + AOP + Redis 防重复提交,核心是「在方法执行前拦截并校验请求是否已存在」。不是靠前端禁用按钮,而是后端主动识别同一用户短时间内的重复操作,拒绝二次执行。
定义防重复注解
注解用来标记哪些接口需要防护,支持灵活配置过期时间:
- 用
@Target(ElementType.METHOD)限定只能加在方法上 - 用
@Retention(RetentionPolicy.RUNTIME)确保运行时可通过反射读取 - 提供
intervalSeconds()属性,默认 3 秒,表示该用户在此时间内不可重复提交
示例:@interface RepeatSubmit { int intervalSeconds() default 3; }
构造唯一请求标识 key
key 决定“谁”和“什么操作”算重复。常见组合方式有:
- 用户维度:登录态下取 userId(如 SecurityUtils.getNullableUserId()),未登录则 fallback 到客户端 IP(用 IpUtils.getClientIp(request))
-
操作维度:拼接 HTTP 方法 + 请求路径(如
"POST:/order") - 增强维度(可选):对请求参数做 SHA256 或 FNV Hash,避免相同 URL 不同参数被误判
最终 key 示例:"repeat_submit:1001:POST:/order" 或 "repeat_submit:192.168.1.100:PUT:/user/profile"
立即学习“Java免费学习笔记(深入)”;
AOP 切面统一拦截
切面在目标方法执行前介入,用 Redis 原子操作 setIfAbsent 判断 key 是否已存在:
- 若 key 不存在 → 写入 Redis(带过期时间),放行执行原方法
- 若 key 已存在 → 直接抛异常或返回固定提示(如 “请勿重复提交”)
- 注意处理非 Web 场景(如定时任务、MQ 消费),避免
RequestContextHolder空指针
关键代码片段:
Boolean locked = redisTemplate.opsForValue().setIfAbsent(key, "1", repeatSubmit.intervalSeconds(), TimeUnit.SECONDS);<br>
if (!locked) { throw new RuntimeException("重复提交"); }
Redis 与降级兜底考虑
生产环境不能强依赖 Redis 可用性:
- 建议使用
RedisTemplate的异步/重试机制,或配置连接超时与失败回调 - 可加入本地缓存(如 Caffeine)作为二级缓存,Redis 不可用时降级为进程内判断
- 幂等 key 的过期时间不宜过长(一般 1–10 秒),避免影响正常高频操作
不复杂但容易忽略。


















