歡迎光臨
每天分享高質量文章

分散式事務 TCC-Transaction 原始碼分析 —— 專案實戰

本文主要基於 TCC-Transaction 1.2.3.3 正式版

  • 1. 概述

  • 2. 物體結構

    • 2.1 商城服務

    • 2.2 資金服務

    • 2.3 紅包服務

  • 3. 服務呼叫

  • 4. 下單支付流程

    • 4.1 Try 階段

    • 4.2 Confirm / Cancel 階段

  • 666. 彩蛋


友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群和【芋艿】搞基嗨皮。
友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群和【芋艿】】搞基嗨皮。
友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群和【芋艿】】搞基嗨皮。


1. 概述

本文分享 TCC 專案實戰。以官方 Maven專案 tcc-transaction-http-sample 為例子( tcc-transaction-dubbo-sample 類似 )。

建議你已經成功啟動了該專案。如果不知道如何啟動,可以先檢視《TCC-Transaction 原始碼分析 —— 除錯環境搭建》。如果再碰到問題,歡迎加微信公眾號( 芋道原始碼 ),我會一一仔細回覆。

OK,首先我們簡單瞭解下這個專案。

  • 首頁 => 商品串列 => 確認支付頁 => 支付結果頁

  • 使用賬戶餘額 + 紅包餘額聯合支付購買商品,並賬戶之間轉賬

專案拆分三個子 Maven 專案:

  • tcc-transaction-http-order :商城服務,提供商品和商品訂單邏輯。

  • tcc-transaction-http-capital :資金服務,提供賬戶餘額邏輯。

  • tcc-transaction-http-redpacket :紅包服務,提供紅包餘額邏輯。

你行好事會因為得到贊賞而愉悅 
同理,開源專案貢獻者會因為 Star 而更加有動力 
為 TCC-Transaction 點贊!傳送門

2. 物體結構

2.1 商城服務

  • Shop,商店表。物體程式碼如下:

    public class Shop {
    /**
    * 商店編號
    */

    private long id;
    /**
    * 所有者使用者編號
    */

    private long ownerUserId;

    }

  • Product,商品表。物體程式碼如下:

    public class Product implements Serializable {
    /**
    * 商品編號
    */

    private long productId;
    /**
    * 商店編號
    */

    private long shopId;
    /**
    * 商品名
    */

    private String productName;
    /**
    * 單價
    */

    private BigDecimal price;

    }

  • Order,訂單表。實現程式碼如下:

    public class Order implements Serializable {
    private static final long serialVersionUID = -5908730245224893590L;

    /**
    * 訂單編號
    */

    private long id;
    /**
    * 支付( 下單 )使用者編號
    */

    private long payerUserId;
    /**
    * 收款( 商店擁有者 )使用者編號
    */

    private long payeeUserId;
    /**
    * 紅包支付金額
    */

    private BigDecimal redPacketPayAmount;
    /**
    * 賬戶餘額支付金額
    */

    private BigDecimal capitalPayAmount;
    /**
    * 訂單狀態
    * - DRAFT :草稿
    * - PAYING :支付中
    * - CONFIRMED :支付成功
    * - PAY_FAILED :支付失敗
    */

    private String status = "DRAFT";
    /**
    * 商戶訂單號,使用 UUID 生成
    */

    private String merchantOrderNo;

    /**
    * 訂單明細陣列
    * 非儲存欄位
    */

    private List orderLines = new ArrayList();

    }

  • OrderLine,訂單明細。物體程式碼如下:

    public class OrderLine implements Serializable {
    private static final long serialVersionUID = 2300754647209250837L;

    /**
    * 訂單編號
    */

    private long id;
    /**
    * 商品編號
    */

    private long productId;
    /**
    * 數量
    */

    private int quantity;
    /**
    * 單價
    */

    private BigDecimal unitPrice;

    }

業務邏輯

下單時,插入訂單狀態為 "DRAFT" 的訂單( Order )記錄,並插入購買的商品訂單明細( OrderLine )記錄。支付時,更新訂單狀態為 "PAYING"

  • 訂單支付成功,更新訂單狀態為 "CONFIRMED"

  • 訂單支付失敗,更新訂單狀體為 "PAY_FAILED"

2.2 資金服務

