大厂必问 · 如何防止订单重复?

news2024/11/18 1:22:08

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

常见的重复提交场景

  1. 网络延迟:用户在提交订单后未收到确认,误以为订单未提交成功,连续点击提交按钮。
  2. 页面刷新:用户在提交订单后刷新页面,触发订单的重复提交。
  3. 用户误操作:用户无意中点击多次订单提交按钮。

防止重复提交的需求

  1. 幂等性保证:确保相同的请求多次提交只能被处理一次,最终结果是唯一的。
  2. 用户体验保障:避免由于重复提交导致用户感知的延迟或错误。

常用解决方案

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

后端幂等处理

  • 利用Token机制:在订单生成前生成一个唯一的Token,保证每个订单提交时只允许携带一次Token。
  • 基于数据库的唯一索引:通过对订单字段(如订单号、用户ID)创建唯一索引来防止重复数据的插入。
  • 分布式锁:使用Redis等分布式缓存加锁,保证同一时间只允许处理一个订单请求。

功能实践

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

功能原理与技术实现

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

请在此添加图片描述

Token机制

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

  1. Token生成:在用户开始提交订单时,服务器生成一个唯一的 OrderToken 并将其存储在 Redis 等缓存中,同时返回给客户端。
  2. Token验证:用户提交订单时,客户端会将 OrderToken 发送回服务器。服务器会验证此 OrderToken 是否有效。
  3. Token销毁:一旦验证通过,服务器会立即销毁 OrderToken,防止重复使用同一个Token提交订单。

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

Redis分布式锁

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

  1. Token存储:生成的Token可以存储在Redis中,Token的存活时间通过设置TTL(如10分钟),保证Token在一定时间内有效。
  2. Token校验与删除:当用户提交订单时,服务器通过Redis查询该Token是否存在,并立即删除该Token,确保同一个订单只能提交一次。

流程设计

  1. 用户发起订单请求时,后端生成一个唯一的Token(例如UUID),并将其存储在Redis中,同时将该Token返回给前端。
  2. 前端提交订单时,将Token携带至后端。
  3. 后端校验该Token是否有效,若有效则执行订单创建流程,同时删除Redis中的该Token,确保该Token只能使用一次。
  4. 如果该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=.html
spring.thymeleaf.mode=HTML
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=false

spring.redis.host=127.0.0.1
spring.redis.port=23456
spring.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
@Slf4j
public 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的唯一性和时效性保证了订单操作的幂等性。

  • Redis缓存:通过Redis的分布式锁和高并发处理能力,确保系统在高并发情况下仍然可以正常运行,并发订单提交的场景中不会出现Token重复使用问题。
  • UUID:使用UUID生成唯一的Token,保证Token的唯一性和安全性。
  • Token时效性:Token通过设置Redis的TTL(过期时间)来控制有效期,避免无效Token长期占用资源。

总结

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

  1. Token的唯一性与时效性:确保每次订单提交前都有唯一且有效的Token。
  2. Token的原子性验证与删除:在验证Token的同时删除它,防止同一个Token被多次使用。
  3. Redis的高效存储与分布式锁:通过Redis在高并发环境中提供稳定的锁机制,保证并发提交的准确性。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2169628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Dapper介绍及特性

一、Dapper介绍及特性 Dapper是一个.NET平台上的轻量级对象关系映射&#xff08;ORM&#xff09;工具&#xff0c;它通过扩展IDbConnection接口&#xff0c;提供了一系列的扩展方法来执行SQL查询并将结果映射到.NET对象中。Dapper以其高性能和简单易用著称&#xff0c;特别适合…

springboot中有哪些方式可以解决跨域问题

文章目录 什么是跨域解决方案CrossOrigin注解实现WebMvcConfigurer接口CorsFilter过滤器如何选择&#xff1f; 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 Talk is cheap &#xff0…

Keepalived+Nginx 高可用集群(双主模式)

1.基础环境配置 [rootlb1 ~]# systemctl stop firewalld # 关闭防火墙 [rootlb1 ~]# sed -i s/^SELINUX.*/SELINUXdisabled/ /etc/sysconfig/selinux # 关闭selinux&#xff0c;重启生效 [rootlb1 ~]# setenforce 0          …

计算机毕业设计 基于Hadoop的智慧校园数据共享平台的设计与实现 Python 数据分析 可视化大屏 附源码 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

深度拆解:如何在Facebook上做跨境电商?

国内社交媒体正在逐渐兴盛&#xff0c;海外也不例外。在数字营销的新时代&#xff0c;Facebook已成为跨境电商不可或缺的平台之一。通过Facebook的巨大流量&#xff0c;卖家可以更好的触及潜在消费者&#xff0c;以实现销售增长。本文就深度拆解一下&#xff0c;卖家如何利用Fb…

STM32基础学习笔记-DHT11单总线协议面试基础题7

第七章、DHT11: 单总线协!议 常见问题 1、DHT11是什么 &#xff1f;有什么特性 &#xff1f; 2、单总线协议是什么 &#xff1f;原理 &#xff1f;DHT11的单总线协议的组成 &#xff1f; ## 1、DHT11定义 单总线协议是一种用于在多个设备之间进行通信的协议&#xff0c;所有…

