如何防止使用者重複提交訂單?(上)

程式設計師志哥發表於2022-11-22

一、摘要

對於投入運營的軟體系統,最近小編在巡檢專案資料庫的時候,發現某些表存在不少的重複資料,對於這樣的髒資料,初步分析大致的來源有以下可能:

  • 1.由於使用者誤操作,多次點選表單提交按鈕
  • 2.由於網速等原因造成頁面卡頓,使用者重複重新整理提交頁面
  • 3.駭客或惡意使用者使用 postman 等網路工具,重複惡意提交表單

這些情況都可能會導致表單重複提交,造成資料重複,比如訂單表,重複提交訂單資料所造成的問題,可能不僅僅是資料上的混亂,也會造成業務混亂。

那麼問題來了,我們該如何防止使用者重複提交資料呢?

方案實踐如下!

二、方案實踐

下面我們以防止重複提交訂單為例,向大家介紹最簡單的、成本最低的解決辦法

我們先來看一張圖,這張圖就是本次方案的核心流程圖。

實現的邏輯,流程如下:

  • 1.當使用者進入訂單提交介面的時候,呼叫後端獲取請求唯一ID,並將唯一ID值埋點在頁面裡面
  • 2.當使用者點選提交按鈕時,後端檢查這個唯一ID是否用過,如果沒有用過,繼續後續邏輯;如果用過,就提示重複提交
  • 3.最關鍵的一步操作,就是把這個唯一ID 存入業務表中,同時設定這個欄位為唯一索引型別,從資料庫層面做防止重複提交

防止重複提交的大體思路如上,實踐程式碼如下!

2.1、給資料庫表增加唯一鍵約束

以訂單表為例,新增一個request_id欄位,並設定為唯一約束,結構如下:

CREATE TABLE tb_order (
  id bigint(20) unsigned NOT NULL,
  order_no varchar(100) NOT NULL,
  ....
  request_id varchar(36) NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uniq_request_id (request_id) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2.2、編寫獲取請求唯一ID的介面

@RestController
@RequestMapping("api")
public class CommonController {

    /**
     * 獲取getRequestId
     * @return
     */
    @RequestMapping("getRequestId")
    public ResResult getRequestId(){
        String uuid = UUID.randomUUID().toString();
        return ResResult.getSuccess(uuid);
    }
}

2.3、業務提交的時候,檢查唯一ID

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

    @Autowired
    private OrderService orderService;

    /**
     * 下單
     * @param request
     * @return
     */
    @PostMapping(value = "order/confirm")
    public ResResult confirm(@RequestBody OrderConfirmRequest request){
        //呼叫訂單下單相關邏輯
        if(StringUtils.isEmpty(request.getRequestId())){
            return ResResult.getSysError("請求ID不能為空!");
        }
        if(request.getRequestId().length() != 36){
            return ResResult.getSysError("請求ID格式錯誤!");
        }
        //檢查當前請求唯一ID,是否已經存在,如果存在,再提交就是重複下單
        Order source = orderService.queryByRequestId(request.getRequestId());
        if(Objects.nonNull(source)){
            return ResResult.getSysError("當前訂單已經提交成功,請勿重複提交");
        }
        orderService.confirm(request);
        return ResResult.getSuccess();
    }
}

如果是併發請求也不用擔心,因為資料庫表已經設定了唯一索引,尤其只有一條有效資料會插入成功,可以防止重複的資料產生

三、小結

對於下單流量不算高的系統,可以採用這種請求唯一ID+資料表增加唯一索引約束的方式,來防止介面重複提交

雖然簡單粗暴,但是十分有效

可能有的人會問,看上面的程式碼生成請求唯一 ID 很簡單,為啥不直接前端生成一個請求唯一ID,然後提交呢?

之所以把獲取請求唯一ID的生成規則放在後端,好處就是生成規則可以自己定義,也並不一定要用uuid來生成,也可以用雪花演算法,或者自己設計一套計算規則,保證當前業務提交時請求ID是唯一的,比如事先生成唯一的訂單號,作為請求唯一ID,然後再提交,規則放在後端來生成,會更加靈活!

相關文章