關係較為簡單,有兩個物體:

  • CapitalAccount,資金賬戶餘額。物體程式碼如下:

    public class CapitalAccount {
    /**
    * 賬戶編號
    */

    private long id;
    /**
    * 使用者編號
    */

    private long userId;
    /**
    * 餘額
    */

    private BigDecimal balanceAmount;

    }

  • TradeOrder,交易訂單表。物體程式碼如下:

    public class TradeOrder {
    /**
    * 交易訂單編號
    */

    private long id;
    /**
    * 轉出使用者編號
    */

    private long selfUserId;
    /**
    * 轉入使用者編號
    */

    private long oppositeUserId;
    /**
    * 商戶訂單號
    */

    private String merchantOrderNo;
    /**
    * 金額
    */

    private BigDecimal amount;
    /**
    * 交易訂單狀態
    * - DRAFT :草稿
    * - CONFIRM :交易成功
    * - CANCEL :交易取消
    */

    private String status = "DRAFT";

    }

業務邏輯

訂單支付支付中,插入交易訂單狀態為 "DRAFT" 的訂單( TradeOrder )記錄,並更新減少下單使用者的資金賬戶餘額。

  • 訂單支付成功,更新交易訂單狀態為 "CONFIRM",並更新增加商店擁有使用者的資金賬戶餘額。

  • 訂單支付失敗,更新交易訂單狀態為 "CANCEL",並更新增加( 恢復 )下單使用者的資金賬戶餘額。

2.3 紅包服務

關係較為簡單,和資金服務 99.99% 相同,有兩個物體:

  • RedPacketAccount,紅包賬戶餘額。物體程式碼如下:

    public class RedPacketAccount {
    /**
    * 賬戶編號
    */

    private long id;
    /**
    * 使用者編號
    */

    private long userId;
    /**
    * 餘額
    */

    private BigDecimal balanceAmount;

    }

  • TradeOrder,交易訂單表。物體程式碼如下:

    public class TradeOrder {
    /**
    * 交易訂單編號
    */

    private long id;
    /**
    * 轉出使用者編號
    */

    private long selfUserId;
    /**
    * 轉入使用者編號
    */

    private long oppositeUserId;
    /**
    * 商戶訂單號
    */

    private String merchantOrderNo;
    /**
    * 金額
    */

    private BigDecimal amount;
    /**
    * 交易訂單狀態
    * - DRAFT :草稿
    * - CONFIRM :交易成功
    * - CANCEL :交易取消
    */

    private String status = "DRAFT";

    }

業務邏輯

訂單支付支付中,插入交易訂單狀態為 "DRAFT" 的訂單( TradeOrder )記錄,並更新減少下單使用者的紅包賬戶餘額。

  • 訂單支付成功,更新交易訂單狀態為 "CONFIRM",並更新增加商店擁有使用者的紅包賬戶餘額。

  • 訂單支付失敗,更新交易訂單狀態為 "CANCEL",並更新增加( 恢復 )下單使用者的紅包賬戶餘額。

3. 服務呼叫

服務之間,透過 HTTP 進行呼叫。

