架構設計 | 介面冪等性原則,防重覆提交Token管理

来源:https://www.cnblogs.com/cicada-smile/archive/2020/05/22/12939875.html
-Advertisement-
Play Games

本文源碼: "GitHub·點這裡" || "GitEE·點這裡" 一、冪等性概念 1、冪等簡介 編程中一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同。就是說,一次和多次請求某一個資源會產生同樣的作用影響。 2、HTTP請求 遵循Http協議的請求,越來越強調Rest請求風格, ...


本文源碼:GitHub·點這裡 || GitEE·點這裡

一、冪等性概念

1、冪等簡介

編程中一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同。就是說,一次和多次請求某一個資源會產生同樣的作用影響。

2、HTTP請求

遵循Http協議的請求,越來越強調Rest請求風格,可以更好的規範和理解介面的設計。

GET:用於獲取資源,不應有副作用,所以是冪等的;

POST:用於創建資源,重覆提交POST請求可能產生兩個不同的資源,有副作用不滿足冪等性;

PUT:用於更新操作,重覆提交PUT請求只會對其URL中指定的資源有副作用,滿足冪等性;

DELETE:用於刪除資源,有副作用,但它應該滿足冪等性;

HEAD:和GET本質是一樣的,但HEAD不含有呈現數據,僅是HTTP頭信息,沒有副作用,滿足冪等性;

OPTIONS:用於獲取當前URL所支持的請求方法,滿足冪等性;

二、場景業務分析

1、訂單支付

實際開發中,經常會面對訂單支付問題,基本流程如下:

  • 客戶端發起訂單支付請求 ;
  • 支付前系統本地相關業務處理 ;
  • 請求第三方支付服務執行扣款;
  • 第三方支付返回處理結果;
  • 本地服務基於支付結果響應客戶端;

該業務流程中要處理相當複雜的問題,比如事務,分散式事務,介面延遲超時,客戶端重覆提交等等,這裡只基於冪等介面角度來看該流程,其他問題後續再聊。

2、冪等介面

當上述流程的支付請求有明確結果的時候:失敗或成功,這樣業務流程都好處理,但是例如支付場景如果請求超時,如何判斷服務的結果狀態:客戶端請求超時,本地服務超時,請求支付超時,支付回調超時,客戶端響應超時等等。

這就需要設計流程化的狀態管理。

3、基礎操作案例

模擬管理上述流程,設計冪等介面:

表結構設計

