news 2026/4/30 6:53:46

【BUG记录】防止记录重复提交方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【BUG记录】防止记录重复提交方案

这是一个很经典的后端开发问题。防止重复提交的核心思路是:在服务端识别并拦截短时间内相同的请求

下面我给你梳理几种主流且实用的方案,按推荐程度排序。

方案一:Token 令牌机制(最推荐,防重最彻底)

前端发起请求前,先去后端获取一个一次性Token,后端保存Token(用Redis)。请求时带上Token,后端处理请求后立即删除Token。

  • 原理:Token一次性有效,能有效防止因网络延迟、用户狂点导致的多笔相同业务(如支付、下单)生成。

  • 适用:所有写操作,尤其涉及金额、库存、订单等核心业务。

  • 步骤

    1. 用户访问表单页,后端生成Token存入Redis(设短期过期),返回给前端。

    2. 前端提交时,携带这个Token。

    3. 后端检查Redis中Token是否存在,存在则合法,删除Token并执行业务;不存在或不匹配则拦截。

方案二:基于 Redis 的幂等性校验(最常用,高性能)

利用Redis的原子性操作,比如SETNX命令(只在键不存在时设置),来判断请求是否重复。

  • 原理:用唯一业务标识(如用户ID:订单ID)作为Key存入Redis,处理完成后删除Key。适用于需要高并发且能接受短暂状态存储的场景。

  • 步骤

    1. 根据请求的关键参数(比如用户ID + 订单ID)生成唯一Key。

    2. 使用SET key value NX EX 5(NX表示不存在时才设置,EX 5表示5秒过期)。

    3. 设置成功则执行业务,业务完成后可立即删除Key;设置失败则说明是重复提交。

方案三:分布式锁(适合分布式环境)

当你的服务部署在多个节点时,用基于Redis或ZooKeeper的分布式锁。

  • 原理:每个请求尝试获取同一个资源的锁,获取不到则被丢弃。这比Redis幂等性更侧重解决“并发修改”问题。

  • 实现

    • Redis锁:用SETNXkey value,配合Lua脚本释放锁,确保原子性。

    • Redisson:提供了封装好的可重入锁,使用简单。

方案四:数据库唯一约束(最后一道防线,绝对防重)

在数据库层面,为关键字段组合(如订单号)设置唯一索引。

  • 原理:任何事务都脱不开底层存储,这是防止数据重复的终极保障。

  • 步骤

    1. 创建表时,对能唯一标识业务记录的字段或字段组合添加UNIQUE KEY

    2. 代码执行INSERT时,捕获DuplicateKeyException异常,将其视为重复提交,提示用户。

方案五:前端控制(用户可见级别,但不能依赖)

在按钮点击后立即置灰,或显示“提交中”的Loading状态。

  • 局限性:这只防君子不防小人。用户刷新页面、用脚本发起请求,前端控制就失效了。必须和后端方案配合使用

不同场景下的选择建议

场景推荐方案
核心交易无状态服务(如支付、订单)Token + 数据库唯一约束,最安全可靠
高并发,耗时1-3秒(如秒杀、抢购)Redis SETNX+ 前端Loading,性能极高
分布式集群部署(多台服务器)分布式锁(如Redisson) + 全局请求ID
低并发,中小项目AOP切面 + 缓存(如Caffeine本地缓存)

一个典型的高并发防重组合策略(推荐架构)

  1. 前端:点击后立即置灰按钮,加上Loading动画。

  2. 网关/过滤器:解析请求中的requestId(如UUID+timestamp),若Redis中requestId已存在,则直接返回“重复提交”;否则将requestId存入Redis并设30秒过期。

  3. 拦截到业务层:对核心资源(如订单号)加分布式锁Redis SETNX

  4. 数据库:为关键字段(如订单表的主键或单号)设置唯一索引,兜底保护。

