防止订单重复提交需要从客户端和后端两个层面进行防护,具体方案如下:
一、前端防重机制
-
按钮禁用/加锁
在用户点击提交按钮时禁用按钮,或通过JavaScript加锁,防止多次点击。
-
Token机制
-
服务器生成唯一Token并存储在Redis中,客户端提交时携带该Token。
-
服务器验证Token有效性后销毁Token,确保同一请求仅处理一次。
-
二、后端幂等处理
-
数据库层面
-
唯一索引 :在订单表中添加唯一索引(如
request_id
),防止重复数据插入。 -
悲观锁 :使用
SELECT ... FOR UPDATE
锁定记录,避免并发场景下的重复操作。
-
-
分布式锁
使用Redis等工具对请求ID加锁,限定时间内仅处理一个请求。
-
主动查询机制
-
设置超时时间(如30秒),超时后主动查询支付结果。
-
使用定时任务扫描待处理订单,避免因网络延迟导致的重复处理。
-
-
接口幂等性
- 接收支付结果后,先查询数据库或缓存,若已处理则忽略后续请求。
三、其他注意事项
-
页面跳转方式 :使用重定向(
302
或303
)而非转发(301
),避免刷新页面重复请求。 -
缓存中间件 :引入Redis等缓存中间件,对请求ID进行去重处理。
-
防重注解 :通过自定义注解(如
@RepeatSubmit
)实现简单防重逻辑。
四、示例代码(Spring Boot + Redis)
@RestController
@RequestMapping("api")
public class OrderController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@PostMapping("/createOrder")
public ResponseEntity<String> createOrder(@RequestParam String requestId, @RequestBody Order order) {
// 检查请求ID是否已存在
if (redisTemplate.hasKey(requestId)) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("不允许重复提交");
}
// 加分布式锁
Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(requestId, "locked", 10, TimeUnit.SECONDS);
if (!lockAcquired) {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("系统繁忙,请稍后");
}
try {
// 业务逻辑处理
// ...
// 删除请求ID
redisTemplate.delete(requestId);
return ResponseEntity.ok("订单创建成功");
} finally {
// 释放锁
redisTemplate.delete(requestId);
}
}
}
通过上述方案,可有效防止订单重复提交,提升系统稳定性和用户体验。