CREATE TABLE `dp_order_state` (
	`order_id` BIGINT (20) NOT NULL AUTO_INCREMENT COMMENT '訂單id',
	`token_id` VARCHAR (50) DEFAULT NULL COMMENT '防重覆提交',
	`state` INT (1) DEFAULT '1' COMMENT '1創建訂單,2本地業務,3支付業務',
	PRIMARY KEY (`order_id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '訂單狀態表';

CREATE TABLE `dp_state_record` (
	`id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
	`order_id` BIGINT (20) NOT NULL COMMENT '訂單id',
	`state_dec` VARCHAR (50) DEFAULT NULL COMMENT '狀態描述',
	PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '狀態記錄表';

模擬業務流程

將訂單創建,本地業務,支付業務,分開分段管理提交。分階段測試異常熔斷的業務。

@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderStateMapper orderStateMapper ;
    @Resource
    private StateRecordMapper stateRecordMapper ;

    @Override
    public OrderState queryOrder(OrderState orderState) {
        Map<String,Object> paramMap = new HashMap<>() ;
        paramMap.put("order_id",orderState.getOrderId());
        List<OrderState> orderStateList = orderStateMapper.selectByMap(paramMap);
        if (orderStateList != null && orderStateList.size()>0){
            return orderStateList.get(0) ;
        }
        return null ;
    }

    @Override
    public boolean createOrder(OrderState orderState) {
        int saveRes = orderStateMapper.insert(orderState);
        if (saveRes > 0){
            saveStateRecord(orderState.getOrderId(),"訂單創建成功");
        }
        return saveRes > 0 ;
    }

    @Override
    public boolean localBiz(OrderState orderState) {
        orderState.setState(2);
        int updateRes = orderStateMapper.updateState(orderState) ;
        if (updateRes > 0){
            saveStateRecord(orderState.getOrderId(),"本地業務成功");
        }
        return updateRes > 0;
    }

    @Override
    public boolean paymentBiz(OrderState orderState) {
        orderState.setState(3);
        int updateRes = orderStateMapper.updateState(orderState) ;
        if (updateRes > 0){
            saveStateRecord(orderState.getOrderId(),"支付業務成功");
        }
        return updateRes > 0;
    }

    private void saveStateRecord (Long orderId,String stateDec){
        StateRecord stateRecord = new StateRecord() ;
        stateRecord.setOrderId(orderId);
        stateRecord.setStateDec(stateDec);
        stateRecordMapper.insert(stateRecord) ;
    }
}

測試介面

根據訂單狀態,分段補償執行未完成的業務,如果該訂單已經完成,多次提交不影響最終結果。

@Api(value = "OrderController")
@RestController
public class OrderController {

    @Resource
    private OrderService orderService ;

    @PostMapping("/submitOrder")
    public String submitOrder (OrderState orderState){
        OrderState orderState01 = orderService.queryOrder(orderState) ;
        if (orderState01 == null){
            // 正常業務流程
            orderService.createOrder(orderState) ;
            orderService.localBiz(orderState) ;
            orderService.paymentBiz(orderState) ;
        } else {
            switch (orderState01.getState()){
                case 1:
                    // 訂單創建成功:後推執行本地和支付業務
                    orderService.localBiz(orderState01) ;
                    orderService.paymentBiz(orderState01) ;
                    break ;
                case 2:
                    // 訂單本地業務成功:後推執行支付業務
                    orderService.paymentBiz(orderState01) ;
                    break ;
                default:
                    break ;
            }
        }
        return "success" ;
    }
}

絮叨一句:實際開發中,該流程是不會由頁面多次提交完成,訂單是不能重覆提交的,下麵會演示如何控制,這裡業務是執行後推到完成,也可能業務向前清理,把整個流程置為失敗,這裡涉及關鍵狀態判斷,要選取一個狀態作為成功或失敗的標識,判斷後續操作流程。在分散式系統中這種複雜流程最難處理的是分散式事務,最終一致性問題,後續再聊。

三、介面重覆提交

1、表單重覆提交

在實際情況中,介面如果處理時間過長,用戶可能會點擊多次提交按鈕,導致數據重覆。

常見的一個解決方案:在表單提交中隱藏一個token_id參數,一起提交到介面服務中,資料庫存儲訂單和關聯的tokenId,如果多次提交,直接返回頁面提示信息即可。

2、演示案例

訂單關聯Token查詢

@Service
public class OrderServiceImpl implements OrderService {
    @Override
    public Boolean queryToken(OrderState orderState) {
        Map<String,Object> paramMap = new HashMap<>() ;
        paramMap.put("order_id",orderState.getOrderId());
        paramMap.put("token_id",orderState.getTokenId());
        List<OrderState> orderStateList = orderStateMapper.selectByMap(paramMap);
        return orderStateList.size() > 0 ;
    }
}

測試介面

@RestController
public class OrderController {
    @Resource
    private OrderService orderService ;

    @PostMapping("/repeatSub")
    public String repeatSub (OrderState orderState){
        boolean flag = orderService.queryToken(orderState) ;
        if (flag){
            return "請勿重覆提交訂單" ;
        }
        return "success" ;
    }
}

四、源代碼地址

GitHub·地址
https://github.com/cicadasmile/data-manage-parent
GitEE·地址
https://gitee.com/cicadasmile/data-manage-parent

推薦閱讀:數據和架構管理

序號 標題
A01 數據源管理:主從庫動態路由,AOP模式讀寫分離
A02 數據源管理:基於JDBC模式,適配和管理動態數據源
A03 數據源管理:動態許可權校驗,表結構和數據遷移流程
A04 數據源管理:關係型分庫分表,列式庫分散式計算
A05 數據源管理:PostGreSQL環境整合,JSON類型應用
A06 數據源管理:基於DataX組件,同步數據和源碼分析
A07 數據源管理:OLAP查詢引擎,ClickHouse集群化管理
C01 架構基礎:單服務.集群.分散式,基本區別和聯繫
C02 架構設計:分散式業務系統中,全局ID生成策略
C03 架構設計:分散式系統調度,Zookeeper集群化管理

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1 緩存導致的可見性問題 一個線程對共用變數的修改,另一個線程可以立即看到,這稱之為可見性。 Java記憶體模型規定所有的變數存儲在主記憶體中。每個線程都有自己的工作記憶體,線程在工作記憶體中保存了使用到的主記憶體中變數的副本拷貝,線程對變數的操作必須在工作記憶體中進行,不能直接讀寫主記憶體中的變數。不同線程之間 ...
  • 在項目中經常遇到需要動態編輯特定事物的一組屬性,這時就涉及到新選擇的值與舊值對比更新,記錄在項目中採用的一種方法,主要採用 PHP array_filter() 函數: $delete_array = $add_new_array = array(); $array1 = array( array( ...
  • 檢測是否存在JDK 接下來可以將 開頭的安裝包均卸載即可 下載JDK 在 /opt/soft 內下載jdk "AdoptOpenJDK" 解壓完之後, /opt/module/ ⽬錄中會出現⼀個 的⽬錄 配置JDK環境變數 編輯 ⽂件,在⽂件尾部加⼊如下 JDK 環境配置即可 然後執⾏如下命令讓環境 ...
  • 學習Django有一段時間了,最近剛好寫了一個小項目,用到了前後端交互,剛開始寫前後端交互確實很讓人頭暈目眩呢,下麵我來給大家介紹三種簡單的Django前後端交互的方法吧! 第一種 通過form表單的get或post請求提交到後端的請求方式 <form> <input type='text' nam ...
  • 聲明區域: 變數等可以聲明的區域 例:全局變數的聲明區域為其聲明所在的文件 潛在作用域: 從聲明點開始,到其聲明域的結尾 作用域: 變數對程式而言可見的範圍(即除去潛在作用域中被局部變數等隱藏的區域) ...
  • Hi,大家好,我是明哥。 在自己學習 Golang 的這段時間里,我寫了詳細的學習筆記放在我的個人微信公眾號 《Go編程時光》,對於 Go 語言,我也算是個初學者,因此寫的東西應該會比較適合剛接觸的同學,如果你也是剛學習 Go 語言,不防關註一下,一起學習,一起成長。 我的線上博客:http://g ...
  • 我的LeetCode:https://leetcode cn.com/u/ituring/ 我的LeetCode刷題源碼[GitHub]:https://github.com/izhoujie/Algorithmcii LeetCode 5. 最長迴文子串 題目 給定一個字元串 s,找到 s 中最長 ...
  • 我的LeetCode:https://leetcode cn.com/u/ituring/ 我的LeetCode刷題源碼[GitHub]:https://github.com/izhoujie/Algorithmcii LeetCode 21. 合併兩個有序鏈表 題目 將兩個升序鏈表合併為一個新的升 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...