欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot利用Redis實(shí)現(xiàn)防止訂單重復(fù)提交的解決方案

 更新時間:2024年10月22日 08:53:00   作者:聶 可 以  
在涉及訂單操作的業(yè)務(wù)中,防止訂單重復(fù)提交是一個常見需求,用戶可能會因誤操作或網(wǎng)絡(luò)延遲而多次點(diǎn)擊提交訂單按鈕,導(dǎo)致訂單重復(fù)提交,所以本文給大家介紹了SpringBoot利用Redis實(shí)現(xiàn)防止訂單重復(fù)提交的解決方案,需要的朋友可以參考下

0. 前言

在涉及訂單操作的業(yè)務(wù)中,防止訂單重復(fù)提交是一個常見需求

用戶可能會因誤操作或網(wǎng)絡(luò)延遲而多次點(diǎn)擊提交訂單按鈕,導(dǎo)致訂單重復(fù)提交,造成數(shù)據(jù)冗余,而且訂單通常與庫存緊密關(guān)聯(lián),重復(fù)提交訂單不僅會影響用戶體驗(yàn),還有可能引發(fā)庫存管理上的混亂,甚至導(dǎo)致財務(wù)數(shù)據(jù)出現(xiàn)偏差,帶來一系列潛在的經(jīng)濟(jì)風(fēng)險

1. 常見的重復(fù)提交訂單的場景

  1. 網(wǎng)絡(luò)延遲:由于網(wǎng)絡(luò)問題,用戶在提交訂單后頁面沒有發(fā)生變化,而且沒有收到通知,用戶誤以為訂單沒有提交成功,連續(xù)點(diǎn)擊提交按鈕
  2. 刷新頁面:用戶提交訂單后刷新頁面,再次提交相同的訂單
  3. 用戶誤操作:用戶無意中點(diǎn)擊多次訂單提交按鈕
  4. 惡意攻擊:大量請求繞過前端頁面直接到達(dá)后端

2. 防止訂單重復(fù)提交的解決方案

2.1 前端(禁用按鈕)

用戶點(diǎn)擊提交訂單按鈕后,在成功跳轉(zhuǎn)到支付頁面之前,禁用提交訂單按鈕,防止用戶多次執(zhí)行提交訂單

禁用提交訂單按鈕只能避免一部分訂單重復(fù)提交的情況,如果用戶點(diǎn)擊支付按鈕之后刷新頁面,依然是可以重復(fù)下單的,要想完全解決訂單重復(fù)提交的問題,后端也要做相應(yīng)的處理

2.2 后端

我們可以借助 Redis 實(shí)現(xiàn)防止訂單重復(fù)提交的功能

  • 生成訂單前的操作:在訂單生成之前,我們以業(yè)務(wù)名+商家唯一標(biāo)識+商品唯一標(biāo)識+用戶唯一標(biāo)識形成的字符串為 key、以任意一個字符串作為 value,將鍵值對保存到 Redis 中,并為鍵值對設(shè)置一個合理的過期時間(過期時間可以根據(jù)業(yè)務(wù)需求來設(shè)定,以確保在用戶完成訂單操作之前,鍵值對始終有效)
  • 訂單處理完成后的操作:一旦訂單成功支付或者被取消,我們就從 Redis 中刪除對應(yīng)的鍵,釋放占用的內(nèi)存資源,防止在鍵值對過期之前對訂單狀態(tài)產(chǎn)生誤判

key 的形式不唯一,但要確保一個 key 對應(yīng)一個訂單

當(dāng)客戶端發(fā)起提交訂單的請求時,后端會檢查 Redis 中是否存在對應(yīng)的鍵

  • 如果存在,表明該訂單已經(jīng)被提交過,這是一個重復(fù)的提交請求,系統(tǒng)將拒絕此次請求,不會生成新的訂單
  • 如果不存在,說明這是一個新的訂單提交請求,系統(tǒng)將繼續(xù)執(zhí)行訂單生成的流程,并存儲新的鍵值對到 Redis 中,以防止后續(xù)的重復(fù)提交

3. 在SpringBoot項(xiàng)目中利用Redis實(shí)現(xiàn)防止訂單重復(fù)提交

本次演示的后端環(huán)境為:JDK 17.0.7 + SpringBoot 3.0.2

