稀土掘金技术社区 2024年12月24日
大厂必问 · 如何防止订单重复?
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文介绍了在电商系统中防止订单重复提交的常见场景和解决方案。文章重点讲解了如何利用Token机制和Redis分布式锁来确保订单操作的幂等性,避免因网络延迟、页面刷新或用户误操作导致的重复提交。通过详细的代码示例,展示了如何使用Spring Boot框架实现Token的生成、存储、验证和销毁,以及如何利用Redis的原子性操作来处理高并发场景下的订单提交。该方案旨在提高系统的稳定性和用户体验,确保订单处理的准确性和一致性。

🔑Token机制: 用户提交订单前,服务器生成唯一Token并存储在Redis中,同时返回给客户端。提交订单时,客户端携带Token,服务器验证后销毁Token,确保唯一性。

🔒Redis分布式锁: 利用Redis的原子性操作,保证高并发下对同一订单的操作不会冲突。Token存储在Redis中,并设置TTL,确保Token在一定时间内有效。

⚙️功能实现: 基于Spring Boot框架,使用RedisTemplate生成和验证Token。前端提交订单时,先获取Token,再提交订单,后端校验Token有效性,并删除Redis中的Token,防止重复提交。

原创 不惑_ 2024-12-24 08:30 重庆

点击关注公众号,“技术干货” 及时达!

前言

在电商系统或任何涉及订单操作的场景中,用户多次点击“「提交订单”按钮」可能会导致重复订单提交,造成数据冗余和业务逻辑错误,导致库存问题、用户体验下降或财务上的错误。因此,防止订单重复提交是一个常见需求。

常见的重复提交场景

    「网络延迟」:用户在提交订单后未收到确认,误以为订单未提交成功,连续点击提交按钮。

    「页面刷新」:用户在提交订单后刷新页面,触发订单的重复提交。

    「用户误操作」:用户无意中点击多次订单提交按钮。

防止重复提交的需求

    「幂等性保证」:确保相同的请求多次提交只能被处理一次,最终结果是唯一的。

    「用户体验保障」:避免由于重复提交导致用户感知的延迟或错误。

常用解决方案

「前端防重机制」:在前端按钮点击时禁用按钮或加锁,防止用户多次点击。

「后端幂等处理」

功能实践

Spring Boot 提供了丰富的工具和库,今天我们基于Spring Boot框架,可以利用 「Token机制」 和 「Redis分布式锁」 来防止订单的重复提交。

「功能原理与技术实现」

通过Redis的原子性操作,我们可以确保高并发情况下多个请求对同一个订单的操作不会冲突。

Token机制

Token机制是一种常见的防止重复提交的手段,通常的工作流程如下:

    「Token生成」:在用户开始提交订单时,服务器生成一个唯一的 OrderToken 并将其存储在 「Redis」 等缓存中,同时返回给客户端。

    「Token验证」:用户提交订单时,客户端会将 OrderToken 发送回服务器。服务器会验证此 OrderToken 是否有效。

    「Token销毁」:一旦验证通过,服务器会立即销毁 OrderToken,防止重复使用同一个Token提交订单。

这种机制确保每次提交订单时都需要一个有效且唯一的Token,从而有效防止重复提交。

Redis分布式锁

在多实例的分布式环境中,Token机制可以借助 「Redis」 来实现更高效的分布式锁:

    「Token存储」:生成的Token可以存储在Redis中,Token的存活时间通过设置TTL(如10分钟),保证Token在一定时间内有效。

    「Token校验与删除」:当用户提交订单时,服务器通过Redis查询该Token是否存在,并立即删除该Token,确保同一个订单只能提交一次。

流程设计

    用户发起订单请求时,后端生成一个唯一的Token(例如UUID),并将其存储在Redis中,同时将该Token返回给前端。

    前端提交订单时,将Token携带至后端。

    后端校验该Token是否有效,若有效则执行订单创建流程,同时删除Redis中的该Token,确保该Token只能使用一次。

    如果该Token已被使用或过期,则返回错误信息,提示用户不要重复提交。

功能实现

依赖配置(pom.xml)

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-web</artifactId></dependency>

application. properties

