java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot Redis订单重复提交

SpringBoot利用Redis实现防止订单重复提交的解决方案

作者:聂 可 以

在涉及订单操作的业务中,防止订单重复提交是一个常见需求,用户可能会因误操作或网络延迟而多次点击提交订单按钮,导致订单重复提交,所以本文给大家介绍了SpringBoot利用Redis实现防止订单重复提交的解决方案,需要的朋友可以参考下

0. 前言

在涉及订单操作的业务中,防止订单重复提交是一个常见需求

用户可能会因误操作或网络延迟而多次点击提交订单按钮,导致订单重复提交,造成数据冗余,而且订单通常与库存紧密关联,重复提交订单不仅会影响用户体验,还有可能引发库存管理上的混乱,甚至导致财务数据出现偏差,带来一系列潜在的经济风险

1. 常见的重复提交订单的场景

  1. 网络延迟:由于网络问题,用户在提交订单后页面没有发生变化,而且没有收到通知,用户误以为订单没有提交成功,连续点击提交按钮
  2. 刷新页面:用户提交订单后刷新页面,再次提交相同的订单
  3. 用户误操作:用户无意中点击多次订单提交按钮
  4. 恶意攻击:大量请求绕过前端页面直接到达后端

2. 防止订单重复提交的解决方案

2.1 前端(禁用按钮)

用户点击提交订单按钮后,在成功跳转到支付页面之前,禁用提交订单按钮,防止用户多次执行提交订单

禁用提交订单按钮只能避免一部分订单重复提交的情况,如果用户点击支付按钮之后刷新页面,依然是可以重复下单的,要想完全解决订单重复提交的问题,后端也要做相应的处理

2.2 后端

我们可以借助 Redis 实现防止订单重复提交的功能

key 的形式不唯一,但要确保一个 key 对应一个订单

当客户端发起提交订单的请求时,后端会检查 Redis 中是否存在对应的键

3. 在SpringBoot项目中利用Redis实现防止订单重复提交

本次演示的后端环境为: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 单机)

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("订单重复提交,请勿重复操作,您可以确认一下有没有未支付的相同订单");
        }

        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>防止订单重复提交</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; /* 设置最大宽度 */
            padding: 50px 0;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        .button-container, .result-container {
            width: 100%;
            max-width: 300px; /* 按钮和结果显示文本同宽 */
            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 = () => {
        // 点击按钮后有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. 需要注意的问题

以上就是SpringBoot利用Redis实现防止订单重复提交的解决方案的详细内容,更多关于SpringBoot Redis订单重复提交的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文