紅包服務和資金服務為商城服務提供呼叫( 以資金服務為例子 )

  • XML 配置如下 :

    // appcontext-service-provider.xml

    <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
          xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
          class="org.mengyun.tcctransaction.sample.http.capital.domain.repository.CapitalAccountRepository"/>

         class="org.mengyun.tcctransaction.sample.http.capital.domain.repository.TradeOrderRepository"/>

         class="org.mengyun.tcctransaction.sample.http.capital.service.CapitalTradeOrderServiceImpl"/>

         class="org.mengyun.tcctransaction.sample.http.capital.service.CapitalAccountServiceImpl"/>

         class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter">
       
                     value="org.mengyun.tcctransaction.sample.http.capital.api.CapitalTradeOrderService"/>


         class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter">
       
                     value="org.mengyun.tcctransaction.sample.http.capital.api.CapitalAccountService"/>



         class="org.springframework.remoting.support.SimpleHttpServerFactoryBean">
       
           
               
               
           

       

       

    beans>


  • Java 程式碼實現如下 :

    public class CapitalAccountServiceImpl implements CapitalAccountService {
    @Autowired
    CapitalAccountRepository capitalAccountRepository;

    @Override
    public BigDecimal getCapitalAccountByUserId(long userId) {
       return capitalAccountRepository.findByUserId(userId).getBalanceAmount();
    }

    }

    public class CapitalAccountServiceImpl implements CapitalAccountService {

    @Autowired
    CapitalAccountRepository capitalAccountRepository;

    @Override
    public BigDecimal getCapitalAccountByUserId(long userId) {
       return capitalAccountRepository.findByUserId(userId).getBalanceAmount();
    }

    }


  • 商城服務呼叫

    • XML 配置如下:

      // appcontext-service-consumer.xml

      <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
            class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor">
         
             
                 
                     
                 

             

         



           class="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager">
         
             
                 
                 
                 
                 
             

         




         
                       value="org.mengyun.tcctransaction.sample.http.capital.api.CapitalTradeOrderService"/>
         



         
                       value="org.mengyun.tcctransaction.sample.http.capital.api.CapitalAccountService"/>
         



         
                       value="org.mengyun.tcctransaction.sample.http.redpacket.api.RedPacketAccountService"/>
         



         
                       value="org.mengyun.tcctransaction.sample.http.redpacket.api.RedPacketTradeOrderService"/>
         

      beans>


  • Java 介面介面如下:

    public interface CapitalAccountService {
       BigDecimal getCapitalAccountByUserId(long userId);
    }

    public interface CapitalTradeOrderService {
       String record(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto);
    }

    public interface RedPacketAccountService {
       BigDecimal getRedPacketAccountByUserId(long userId);
    }

    public interface RedPacketTradeOrderService {
       String record(TransactionContext transactionContext, RedPacketTradeOrderDto tradeOrderDto);
    }
  • 4. 下單支付流程

    ps:資料訪問的方法,請自己拉取程式碼,使用 IDE 檢視。謝謝。?

    下單支付流程,整體流程如下圖( 開啟大圖 ):

    點選【支付】按鈕,下單支付流程。實現程式碼如下:

    @Controller
    @RequestMapping("")
    public class OrderController {

           @RequestMapping(value = "/placeorder", method = RequestMethod.POST)
       public ModelAndView placeOrder(@RequestParam String redPacketPayAmount,
                                      @RequestParam long shopId,
                                      @RequestParam long payerUserId,
                                      @RequestParam long productId)
    {
           PlaceOrderRequest request = buildRequest(redPacketPayAmount, shopId, payerUserId, productId);
           // 下單並支付訂單
           String merchantOrderNo = placeOrderService.placeOrder(request.getPayerUserId(), request.getShopId(),
                   request.getProductQuantities(), request.getRedPacketPayAmount());
           // 傳回
           ModelAndView mv = new ModelAndView("pay_success");
           // 查詢訂單狀態
           String status = orderService.getOrderStatusByMerchantOrderNo(merchantOrderNo);
           // 支付結果提示
           String payResultTip = null;
           if ("CONFIRMED".equals(status)) {
               payResultTip = "支付成功";
           } else if ("PAY_FAILED".equals(status)) {
               payResultTip = "支付失敗";
           }
           mv.addObject("payResult", payResultTip);
           // 商品資訊
           mv.addObject("product", productRepository.findById(productId));
           // 資金賬戶金額 和 紅包賬戶金額
           mv.addObject("capitalAmount", accountService.getCapitalAccountByUserId(payerUserId));
           mv.addObject("redPacketAmount", accountService.getRedPacketAccountByUserId(payerUserId));
           return mv;
       }

    }
    • 呼叫 PlaceOrderService#placeOrder(...) 方法,下單並支付訂單。

    • 呼叫 OrderService#getOrderStatusByMerchantOrderNo(...) 方法,查詢訂單狀態。


    呼叫 PlaceOrderService#placeOrder(...) 方法,下單並支付訂單。實現程式碼如下:

    @Service
    public class PlaceOrderServiceImpl {

       public String placeOrder(long payerUserId, long shopId, List> productQuantities, BigDecimal redPacketPayAmount) {
           // 獲取商店
           Shop shop = shopRepository.findById(shopId);
           // 建立訂單
           Order order = orderService.createOrder(payerUserId, shop.getOwnerUserId(), productQuantities);
           // 發起支付
           Boolean result = false;
           try {
               paymentService.makePayment(order, redPacketPayAmount, order.getTotalAmount().subtract(redPacketPayAmount));
           } catch (ConfirmingException confirmingException) {
               // exception throws with the tcc transaction status is CONFIRMING,
               // when tcc transaction is confirming status,
               // the tcc transaction recovery will try to confirm the whole transaction to ensure eventually consistent.
               result = true;
           } catch (CancellingException cancellingException) {
               // exception throws with the tcc transaction status is CANCELLING,
               // when tcc transaction is under CANCELLING status,
               // the tcc transaction recovery will try to cancel the whole transaction to ensure eventually consistent.
           } catch (Throwable e) {
               // other exceptions throws at TRYING stage.
               // you can retry or cancel the operation.
               e.printStackTrace();
           }
           return order.getMerchantOrderNo();
       }

    }
    • 呼叫 ShopRepository#findById(...) 方法,查詢商店。

    • 呼叫 OrderService#createOrder(...) 方法,建立訂單狀態為 "DRAFT" 的商城訂單。實際業務不會這麼做,此處僅僅是例子,簡化流程。實現程式碼如下:

      @Service
      public class OrderServiceImpl {
      @Transactional
      public Order createOrder(long payerUserId, long payeeUserId, List> productQuantities) {
         Order order = orderFactory.buildOrder(payerUserId, payeeUserId, productQuantities);
         orderRepository.createOrder(order);
         return order;
      }

      }

    • 呼叫 PaymentService#makePayment(...) 方法,發起支付,TCC 流程

    • 生產程式碼對於異常需要進一步處理

    • 生產程式碼對於異常需要進一步處理

    • 生產程式碼對於異常需要進一步處理

    4.1 Try 階段

    商城服務

    呼叫 PaymentService#makePayment(...) 方法,發起 Try 流程,實現程式碼如下:

    @Compensable(confirmMethod = "confirmMakePayment", cancelMethod = "cancelMakePayment")
    @Transactional
    public void makePayment(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) {
      System.out.println("order try make payment called.time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss"));
      // 更新訂單狀態為支付中
      order.pay(redPacketPayAmount, capitalPayAmount);
      orderRepository.updateOrder(order);
      // 資金賬戶餘額支付訂單
      String result = tradeOrderServiceProxy.record(null, buildCapitalTradeOrderDto(order));
      // 紅包賬戶餘額支付訂單
      String result2 = tradeOrderServiceProxy.record(null, buildRedPacketTradeOrderDto(order));
    }
    • 設定方法註解 @Compensable

      • 事務傳播級別 Propagation.REQUIRED ( 預設值 )

      • 設定 confirmMethod / cancelMethod 方法名

      • 事務背景關係編輯類 DefaultTransactionContextEditor ( 預設值 )

    • 設定方法註解 @Transactional,保證方法操作原子性。

    • 呼叫 OrderRepository#updateOrder(...) 方法,更新訂單狀態為支付中。實現程式碼如下:

      // Order.java
      public void pay(BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) {
        this.redPacketPayAmount = redPacketPayAmount;
        this.capitalPayAmount = capitalPayAmount;
        this.status = "PAYING";
      }
    • 呼叫 TradeOrderServiceProxy#record(...) 方法,資金賬戶餘額支付訂單。實現程式碼如下:

      // TradeOrderServiceProxy.java
      @Compensable(propagation = Propagation.SUPPORTS, confirmMethod = "record", cancelMethod = "record", transactionContextEditor = Compensable.DefaultTransactionContextEditor.class)
      public String record(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {
        return capitalTradeOrderService.record(transactionContext, tradeOrderDto);
      }

      // CapitalTradeOrderService.java
      public interface CapitalTradeOrderService {
         String record(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto);
      }
      • 本地方法呼叫時,引數 transactionContext 傳遞 null 即可,TransactionContextEditor 會設定。在《TCC-Transaction 原始碼分析 —— TCC 實現》「6.3 資源協調者攔截器」有詳細解析。

      • 遠端方法呼叫時,引數 transactionContext 需要傳遞。Dubbo 遠端方法呼叫實際也進行了傳遞,傳遞方式較為特殊,透過隱式船艙,在《TCC-Transaction 原始碼分析 —— Dubbo 支援》「3. Dubbo 事務背景關係編輯器」有詳細解析。

      • propagation=Propagation.SUPPORTS :支援當前事務,如果當前沒有事務,就以非事務方式執行。為什麼不使用 REQUIRED ?如果使用 REQUIRED 事務傳播級別,事務恢復重試時,會發起新的事務。

      • confirmMethodcancelMethod 使用和 try 方法相同方法名本地發起遠端服務 TCC confirm / cancel 階段,呼叫相同方法進行事務的提交或回滾。遠端服務的 CompensableTransactionInterceptor 會根據事務的狀態是 CONFIRMING / CANCELLING 來呼叫對應方法。

      • 設定方法註解 @Compensable

      • 呼叫 CapitalTradeOrderService#record(...) 方法,遠端呼叫,發起資金賬戶餘額支付訂單。

    • 呼叫 TradeOrderServiceProxy#record(...) 方法,紅包賬戶餘額支付訂單。和資金賬戶餘額支付訂單 99.99% 類似,不重覆“複製貼上”。


    資金服務

    呼叫 CapitalTradeOrderServiceImpl#record(...) 方法,紅包賬戶餘額支付訂單。實現程式碼如下:

    @Override
    @Compensable(confirmMethod = "confirmRecord", cancelMethod = "cancelRecord", transactionContextEditor = Compensable.DefaultTransactionContextEditor.class)
    @Transactional
    public String record(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {
      // 除錯用
      try {
          Thread.sleep(1000l);
    //            Thread.sleep(10000000L);
      } catch (InterruptedException e) {
          throw new RuntimeException(e);
      }
      System.out.println("capital try record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss"));
      // 生成交易訂單
      TradeOrder tradeOrder = new TradeOrder(
              tradeOrderDto.getSelfUserId(),
              tradeOrderDto.getOppositeUserId(),
              tradeOrderDto.getMerchantOrderNo(),
              tradeOrderDto.getAmount()
      );
      tradeOrderRepository.insert(tradeOrder);
      // 更新減少下單使用者的資金賬戶餘額
      CapitalAccount transferFromAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getSelfUserId());
      transferFromAccount.transferFrom(tradeOrderDto.getAmount());
      capitalAccountRepository.save(transferFromAccount);
      return "success";
    }
    • 設定方法註解 @Compensable

      • 事務傳播級別 Propagation.REQUIRED ( 預設值 )

      • 設定 confirmMethod / cancelMethod 方法名

      • 事務背景關係編輯類 DefaultTransactionContextEditor ( 預設值 )

    • 設定方法註解 @Transactional,保證方法操作原子性。

    • 呼叫 TradeOrderRepository#insert(...) 方法,生成訂單狀態為 "DRAFT" 的交易訂單。

    • 呼叫 CapitalAccountRepository#save(...) 方法,更新減少下單使用者的資金賬戶餘額。Try 階段鎖定資源時,一定要先扣。TCC 是最終事務一致性,如果先新增,可能被使用

    4.2 Confirm / Cancel 階段

    當 Try 操作全部成功時,發起 Confirm 操作。 
    當 Try 操作存在任務失敗時,發起 Cancel 操作。

    4.2.1 Confirm

    商城服務

    呼叫 PaymentServiceImpl#confirmMakePayment(...) 方法,更新訂單狀態為支付成功。實現程式碼如下:

    public void confirmMakePayment(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) {
      // 除錯用
      try {
          Thread.sleep(1000l);
      } catch (InterruptedException e) {
          throw new RuntimeException(e);
      }
      System.out.println("order confirm make payment called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss"));
      // 更新訂單狀態為支付成功
      order.confirm();
      orderRepository.updateOrder(order);
    }
    • 生產程式碼該方法需要加下 @Transactional 註解,保證原子性

    • 呼叫 OrderRepository#updateOrder(...) 方法,更新訂單狀態為支付成功。實現程式碼如下:

      // Order.java
      public void confirm() {
        this.status = "CONFIRMED";
      }

    資金服務

    呼叫 CapitalTradeOrderServiceImpl#confirmRecord(...) 方法,更新交易訂單狀態為交易成功

    @Transactional
    public void confirmRecord(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {
      // 除錯用
      try {
          Thread.sleep(1000l);
      } catch (InterruptedException e) {
          throw new RuntimeException(e);
      }
      System.out.println("capital confirm record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss"));
      // 查詢交易記錄
      TradeOrder tradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo());
      // 判斷交易記錄狀態。因為 `#record()` 方法,可能事務回滾,記錄不存在 / 狀態不對
      if (null != tradeOrder && "DRAFT".equals(tradeOrder.getStatus())) {
          // 更新訂單狀態為交易成功
          tradeOrder.confirm();
          tradeOrderRepository.update(tradeOrder);
          // 更新增加商店擁有者使用者的資金賬戶餘額
          CapitalAccount transferToAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getOppositeUserId());
          transferToAccount.transferTo(tradeOrderDto.getAmount());
          capitalAccountRepository.save(transferToAccount);
      }
    }
    • 設定方法註解 @Transactional,保證方法操作原子性。

    • 判斷交易記錄狀態。因為 #record() 方法,可能事務回滾,記錄不存在 / 狀態不對。

    • 呼叫 TradeOrderRepository#update(...) 方法,更新交易訂單狀態為交易成功

    • 呼叫 CapitalAccountRepository#save(...) 方法,更新增加商店擁有者使用者的資金賬戶餘額。實現程式碼如下:

      // CapitalAccount.java
      public void transferTo(BigDecimal amount) {
        this.balanceAmount = this.balanceAmount.add(amount);
      }

    紅包服務

    資源服務 99.99% 相同,不重覆“複製貼上”。

    4.2.2 Cancel

    商城服務

    呼叫 PaymentServiceImpl#cancelMakePayment(...) 方法,更新訂單狀態為支付失敗。實現程式碼如下:

    public void cancelMakePayment(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) {
      // 除錯用
      try {
          Thread.sleep(1000l);
      } catch (InterruptedException e) {
          throw new RuntimeException(e);
      }
      System.out.println("order cancel make payment called.time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss"));
      // 更新訂單狀態為支付失敗
      order.cancelPayment();
      orderRepository.updateOrder(order);
    }
    • 生產程式碼該方法需要加下 @Transactional 註解,保證原子性

    • 呼叫 OrderRepository#updateOrder(...) 方法,更新訂單狀態為支付失敗。實現程式碼如下:

      // Order.java
      public void cancelPayment() {
         this.status = "PAY_FAILED";
      }

    資金服務

    呼叫 CapitalTradeOrderServiceImpl#cancelRecord(...) 方法,更新交易訂單狀態為交易失敗

    @Transactional
    public void cancelRecord(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {
      // 除錯用
      try {
          Thread.sleep(1000l);
      } catch (InterruptedException e) {
          throw new RuntimeException(e);
      }
      System.out.println("capital cancel record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss"));
      // 查詢交易記錄
      TradeOrder tradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo());
      // 判斷交易記錄狀態。因為 `#record()` 方法,可能事務回滾,記錄不存在 / 狀態不對
      if (null != tradeOrder && "DRAFT".equals(tradeOrder.getStatus())) {
          // / 更新訂單狀態為交易失敗
          tradeOrder.cancel();
          tradeOrderRepository.update(tradeOrder);
          // 更新增加( 恢復 )下單使用者的資金賬戶餘額
          CapitalAccount capitalAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getSelfUserId());
          capitalAccount.cancelTransfer(tradeOrderDto.getAmount());
          capitalAccountRepository.save(capitalAccount);
      }
    }
    • 設定方法註解 @Transactional,保證方法操作原子性。

    • 判斷交易記錄狀態。因為 #record() 方法,可能事務回滾,記錄不存在 / 狀態不對。

    • 呼叫 TradeOrderRepository#update(...) 方法,更新交易訂單狀態為交易失敗

    • 呼叫 CapitalAccountRepository#save(...) 方法,更新增加( 恢復 )下單使用者的資金賬戶餘額。實現程式碼如下:

      // CapitalAccount.java
      public void cancelTransfer(BigDecimal amount) {
         transferTo(amount);
      }

    紅包服務

    資源服務 99.99% 相同,不重覆“複製貼上”。

    666. 彩蛋

    知識星球

    嘿嘿,程式碼只是看起來比較多,實際不多。

    螞蟻金融雲提供了銀行間轉賬的 TCC 過程例子,有興趣的同學可以看看:《螞蟻金融雲 —— 分散式事務服務(DTS) —— 場景介紹》。

    本系列 EOF ~撒花

    胖友,分享個朋友圈,可好?!

    贊(0)

    分享創造快樂