一个简单的代码示例(基于 Redis + AOP)

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoRepeatSubmit { int expireSeconds() default 5; // 锁的过期时间 } // 切面实现 @Around("@annotation(noRepeatSubmit)") public Object around(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) throws Throwable { String key = "repeat:submit:" + getRequestUniqueKey(); // 比如用户ID + 请求URL + 参数MD5 // Redis SETNX操作,设置成功后老的时间为expireSeconds秒 Boolean success = redisTemplate.opsForValue().setIfAbsent(key, "1", Duration.ofSeconds(noRepeatSubmit.expireSeconds())); if (success == null || !success) { throw new RepeatSubmitException("请勿重复提交"); } try { return joinPoint.proceed(); } finally { // 业务执行完后,可以选择删除key,或者让它自然过期 // 注意:对于耗时很短的业务,可以立刻删除,否则让其自动过期即可 // redisTemplate.delete(key); } }

容易踩的坑

  1. 锁的粒度太大:对用户ID加锁可能导致该用户的所有请求被串行化。建议使用用户ID + 业务类型 + 关键参数的组合。

  2. 没有兜底方案:只依赖Redis。Redis挂了会导致请求无法提交或全部放行。务必加上数据库唯一索引作为最后防线。

  3. 业务执行时间超过锁的过期时间:业务还没处理完,锁自动释放了,导致下一个请求进来看不到锁,又提交了一次。

    • 解决方案:使用看门狗(Watch Dog)机制,如Redisson的tryLock会自动续期。或根据业务平均耗时,将锁过期时间设为足够长(如15-30秒)。

总结一句话后端用Redis做快速拦截,数据库唯一索引进最后兜底,前端做按钮防抖提升用户体验。根据业务重要性,选择组合即可。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/30 6:51:45

注册表,项,值,数据,微软这套命名完全反人类

太对了,微软这套命名完全反人类、逻辑颠倒,你吐槽得一点毛病没有。1. 先戳穿微软的命名 BUG正常人逻辑应该是:Key 键名(你现在叫的「值名称」LoadBehavior)Path 目录 / 路径(一层层文件夹)Val…

作者头像 李华
网站建设 2026/4/30 6:49:27

商汤校招 C++ 考试题到底怎么考?这篇只能写题型线索,不能硬装完整真题

如果你点开这篇,是想直接看一套“商汤 C++ 完整真题”,那先停一下。 这不是当前资料能诚实支持的写法。 先把最重要的一句话放前面: 商汤这篇,不能写成“完整 C++ 笔试真题还原”。 现有资料不支持这么写。 如果硬写,不仅容易失真,还会把真正有价值的部分写没了。 …

作者头像 李华
网站建设 2026/4/30 6:46:21

设计师都是在哪里购买字体版权的?

在日常工作中,字体选择不仅关乎美学,更直接关系到项目的法律安全。一个不经意的“免费下载”,很可能让设计师和公司陷入侵权纠纷。那么,靠谱的设计师都是从哪些渠道获取字体版权的呢?下面梳理三类主流途径。一、综合性…

作者头像 李华
网站建设 2026/4/30 6:43:32

“流水线冒险”,CPU如何解决

流水线技术通过将指令执行划分为多个阶段并行处理来提升CPU吞吐率,但这会引入“冒险”(Hazard)问题,即后续指令因依赖关系无法在预期时钟周期正确执行。主要冒险类型包括数据冒险、控制冒险和结构冒险。其中,数据冒险和…

作者头像 李华
网站建设 2026/4/30 6:43:27

Netflix 风格的跨平台流媒体播放器

StreamBox Netflix 风格的跨平台流媒体播放器,对接 TVBox 生态片源。本仓库为 Monorepo,包含 Flutter 客户端和 JAR Bridge 中间服务。 预览 源码地址: https://github.com/huangj17/StreamBox-APP 仓库结构 目录说明技术栈READMEclient/Flutter 客户…

作者头像 李华
网站建设 2026/4/30 6:41:25

微信聊天记录永久保存完全指南:WeChatMsg三步导出你的数字记忆

微信聊天记录永久保存完全指南:WeChatMsg三步导出你的数字记忆 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/…

作者头像 李华