当前位置: 首页 > news >正文

我是怎么设计一个防重复提交机制的(库存出库场景)

我是怎么设计一个防重复提交机制的(库存出库场景)

一、背景

我在做一个库存系统的时候,遇到了一个很头疼的问题:用户提交出库单时,经常因为网络延迟、页面卡顿、或者手速太快,导致同一笔请求被重复提交。

这直接带来的后果就是:

  • 同一批商品被多次出库
  • Redis 与数据库库存不一致
  • 系统出现超卖、数据异常等问题

这些可不是小问题,尤其是库存扣错了,后续很难对账、补救成本也高。

所以,我决定设计一个通用、有效、可扩展的防重复提交机制,来解决这个问题。


二、我是怎么做的?

我最终采用的是 Token + Redis 的方式,实现了一个简单、安全、高效的幂等性控制方案

整个流程是这样的:

  1. 前端点击“出库”按钮前,先调用接口获取一个 Token
  2. 后端生成唯一 Token,并写入 Redis,设置有效期
  3. 前端带上 Token 提交出库请求
  4. 后端校验 Token 是否存在,存在则执行业务逻辑并删除 Token
  5. Token 用完即失效,防止重复提交

1. Token 生成规则

我使用 UUID 生成唯一 Token:

String token = UUID.randomUUID().toString().replace("-", "");

UUID 生成的 Token 基本可以保证全局唯一,长度适中,适合用作请求标识。


2. Redis Key 设计

使用 token:${token} 作为 Redis Key,例如:

SET token:abc123xyz 1 EX 60 NX
  • EX 60:设置过期时间为 60 秒,防止 Token 长时间堆积
  • NX:仅当 Key 不存在时才设置成功,防止并发重复设置

这样设计的好处是:

  • 每个 Token 只能使用一次
  • 自动过期,无需手动清理
  • 可扩展性强,可结合用户 ID、业务类型进一步隔离

3. 获取 Token 接口

@RestController
@RequestMapping("/stock")
public class TokenController {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@GetMapping("/token")public Map<String, String> getToken() {String token = UUID.randomUUID().toString().replace("-", "");String redisKey = "token:" + token;redisTemplate.opsForValue().set(redisKey, "1", 60, TimeUnit.SECONDS);return Collections.singletonMap("token", token);}
}

前端调用这个接口获取 Token,然后带着 Token 去提交出库请求。


4. 出库接口(带 Token 校验)

@PostMapping("/out")
public ResponseEntity<String> batchOutStock(@RequestParam String token,@RequestBody List<OutStockItem> items) {String redisKey = "token:" + token;// 1. 校验 Token 是否存在Boolean exists = redisTemplate.hasKey(redisKey);if (exists == null || !exists) {return ResponseEntity.status(HttpStatus.FORBIDDEN).body("请勿重复提交");}// 2. 删除 Token(防止重复提交)redisTemplate.delete(redisKey);// 3. 执行出库逻辑stockService.batchDeductStock(items);return ResponseEntity.ok("出库成功");
}

这个接口的逻辑很清晰:

  • Token 不存在,说明已经提交过,直接拒绝
  • Token 存在,说明是第一次提交,执行业务逻辑后删除 Token
  • Token 用完即失效,防止复用

5. 前端配合

前端在点击“出库”按钮前,先调用接口获取 Token:

axios.get('/stock/token').then(res => {const token = res.data.token;axios.post('/stock/out', items, {params: { token }}).then(response => {console.log('出库成功');}).catch(err => {console.error('出库失败,请重试');});
});
  • 每次提交使用新的 Token
  • Token 用完即失效,防止复用
  • 若请求失败,前端提示用户重新提交

三、遇到的难点与优化点

1. Token 被恶意刷取怎么办?

  • 每个 Token 只能使用一次
  • Token 用完即失效
  • 可结合用户 ID、业务类型进行 Token 隔离,防止刷 Token

2. Token 删除后业务执行失败怎么办?

  • 如果 Token 删除了,但出库失败,前端提示用户“提交失败,请重新提交”
  • 不建议自动恢复 Token,避免引入幂等性问题
  • 可记录请求状态日志,供后续查询或补偿使用

3. 如何防止 Token 被滥用?

  • 每次请求生成新的 Token,旧 Token 无法复用
  • Redis 自动过期,避免 Token 堆积
  • 后端限制单位时间内获取 Token 的频率(可选)

四、实现亮点

亮点说明
简单易用前后端配合简单,易于实现
安全可靠Token 用完即失效,防止复用
高并发支持Redis 快速判断是否存在,性能好
可扩展性强可结合用户ID、业务类型进一步优化

五、实际效果如何?

这套方案在我们系统中上线后,重复提交导致的超卖问题基本消失,日志里也不再出现“库存不一致”的异常。

  • 接口幂等性得到保障
  • 用户体验提升,前端提示更清晰
  • 后端逻辑稳定,无并发问题

六、总结

通过基于 Token 的防重复提交机制,我实现了一个通用、安全、高效的幂等性控制方案,适用于库存出库、订单提交、支付操作等关键业务场景。

如果你也在开发类似的库存系统、订单系统或支付系统,不妨尝试这套方案。希望这篇文章能对你有所帮助!

http://www.dtcms.com/a/287836.html

相关文章:

  • 【C语言进阶】结构体
  • Windows原生环境配置Claude Code MCP(通过JSON)
  • 简单易懂,快表 详解
  • 有趣的算法题:有时针分针秒针的钟表上,一天之内,时针和分针可重合多少次?分别在什么时刻重合?
  • 【Python】Pandas
  • rustdesk客户端编译
  • QT窗口(7)-QColorDiag
  • 根据ARM手册,分析ARM架构中,原子操作的软硬件实现的底层原理
  • tailscale在ubuntu22.04上使用
  • Unity物理响应函数与触发器
  • LVS详解
  • GitHub 趋势日报 (2025年07月18日)
  • 【图像处理基石】什么是小波变换?
  • CAN总线负载率计算及示例说明
  • 【CVPR2025】计算机视觉|RORem:让物体移除“脱胎换骨”!
  • Streamlit 官翻 2 - 开发指南 Develop Concepts
  • 昇思+香橙派 AI 开发实践:DeepSeek 全流程指南(基于 openEuler)
  • lesson18:Python函数的闭包与装饰器(难)
  • TypeScript 泛型详解:从基础到实战应用
  • 3.条件判断:让程序学会做选择
  • Web开发 03
  • import.meta.glob 与 import.meta.env、import的几个概念的简单回顾
  • react+antd+表格拖拽排序以及上移、下移、移到顶部、移到底部
  • 408数据结构强化(自用)
  • 实现el-select下拉框,下拉时加载数据
  • MYSQL 第一次作业
  • 《命令行参数与环境变量:从使用到原理的全方位解析》
  • Flink实时流量统计:基于窗口函数与Redis Sink的每小时PV监控系统(学习记录)
  • UniApp 自定义导航栏:解决安全区域适配问题的完整实践
  • C++基于muduo库从零实现Rpc框架