# Thymeleaf ??spring.thymeleaf.prefix=classpath:/templates/spring.thymeleaf.suffix=.htmlspring.thymeleaf.mode=HTMLspring.thymeleaf.encoding=UTF-8spring.thymeleaf.cache=false
spring.redis.host=127.0.0.1spring.redis.port=23456spring.redis.password=pwd

订单Token生成服务

「生成Token并存储到Redis」:当用户请求生成订单页面时,服务器生成一个唯一的UUID作为订单Token,并将其与用户ID一起存储在Redis中。

package com.example.demo.service;
import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Service;
import java.util.UUID;import java.util.concurrent.TimeUnit;
@Service@Slf4jpublic class OrderTokenService {
@Autowired private RedisTemplate<String, String> redisTemplate; // 生成订单Token public String generateOrderToken(String userId) { String token = UUID.randomUUID().toString(); // 将Token存储在Redis中,设置有效期10分钟 redisTemplate.opsForValue().set("orderToken:" + userId, token, 10, TimeUnit.MINUTES); return token; } // 验证订单Token public boolean validateOrderToken(String userId, String token) { String redisToken = redisTemplate.opsForValue().get("orderToken:" + userId); log.info("@@ 打印Redis中记录的redisToken :{} `VS` @@ 打印当前请求过来的token :{}", redisToken, token); if (token.equals(redisToken)) { // 验证成功,删除Token redisTemplate.delete("orderToken:" + userId); return true; } return false; }}

订单控制器

「订单提交与验证Token」:提交订单时,系统会检查用户传递的Token是否有效,若有效则允许提交并删除该Token,确保同一Token只能提交一次。

package com.example.demo.controller;
import com.example.demo.entity.Order;import com.example.demo.service.OrderTokenService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;
@RestController@RequestMapping("/order")public class OrderController {
@Autowired private OrderTokenService orderTokenService; // 获取订单提交的Token @GetMapping("/getOrderToken") public ResponseEntity<String> getOrderToken(@RequestParam String userId) { String token = orderTokenService.generateOrderToken(userId); return ResponseEntity.ok(token); } // 提交订单 @PostMapping("/submitOrder") public ResponseEntity<String> submitOrder(Order order) { // 校验Token if (!orderTokenService.validateOrderToken(order.getUserId(), order.getOrderToken())) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("订单重复提交,请勿重复操作"); }
// 此处处理订单逻辑 // ... // 假设订单提交成功 return ResponseEntity.ok("订单提交成功"); }}

前端实现

前端通过表单提交订单,并在每次提交前从服务器获取唯一的订单Token:


<script>
document.getElementById('orderForm').addEventListener('submit', function(event) { event.preventDefault();
const userId = document.getElementById('userId').value; if (!userId) { alert("请填写用户ID"); return; }
// 先获取Token,再提交订单 fetch(`/order/getOrderToken?userId=${userId}`) .then(response => response.text()) .then(token => { document.getElementById('orderToken').value = token;
// 提交订单请求 const formData = new FormData(document.getElementById('orderForm')); fetch('/order/submitOrder', { method: 'POST', body: formData }) .then(response => response.text()) .then(result => { document.getElementById('message').textContent = result; }) .catch(error => { document.getElementById('message').textContent = '订单提交失败,请重试'; }); }) .catch(error => { document.getElementById('message').textContent = '获取Token失败'; });});
</script>

为了验证功能,我们在代码中增加  Thread.sleep(2000); 来进行阻塞。

然后快速点击提交表单,可以看到提示表单重复提价的信息

技术选型与优化: 通过Redis结合Token机制,我们有效地防止了订单的重复提交,并通过Token的唯一性和时效性保证了订单操作的幂等性。

总结

防止订单重复提交的关键在于:

    「Token的唯一性与时效性」:确保每次订单提交前都有唯一且有效的Token。

    「Token的原子性验证与删除」:在验证Token的同时删除它,防止同一个Token被多次使用。

    「Redis的高效存储与分布式锁」:通过Redis在高并发环境中提供稳定的锁机制,保证并发提交的准确性。

这套基于Token机制和Redis的解决方案具有简单、高效、可扩展的特点,适合各种高并发场景下防止重复订单提交。

点击关注公众号,“技术干货” 及时达!

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

重复提交 Token机制 Redis 分布式锁 Spring Boot
相关文章