Spring Boot 接口幂等实战:用 Token + Redis 防重复提交
📅 2026/7/23 22:49:29
👁️ 次浏览
Spring Boot 接口幂等实战:用 Token Redis 防重复提交用户手抖双击了「提交订单」,或者网络卡了页面转圈他又点一次,结果后台生成了两条一模一样的订单。这是几乎每个交易类接口都要面对的问题:同一个业务操作被重复执行。前端加个 loading 禁用按钮只能挡君子,挡不住重发请求、挡不住网络重试、更挡不住恶意刷单。真正靠谱的做法是让接口本身幂等——同一个请求打多少次,结果都只生效一次。这篇用最常见的「Token Redis」方案手把手实现一遍。先看朴素做法为什么不够第一反应可能是「查一下数据库有没有相同订单」:// ❌ 查重再插入,并发下依然会重复if(orderMapper.findByBizId(bizId)null){orderMapper.insert(order);}问题在于「查」和「插」是两步,不是原子的。两个并发请求可能同时查到「没有」,然后同时插入,查重形同虚设。加数据库唯一索引能兜底,但那是最后一道防线,还会给库带来大量重复写压力。我们要在更前面把重复请求挡掉。方案思路:一次性 Token核心是「令牌只能用一次」:用户进入表单页时,先向后端申请一个唯一 Token,后端把它存进 Redis 并返回给前端。提交表单时,前端把 Token 放在请求头里带上。后端校验:Token 存在就原子地删除它并放行;不存在(已被删)就是重复提交,拒绝。关键就在第 3 步的「校验并删除」必须是原子操作,否则并发下又会出现两个请求都查到 Token 还在、都通过的情况。第一步:提供获取 Token 的接口RestControllerRequestMapping(/api/idempotent)publicclassTokenController{ResourceprivateStringRedisTemplateredis;privatestaticfinalStringPREFIXidem:token:;GetMapping(/token)publicMapString,StringgetToken(){StringtokenUUID.randomUUID().toString();// 存 Redis 并设过期,防止申请了不用堆积垃圾 keyredis.opsForValue().set(PREFIXtoken,1,5,TimeUnit.MINUTES);returnMap.of(token,token);}}Token 一定要设过期时间,用户申请了不提交是常态,不能让它永久占内存。第二步:用 Lua 脚本保证「校验删除」原子先看错误写法,分两步会有并发漏洞:// ❌ get 和 delete 之间有窗口,两个请求可能都 get 到 1if(redis.opsForValue().get(key)!null){redis.delete(key);// 放行……但可能已经有另一个请求也走到这}正确做法是用 Lua 脚本把「判断存在 删除」放在 Redis 里一次执行,Redis 单线程执行脚本天然原子:ComponentpublicclassIdempotentChecker{ResourceprivateStringRedisTemplateredis;// KEYS[1] 存在则删除并返回 1,不存在返回 0privatestaticfinalStringLUAif redis.call(get, KEYS[1]) then return redis.call(del, KEYS[1]) else return 0 end;privatefinalDefaultRedisScriptLongscriptnewDefaultRedisScript(LUA,Long.class);/** 返回 true 表示这是首次、放行;false 表示重复提交 */publicbooleancheck(Stringtoken){Longrredis.execute(script,Collections.singletonList(idem:token:token));returnr!nullr0;}}第三步:用注解 拦截器优雅接入不想每个接口都手写校验,可以做个注解,靠 AOP 统一拦:Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)publicinterfaceIdempotent{}AspectComponentpublicclassIdempotentAspect{ResourceprivateIdempotentCheckerchecker;Around(annotation(idempotent))publicObjectaround(ProceedingJoinPointpjp,Idempotentidempotent)throwsThrowable{HttpServletRequestreq((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();Stringtokenreq.getHeader(Idempotent-Token);if(tokennull||!checker.check(token)){// Token 缺失或已被用过,判定为重复提交thrownewIllegalStateException(请勿重复提交);}returnpjp.proceed();}}业务接口只需一个注解:PostMapping(/order)Idempotent// 加上它,重复提交自动被挡publicOrdercreateOrder(RequestBodyOrderReqreq){returnorderService.create(req);}几个容易忽略的坑Token 校验要放在业务执行之前。如果先执行业务再删 Token,业务耗时期间的重复请求还是会漏进来。过期时间要覆盖真实操作时长。用户填表可能填很久,5 分钟太短会误伤,按业务合理设置。Token 方案挡的是「重复提交」,不是「重复请求同一资源」。对于「同一笔支付回调可能被支付方重推多次」这种,业务方没法帮你申请 Token,应该用业务唯一键(如支付流水号)做幂等,思路一样:拿业务键去 RedissetIfAbsent,成功才处理。数据库唯一索引仍要保留。Redis 挂了、key 被误删都可能让防线失效,唯一索引是最后的兜底。小结Token Redis 幂等方案的要点:进页面先申请一次性Token 存 Redis,提交时带回来。校验用Lua 脚本原子地「判断删除」,别用 get 完再 delete,那有并发窗口。用注解 AOP统一接入,业务代码零侵入。Redis 是第一道防线,数据库唯一索引是最后兜底,两者都要有。一句话记忆点:幂等的本质是「让重复请求识别得出、且只有一个能通过」,而识别通过这一步必须原子——Lua 脚本就是干这个的。
Next.js 图片优化实战:next/image 的 sizes、priority 与 CLS 修复
页面加载时图片「跳一下」,把下面的文字往下顶——这就是 CLS(累积布局偏移),Lighthouse 里扣分最狠的指标之一。用 Next.js 的人常以为把 <img> 换成 next/image 就万事大吉,结果打开控制台一看:图片还…
📅 2026/7/23 22:49:29
1. 项目背景与痛点解析"百考通"这个项目名称本身就暗示了其核心定位——面向频繁需要撰写各类考核文档的用户群体。在教育与职场场景中,实践报告和实习总结是最常见的两种考核文档类型,也是许多学生和职场新人最头疼的写作任务。我接触过大量实…
📅 2026/7/23 22:48:29
“课题申报有没有套路?有。但套路不是坏事,就像写信有格式、写文章有规范一样——关键是套路里装的是不是真东西。我自己也是花了半年时间,把立项的、没立项的放在一起逐项对比,才摸清门道——说白了就是搞清楚评审在看什么&#…
📅 2026/7/23 22:48:29
1. 项目概述:为什么我们要重新审视字符串在虚幻引擎(Unreal Engine)的开发中,FString、FName和FText这三个字符串类型是每天都要打交道的“老朋友”。从UE4到UE5,引擎底层经历了翻天覆地的变化,尤其是引入了…
📅 2026/7/24 2:27:50
1. 先搞清楚这个资源包到底包含什么看到“神探狄仁杰第二部30评论自提网盘版”这个标题,很多人第一反应可能是剧集下载,但“评论自提”这个关键词其实更值得关注。这通常意味着资源包里除了剧集本身,还包含了观众评论或观后感文档。对于想深入…
📅 2026/7/24 2:27:50
1. 项目概述与CRC技术背景在嵌入式系统开发中,数据完整性校验是确保通信可靠、存储安全的关键一环。无论是通过UART、SPI、I2C接收一串传感器数据,还是从外部Flash读取一段配置参数,你都得心里有底:这数据在路上没被干扰、没出岔子…
📅 2026/7/24 2:27:50
1. 项目概述:当论文写作遇上AI数据分析在学术写作的战场上,数据分析往往是最耗费精力的环节。传统的数据处理流程需要研究者手动清洗数据、选择算法、调试参数、可视化结果,这个过程可能占据整个研究周期的60%以上时间。而"书匠策AI&quo…
📅 2026/7/24 2:27:50
1. 项目概述与核心挑战在嵌入式系统开发,尤其是涉及USB接口的工控、数据采集或通信设备项目中,我们经常会遇到一个看似基础、实则暗藏玄机的问题:为什么我做的USB设备插到电脑上,有时候能被正确识别,有时候却弹出一个未…
📅 2026/7/24 2:27:50
说实话,以前我也迷信过那些界面炫酷、广告满天飞的定位APP。每次打开,好家伙,弹窗比内容还多。想导个KML文件?先充个VIP再说。这哪是玩定位,简直是玩心态。直到我遇到了Geo Tracker安卓,真有一种相见恨晚的感觉。这玩意儿,不整那些虚头巴脑的。它就是纯粹的工具,像一把…
📅 2026/7/24 2:26:55
大家好,我是一名编程初学者,同时这也是我编程学习之路上的第一篇博客。在这里,我想要向大家介绍我的一些想法和规划。a.自我介绍我是一个刚刚接触编程的新手,目前在学习c语言,我对编程世界充满了强烈的好奇。当然&…
📅 2026/7/24 0:00:05
srcampy /libsrcampy 名称释义先明确结论: 官方文档没有公布标准化英文全称,是地平线内部项目缩写;行业公认拆解如下:srcampy Source Amplifier Python bindingsrc Source(图像源:MIPI Sensor、视频源&am…
📅 2026/7/24 0:01:06
该案例基于Highcharts scatter3d 三维散点图实现空间立方体散点可视化,核心特色:三维 X/Y/Z 三轴空间,所有散点分布在 0~10 立方体空间内;散点使用径向渐变实现立体 3D 圆球质感;支持鼠标 / 触屏拖拽画布,…
📅 2026/7/24 0:01:06
1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…
📅 2026/7/24 1:07:52
1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…
📅 2026/7/24 1:07:52
更多请点击:
https://intelliparadigm.com
第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…
📅 2026/7/24 1:07:52
目录
第一步:选对模板,省心一半
第二步:打开扫码点餐功能
开启功能按钮
桌台管理与桌码生成
第三步:个性化设计,打造品牌感
调整点餐页面
设置点餐规则 你还在让顾客站着排队点餐吗?2025年ÿ…
📅 2026/7/23 7:06:56
在业务中快速构建一个能理解私有文档、准确回答专业问题的智能助手,是很多开发团队面临的共同挑战。传统方案往往需要从零开始搭建复杂的 RAG(检索增强生成)系统,涉及文档解析、向量化、检索、大模型调用等多个环节,整…
📅 2026/7/23 17:07:28
FAE放射组学分析工具:医学影像特征探索的完整解决方案 【免费下载链接】FAE FeAture Explorer 项目地址: https://gitcode.com/gh_mirrors/fae/FAE
你是否曾经面对海量医学影像数据感到无从下手?想要从CT、MRI等影像中提取有价值的定量特征&#…
📅 2026/7/23 5:06:50