最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

0. 前言

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

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

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

  1. 網(wǎng)絡(luò)延遲:由于網(wǎng)絡(luò)問題,用戶在提交訂單后頁面沒有發(fā)生變化,而且沒有收到通知,用戶誤以為訂單沒有提交成功,連續(xù)點(diǎn)擊提交按鈕
  2. 刷新頁面:用戶提交訂單后刷新頁面,再次提交相同的訂單
  3. 用戶誤操作:用戶無意中點(diǎn)擊多次訂單提交按鈕
  4. 惡意攻擊:大量請(qǐng)求繞過前端頁面直接到達(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)識(shí)+商品唯一標(biāo)識(shí)+用戶唯一標(biāo)識(shí)形成的字符串為 key、以任意一個(gè)字符串作為 value,將鍵值對(duì)保存到 Redis 中,并為鍵值對(duì)設(shè)置一個(gè)合理的過期時(shí)間(過期時(shí)間可以根據(jù)業(yè)務(wù)需求來設(shè)定,以確保在用戶完成訂單操作之前,鍵值對(duì)始終有效)
  • 訂單處理完成后的操作:一旦訂單成功支付或者被取消,我們就從 Redis 中刪除對(duì)應(yīng)的鍵,釋放占用的內(nèi)存資源,防止在鍵值對(duì)過期之前對(duì)訂單狀態(tài)產(chǎn)生誤判

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

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

  • 如果存在,表明該訂單已經(jīng)被提交過,這是一個(gè)重復(fù)的提交請(qǐng)求,系統(tǒng)將拒絕此次請(qǐng)求,不會(huì)生成新的訂單
  • 如果不存在,說明這是一個(gè)新的訂單提交請(qǐng)求,系統(tǒng)將繼續(xù)執(zhí)行訂單生成的流程,并存儲(chǔ)新的鍵值對(duì)到 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ù)提交,請(qǐng)勿重復(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 = '提交失敗,請(qǐng)重試。'
                    console.error('error =', error);
                })

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

4. 需要注意的問題

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

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

相關(guān)文章

  • SpringBoot中必須掌握的常用注解小結(jié)

    SpringBoot中必須掌握的常用注解小結(jié)

    Spring?Boot?提供了大量的注解,用于簡化配置和加速程序開發(fā),這篇文章主要來和大家Spring?Boot中的常用注解的具體使用,希望對(duì)大家有所幫助
    2024-11-11
  • java多線程下載實(shí)例詳解

    java多線程下載實(shí)例詳解

    這篇文章主要介紹了java多線程下載,結(jié)合實(shí)例形式詳細(xì)分析了Java多線程文件傳輸?shù)脑砼c多線程下載的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2015-12-12
  • Java的反射機(jī)制之類加載詳解

    Java的反射機(jī)制之類加載詳解

    這篇文章主要介紹了Java的反射機(jī)制之類加載詳解,反射機(jī)制是java實(shí)現(xiàn)動(dòng)態(tài)語言的關(guān)鍵,也就是通過反射實(shí)現(xiàn)類動(dòng)態(tài)加載,靜態(tài)加載是指在編譯時(shí)期確定要加載的類的類型,即通過class關(guān)鍵字和類名來獲取對(duì)應(yīng)類的類型,需要的朋友可以參考下
    2023-09-09
  • Feign的原理及概念詳解

    Feign的原理及概念詳解

    文章介紹了Feign,一個(gè)聲明式、模板化的HTTP客戶端,以及其在Spring Cloud中的增強(qiáng),Feign通過代理類簡化HTTP請(qǐng)求的調(diào)用,并提供了詳細(xì)的調(diào)用流程、優(yōu)化配置和架構(gòu)說明,幫助開發(fā)者更方便地進(jìn)行服務(wù)調(diào)用和管理
    2025-11-11
  • MyBatis常用的jdbcType數(shù)據(jù)類型

    MyBatis常用的jdbcType數(shù)據(jù)類型

    這篇文章主要介紹了MyBatis常用的jdbcType數(shù)據(jù)類型的相關(guān)資料,需要的朋友可以參考下
    2016-12-12
  • 五種JAVA GUI布局管理的方式

    五種JAVA GUI布局管理的方式

    這篇文章主要介紹了JAVA幾種GUI布局管理的相關(guān)知識(shí),文中代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • Mybatis-plus如何查詢返回對(duì)象內(nèi)有List<String>屬性

    Mybatis-plus如何查詢返回對(duì)象內(nèi)有List<String>屬性

    在使用Mybatis-Plus進(jìn)行開發(fā)時(shí),我們經(jīng)常會(huì)遇到需要處理一對(duì)多關(guān)系映射的情況,例如,查詢用戶數(shù)據(jù)時(shí),可能需要同時(shí)獲取該用戶管理的所有小區(qū)名稱列表,這要求我們?cè)诜祷氐膶?shí)體類中包含一個(gè)List<String>屬性,用于存放小區(qū)名稱,實(shí)現(xiàn)這一功能
    2024-10-10
  • Spring Framework常用面試題及答案匯總

    Spring Framework常用面試題及答案匯總

    這篇文章主要介紹了Spring Framework常用面試題及答案匯總,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • SSH框架網(wǎng)上商城項(xiàng)目第21戰(zhàn)之詳解易寶支付的流程

    SSH框架網(wǎng)上商城項(xiàng)目第21戰(zhàn)之詳解易寶支付的流程

    這篇文章主要為大家詳細(xì)介紹了SSH框架網(wǎng)上商城項(xiàng)目第21戰(zhàn)之易寶支付的流程,感興趣的小伙伴們可以參考一下
    2016-06-06
  • 解決SpringBoot打成jar運(yùn)行后無法讀取resources里的文件問題

    解決SpringBoot打成jar運(yùn)行后無法讀取resources里的文件問題

    這篇文章主要介紹了解決SpringBoot打成jar運(yùn)行后無法讀取resources里的文件問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08

最新評(píng)論

茂名市| 自贡市| 平果县| 蒲城县| 柳林县| 东港市| 鄂托克旗| 济宁市| 富蕴县| 贺兰县| 吴桥县| 乌鲁木齐市| 邹城市| 启东市| 五台县| 泽普县| 浦北县| 丰城市| 隆化县| 田东县| 光山县| 双江| 互助| 河池市| 绥中县| 镇巴县| 宿松县| 五家渠市| 布尔津县| 尚义县| 咸宁市| 博客| 南丰县| 加查县| 安福县| 耿马| 靖安县| 夹江县| 巴马| 延吉市| 平度市|