从 Shapley 到 SHAP — 数学理解

如何计算 SHAP 特征贡献的概述 假设你(玩家 1)和朋友(玩家 2)参加了一场 Kaggle 比赛,你最终赢得了 10,000 元的一等奖。现在,你想公平地分配这笔钱。你的朋友建议你平分。但是,你的超参数调整技能更出色。你相信你应该得到更大的份额,因为你为团队做出了更多贡献。考虑…

LeetCode每日一练 —— 88. 合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 思路&#xff1a;用 下标充当指针&#xff0c;…

排序--希尔排序

希尔排序介绍 希尔排序核心思想就是:1,分组;2,直接插入排序:越有序越快 希尔排序就是多次利用直接插入排序的一个排序算法. 希尔排序的算法思想:间隔式分组,利用直接插入排序让组内有序,然后缩小分组再次排序,直到组数为1希尔排序的理论基础就是直接插入排序越有序越快; 希尔排…

《向量数据库指南》——Zilliz迁移服务:一键解锁跨平台数据迁移新纪元

在数据驱动的时代背景下&#xff0c;非结构化数据的处理与迁移已成为企业数字化转型中不可或缺的一环。随着向量数据库技术的飞速发展&#xff0c;尤其是像Milvus这样的高性能向量数据库系统的广泛应用&#xff0c;如何高效、安全、准确地实现数据在不同系统间的迁移&#xff0…

软考高级:系统设计 - MDA 模型 AI 解读

生活化例子 想象一下&#xff0c;你要建造一栋房子。建房子需要三个阶段&#xff1a; CIM (概念阶段)&#xff1a;这是你想象中的房子。你大概知道房子需要几间卧室、厨房、卫生间&#xff0c;但是还没有详细的设计图。就像在脑海中有个大概的想法&#xff1a;我要建个温馨的…

Spring Boot应用:电子商务平台开发

第2章 关键技术简介 2.1 Java技术 Java是一种非常常用的编程语言&#xff0c;在全球编程语言排行版上总是前三。在方兴未艾的计算机技术发展历程中&#xff0c;Java的身影无处不在&#xff0c;并且拥有旺盛的生命力。Java的跨平台能力十分强大&#xff0c;只需一次编译&#xf…

2024年9月26日--- Spring-AOP

SpringAOP 在学习编程过程中&#xff0c;我们对于公共方法的处理应该是这样的一个过程&#xff0c;初期阶段如下 f1(){Date now new Date();System.out.println("功能执行之前的各种前置工作"now)//...功能代码//...功能代码System.out.println("功能执行之前…

局域网广域网,IP地址和端口号,TCP/IP 4层协议,协议的封装和分用

前言 在古老的年代&#xff0c;如果我们要实现两台机器进行数据传输&#xff0c; A员工就得去B员工的办公电脑传数据&#xff08;B休息&#xff0c;等A传完&#xff09;&#xff0c;这样就很浪费时间 所以能不能不去B的工位的同时&#xff0c;还能传数据。这时候网络通信就出来…

springboot异常(三):异常处理原理

&#x1f345;一、BasicErrorController ☘️1.1 描述 BasicErrorController是Springboot中默认的异常处理方法&#xff0c;无需额外的操作&#xff0c;当程序发生了异常之后&#xff0c;Springboot自动捕获异常&#xff0c;重新请求到BasicErrorController中&#xff0c;在B…

JS设计模式之桥接模式:搭建跨越维度的通路

引言 在软件开发中&#xff0c;我们经常遇到需要对不同的抽象类进行不同的实现的情况&#xff0c;而传统的对象嵌套并不是一个优雅且可扩展的解决方案&#xff0c;因此这正是桥接模式的用武之地。桥接模式通过将抽象与实现分离&#xff0c;使得它们可以独立变化&#xff0c;从…

前缀和(5)_和为k的子数组

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 前缀和(5)_和为k的子数组 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1. 题目…

列表控件QListWidget

显示模式 有两种显示模式&#xff0c;列表模式和图标模式 // 获取和设置显示模式 QListView::ViewMode viewMode() const void setViewMode(QListView::ViewMode mode) QListView::ViewMode有两个取值 QListView::ListMode 列表模式 QListView::IconMode 图标模式 交替背…

《银河战星:僵局》风灵月影修改器使用指南,轻松驾驭宇宙战场

在策略射击游戏《银河战星&#xff1a;僵局》中&#xff0c;合理利用风灵月影修改器能极大提升你的游戏体验。 以下是简明操作步骤&#xff0c;助你迅速上手&#xff0c;遨游星际&#xff1a; 1.下载安装&#xff1a; 首先&#xff0c;确保从正规渠道获取风灵月影修改器&…

冒泡排序-C语言

1.问题&#xff1a; 从小到大对10个数进行排序&#xff0c;要求使用冒泡排序实现。 2.解答&#xff1a; 排序规律有两种&#xff1a;一种是“升序”&#xff0c;从小到大&#xff1b;另一种是“降序”&#xff0c;从大到小。 3.代码&#xff1a; #include<stdio.h>//头…