Spring Boot防重实战:Redis缓存+哈希锁方案性能测评
本文将带你实现一种“哈希 + 缓存”双重保障的接口防重复提交方案, 无需前端配合,不依赖额外 Token,仅通过请求特征动态生成哈希签名,即可快速判断重复请求。 我们将基于 Spring Boot
本文将介绍一种基于“哈希 + 缓存”双重防护机制的接口防重复提交方案,无需前端配合,不依赖额外 Token,仅通过请求特征动态生成哈希签名即可快速识别重复请求。我们将采用 Spring Boot + AOP + Redis/Caffeine 架构实现这一机制,整体方案轻量高效,具备实战级复用价值。
在高并发业务场景中,接口被重复点击或短时间内多次提交请求,是常见却极具破坏性的隐患。例如在电商系统中,用户多次点击“提交订单”按钮可能导致重复订单生成;支付接口被异常触发可能造成重复扣费;表单接口因网络波动被重复提交,极易产生脏数据。
这类问题虽看似小概率事件,但在真实生产环境中往往引发严重后果。为规避此类“重复提交”带来的混乱,我们需要在服务端层面构建一个高可靠的防重机制。
本方案通过构建全局唯一哈希值,结合动态缓存实现高效防重。当检测到相同哈希值在有效期内重复出现时,系统将自动拦截并返回提示,从源头杜绝数据异常。
防重原理与方案选型
什么是防重复提交
防重复提交(Prevent Duplicate Request)指防止用户在短时间内对同一接口重复触发操作,从而避免数据重复创建、状态异常或业务逻辑错误。
典型场景包括:
下单接口防止同一用户创建两笔相同订单;表单提交避免页面卡顿或多次点击产生重复记录;支付操作防止短时间内重复支付。常见实现方式
本方案采用第三种方式,通过 URL + 请求方法 + 请求参数构建全局唯一哈希值,并将其存储在缓存中。当系统检测到相同哈希值在有效期内再次出现时,即判定为重复请求。
系统架构与流程设计
目录结构如下
/src
├── /main
│ └── /java/com/icoderoad/duplicate
│ ├── annotation/PreventDuplicate.java
│ ├── aspect/PreventDuplicateAspect.java
│ ├── storage/DuplicateStorage.java
│ ├── storage/impl/RedisStorage.java
│ ├── storage/impl/CaffeineStorage.java
│ └── util/RequestParameterUtils.java
└── /resources
└── application.yml
防重复机制核心流程如下:
请求进入控制层;AOP 拦截目标方法;提取 URL、请求方法、参数信息;计算 SHA-256 哈希值作为 Key;查询缓存是否存在该 Key;存在则拒绝请求,不存在则执行方法并写入缓存。核心实现代码
自定义注解 @PreventDuplicate
package com.icoderoad.duplicate.annotation;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 防重复提交注解
* 可应用在 Controller 层接口上
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreventDuplicate {
/** 防重复提交时间(单位:秒) */
int expire() default 3;
/** 时间单位,默认秒 */
TimeUnit timeUnit() default TimeUnit.SECONDS;
/** 可指定参与生成哈希的主要字段 */
String[] field() default {};
/** 提示信息 */
String message() default "请勿重复提交!";
}
AOP 拦截器 PreventDuplicateAspect
package com.icoderoad.duplicate.aspect;
import cn.hutool.crypto.digest.DigestUtil;
import com.icoderoad.duplicate.annotation.PreventDuplicate;
import com.icoderoad.duplicate.storage.DuplicateStorage;
import com.icoderoad.duplicate.storage.DuplicateStorageFactory;
import com.icoderoad.duplicate.util.RequestParameterUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
@RequiredArgsConstructor
public class PreventDuplicateAspect {
private final HttpServletRequest request;
private final DuplicateStorageFactory storageFactory;
@Around("@annotation(preventDuplicate)")
public Object handle(ProceedingJoinPoint joinPoint, PreventDuplicate preventDuplicate) throws Throwable {
String method = request.getMethod();
String uri = request.getRequestURI();
String params = RequestParameterUtils.getAllParamsAsString(joinPoint, preventDuplicate.field());
// 拼接唯一签名源
String signSource = method + ":" + uri + ":" + params;
long start = System.currentTimeMillis();
String key = DigestUtil.sha256Hex(signSource);
long end = System.currentTimeMillis();
System.out.println("生成哈希耗时:" + (end - start) + "ms");
DuplicateStorage storage = storageFactory.getStorage();
if (storage.exists(key)) {
throw new RuntimeException(preventDuplicate.message());
}
storage.put(key, preventDuplicate.expire(), preventDuplicate.timeUnit());
return joinPoint.proceed();
}
}
控制层示例
package com.icoderoad.duplicate.controller;
import com.icoderoad.duplicate.annotation.PreventDuplicate;
import com.icoderoad.duplicate.model.ArticleDTO;
import com.icoderoad.duplicate.model.UserInfo;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/demo")
public class DemoController {
@GetMapping("/hello")
@PreventDuplicate
public String hello(String name, String age, String address) {
return "防重复测试:" + name + " " + age + " " + address;
}
@PostMapping("/saveUserInfo")
@PreventDuplicate(expire = 5)
public String saveUserInfo(@RequestBody UserInfo userInfo) {
System.out.println(userInfo);
return "请求时间:" + DateTime.now() + " 保存成功";
}
@PostMapping("/saveContent")
@PreventDuplicate(expire = 10)
public String saveContent(@RequestBody ArticleDTO articleDTO) {
System.out.println(articleDTO);
return "请求时间:" + DateTime.now() + " 内容保存成功";
}
}
测试效果:当短时间内重复发送相同参数请求时,系统将直接返回
"请勿重复提交!"异常提示。
性能验证
为验证哈希计算的性能表现,我们模拟生成一篇 3 万字文章内容并重复请求测试。测试数据表明:
首次生成哈希值耗时约9ms(JVM 预热阶段);多次请求后平均耗时降至0ms;即使请求参数极大,对接口性能几乎无影响。结论:SHA-256 哈希算法在防重场景中既具备唯一性又满足高性能要求,完全可支撑高并发接口的防重复需求。
总结与实践建议
通过本方案,我们实现了一个无侵入、通用性强、性能优越的防重复提交机制。核心优势体现在:
使用AOP 切面拦截请求,避免侵入业务逻辑;基于请求路径 + 方法 + 参数哈希生成唯一标识;通过Redis / Caffeine 缓存实现分布式与本地防重双模式;支持灵活配置提交间隔与关键字段粒度。该方案不仅适用于表单、下单、支付等关键接口,还可扩展至异步任务提交、API 幂等控制等更广泛场景。
未来还可进一步优化:
加入异步清理机制;为Key 结构添加命名空间前缀;结合分布式锁提升集群环境下的安全性。一句话总结:
防重不是“锦上添花”的优化,而是“防止灾难”的必要保护。用哈希 + 缓存双重保险,为你的接口上好“安全带”!
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
前阿里技术大牛领衔字节自研智驾浮出水面华大地魔遇最强对手
终于,字节跳动还是踏出了这一步。 据36kr报道,字节正步步为营进军自动驾驶领域,具体事项由Seed的世界模型团队负责,该团队由前阿里通义大模型技术负责人周畅领导。 现阶段,高阶智驾已成中高端车型必备功能,并且不断下沉市场,部分10万元以内的产品已经用上了L2+级高阶辅助驾驶,还有少量车型配备了激光
砺算LX 7G100零售版显卡于7月17日京东首发上市 支持8K6
砺算科技官方宣布,旗下消费级显卡砺算 LX 7G100 零售版将于2026年7月17日在京东自营旗舰店正式开售。此前推出的创始版已全部售罄,本次零售版将作为后续供应,面向所有用户开放购买。 这款显卡基于砺算自主研发的TrueGPU架构,集成了多项硬核显示技术:支持NRSS超分辨率技术、10bit色深
亚马逊云科技开源MCP服务器 数秒免费检索全球科研数据
7月15日,在日内瓦联合国国际电信联盟举办的“人工智能向善”全球峰会上,亚马逊云科技推出了一款全新工具——开源模型上下文协议(MCP)服务器。简单来说,科学家和研究人员现在可以直接与AI对话,几秒钟内就能从亚马逊云科技开放数据注册表(RODA)中检索到所需的数据集。这一工具整合了全球最大的免费公开科
三星T9运动相机内存卡推荐户外影像存储一步到位
对于运动相机内存卡推荐,许多用户最担心的就是低速卡导致录制中断,或者容量太小装不下大量素材。市面上可选产品众多,但真正能够兼顾高速稳定、大容量和多设备兼容的专业存储卡并不多见。今天我们将重点聚焦三星T9 microSD存储卡,深入解析其核心优势——看完这篇,你无需再反复对比其他品牌,它几乎能覆盖所有
靠谱大容量TF卡推荐三星T7满足多设备扩容需求
说实话,现在手里的数码设备越来越多——平板、Switch、航拍无人机、运动相机……哪个不是存储空间的大户?偏偏很多设备出厂给的容量就那么一点点,扩容几乎成了刚需。这时候,一张靠谱的microSD TF卡就是最直接的解决方案。但低价杂牌卡的坑,相信不少人已经踩过。想要稳定性、大容量还兼顾通用性,三星原
- 热门数据榜
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
相关攻略
2026-07-16 22:42
2026-07-16 22:41
2026-07-16 22:40
2026-07-16 22:39
2026-07-16 22:39
2026-07-16 22:38
2026-07-16 22:38
2026-07-16 22:38
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程

