SpringBoot实战:接口防重复提交的最佳实践
在前端网络抖动、用户手抖连击或者微服务重试机制下同一个接口可能在极短时间内被多次调用。如果接口不是幂等的如支付扣款、创建订单就会导致数据错乱。本文将深入讲解如何利用 Redis AOP 自定义注解 构建一套优雅的防重复提交方案。一、 什么是接口幂等性幂等性 (Idempotency)是指任意多次执行所产生的影响均与一次执行的影响相同。在 Web 开发中常见的非幂等场景包括用户提交订单时连点两次“支付”导致扣款两次。前端网络超时重试导致插入两条相同的记录。消息队列MQ消费者重试导致同一条消息被处理多次。为了保证系统的数据一致性我们需要对关键接口进行防重复提交处理。二、 常见解决方案对比方案原理优点缺点适用场景数据库唯一索引利用唯一约束拦截重复插入最底层兜底保障仅适用于插入场景无法拦截所有业务逻辑所有涉及唯一数据的场景Token 机制提交前请求 Token提交时校验并删除通用性强逻辑清晰需要前端配合多了一步交互表单提交、关键操作分布式锁基于请求参数或用户 ID 加锁实现简单无需前端改代码锁粒度控制复杂容易误伤正常请求简单防重非强一致性要求状态机控制通过记录状态流转如订单状态业务层面防重侵入业务代码订单、审批流本文重点实现Token 机制 AOP Redis这是目前企业中最通用、体验最好的方案。三、 核心思路Token 机制流程如下用户进入页面或点击提交前前端调用/api/token获取一个唯一的 Token。后端将 Token 存入 Redis设置过期时间如 5 分钟。前端在提交业务数据时将 Token 放在 Header 中。后端通过 AOP 拦截请求检查 Redis 中是否存在该 Token存在删除 Token放行请求。不存在说明是重复请求或 Token 已失效拒绝请求。为什么先存后删利用 Redis 的del操作是原子性的结合setnx(Set if Not Exists) 可以保证只有一个请求能成功拿到 Token。四、 实战编码1. 定义自定义注解我们希望防重逻辑对业务代码无侵入只需加一个注解即可。Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)DocumentedpublicinterfaceNoRepeatSubmit{/** * 锁过期时间默认 5 秒 */longexpireTime()default5;/** * 提示信息 */Stringmessage()default请勿重复提交;}2. Redis 工具类封装我们需要利用 Redis 的原子操作。这里假设你已经集成了RedisTemplate。ComponentpublicclassRedisUtil{AutowiredprivateStringRedisTemplateredisTemplate;privatestaticfinalStringPREFIXno_repeat_submit:;/** * 尝试获取锁 (对应 Redis SETNX) * param key Key * param expireTime 过期时间 (秒) * return 如果 key 不存在则设置成功返回 true否则返回 false */publicbooleantryLock(Stringkey,longexpireTime){BooleansuccessredisTemplate.opsForValue().setIfAbsent(PREFIXkey,1,expireTime,TimeUnit.SECONDS);returnBoolean.TRUE.equals(success);}/** * 删除锁 (验证通过后删除) * 注意Token 机制通常是获取到就删除或者验证通过就删除。 * 如果是分布式锁模式则不需要这步依靠过期时间自动释放。 * 本方案采用 Token 模式请求来时检查是否有有则删除并放行。 */publicbooleanreleaseLock(Stringkey){// 这里为了简化直接删除。实际 Token 机制中tryLock 返回 true 即代表获取成功。// 如果是“检查并删除”的原子操作需要 Lua 脚本。// 但 Token 模式通常是前端拿 token - Redis set token - 提交带 token - 后端 check and delete token// 下面 AOP 中我们将演示“检查并删除”的逻辑returnBoolean.TRUE.equals(redisTemplate.delete(PREFIXkey));}}3. 编写 AOP 切面这是最核心的部分。Slf4jAspectComponentpublicclassNoRepeatSubmitAspect{AutowiredprivateRedisUtilredisUtil;Pointcut(annotation(com.example.annotation.NoRepeatSubmit))publicvoidpointcut(){}Around(pointcut())publicObjectdoAround(ProceedingJoinPointjoinPoint)throwsThrowable{// 1. 获取注解信息MethodSignaturesignature(MethodSignature)joinPoint.getSignature();Methodmethodsignature.getMethod();NoRepeatSubmitannotationmethod.getAnnotation(NoRepeatSubmit.class);// 2. 生成 Key (策略User ID 请求 URI 参数 Hash)// 这样防止不同接口之间冲突也防止同一接口不同参数冲突HttpServletRequestrequestgetRequest();StringuserIdgetCurrentUserId();// 假设从 SecurityContext 获取Stringurirequest.getRequestURI();StringparamsHashgetParamsHash(joinPoint.getArgs());StringlockKeyuserId:uri:paramsHash;// 3. 检查并删除 Token (原子操作)// 注意RedisTemplate 默认没有“检查并删除”的原子方法建议使用 Lua 脚本// 这里为了演示清晰使用简单的 exists delete实际高并发推荐 Luaif(redisUtil.releaseLock(lockKey)){log.info(重复提交校验通过: key{},lockKey);returnjoinPoint.proceed();}else{log.warn(重复提交拦截: key{},lockKey);thrownewBizException(400,annotation.message());}}// ... 辅助方法getRequest(), getCurrentUserId(), getParamsHash()// getParamsHash 可以通过计算 args 的 MD5 或 JSON HashCode 实现}修正Token 模式 vs 分布式锁模式Token 模式前端先请求/token后端redis.set(token, 1)。提交时后端redis.del(token)。如果删除成功返回 1说明是第一次提交如果删除失败返回 0说明是重复提交。这种方式更精准不会误杀并发请求。AOP 锁模式如上文 AOP 代码所示直接拦截。如果两个请求几乎同时到达del操作可能产生竞争。为了严谨AOP 防重提交通常采用 Redis Lua 脚本保证 “Get and Delete” 的原子性或者使用Token 模式。4. 进阶基于 Lua 脚本的原子删除为了在 AOP 中安全地实现“检查并删除”我们使用 Lua 脚本。-- Lua script: if key exists, delete it and return 1, else return 0ifredis.call(exists,KEYS[1])1thenreturnredis.call(del,KEYS[1])elsereturn0end在 Java 中封装publicbooleancheckAndDelete(Stringkey){Stringscriptif redis.call(exists, KEYS[1]) 1 then return redis.call(del, KEYS[1]) else return 0 end;DefaultRedisScriptLongredisScriptnewDefaultRedisScript(script,Long.class);LongresultredisTemplate.execute(redisScript,Collections.singletonList(PREFIXkey));returnresult!nullresult0;}将 AOP 中的判断逻辑替换为if (redisUtil.checkAndDelete(lockKey))即可。五、 方案落地总结前端配合如果是 Token 模式需要前端在进入页面时请求 Token 并缓存在变量中提交时携带。Key 的生成Key 必须包含用户标识防止 A 用户提交的 Token 被 B 用户复用。过期时间一定要设置过期时间防止 Redis 内存泄漏。粒度控制如果希望“同一用户同一接口不管参数如何都不能并发”Key 中可以去掉参数 Hash如果希望“不同参数可以并发”则必须带上参数 Hash。六、 常见坑点误杀正常请求如果 AOP 中使用简单的lock(key)(如setnxexpire)当请求处理时间超过过期时间锁自动释放第二个请求就会进来。这适合短时间防抖但不适合 Token 模式。Token 模式必须是“一次性的”。事务问题如果方法内有TransactionalAOP 的执行顺序很重要。确保防重校验在事务开启之前执行默认 AOP 顺序符合。分布式环境必须使用 Redis 或 ZooKeeper 等共享存储不能使用ConcurrentHashMap本地锁。七、 总结通过自定义注解 AOP Redis我们将防重复提交的逻辑从业务代码中剥离实现了开箱即用的效果。业务开发人员只需在 Controller 方法上加一行NoRepeatSubmit即可轻松解决并发提交导致的数据脏乱问题极大地提升了系统的健壮性。 福利时间如果你正在备战面试或者想要学习其他知识给大家推荐一个宝藏知识库作者整理了一些列 Java 程序员需要掌握的核心知识有需要的自取不谢。知识库地址https://farerboy.com/

相关新闻

真理映射型人工智能(TMAI)研究综述——基于鸽姆智库与贾子理论体系(KTS)的范式革命分析

真理映射型人工智能(TMAI)研究综述——基于鸽姆智库与贾子理论体系(KTS)的范式革命分析

真理映射型人工智能(TMAI)研究综述——基于鸽姆智库与贾子理论体系(KTS)的范式革命分析 摘要 真理映射型人工智能(Truth-Mapping AI,TMAI)是由鸽姆智库(GG3M Think Tank&#xff0…

2026/10/6 23:07:02 阅读更多 →
贾子时空缩微定律(KSTS)在后摩尔时代的统一作用:面向全球 AI 芯片、大模型与具身智能的文明级工程范式研究

贾子时空缩微定律(KSTS)在后摩尔时代的统一作用:面向全球 AI 芯片、大模型与具身智能的文明级工程范式研究

贾子时空缩微定律(KSTS)在后摩尔时代的统一作用:面向全球 AI 芯片、大模型与具身智能的文明级工程范式研究 The Unified Role of Jias Kinetic Spatiotemporal Scaling (KSTS) in the Post-Moore Era: A Civilizational-Level Engineering Pa…

2026/9/28 12:36:38 阅读更多 →
真理的异化与重构:基于贾子理论对主流学术范式的结构性批判及独立认知评价体系的建立

真理的异化与重构:基于贾子理论对主流学术范式的结构性批判及独立认知评价体系的建立

真理的异化与重构:基于贾子理论对主流学术范式的结构性批判及独立认知评价体系的建立摘要当代全球学术体系正面临一场深刻的认识论危机。本文基于“贾子理论”的核心框架,对以西方主流学术界为代表的现行科学范式进行了系统性的解构与批判。研究指出&…

2026/10/7 19:24:11 阅读更多 →

最新新闻

多智能体协作实战:从提示词堆砌到团队化分工调度

多智能体协作实战:从提示词堆砌到团队化分工调度

做AI应用这些年,我越来越觉得“单智能体包打天下”这个思路在真实业务约束下并不可靠。最近我搭了一套内部代号叫agency-agents的模拟项目,核心就是让多个智能体像一个小团队一样分工协作。它解决的场景很典型:一次任务里既要做资料搜集&…

2026/10/10 7:35:25 阅读更多 →
基于Python的Django+Flask畜牧站疾病防控与检测系统实战解析

基于Python的Django+Flask畜牧站疾病防控与检测系统实战解析

做基层畜牧站的信息化项目,最头疼的不是算法,而是把一堆琐碎的防疫流程理顺。最近在开发一个基于Python的畜牧站疾病防控与检测系统,技术栈选了DjangoFlask这个组合。很多人第一反应是“一个项目为什么要混用两个框架”,其实真正落…

2026/10/10 7:35:25 阅读更多 →
Django+Flask搭建智慧养老饮食推荐系统:从禁忌过滤到推荐引擎实践

Django+Flask搭建智慧养老饮食推荐系统:从禁忌过滤到推荐引擎实践

前阵子接手了一个智慧养老服务平台的后端改造任务,其中工作量最大的不是管理后台的权限模块,也不是报表统计,而是“老人饮食推荐系统”这一块。项目技术栈定的是Django Flask,都是Python生态里的老熟人,但要把两者配合…

2026/10/10 7:35:25 阅读更多 →
libsrtp实战指南:RTP媒体加密与WebRTC安全接入

libsrtp实战指南:RTP媒体加密与WebRTC安全接入

简介:这是一份面向VoIP、视频会议等实时通信场景的SRTP开源库压缩包,适合需要集成安全实时传输能力的C开发者。资源内含已经用VC7(Visual Studio 2005)编译好的工程文件,可直接打开编译与调试,省去自行配置…

2026/10/10 7:35:25 阅读更多 →
张家口整体淋浴房厂家排名真相:选厂考察与避坑指南

张家口整体淋浴房厂家排名真相:选厂考察与避坑指南

这年头做卫浴生意的人,谁的手机里没搜过几回“淋浴房厂家排名”?2026年我明显感觉到,越来越多的经销商、装修公司,甚至做工程集采的朋友,愿意直接跑到张家口看货。原因不复杂:整体淋浴房的生产链条正在往北…

2026/10/10 7:35:25 阅读更多 →
从单体到多Agent协作:实战搭建agency-agents多代理系统

从单体到多Agent协作:实战搭建agency-agents多代理系统

“agency-agents”这个名字,乍看像某些公司内部的组织架构目录。但如果你常关注AI应用开发,大概能猜到我指的是什么——把多个AI代理(Agent)组织成一个“代理机构”,让它们像一间公司里的员工一样协作干活。我最近一半…

2026/10/10 7:34:25 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →