用自定义注解+Redis实现防重复提交,核心是在方法执行前拦截相同请求:通过@Target和@Retention定义注解,AOP切面生成唯一key(默认类名#方法名+参数SHA256)并setIfAbsent校验,支持自定义key生成器、token机制、Redis失败降级等。

用自定义注解 + Redis 实现防重复提交,核心是「在方法执行前拦截相同请求」,靠注解标记需幂等的方法,用 Redis 存储唯一请求标识(如 token 或业务 key),通过反射获取参数并生成校验 key,存在则拒绝,不存在则写入并放行。
定义防重复提交注解
声明一个运行时保留、可作用于方法的注解,支持配置过期时间与自定义 key 生成策略:
- 用 @Target(ElementType.METHOD) 限定只能加在方法上
- 用 @Retention(RetentionPolicy.RUNTIME) 确保运行时可通过反射读取
- 添加 expire() 属性设默认 5 秒,keyGenerator() 指定 key 构建类(可选)
编写 AOP 切面拦截逻辑
切点匹配带该注解的方法,围绕执行做三件事:生成请求唯一 key → 查 Redis → 决定是否放行:
- 通过 JoinPoint 获取目标方法、参数和注解实例
- 调用 key 生成器(默认用 方法名 + 参数 JSON 字符串 + 用户 ID(如有) 拼接)
- 用 RedisTemplate.opsForValue().setIfAbsent(key, "1", expire, TimeUnit.SECONDS) 原子写入
- 返回 false 则抛出异常(如 RepeatSubmitException),由全局异常处理器返回「重复提交」提示
支持灵活 key 策略的反射适配
避免硬编码拼接逻辑,把 key 构建抽成接口,让不同场景可自定义:
- 定义 IdempotentKeyGenerator 接口,含 generate(Method method, Object[] args) 方法
- 默认实现类按「类名#方法名 + SHA256(JSON.toJSONString(args))」生成,防长参数超长
- 业务方可在注解中指定自己的实现类,例如 @Idempotent(keyGenerator = OrderKeyGenerator.class)
- AOP 中通过 applicationContext.getBean(annotation.keyGenerator()) 反射获取实例
注意事项与常见坑
实际落地时几个关键细节决定是否稳定可用:
- Token 方式更安全:前端需先调 /api/token 获取一次性 submitToken,后提交时带上,后端校验并消耗;比纯参数拼接更能防刷
- 注意参数顺序与类型:反射获取 args 是 Object[],JSON 序列化前建议统一转为 LinkedHashMap 保证字段顺序一致
- Redis 连接失败要降级:加 try-catch,捕获 RedisConnectionFailureException 后记录日志但允许通过,避免雪崩
- 不要锁整个方法:setIfAbsent 是原子操作,无需额外加 synchronized 或分布式锁


















