TCC方案属于两阶段型/补偿型
GitHub上一个开源的TCC框架实现,以此项目为基础讲解TCC框架的实现(主要是在项目源码上添加一些注释)
以订单处理流程为例:账户扣款->使用红包优惠券->订单状态改变为例
一个主服务调用从业务服务,被TCC控制的方法都应该具有三种方法:主方法(try方法)/确认方法(confirm方法)/取消方法(cancel方法)
@Service("pointAccountService")
public class pointAccountServiceImpl implements pointAccountService{
private static final Logger LOG = LoggerFactory.getLogger(pointAccountServiceImpl.class);
@Autowired
private pointAccountDao pointAccountDao;
@Autowired
private pointAccountHistoryDao pointAccountHistoryDao;
@Override
public void saveData(pointAccount pointAccount) {
pointAccountDao.insert(pointAccount);
}
@Override
public void updateData(pointAccount pointAccount) {
pointAccountDao.update(pointAccount);
}
/**
* 积分账户加款 Trying
* @param transactionContext
* @param userNo
* @param pointAmount
* @param requestNo
* @param bankTrxNo
* @param trxType
* @param remark
* @throws BizException
*/
@Override
@Transactional(rollbackFor = Exception.class)
@Compensable(confirmMethod = "confirmCreditToPointAccountTcc",cancelMethod = "cancelCreditToPointAccountTcc")
public void creditToPointAccountTcc(TransactionContext transactionContext, String userNo, Integer pointAmount, String requestNo, String bankTrxNo, String trxType, String remark) throws BizException {
LOG.info("===>creditToPointAccountTcc TRYING begin");
//根据商户编号获取商户积分账户
pointAccount pointAccount = pointAccountDao.getByUserNo(userNo);
if (pointAccount == null){//如果不存在商户积分账户,创建一条新的积分账户
pointAccount = new pointAccount();
pointAccount.setBalance(0);
pointAccount.setUserNo(userNo);
pointAccount.setStatus(PublicEnum.YES.name());
pointAccount.setCreateTime(new Date());
pointAccount.setId(StringUtil.get32UUID());
pointAccountDao.insert(pointAccount);
}
pointAccountHistory pointAccountHistory = pointAccountHistoryDao.getByRequestNo(requestNo);
// 幂等判断
if ( pointAccountHistory == null ){//防止多次提交
pointAccountHistory = new pointAccountHistory();
pointAccountHistory.setId(StringUtil.get32UUID());
pointAccountHistory.setCreateTime(new Date());
pointAccountHistory.setStatus(PointAccountHistoryStatusEnum.TRYING.name());//消息不可用
pointAccountHistory.setAmount(pointAmount);///积分账户变动额
pointAccountHistory.setBalance(pointAccount.getBalance() + pointAmount);
pointAccountHistory.setBankTrxNo(bankTrxNo);//银行流水号
pointAccountHistory.setRequestNo(requestNo);//请求号
pointAccountHistory.setFundDirection(PointAccountFundDirectionEnum.ADD.name());
pointAccountHistory.setTrxType(trxType);
pointAccountHistory.setRemark(remark);
pointAccountHistory.setUserNo(userNo);
pointAccountHistoryDao.insert(pointAccountHistory);
}else if (PointAccountHistoryStatusEnum.CANCEL.name().equals(pointAccountHistory.getStatus())){
//如果是取消的,有可能是之前的业务出现异常问题而取消,那么重试阶段,再将状态更新为TYING状态,而不是重新创建一条
LOG.info("之前因为业务问题取消后,又重试的{}" , pointAccountHistory.getBankTrxNo());
pointAccountHistory.setStatus(PointAccountHistoryStatusEnum.TRYING.name());
this.pointAccountHistoryDao.update(pointAccountHistory);
}
//添加一条不可用的积分账户流水
LOG.info("===>creditToPointAccountTcc TRYING end");
}
/**
* 积分账户增加确认
* @param transactionContext
* @param userNo
* @param pointAmount
* @param requestNo
* @param bankTrxNo
* @param trxType
* @param remark
* @return
* @throws BizException
*/
@Transactional(rollbackFor = Exception.class)
public void confirmCreditToPointAccountTcc(TransactionContext transactionContext, String userNo, Integer pointAmount, String requestNo, String bankTrxNo, String trxType, String remark) throws BizException {
LOG.info("===>confirmCreditToPointAccountTcc begin");
//根据请求号获取账户基本流水
pointAccountHistory pointAccountHistory = pointAccountHistoryDao.getByRequestNo(requestNo);
// 幂等判断
if ( pointAccountHistory == null || PointAccountHistoryStatusEnum.CONFORM.name().equals(pointAccountHistory.getStatus())){//该笔交易流水已处理过,不需再处理
return;
}
pointAccountHistory.setStatus(PointAccountHistoryStatusEnum.CONFORM.name());
pointAccountHistoryDao.update(pointAccountHistory);
pointAccount pointAccount = pointAccountDao.getByUserNo(userNo);//获取用户积分账户
pointAccount.setBalance(pointAccount.getBalance() + pointAmount);//增加账户余额
pointAccountDao.update(pointAccount);
LOG.info("===>confirmCreditToPointAccountTcc end");
}
/**
*积分账户增加回滚
* @param transactionContext
* @param userNo
* @param pointAmount
* @param requestNo
* @param bankTrxNo
* @param trxType
* @param remark
* @throws BizException
*/
@Transactional(rollbackFor = Exception.class)
public void cancelCreditToPointAccountTcc(TransactionContext transactionContext, String userNo, Integer pointAmount, String requestNo, String bankTrxNo, String trxType, String remark) throws BizException {
LOG.info("===>cancelCreditToPointAccountTcc begin");
pointAccountHistory pointAccountHistory = pointAccountHistoryDao.getByRequestNo(requestNo);
// 幂等判断
if ( pointAccountHistory == null || !PointAccountHistoryStatusEnum.TRYING.name().equals(pointAccountHistory.getStatus())){//该笔交易流水已处理过,不需再处理
return;
}
pointAccountHistory.setStatus(PointAccountHistoryStatusEnum.CANCEL.name());
pointAccountHistoryDao.update(pointAccountHistory);
LOG.info("===>cancelCreditToPointAccountTcc end");
}
/**
* 积分账户加款 Trying
* @param userNo
* @param pointAmount
* @param requestNo
* @param bankTrxNo
* @param trxType
* @param remark
* @throws BizException
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void creditToPointAccount(String userNo, Integer pointAmount, String requestNo, String bankTrxNo, String trxType, String remark) throws BizException {
//根据商户编号获取商户积分账户
pointAccount pointAccount = pointAccountDao.getByUserNo(userNo);
if (pointAccount == null){//如果不存在商户积分账户,创建一条新的积分账户
pointAccount = new pointAccount();
pointAccount.setBalance(0);
pointAccount.setUserNo(userNo);
pointAccount.setStatus(PublicEnum.YES.name());
pointAccount.setCreateTime(new Date());
pointAccount.setId(StringUtil.get32UUID());
pointAccountDao.insert(pointAccount);
}
//添加一条积分历史
pointAccountHistory pointAccountHistory = pointAccountHistoryDao.getByRequestNo(requestNo);
if ( pointAccountHistory == null ){//防止多次提交
pointAccountHistory = new pointAccountHistory();
pointAccountHistory.setId(StringUtil.get32UUID());
pointAccountHistory.setCreateTime(new Date());
pointAccountHistory.setStatus(PointAccountHistoryStatusEnum.CONFORM.name());//可用
pointAccountHistory.setAmount(pointAmount);///积分账户变动额
pointAccountHistory.setBalance(pointAccount.getBalance() + pointAmount);
pointAccountHistory.setBankTrxNo(bankTrxNo);//银行流水号
pointAccountHistory.setRequestNo(requestNo);//请求号
pointAccountHistory.setFundDirection(PointAccountFundDirectionEnum.ADD.name());
pointAccountHistory.setTrxType(trxType);
pointAccountHistory.setRemark(remark);
pointAccountHistory.setUserNo(userNo);
pointAccountHistoryDao.insert(pointAccountHistory);
}
//增加积分账户
pointAccount.setBalance(pointAccount.getBalance() + pointAmount);//增加账户余额
pointAccountDao.update(pointAccount);
}
@Override
public pointAccount getDataById(String id) {
return pointAccountDao.getById(id);
}
@Override
public PageBean listPage(PageParam pageParam, pointAccount pointAccount) {
Map paramMap = new HashMap();
return pointAccountDao.listPage(pageParam, paramMap);
}
}