无标题文档
来源:无标题文档
口播文案:
重复提交问题如何避免面试问得很多!为什么前端加了防止手抖多次提交,后端还会出现重复订单?Redis分布式锁防止重复提交时服务突然宕机,key 没删导致用户没法重试怎么办?数据库加了唯一索引,还是拦不住重复提交,问题到底出在哪?这些防止重复提交的坑,踩过一次就够!今天 3 分钟,不仅给你 4 个能直接抄的方案,更带你避开每个方案的隐藏雷区,从单实例到分布式架构,彻底解决重复提交问题!
首先看单实例场景 —— 本地缓存方案,别随便用个 HashMap 就上!必须用ConcurrentHashMap(线程安全,避免多线程下的并发问题),自定义一个 @NoRepeatSubmit 注解,配合 AOP 拦截所有加了注解的接口。拦截时把 “用户 ID + 接口名 + 请求参数 MD5” 作为 key(只拼用户 ID 和接口名会误判不同参数的请求),存到 Map 里,再设个过期时间(比如 3 秒,比接口处理时间长一点,避免请求还在处理 key 就失效)。重复请求一来,发现 key 存在就直接返回 “请勿重复提交”。这个方案的坑在哪? 只能单实例用!如果部署多台服务,Instance A 存了 key,Instance B 没存,照样会出现重复提交,千万别在分布式场景瞎用!
分布式场景必须上 Redis,核心是用setIfAbsent 原子操作(相当于加锁,不存在才存 key,避免并发问题),而不是普通的 set。key 还是 “用户 ID + 接口名 + 请求参数 MD5”,再设个过期时间(比如 5 秒,兜底防止服务宕机 key 没删)。业务执行完要主动删 key 吗?必须删! 这样用户如果提交失败,能快速重试;但如果业务执行失败(比如库存不足),别删 key,让用户稍后再试,避免重复失败。这里有个关键细节: 别用 “业务执行完再设 key” 的逻辑,一定要 “先设 key 再执行业务”,否则两个请求同时进来,都判断 key 不存在,还是会重复执行业务!
核心场景(比如订单、支付)必须加双重保障 —— 数据库唯一索引!光靠缓存防重不够,万一缓存抽风(比如 Redis 集群切换),还有数据库最后一道防线。索引怎么加?别乱加!要建 “用户 ID + 业务唯一标识” 的联合唯一索引 (比如订单表加 “user_id+order_no”,支付表加 “user_id+pay_no”),单一字段(比如 order_no)不够,因为可能不同用户生成相同的业务号(虽然概率低,但要杜绝)。注意: 唯一索引冲突会抛 SQL 异常,要在全局异常处理器里捕获,返回 “操作太频繁,请稍后再试”,别直接给用户抛 500 错误!
前后端分离场景更优雅的方案 ——Token 验证!前端进入提交页(比如下单页)时,先调用后端 “获取 Token” 接口,后端生成一个 UUID 作为 Token,存到 Redis(设 10 分钟过期,和页面有效期匹配),返回给前端。前端提交时,把 Token 放在请求头或参数里,后端先查 Redis 有没有这个 Token:有就删除 Token(保证只用一次),继续执行业务;没有就拦截。这个方案还有个隐藏好处: 能顺便防 CSRF 攻击!因为 Token 是动态获取的,不是 Cookie 自动携带的,恶意网站拿不到 Token,没法伪造请求。
最后总结选型铁律:单实例用 “本地缓存 + AOP”,分布式用 “Redis 原子操作”,核心场景加 “数据库唯一索引” 双重保障,前后端分离用 “Token 验证”。每个方案都有现成代码,不用自己踩坑!评论区留 “防重”,我把 AOP 拦截器、Redis 工具类、全局异常处理的完整代码包发你,下次再遇到重复提交,直接怼方案就行,再也不用凌晨爬起来删订单擦屁股!