3.1 引入依賴

Redis

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

Web

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

3.2 編寫配置文件

application.yml(Redis 單機(jī))

spring:
  data:
    redis:
      host: localhost
      port: 6379
      password: 123456
      timeout: 5000ms
      database: 0

server:
  port: 10016

application.yml(Redis 集群)

spring:
  data:
    redis:
      cluster:
        nodes: 127.0.0.1:6379

server:
  port: 10016

3.3 OrderService.java

利用 Redis 提供的 setnx 指令

在這里插入圖片描述

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class OrderService {

    private final StringRedisTemplate stringRedisTemplate;

    public OrderService(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public void generateToken(String key) {
        stringRedisTemplate.opsForValue().setIfAbsent(key, "uniqueTokenForOrder", 10, TimeUnit.MINUTES);
    }

    public boolean isOrderDuplicate(String token) {
        return Boolean.TRUE.equals(stringRedisTemplate.hasKey(token));
    }

}

3.4 OrderController.java

在這里插入圖片描述

import cn.edu.scau.pojo.SubmitOrderDto;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/order")
public class OrderController {

    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @PostMapping("/pay")
    public ResponseEntity<String> pay(@RequestBody SubmitOrderDto submitOrderDto) {
        String key = "order:" + submitOrderDto.getBusinessId() + ":" + submitOrderDto.getGoodsId() + ":" + submitOrderDto.getUserId();
        if (orderService.isOrderDuplicate(key)) {
            return ResponseEntity.ok("訂單重復(fù)提交,請勿重復(fù)操作,您可以確認(rèn)一下有沒有未支付的相同訂單");
        }

        orderService.generateToken(key);

        // 處理訂單邏輯

        return ResponseEntity.ok("訂單提交成功");
    }

}

SubmitOrderDto.java

public class SubmitOrderDto {

    private String businessId;

    private String goodsId;

    private String userId;

    public String getBusinessId() {
        return businessId;
    }

    public void setBusinessId(String businessId) {
        this.businessId = businessId;
    }

    public String getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(String goodsId) {
        this.goodsId = goodsId;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    @Override
    public String toString() {
        return "SubmitOrderDto{" +
                "businessId='" + businessId + '\'' +
                ", goodsId='" + goodsId + '\'' +
                ", userId='" + userId + '\'' +
                '}';
    }

}

3.5 index.html

簡單起見,本次演示前后端不分離,index.html 文件存放在 resources/static 目錄下

在這里插入圖片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>防止訂單重復(fù)提交</title>
    <style>
        body, html {
            height: 100%;
            margin: 0;
            font-family: 'Arial', sans-serif;
            background-color: #f4f4f9;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .container {
            width: 100%;
            max-width: 400px; /* 設(shè)置最大寬度 */
            padding: 50px 0;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        .button-container, .result-container {
            width: 100%;
            max-width: 300px; /* 按鈕和結(jié)果顯示文本同寬 */
            margin-bottom: 20px; /* 添加底部外邊距 */
        }

        button {
            width: 276px;
            height: 67px;
            padding: 20px;
            font-size: 18px;
            color: #ffffff;
            background-color: #6a8eff;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            outline: none;
            transition: background-color 0.3s ease;
        }

        button:hover {
            background-color: #527bff;
        }

        #result {
            padding: 20px;
            font-size: 18px;
            color: #333333;
            background-color: #ffffff;
            border: 1px solid #e1e1e1;
            border-radius: 8px;
            text-align: center;
            box-sizing: border-box;
            width: 276px;
            height: 67px;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="button-container">
        <button onclick="submitOrder()">提交訂單</button>
    </div>
    <div class="result-container" id="result"></div>
</div>

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    const submitOrder = () => {
        // 點(diǎn)擊按鈕后有0.5秒的加載效果
        document.getElementById('result').innerText = '正在提交訂單...'
        let timer = setTimeout(() => {
            axios
                .post('/order/pay', {
                    businessId: '123456',
                    goodsId: '123456',
                    userId: '123456'
                })
                .then((response) => {
                    console.log('response =', response);
                    document.getElementById('result').innerText = response.data
                })
                .catch((error) => {
                    document.getElementById('result').innerText = '提交失敗,請重試。'
                    console.error('error =', error);
                })

            clearTimeout(timer)
        }, 500)
    }
</script>
</body>
</html>

4. 需要注意的問題

  • 如果在訂單生成過程中出現(xiàn)錯誤,要確保有一個機(jī)制能夠回滾之前的操作,比如刪除已經(jīng)插入 Redis 的鍵
  • 避免因意外情況導(dǎo)致鍵未被及時清理,影響后續(xù)請求
  • 如果處理的邏輯比較復(fù)雜,我們可以考慮使用通過切面(AOP)來解決,在切面中編寫防止訂單重復(fù)提交的代碼

以上就是SpringBoot利用Redis實(shí)現(xiàn)防止訂單重復(fù)提交的解決方案的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Redis訂單重復(fù)提交的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Springboot使用POI實(shí)現(xiàn)導(dǎo)出Excel文件示例

    Springboot使用POI實(shí)現(xiàn)導(dǎo)出Excel文件示例

    本篇文章主要介紹了Springboot使用POI實(shí)現(xiàn)導(dǎo)出Excel文件示例,非常具有實(shí)用價值,需要的朋友可以參考下。
    2017-02-02
  • 淺談Java中Int、Integer、Integer.valueOf()、new Integer()之間的區(qū)別

    淺談Java中Int、Integer、Integer.valueOf()、new Integer()之間的區(qū)別

    本文主要介紹了淺談Java中Int、Integer、Integer.valueOf()、new Integer()之間的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • SpringCloud實(shí)現(xiàn)灰度發(fā)布的方法步驟

    SpringCloud實(shí)現(xiàn)灰度發(fā)布的方法步驟

    本文主要介紹了SpringCloud實(shí)現(xiàn)灰度發(fā)布的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • Spring Boot Jar 包部署腳本的實(shí)例講解

    Spring Boot Jar 包部署腳本的實(shí)例講解

    在本篇文章里小編給大家整理的是一篇關(guān)于Spring Boot Jar 包部署腳本的實(shí)例講解內(nèi)容,對此有興趣的朋友們可以跟著學(xué)習(xí)下。
    2021-12-12
  • SpringBoot?整合?Grizzly的過程

    SpringBoot?整合?Grizzly的過程

    Grizzly?是一個高性能的、異步的、非阻塞的?HTTP?服務(wù)器框架,它可以與?Spring?Boot?一起提供比傳統(tǒng)的?Tomcat?或?Jetty?更高的吞吐量和更低的延遲,這篇文章主要介紹了SpringBoot?整合?Grizzly的過程,需要的朋友可以參考下
    2025-01-01
  • Java中File的實(shí)例詳解

    Java中File的實(shí)例詳解

    這篇文章主要介紹了Java中File的實(shí)例詳解的相關(guān)資料,File代表文件或者目錄的類,這里對使用方法進(jìn)行詳細(xì)介紹,需要的朋友可以參考下
    2017-08-08
  • Java排序算法之桶排序算法解析

    Java排序算法之桶排序算法解析

    這篇文章主要介紹了Java排序算法之桶排序算法解析,桶排序 (Bucket sort)或所謂的箱排序,是一個排序算法,工作原理是將數(shù)組分到有限數(shù)量的桶子里,每個桶子再個別排序,有可能再使用別的排序算法或是以遞歸方式繼續(xù)使用桶排序進(jìn)行排序,需要的朋友可以參考下
    2023-10-10
  • Java微信公眾平臺之群發(fā)接口(高級群發(fā))

    Java微信公眾平臺之群發(fā)接口(高級群發(fā))

    這篇文章主要為大家詳細(xì)介紹了Java微信公眾平臺之群發(fā)接口,高級群發(fā)功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • spring-gateway filters添加自定義過濾器實(shí)現(xiàn)流程分析(可插拔)

    spring-gateway filters添加自定義過濾器實(shí)現(xiàn)流程分析(可插拔)

    這篇文章主要介紹了spring-gateway filters添加自定義過濾器實(shí)現(xiàn)流程分析(可插拔),本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2025-05-05
  • Java 定時器的多種實(shí)現(xiàn)方式

    Java 定時器的多種實(shí)現(xiàn)方式

    本文介紹了Java中定時器的多種實(shí)現(xiàn)方式,有此需求的朋友可以根據(jù)實(shí)際選擇適合自己的方式
    2021-06-06

最新評論