本文主要基於 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 ListorderLines = 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 事務傳播級別,事務恢復重試時,會發起新的事務。 -
confirmMethod
、cancelMethod
使用和 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 ~撒花
胖友,分享個朋友圈,可好?!