分布式事务 TCC-Transaction 源码分析 —— 项目实战

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<OrderLine> orderLines = new ArrayList<OrderLine>(); }
  • 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 <?xml version="1.0" encoding="UTF-8"?> <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">&lt;bean name="capitalAccountRepository" class="org.mengyun.tcctransaction.sample.http.capital.domain.repository.CapitalAccountRepository"/&gt; &lt;bean name="tradeOrderRepository" class="org.mengyun.tcctransaction.sample.http.capital.domain.repository.TradeOrderRepository"/&gt; &lt;bean name="capitalTradeOrderService" class="org.mengyun.tcctransaction.sample.http.capital.service.CapitalTradeOrderServiceImpl"/&gt; &lt;bean name="capitalAccountService" class="org.mengyun.tcctransaction.sample.http.capital.service.CapitalAccountServiceImpl"/&gt; &lt;bean name="capitalTradeOrderServiceExporter" class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter"&gt; &lt;property name="service" ref="capitalTradeOrderService"/&gt; &lt;property name="serviceInterface" value="org.mengyun.tcctransaction.sample.http.capital.api.CapitalTradeOrderService"/&gt; &lt;/bean&gt; &lt;bean name="capitalAccountServiceExporter" class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter"&gt; &lt;property name="service" ref="capitalAccountService"/&gt; &lt;property name="serviceInterface" value="org.mengyun.tcctransaction.sample.http.capital.api.CapitalAccountService"/&gt; &lt;/bean&gt; &lt;bean id="httpServer" class="org.springframework.remoting.support.SimpleHttpServerFactoryBean"&gt; &lt;property name="contexts"&gt; &lt;util:map&gt; &lt;entry key="/remoting/CapitalTradeOrderService" value-ref="capitalTradeOrderServiceExporter"/&gt; &lt;entry key="/remoting/CapitalAccountService" value-ref="capitalAccountServiceExporter"/&gt; &lt;/util:map&gt; &lt;/property&gt; &lt;property name="port" value="8081"/&gt; &lt;/bean&gt; </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 <?xml version="1.0" encoding="UTF-8"?> <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">&lt;bean id="httpInvokerRequestExecutor" class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor"&gt; &lt;property name="httpClient"&gt; &lt;bean class="org.apache.commons.httpclient.HttpClient"&gt; &lt;property name="httpConnectionManager"&gt; &lt;ref bean="multiThreadHttpConnectionManager"/&gt; &lt;/property&gt; &lt;/bean&gt; &lt;/property&gt; &lt;/bean&gt; &lt;bean id="multiThreadHttpConnectionManager" class="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager"&gt; &lt;property name="params"&gt; &lt;bean class="org.apache.commons.httpclient.params.HttpConnectionManagerParams"&gt; &lt;property name="connectionTimeout" value="200000"/&gt; &lt;property name="maxTotalConnections" value="600"/&gt; &lt;property name="defaultMaxConnectionsPerHost" value="512"/&gt; &lt;property name="soTimeout" value="5000"/&gt; &lt;/bean&gt; &lt;/property&gt; &lt;/bean&gt; &lt;bean id="captialTradeOrderService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"&gt; &lt;property name="serviceUrl" value="http://localhost:8081/remoting/CapitalTradeOrderService"/&gt; &lt;property name="serviceInterface" value="org.mengyun.tcctransaction.sample.http.capital.api.CapitalTradeOrderService"/&gt; &lt;property name="httpInvokerRequestExecutor" ref="httpInvokerRequestExecutor"/&gt; &lt;/bean&gt; &lt;bean id="capitalAccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"&gt; &lt;property name="serviceUrl" value="http://localhost:8081/remoting/CapitalAccountService"/&gt; &lt;property name="serviceInterface" value="org.mengyun.tcctransaction.sample.http.capital.api.CapitalAccountService"/&gt; &lt;property name="httpInvokerRequestExecutor" ref="httpInvokerRequestExecutor"/&gt; &lt;/bean&gt; &lt;bean id="redPacketAccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"&gt; &lt;property name="serviceUrl" value="http://localhost:8082/remoting/RedPacketAccountService"/&gt; &lt;property name="serviceInterface" value="org.mengyun.tcctransaction.sample.http.redpacket.api.RedPacketAccountService"/&gt; &lt;property name="httpInvokerRequestExecutor" ref="httpInvokerRequestExecutor"/&gt; &lt;/bean&gt; &lt;bean id="redPacketTradeOrderService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"&gt; &lt;property name="serviceUrl" value="http://localhost:8082/remoting/RedPacketTradeOrderService"/&gt; &lt;property name="serviceInterface" value="org.mengyun.tcctransaction.sample.http.redpacket.api.RedPacketTradeOrderService"/&gt; &lt;property name="httpInvokerRequestExecutor" ref="httpInvokerRequestExecutor"/&gt; &lt;/bean&gt; </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<Pair<Long, Integer>> 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&lt;Pair&lt;Long, Integer&gt;&gt; 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); }

原文发布于微信公众号 - 芋道源码(YunaiV)

原文发表时间:2018-03-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏区块链入门

第二十六课 如何从零开始搭建一个Truffle框架的DAPP应用1,摘要2, 需求描述3,操作步骤4,总结

第六课 技术小白如何开发一个DAPP区块链应用(以宠物商店为例)介绍了如何获取宠物商店的TRUFLLE框架代码,并完成部署的过程。 但是这个是已经成熟的代码框...

1581
来自专栏智能计算时代

超级账本项目:架构设计

image.png 架构设计 整个架构如下图所示。 ? 包括三大组件:区块链服务(Blockchain)、链码服务(Chaincode)、成员权限管理(Memb...

3254
来自专栏极客编程

开发基于以太坊智能合约的DApp

最近要找个H5的前端写个简单的DApp,聊过几个H5的工程师,都被跟以太坊交互的部分吓住了。虽然网上有N多的教程,但是对于H5工程师来说,还是有些困难。分析其原...

1952
来自专栏SeanCheney的专栏

Hyperledger Fabric 架构设计整理

整个功能架构如下图所示。 ? 包括三大组件:区块链服务(Blockchain)、链码服务(Chaincode)、成员权限管理(Membership)。 概念术语...

3906
来自专栏比原链

Bytom交易说明(UTXO用户自己管理模式)

Gitee地址:https://gitee.com/BytomBlockchain/bytom

1487
来自专栏区块链大本营

教程 | 只用5步,教你从零用Truffle打造第一个以太坊Dapp!

7716
来自专栏申龙斌的程序人生

同时给200多人发送比特币,程序员是这样做到的

在币圈中,使用自己的钱包给他人发币时,就跟平常的微信、支付宝扫码付款类似,输入BTC地址和金额,再输入支付密码,不管对方是否在地球的另一端,不需要任何银行的中介...

1702
来自专栏区块链入门

第六课 技术小白如何开发一个DAPP区块链应用(以宠物商店为例)

通过逐步的指导和截图举证,一步步带领一个技术小白完成一个宠物商店DAPP应用的开发和部署。

2354
来自专栏极客编程

如何用Python和Flask框架开发以太坊智能合约

将数据存储在数据库中是任何软件应用程序不可或缺的一部分。无论如何控制该数据库都有一个该数据的主控。区块链技术将数据存储到区块链网络内的区块中。因此,只要某个节点...

1633
来自专栏醒者呆

区块链 + 大数据:EOS存储

默克尔树的演化路线是 Hash => Hash Tree => Merkle Tree ,他们都是为解决数据一致性而存在的,具体的含义如下:

4163

扫码关注云+社区

领取腾讯云代金券