点击左侧菜单栏的"转账"按钮后,便可以进入转账主界面。在界面中,需要用户填写收款账号和收款账户名以及转账金额等信息。
在用户填写完整的信息后,便可以点击"转账"按钮继续操作。
点击"转账"按钮后便会弹出输入支付密码的弹窗,用户需要在输入框中填写支付密码后,点击"确定"按钮进行转账操作,如果想取消本次转账可以点击取消按钮进行取消。
当用户填写的所有信息都正确,切余额足够时,系统便会弹出转账成功的提示框。
如果收款账户不存在,系统会弹出“账户不存在”的弹窗提示。
若输入的收款名错误,系统会弹出"转账失败"的弹窗提示。
若用户的余额不足,系统会弹出"余额不足"的弹窗提示。
若用户的支付密码输入错误,则会弹出"密码错误"的提示框。
下订单、减库存、减积分是不可分割的业务逻辑操作,这三个操作要么同时成功,要么同时失败。
转账前和转账后的总金额不变。
下订单、扣库存、减账户余额这3个操作是同一个事务,要么同时成功要么同时失败。
本地事务指下订单、扣库存、减账户余额这3个操作连接的是同一个数据库,与数据库建立了同一条连接,在该连接发送了3条SQL(下订单、扣库存、减账户),只要有一条SQL执行失败,则3个SQL全部回滚。
使用注解@Transactional
需要注意隔离级别和传播行为。
参考:
事务的隔离级别有哪些?
https://www.xiaolincoding.com/mysql/transaction/mvcc.html#%E4%BA%8B%E5%8A%A1%E7%9A%84%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB%E6%9C%89%E5%93%AA%E4%BA%9B
使用读未提交隔离级别时,并发事务时可能会导致脏读、不可重复读、幻读等现象。
脏读举例:数据库中字段price为1000,A修改数据库为1500,此时B查看price为1500,结果A回滚了事务,数据库中数据还是1000。
结果:B出现脏读(B持有的price为1500,实际上应为1000)。
Read committed
(读已提交):指一个事务提交之后,它做的变更才能被其他事务看到(一个事务要等另一个事务提交后才能读取数据)。(针对的是UPDATE操作);isolation
来声明事务隔离级别。使用读已提交隔离级别时,并发事务时可能会导致不可重复读、幻读等现象。
举例:数据库中字段price为1000,A查看数据为1000,告诉了C,此时B将数据进行了修改,修改为1600并提交,当C去查看price为1600。
结果:出现了不可重复读现象。
Repeatable read
(可重复读):指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别;isolation
来声明事务隔离级别;使用可重复读隔离级别时,并发事务时可能会导致幻读等现象。
举例:数据库中只有一条记录,当A查看时只有一条,A告诉C,此时B去数据库插入一条数据,C去查看时有两条。(可重复读针对的是同一条数据,前后读取同一条数据,得到的结果是一致的;但并不能保证前后读取数据库表的数据记录不变)
结果:出现了幻读现象。
Serializable
(串行化):会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;isolation
来声明事务隔离级别。使用串行化隔离级别时,可以避免脏读、不可重复读与幻读。
想要发生传播就一定要有两个以上的事务,而这里指的是两个方法都要在事务中进行,当一个事务方法A调用另一个事务方法B时,另一个事务方法B该如何运行。
A方法中调用了B、C方法,传播行为指B、C这俩小事务要不要和A共有一个事务。
如,B的传播行为Propagation.REQUIRED,则说明B需要一个事务,此时如果A有事务,就使用A的事务(相当于B和A在同一条连接中执行),若A没有事务,就新建事务。
@Transactional(timeout = 10)// A事务的所有设置会传播到和他共有一个事务的方法(即B事务的任何设置都是无效,都得遵循A的事务设置),而C的新事务不会受A的影响。
public void A(){
// 使用了代理,B、C的事务传播行为才会生效
bService.B();
cService.C();
}
// 此时如果A有事务,就使用A的事务,A没有事务,就新建事务
@Transactional(propagation = Propagation.REQUIRED,timeout = 5)
public void B(){
}
// 不和A公用一个事务,新建事务
@Transactional(propagation = Propagation.REQUIRES_NEW,timeout = 20)
public void C(){
}
@Transactional(timeout = 10)
public void A(){
// 使用了代理,B、C的事务传播行为才会生效
// bService.B();
// cService.C();
// 直接调用B、C方法,相当于跳过了代理,b,c此时做任何设置都没作用,它们此时都是和A公用一个事务,此时不生效,要使用代理
B();
C();
}
// 此时如果A有事务,就使用A的事务,A没有事务,就新建事务
@Transactional(propagation = Propagation.REQUIRED,timeout = 5)
public void B(){
}
// 不和A公用一个事务,新建事务
@Transactional(propagation = Propagation.REQUIRES_NEW,timeout = 20)
public void C(){
}
同一个对象内事务方法互调默认失效,原因:绕过了代理对象。
在方法上面加上
@Transaction
,那么在本类中调用的时候实际上就是通过cglib
继承代理对象来调用,增强,然后调用原来对象的方法。
如果嵌套其它加上注解的方法,那么这些方法是不起作用的,原因就是第一个被调用的方法是通过代理对象调用的,但是其他方法是直接调用,没有经过事务的增强调用。
本地事务失效问题:
同一个对象内事务方法互调默认失效。,
原因:绕过了代理对象,事务使用代理对象来控制的
解决:使用代理对象来调用事务方法
针对:本类方法互调使用事务
可以通过开启aspectj动态代理注解@EnableAspectJAutoProxy(exposeProxy = true),创建一个代理对象,然后通过代理对象来调用这些方法。
那么就可以解决本地事务失效问题,相当于就是把所有方法增强之后的一个代理对象来调用这些方法。本质上就是通过代理对象来调用。也就是方法已经经过增强了。
@Transactional
public void a(){
OrderServiceImpl o = (OrderServiceImpl) AopContext.currentProxy();
o.b();
o.c();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void b(){
}
@Transactional(propagation = Propagation.REQUIRED)
public void c(){
}
分布式事务产生的场景:
分布式系统会把一个应用系统拆分为可独立部署的多个服务,因此需要服务与服务之间远程协作才能完成事务操作。
这种分布式系统环境下由不同的服务之间通过网络远程协作完成事务称之为分布式事务,例如用户注册送积分事务、创建订单减库存事务,银行转账事务等都是分布式事务。
分布式经常出现的异常:机器宕机,网络异常,消息丢失,消息乱序,数据错误,不可靠的TCP,存储数据丢失。
参考:
差点跪了!阿里3面真题:CAP和BASE理论了解么?可以结合实际案例说下不?
https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247495298&idx=1&sn=965be0f54ab44bda818656db1f21a39f&chksm=cea1a149f9d6285f1169413ab7663ca2a9c1a8440a5ae5816566eb66b20e4d86f5db1002f66c&token=657875872&lang=zh_CN#rd
面试指北
https://www.yuque.com/books/share/04ac99ea-7726-4adb-8e57-bf21e2cc7183/ng9vmg#UzNZp
什么是网络分区?
分布式系统中,多个节点之前的网络本来是连通的,但是因为某些故障(比如部分节点网络出了问题)某些节点之间不连通了,整个网络就分成了几块区域,这就叫网络分区。
当发生网络分区的时候,如果我们要继续服务,那么强一致性和可用性只能 2 选 1。也就是说当网络分区之后 P 是前提,决定了 P 之后才有 C 和 A 的选择。也就是说分区容错性(Partition tolerance)我们是必须要实现的。
简而言之就是:CAP 理论中分区容错性 P 是一定要满足的,在此基础上,只能满足可用性 A 或者一致性 C。
因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。
举个例子:若系统出现"分区",系统中的某个节点在进行写操作。为了保证 C, 必须要禁止其他所有集群节点(不是不同分区的节点?)的读写操作,这就和 A 发生冲突了。如果为了保证 A,其他节点的读写操作正常的话,那就和 C 发生冲突了。
如下图,假设3个机器都要保存8号数据。此时,一个请求过来,让这3个备份节点都保存8号数据。
选择的关键在于当前的业务场景,没有定论,比如对于需要确保强一致性的场景如银行一般会选择保证CP。
BASE 是 Basically Available(基本可用) 、Soft-state(软状态) 和 Eventually Consistent(最终一致性) 三个短语的缩写。BASE 理论是对 CAP 中一致性 C 和可用性 A 权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于 CAP 定理逐步演化而来的,它大大降低了我们对系统的要求。
BASE理论的核心思想:
即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
也就是牺牲数据的强一致性来满足系统的高可用性,系统中一部分数据不可用或者不一致时,仍需要保持系统整体"主要可用"。
BASE 理论本质上是对 CAP 的延伸和补充,更具体地说,是对 CAP 中 AP 方案的一个补充。
如,之前所述的下订单(节点1)、减库存(节点2)、加积分业务(节点3),当减库存事务执行成功,但由于网络问题响应超时,使得下订单事务回滚,但远程服务器中的减库存事务并不会回滚(本地事务无法控制远程事务),那么会出现下订单的服务器中的商品数据与减库存服务中的商品数据不一致问题。
但是BASE理论允许各节点继续保持可用,等过段时间后,系统检查发现刚才减出来的库存没人用,那么会在库存表中将减掉的库存加回来。
最终结果还是会保持节点1和节点2的数据一致性。
ACID 是数据库事务完整性的理论,CAP 是分布式系统设计理论,BASE 是 CAP 理论中 AP 方案的延伸。
参考:
分布式事务解决方案实战
https://blog.csdn.net/huxiang19851114/article/details/114607489?spm=1001.2014.3001.5506
数据库支持的2PC【二阶段提交】,又被称为XA Transactions。Mysql从5.5开始支持,SQL Server 2005开始支持,Oracle 7开始支持。
其中XA是一个两阶段提交协议:
其中如果有任何一个数据库否决此次提交,那么所有数据库都会被要求回滚他们在此事务中的那部分信息。
一句话:意思就是一个大的事务管理器连接多个服务,每次要执行业务时都去问服务准备好了吗,如果准备好那么就开始执行。如果没有那么就不执行。问题就是性能不行,每次都要询问。
举例:
队长:要打BOSS了,各位你们就位了吗?(Prepare)
队员A:我就位了(Ready)
队员B:我就位了(Ready)
队员C:我就位了(Ready)
队员D:我就位了(Ready)
队长:所有人都就位了,兄弟们,丢技能吧!(Commit)
---------------------------------------------------------------
队长:要打BOSS了,各位你们就位了吗?(Prepare)
队员A:我就位了(Ready)
队员B:我就位了(Ready)
队员C:我就位了(Ready)
队员D:我没蓝....(No Ready)
队长:各位兄弟,还有个傻X没准备好,本次团战计划取消,大家继续打野(Cancel)
优缺点:
也有3PC,引入超时机制(无论协调者还是参与者,在向对方发送请求后,若长时间未收到回应则做出相应处理)
意思就是把业务按照3个接口写入,try,confirm和cancel。先写好业务交给各自的服务数据库try,然后就去confirm是否执行成功,如果没有那么就通过canel来回滚补偿。
两个服务订阅一个消息队列。大模块调用两个服务但是失败了,就把失败信息交给消息队列,两个服务接收到之后就去回滚。而且为了防止服务不知道,它会多次发送消息给消息队列,直到两个服务都知道了。
可靠消息+最终一致性与最大努力通知相似。
seata官网教程:http://seata.io/zh-cn/docs/overview/what-is-seata.html
Day433.Seata使用 -谷粒商城
https://blog.csdn.net/qq_43284469/article/details/121025269?ops_request_misc=&request_id=&biz_id=102&utm_term=%E8%B0%B7%E7%B2%92%E5%95%86%E5%9F%8E%20seata&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-121025269.142^v50^control_1,201^v3^control_1&spm=1018.2226.3001.4187
谷粒商城(RabbitMq简介、订单服务、分布式事务)思路详解
https://blog.csdn.net/m0_46388866/article/details/120523932
at方式的seata分布式事务处理其实就是提交事务,如果发生问题,那么就可以通过undolog来完成回滚。但是这种问题就是需要一个订单一个订单的处理无法处理高并发情况
https://blog.csdn.net/qq_43284469/article/details/121025269?ops_request_misc=&request_id=&biz_id=102&utm_term=%E8%B0%B7%E7%B2%92%E5%95%86%E5%9F%8E%20seata&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-121025269.142^v50^control_1,201^v3^control_1&spm=1018.2226.3001.4187
Day433.Seata使用 -谷粒商城
https://blog.csdn.net/qq_43284469/article/details/121025269?ops_request_misc=&request_id=&biz_id=102&utm_term=%E8%B0%B7%E7%B2%92%E5%95%86%E5%9F%8E%20seata&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-121025269.142^v50^control_1,201^v3^control_1&spm=1018.2226.3001.4187
谷粒商城(RabbitMq简介、订单服务、分布式事务)思路详解
https://blog.csdn.net/m0_46388866/article/details/120523932
https://blog.csdn.net/huxiang19851114/article/details/114607489?spm=1001.2014.3001.5506
https://blog.csdn.net/weixin_44244088/article/details/125417959
https://blog.csdn.net/m0_45406092/article/details/121263208
AT模式最大的问题是运行期锁导致性能低!
举例:两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。
m = 1000 - 100 = 900
。本地事务提交前,需要先拿到该记录的全局锁(可以理解是分布式锁,保证全局事务的提交或回滚) ,然后本地提交并释放本地锁;m = 900 - 100 = 800
。本地事务提交前,需要尝试拿该记录的全局锁 ,tx1全局提交前,该记录的全局锁被 tx1持有,tx2需要重试等待全局锁。因为整个过程全局锁在tx1结束前一直是被tx1持有的,所以不会发生脏写的问题。(但是可能会有脏读)
总结:这两种情况时,情况1需要等待,情况2陷入死锁并最终都放弃并回滚(啥也没干成),所以不管哪种情况发生,自然会导致性能严重下降。
https://www.yuque.com/books/share/04ac99ea-7726-4adb-8e57-bf21e2cc7183/ng9vmg#Q3gvA
前面讲过了Seata实现分布式解决方案AT模式,使用非常简单,对业务代码也没有侵入,但是超高并发情况下也发现了其中两个最大的问题:
这也就是AT模式提交最大的痛点!所以我们针对高并发场景推出TCC模式,他的特点是:高性能,但代码侵入!
TCC模式也属于二阶段提交,它 将事务提交分为Try - Confirm - Cancel
3个操作。其和两阶段提交有点类似,Try为第一阶段,Confirm - Cancel为第二阶段,是一种应用层面侵入业务的两阶段提交。
其核心在于将业务分为两个操作步骤完成。不依赖 RM (业务模块)对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务。
分布式事务(七)Seata TCC模式-Spring Cloud微服务添加 TCC 分布式事务
https://wanght.blog.csdn.net/article/details/107702760
分布式事务(六)Seata TCC模式-TCC模式介绍
https://blog.csdn.net/weixin_38305440/article/details/107692098
在分布式银行系统中,由于数据不是存储在同一个数据库进行操作,转账过程中涉及跨数据库的事务,故一对一转账系统中需要解决分布式事务的问题。
在跨数据库情况下在复杂的网络环境中保证转账业务的安全。
https://blog.csdn.net/huxiang19851114/article/details/114607489?spm=1001.2014.3001.5506
https://blog.csdn.net/it__ls/article/details/125622729
https://wanght.blog.csdn.net/article/details/107702760
https://www.imooc.com/article/320178
牛客论坛项目的编程式事务:
https://blog.csdn.net/qq_60225495/article/details/122793859?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166393900316800182176115%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=166393900316800182176115&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-122793859-null-null.nonecase&utm_term=%E7%BC%96%E7%A8%8B%E5%BC%8F%E4%BA%8B%E5%8A%A1&spm=1018.2226.3001.4450
Seata 实现 TCC 操作需要定义一个接口,我们在接口中添加以下方法:
package com.icbc.distributed.transfer.action;
import com.icbc.distributed.transfer.entity.AccBalanceTableEntity;
import com.icbc.distributed.transfer.entity.TransferInfoEntity;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
/**
* The interface Tcc action one.
* TCC事务参与者1:扣款
*/
@LocalTCC
public interface TccActionOne {
/**
* 第一阶段的方法————准备
* @TwoPhaseBusinessAction:二阶段TCC补偿提交注解
* name:TCC参与者名称,你高兴怎么取名都可以;
* commitMethod:二阶段提交方法,与下面接口名对应(底层就是invoke反射);
* rollbackMethod:二阶段回滚方法,注意与下面接口名对应;
* @param actionContext 用于在上下文中获取全局事务ID(用于在两个阶段之间传递数据。)
* @param uuid 业务唯一编码
* @param amount 扣除金额
* @param accBalanceTableEntityFrom 账户余额表
* @param transferInfoEntity 这是前端表单传过来的
*
* 通过@TwoPhaseBusinessAction注解指定第二阶段的两个方法名(commit、rollback)
* BusinessActionContext 上下文对象,用来在两个阶段之间传递数据。
* @BusinessActionContextParameter注解的参数数据会被存入BusinessActionContext
* 原文链接:https://blog.csdn.net/weixin_38305440/article/details/107702760
*/
@TwoPhaseBusinessAction(name = "TccActionOne" , commitMethod = "commit", rollbackMethod = "rollback")
public boolean prepare(BusinessActionContext actionContext,
@BusinessActionContextParameter(paramName = "transferInfoEntity") TransferInfoEntity transferInfoEntity,
@BusinessActionContextParameter(paramName = "uuid") String uuid,
@BusinessActionContextParameter(paramName = "amount") Double amount,
@BusinessActionContextParameter(paramName = "accBalanceTableEntityFrom") AccBalanceTableEntity accBalanceTableEntityFrom);
/**
* Commit boolean.
* 第二阶段 - 提交
* @param actionContext the action context
* @return the boolean
*/
public boolean commit(BusinessActionContext actionContext);
/**
* Rollback boolean.
* 第二阶段 - 回滚
* @param actionContext the action context
* @return the boolean
*/
public boolean rollback(BusinessActionContext actionContext);
}
package com.icbc.distributed.transfer.action.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.icbc.distributed.transfer.entity.AccBalanceTableEntity;
import com.icbc.distributed.transfer.entity.TransferInfoEntity;
import com.icbc.distributed.transfer.action.ResultHolder;
import com.icbc.distributed.transfer.action.TccActionOne;
import com.icbc.distributed.transfer.tccservice.SeataTccTransferService;
import com.icbc.distributed.transfer.service.AccAgrtTableService;
import com.icbc.distributed.transfer.service.AccBalanceTableService;
import com.icbc.distributed.transfer.service.AccDetailsTableService;
import io.seata.core.context.RootContext;
import io.seata.rm.tcc.api.BusinessActionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import java.math.BigDecimal;
import java.util.HashMap;
/**
* The type Tcc action one.
* TCC事务参与者1:扣款
*/
@Service("tccActionOne")
public class TccActionOneImpl implements TccActionOne {
@Autowired
SeataTccTransferService seataTccTransferService;
@Autowired
private TransactionTemplate fromDsTransactionTemplate;
@Autowired
private AccBalanceTableService accBalanceTableService;
@Autowired
private AccAgrtTableService accAgrtTableService;
@Autowired
private AccDetailsTableService accDetailsTableService;
/*
* 一阶段:冻结付方资金
*/
// @Transactional
@Override
public boolean prepare(BusinessActionContext actionContext, TransferInfoEntity transferInfoEntity, String uuid, Double amount, AccBalanceTableEntity accBalanceTableEntityFrom) {
//分布式事务ID
String xid = actionContext.getXid();
System.out.println("TccActionOne prepare, xid:" + xid);
System.out.println("一阶段");
// 编程式事务(使用编程式事务,就不需要在prepare、commit、rollback方法前加@Transactional注解了)
return fromDsTransactionTemplate.execute(new TransactionCallback<Boolean>(){
@Override
public Boolean doInTransaction(TransactionStatus status) {
try {
System.out.println("deductbalance ... xid: " + RootContext.getXID());
RootContext.bind(RootContext.getXID());// 当前线程绑定的分布式事务ID为xid
System.out.println("付方一阶段执行");
// 根据用户账户获取账户余额信息
QueryWrapper querywrapper = new QueryWrapper<AccBalanceTableEntity>();
querywrapper.eq("acc_id",accBalanceTableEntityFrom.getAccId());
AccBalanceTableEntity accBalanceTableEntity = accBalanceTableService.getOne(querywrapper);
// 不用TCC的话,更新账户余额、更新账户冻结金额的操作应如下代码
// Double balance = accBalanceTableEntityFrom.getCurBalance()-amount;
// accBalanceTableEntity.setCurBalance(balance);//更新余额
// accBalanceTableEntity.setFreezeBalance(accBalanceTableEntity.getFreezeBalance()+amount);
// 1.冻结转账金额(获取原始冻结金额,在此基础上增加新的转账金额)
Double frzeeBalance;
// 1.1.获取原始冻结金额
if(accBalanceTableEntity.getFreezeBalance()==null){
// 如果账户没有原始冻结金额,则初始化原始冻结金额frzeeBalance(设置为0)
frzeeBalance = Double.valueOf(0);
}else {
// 如果账户有原始冻结金额,则获取原始冻结金额,赋给frzeeBalance。
frzeeBalance = accBalanceTableEntity.getFreezeBalance();
}
// 1.2.新的冻结金额(保留2位小数)
// 冻结资金中增加amount元(amount:转账金额)。账户当前余额中减去amount元
Double newFrzeeBalance = Double.valueOf(String.format("%17.2f", frzeeBalance+amount ));
// 减去转账金额后,新的账户余额
Double newCurBalance = Double.valueOf(String.format("%17.2f", accBalanceTableEntity.getCurBalance()-amount));
// 2.更新数据库用户余额表(更新表中的冻结金额、用户余额)
// 更新账户交易明细详情表(用户余额更新了,所以账户交易明细详情表AccDetailsTableEntity中的余额也要相应更新)
accBalanceTableEntity.setFreezeBalance(newFrzeeBalance);
accBalanceTableEntity.setCurBalance(newCurBalance);
System.out.printf("现有余额%f,转账%f",accBalanceTableEntity.getCurBalance(),amount);
// accBalanceTableEntityFrom.setLastTxnDate(transferInfoEntity.getExecDate());//更新最后交易日
accBalanceTableEntity.setRegionId(null);
// System.out.println(accBalanceTableEntity);
// 更新数据库用户余额表、账户交易明细详情表
accBalanceTableService.saveOrUpdate(accBalanceTableEntity);//插表
seataTccTransferService.addDetail(transferInfoEntity,uuid,amount,accBalanceTableEntity);
if("1900140000000490".equals(accBalanceTableEntity.getAccId())){
throw new RuntimeException("付方一阶段失败.");
}
// throw new RuntimeException("TccActionTwo failed.");
return true;
} catch (Throwable t) {
t.printStackTrace();
return false;
}
}
});
}
/*
* 二阶段:执行扣款
*/
// @Transactional
@Override
public boolean commit(BusinessActionContext actionContext) {
//分布式事务ID
String xid = actionContext.getXid();
System.out.println("TccActionOne commit, xid:" + xid);
System.out.println(actionContext.toString());
//用户余额表accBalanceTableEntityFrom、转账金额amount等数据没有通过方法传参传进来,这里使用BusinessActionContext来获取这些数据。
//用户余额表对象
Object object = actionContext.getActionContext("accBalanceTableEntityFrom");
AccBalanceTableEntity accBalanceTableEntityFrom = JSON.parseObject(object.toString(), AccBalanceTableEntity.class);
System.out.println(accBalanceTableEntityFrom.toString());
//转账金额
BigDecimal bigDecimal = (BigDecimal)actionContext.getActionContext("amount");
Double amount = bigDecimal.doubleValue();
ResultHolder.setActionOneResult(xid, "T");
// 编程式事务
return fromDsTransactionTemplate.execute(new TransactionCallback<Boolean>() {
// @Transactional
@Override
public Boolean doInTransaction(TransactionStatus status) {
try{
System.out.println("deductbalance ... xid: " + RootContext.getXID());
// 在每个微服务中调用方法 RootContext.getXID() 检查XID?(当前线程绑定的分布式事务ID为xid?)
RootContext.bind(RootContext.getXID());
System.out.println("付方一阶段提交");
// 根据账户获取账户余额信息
QueryWrapper querywrapper = new QueryWrapper<AccBalanceTableEntity>();
querywrapper.eq("acc_id",accBalanceTableEntityFrom.getAccId());
AccBalanceTableEntity accBalanceTableEntity = accBalanceTableService.getOne(querywrapper);
Double frzeeBalance;
// 获取原始冻结金额
if(accBalanceTableEntity.getFreezeBalance()==null){
// 如果账户没有原始冻结金额,则初始化原始冻结金额frzeeBalance(设置为0)
frzeeBalance = Double.valueOf(0);
}else {
// 如果账户有原始冻结金额,则获取原始冻结金额,赋给frzeeBalance。
frzeeBalance = accBalanceTableEntity.getFreezeBalance();
}
// 不用TCC的话,在账户余额扣除转账金额的操作应如下代码
// accBalanceTableEntity.setCurBalance(accBalanceTableEntity.getCurBalance()-amount);
// 1.付方用户的冻结资金额减去转账金额(释放付方账户的冻结金额)
Double newFrzeeBalance = Double.valueOf(String.format("%17.2f", frzeeBalance-amount ));
// 2.更新数据库用户余额表(更新表中的冻结金额、最后交易日)
accBalanceTableEntity.setFreezeBalance(newFrzeeBalance);
accBalanceTableEntity.setLastTxnDate(accBalanceTableEntityFrom.getLastTxnDate());//更新最后交易日
accBalanceTableEntity.setRegionId(null);
accBalanceTableService.saveOrUpdate(accBalanceTableEntity);//插表(用户余额表)
System.out.println("付方一阶段提交成功");
if("3700120000000086".equals(accBalanceTableEntity.getAccId())){
throw new RuntimeException("付方二阶段提交失败");
}
// throw new RuntimeException("TccActionTwo failed.");
return true;
}catch (Throwable t){
t.printStackTrace();
status.setRollbackOnly();
return false;
}
}
});
}
/*
* 二阶段:回滚操作
*/
// @Transactional
@Override
public boolean rollback(BusinessActionContext actionContext) {
//分布式事务ID
String xid = actionContext.getXid();
System.out.println("TccActionOne rollback, xid:" + xid);
System.out.println("付方一阶段开始回滚");
System.out.println(actionContext.toString());
// TransferInfoEntity transferInfoEntity = (TransferInfoEntity) actionContext.getActionContext("transferInfoEntity");
//用户余额表accBalanceTableEntityFrom、转账金额amount等数据没有通过方法传参传进来,这里使用BusinessActionContext来获取这些数据。
//业务唯一编码
String uuid = (String) actionContext.getActionContext("uuid");
//转账金额
BigDecimal bigDecimal = (BigDecimal)actionContext.getActionContext("amount");
Double amount = bigDecimal.doubleValue();
//用户余额表对象
Object object = actionContext.getActionContext("accBalanceTableEntityFrom");
AccBalanceTableEntity accBalanceTableEntityFrom = JSON.parseObject(object.toString(), AccBalanceTableEntity.class);
System.out.println(accBalanceTableEntityFrom.toString());
ResultHolder.setActionOneResult(xid, "R");
return fromDsTransactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
try{
System.out.println("deductbalance ... xid: " + RootContext.getXID());
RootContext.bind(RootContext.getXID());
// HashMap formmap = new HashMap<>();
// formmap.put("acc_id",accBalanceTableEntityFrom.getAccId());
// accDetailsTableService.listByMap(formmap);
// 根据账户获取账户余额信息
QueryWrapper querywrapper = new QueryWrapper<AccBalanceTableEntity>();
querywrapper.eq("acc_id",accBalanceTableEntityFrom.getAccId());
AccBalanceTableEntity accBalanceTableEntity = accBalanceTableService.getOne(querywrapper);
//不用TCC的话,在账户余额回复已扣除的转账金额的操作应如下代码
// accBalanceTableEntity.setCurBalance(accBalanceTableEntity.getFreezeBalance()+accBalanceTableEntity.getCurBalance());
Double frzeeBalance;
if(accBalanceTableEntity.getFreezeBalance()==null){
frzeeBalance = Double.valueOf(0);
}else {
frzeeBalance = accBalanceTableEntity.getFreezeBalance();
}
//1.释放冻结金额(付方用户的冻结资金额减去转账金额)
Double newFrzeeBalance = Double.valueOf(String.format("%17.2f", frzeeBalance-amount ));
//2.回滚用户余额,恢复用户转账前的用户余额(付方用户的用户余额加上之前扣除的转账金额)
Double newCurBalance = Double.valueOf(String.format("%17.2f", accBalanceTableEntity.getCurBalance()+amount ));
//3.更新数据库用户余额表(更新表中的冻结金额、用户余额最后交易日)
accBalanceTableEntity.setFreezeBalance(newFrzeeBalance);
accBalanceTableEntity.setCurBalance(newCurBalance);
accBalanceTableEntity.setLastTxnDate(accBalanceTableEntityFrom.getLastTxnDate());//更新最后交易日
accBalanceTableEntity.setRegionId(null);
System.out.println(accBalanceTableEntity);
accBalanceTableService.saveOrUpdate(accBalanceTableEntity);//插表
HashMap<String,Object> txnmap = new HashMap<>();
txnmap.put("txn_id",uuid);//业务唯一编码
// 根据业务唯一编码txnmap去删除因为转账而账户交易明细详情表
accDetailsTableService.removeByMap(txnmap);
System.out.println("一阶段回滚成功");
// if(accBalanceTableEntity.getAccId().equals(4000000200000043L)){
// throw new RuntimeException("付方一阶段提交失败");
// }
return true;
}catch (Throwable t){
t.printStackTrace();
status.setRollbackOnly();
return false;
}
}
});
}
}
package com.icbc.distributed.transfer.action;
import com.icbc.distributed.transfer.entity.AccBalanceTableEntity;
import com.icbc.distributed.transfer.entity.TransferInfoEntity;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
/**
* The interface Tcc action two.
* TCC事务参与者2:收款
*/
@LocalTCC
public interface TccActionTwo {
/**
* Prepare boolean.
*
* @param actionContext the action context
* @param
* @return the boolean
*/
@TwoPhaseBusinessAction(name = "TccActionTwo" , commitMethod = "commit", rollbackMethod = "rollback")
public boolean prepare(BusinessActionContext actionContext,
@BusinessActionContextParameter(paramName = "transferInfoEntity") TransferInfoEntity transferInfoEntity,
@BusinessActionContextParameter(paramName = "uuid") String uuid,
@BusinessActionContextParameter(paramName = "amount") Double amount,
@BusinessActionContextParameter(paramName = "accBalanceTableEntityTo") AccBalanceTableEntity accBalanceTableEntityTo);
/**
* Commit boolean.
*
* @param actionContext the action context
* @return the boolean
*/
public boolean commit(BusinessActionContext actionContext);
/**
* Rollback boolean.
*
* @param actionContext the action context
* @return the boolean
*/
public boolean rollback(BusinessActionContext actionContext);
}
package com.icbc.distributed.transfer.action.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.icbc.distributed.transfer.entity.AccBalanceTableEntity;
import com.icbc.distributed.transfer.entity.TransferInfoEntity;
import com.icbc.distributed.transfer.action.ResultHolder;
import com.icbc.distributed.transfer.action.TccActionTwo;
import com.icbc.distributed.transfer.tccservice.SeataTccTransferService;
import com.icbc.distributed.transfer.service.AccAgrtTableService;
import com.icbc.distributed.transfer.service.AccBalanceTableService;
import com.icbc.distributed.transfer.service.AccDetailsTableService;
import io.seata.core.context.RootContext;
import io.seata.rm.tcc.api.BusinessActionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import java.math.BigDecimal;
import java.util.HashMap;
/**
* The type Tcc action two.
* TCC事务参与者2:收款
*/
@Service("tccActionTwo")
public class TccActionTwoImpl implements TccActionTwo {
@Autowired
SeataTccTransferService seataTccTransferService;
@Autowired
private TransactionTemplate toDsTransactionTemplate;
@Autowired
private AccBalanceTableService accBalanceTableService;
@Autowired
private AccAgrtTableService accAgrtTableService;
@Autowired
private AccDetailsTableService accDetailsTableService;
@Override
public boolean prepare(BusinessActionContext actionContext, TransferInfoEntity transferInfoEntity, String uuid, Double amount, AccBalanceTableEntity accBalanceTableEntityTo) {
String xid = actionContext.getXid();
System.out.println("TccActionTwo prepare, xid:" + xid);
return toDsTransactionTemplate.execute(new TransactionCallback<Boolean>() {
// @Transactional
@Override
public Boolean doInTransaction(TransactionStatus status) {
try{
System.out.println("addbalance ... xid: " + RootContext.getXID());
System.out.println("收方开始执行阶段");
RootContext.bind(RootContext.getXID());
//业务
// accBalanceTableEntityTo.setCurBalance(accBalanceTableEntityTo.getCurBalance()+amount);//更新余额
// accBalanceTableEntityTo.setLastTxnDate(transferInfoEntity.getExecDate());//更新最后交易日
QueryWrapper querywrapper = new QueryWrapper<AccBalanceTableEntity>();
querywrapper.eq("acc_id",accBalanceTableEntityTo.getAccId());
AccBalanceTableEntity accBalanceTableEntity = accBalanceTableService.getOne(querywrapper);
Double frzeeBalance;
if(accBalanceTableEntity.getFreezeBalance()==null){
frzeeBalance = Double.valueOf(0);
}else {
frzeeBalance = accBalanceTableEntity.getFreezeBalance();
}
//待转入资金作为不可用金额(冻结金额)
Double newFrzeeBalance = Double.valueOf(String.format("%17.2f", frzeeBalance+amount ));
accBalanceTableEntity.setFreezeBalance(newFrzeeBalance);
accBalanceTableEntity.setRegionId(null);
// 更新数据库
accBalanceTableService.saveOrUpdate(accBalanceTableEntity);//插表
seataTccTransferService.addDetail(transferInfoEntity,uuid,amount,accBalanceTableEntityTo);
if("2000100000000517".equals(accBalanceTableEntity.getAccId())){
throw new RuntimeException("二阶段执行失败");
}
// System.out.println(String.format("Undo prepareAdd account[%s] amount[%f], dtx transaction id: %s.", accountNo, amount, xid));
return true;
}catch (Throwable t){
t.printStackTrace();
return false;
}
}
});
}
@Override
public boolean commit(BusinessActionContext actionContext) {
String xid = actionContext.getXid();
System.out.println("TccActionTwo commit, xid:" + xid);
System.out.println(actionContext.toString());
BigDecimal bigDecimal = (BigDecimal)actionContext.getActionContext("amount");
Double amount = bigDecimal.doubleValue();
Object object = actionContext.getActionContext("accBalanceTableEntityTo");
AccBalanceTableEntity accBalanceTableEntityTo = JSON.parseObject(object.toString(), AccBalanceTableEntity.class);
ResultHolder.setActionTwoResult(xid, "T");
return toDsTransactionTemplate.execute(new TransactionCallback<Boolean>() {
// @Transactional
@Override
public Boolean doInTransaction(TransactionStatus status) {
try{
RootContext.bind(actionContext.getXid());//RootContext.getXID()
System.out.println("deductbalance ... xid: " + RootContext.getXID());
System.out.println("收方二阶段提交");
QueryWrapper querywrapper = new QueryWrapper<AccBalanceTableEntity>();
querywrapper.eq("acc_id",accBalanceTableEntityTo.getAccId());
AccBalanceTableEntity accBalanceTableEntity = accBalanceTableService.getOne(querywrapper);
Double frzeeBalance;
if(accBalanceTableEntity.getFreezeBalance()==null){
frzeeBalance = Double.valueOf(0);
}else {
frzeeBalance = accBalanceTableEntity.getFreezeBalance();
}
//释放收方账户中的冻结金额
Double newFrzeeBalance = Double.valueOf(String.format("%17.2f", frzeeBalance - amount ));
//收方账户加钱
Double newCurBalance = Double.valueOf(String.format("%17.2f", accBalanceTableEntity.getCurBalance() + amount ));
// 更新用户余额表
accBalanceTableEntity.setCurBalance(newCurBalance);//更新余额
accBalanceTableEntity.setFreezeBalance(newFrzeeBalance);//更新余额
accBalanceTableEntity.setLastTxnDate(accBalanceTableEntityTo.getLastTxnDate());//更新最后交易日
accBalanceTableEntity.setRegionId(null);
accBalanceTableService.saveOrUpdate(accBalanceTableEntity);//插表
// if("52001400000005300".equals(accBalanceTableEntity.getAccId())){
// throw new RuntimeException("二阶段提交失败");
// }
// throw new RuntimeException("TccActionTwo failed.");
return true;
}catch (Throwable t){
t.printStackTrace();
status.setRollbackOnly();
return false;
}
}
});
}
@Override
public boolean rollback(BusinessActionContext actionContext) {
String xid = actionContext.getXid();
System.out.println("TccActionTwo rollback, xid:" + xid);
ResultHolder.setActionTwoResult(xid, "R");
System.out.println("收方二阶段开始回滚");
System.out.println(actionContext.toString());
// TransferInfoEntity transferInfoEntity = (TransferInfoEntity) actionContext.getActionContext("transferInfoEntity");
String uuid = (String) actionContext.getActionContext("uuid");
System.out.println(uuid);
BigDecimal bigDecimal = (BigDecimal)actionContext.getActionContext("amount");
Double amount = bigDecimal.doubleValue();
Object object = actionContext.getActionContext("accBalanceTableEntityTo");
AccBalanceTableEntity accBalanceTableEntityTo = JSON.parseObject(object.toString(), AccBalanceTableEntity.class);
System.out.println(accBalanceTableEntityTo.toString());
return toDsTransactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
try{
System.out.println("deductbalance ... xid: " + RootContext.getXID());
RootContext.bind(RootContext.getXID());
QueryWrapper querywrapper = new QueryWrapper<AccBalanceTableEntity>();
querywrapper.eq("acc_id",accBalanceTableEntityTo.getAccId());
AccBalanceTableEntity accBalanceTableEntity = accBalanceTableService.getOne(querywrapper);
Double frzeeBalance;
if(accBalanceTableEntity.getFreezeBalance()==null){
frzeeBalance = Double.valueOf(0);
}else {
frzeeBalance = accBalanceTableEntity.getFreezeBalance();
}
//冻结金额清除
Double newFrzeeBalance = Double.valueOf(String.format("%17.2f", frzeeBalance-amount ));
// 更新数据库用户余额表
accBalanceTableEntity.setFreezeBalance(newFrzeeBalance);
accBalanceTableEntity.setLastTxnDate(accBalanceTableEntityTo.getLastTxnDate());//更新最后交易日
accBalanceTableEntity.setRegionId(null);
System.out.println(accBalanceTableEntity);
accBalanceTableService.saveOrUpdate(accBalanceTableEntity);//插表
HashMap<String,Object> txnmap = new HashMap<>();
txnmap.put("txn_id",uuid);
// 删除账户交易明细详情表
accDetailsTableService.removeByMap(txnmap);
System.out.println("二阶段回滚成功");
return true;
}catch (Throwable t){
t.printStackTrace();
status.setRollbackOnly();
return false;
}
}
});
}
}
package com.icbc.distributed.transfer.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.icbc.common.utils.ParseJwt;
import com.icbc.common.utils.R;
import com.icbc.distributed.transfer.entity.AccBalanceTableEntity;
import com.icbc.distributed.transfer.entity.AccInfoTableEntity;
import com.icbc.distributed.transfer.entity.TransferInfoEntity;
import com.icbc.distributed.transfer.tccservice.TccTransactionService;
import com.icbc.distributed.transfer.service.*;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RefreshScope
@RestController
@RequestMapping("/transfer")
public class TransferController {
@Autowired
TccTransactionService transferService;
@Autowired
private AccBalanceTableService accBalanceTableService;
@Autowired
private AccDetailsTableService accDetailsTableService;
@Autowired
private BizRegTableService bizRegTableService;
@Autowired
private AccInfoTableService accInfoTableService;
// @RequestBody TransferInfoEntity transferInfoEntity
// 1.收付方账户信息校验
@RequestMapping(value = "/check", method = RequestMethod.POST)
@ResponseBody
public R checkAcc(@RequestBody TransferInfoEntity transferInfoEntity) throws Exception {
System.out.println("账号检查");
System.out.println(transferInfoEntity);
try {
R ret = transferService.checkAcc(transferInfoEntity);
return ret;
}catch (Throwable t){
t.printStackTrace();
return R.error("账号异常");
}
}
// 2.支付密码校验
@RequestMapping(value = "/transfer", method = RequestMethod.POST)
@ResponseBody
public R CheckPayPassword(@RequestBody TransferInfoEntity transferInfoEntity,@RequestHeader("token") String token){
try{
// 2.1.token校验(校验通过后,确定是登录用户所在的浏览器在操作,才会进行后续的转账业务)
ParseJwt.parseJWT(token);
}catch (Exception e){
return R.error(500,"wrong token");
}
String accId = transferInfoEntity.getAccIdFrom();
// 将前端表单传过来的密码进行加密(数据库中保存的就是加密后的密码,只有同为加密的密码,才能进行比较)
String paypassword= DigestUtils.md5Hex(transferInfoEntity.getPassword() + transferInfoEntity.getAccIdFrom());
// 根据账户ID查询账户信息
AccInfoTableEntity accInfoTableEntity = accInfoTableService.getOne(new QueryWrapper<AccInfoTableEntity>().eq("acc_id",accId));
if(accInfoTableEntity == null){
return R.error("账号不存在");
}
System.out.println(paypassword+"\n");
System.out.println(accInfoTableEntity.getPayPassword());
// 2.2.判断前端传过来的密码和数据库中保存的密码是否一致,一致的话才开始TCC转账事务
if(paypassword.equals(accInfoTableEntity.getPayPassword())){
//3.开始转账
try {
R ret = transferService.doTransactionCommit(transferInfoEntity);
return ret;
}catch (Throwable t){
t.printStackTrace();
return R.error("转账异常");
}
}else {
return R.error("密码错误");
}
}
// 4.转账结束后更新账户余额
@PostMapping("/accId/{accId}")
public Collection<AccBalanceTableEntity> getListMap(@PathVariable("accId") Long accId) {
Map<String, Object> map = new HashMap<>();
//kay是字段名 value是字段值
map.put("acc_id", accId);
List<AccBalanceTableEntity> accBalanceEntityList = accBalanceTableService.listByMap(map);
AccBalanceTableEntity accBalanceTableEntity = accBalanceEntityList.get(0);
accBalanceTableEntity.setCurBalance(100 + accBalanceTableEntity.getCurBalance());
accBalanceTableService.saveOrUpdate(accBalanceTableEntity);
return accBalanceEntityList;
}
}
其中,收付方账户信息校验(checkAcc)具体为:
public R checkAcc(TransferInfoEntity transferInfoEntity){
Map<String, Object> map = new HashMap<>();
//kay是字段名 value是字段值
map.put("acc_id", transferInfoEntity.getAccIdFrom());
//1数据初始化和检查(获取前端转账表单填写的转账金额)
Double amount = transferInfoEntity.getAmount();
// 1.1.核对付方的账户ID和名字,判断付方账号是否存在
// 将数据库中的付方账号ID和前端表单输入的付方ID进行核对(拿前端输入的付款账号去数据库表中查询该账号是否存在于数据库表中)
List<AccInfoTableEntity> accInfoTableEntityFromList = accInfoTableService.listByMap(map);
if(accInfoTableEntityFromList.size()<1){
return R.error("账号错误, 账号不存在");
}
// 判断账号是否异常
// 1.2.将数据库中的付方账号名字和前端表单输入的付方户名进行核对
AccInfoTableEntity accInfoTableEntityFrom = accInfoTableEntityFromList.get(0);
if(!accInfoTableEntityFrom.getAccTitle().equals(transferInfoEntity.getAccTitleFrom())){
return R.error("付方账号与用户名对不上");
}
if(accInfoTableEntityFrom.getAccStatus()==-1){
return R.error("账号错误, 账号存在异常");
}
// 1.3.核对收方的账号ID和名字
// 将数据库中的收方账号ID和前端表单输入的收方ID进行核对
map.put("acc_id", transferInfoEntity.getAccIdTo());
List<AccInfoTableEntity> accInfoTableEntityToList = accInfoTableService.listByMap(map);
if(accInfoTableEntityToList.size()<1){
return R.error("信息错误, 付方账号不存在");
}
//判断收方账号是否存在异常
//1.4.将数据库中的收方账号名字和前端表单输入的收方户名进行核对
AccInfoTableEntity accInfoTableEntityTo = accInfoTableEntityToList.get(0);
if(!accInfoTableEntityTo.getAccTitle().equals(transferInfoEntity.getAccTitleTo())){
return R.error("收方账号与用户名对不上");
}
if(accInfoTableEntityTo.getAccStatus()==-1){
return R.error("账号错误,付方账号存在异常");
}
//1.5.查询付方账户余额信息是否被正常记录
map.put("acc_id", transferInfoEntity.getAccIdFrom());
List<AccBalanceTableEntity> accBalanceEntityListFrom = accBalanceTableService.listByMap(map);
if(accBalanceEntityListFrom.size()==0){
return R.error("账户余额信息缺失");
}else if(accBalanceEntityListFrom.size()>1){
return R.error("余额信息重复");
}
//1.6.查询收方账户余额信息是否被正常记录
map.put("acc_id", transferInfoEntity.getAccIdTo());
List<AccBalanceTableEntity> accBalanceEntityListTo = accBalanceTableService.listByMap(map);
if(accBalanceEntityListTo.size()==0){
return R.error("收方账户余额信息缺失");
}else if(accBalanceEntityListTo.size()>1){
return R.error("收方余额重复");
}
//1.7.查询付方余额是否充足
AccBalanceTableEntity accBalanceTableEntityFrom = accBalanceEntityListFrom.get(0);
if (accBalanceTableEntityFrom.getCurBalance() < amount || accBalanceTableEntityFrom.getCurBalance()<0) {
return R.error("余额不足");
}
return R.ok("请输入支付密码");
}
一对一转账(doTransactionCommit)具体为:
/**
* 发起转账事务
*
* @return string string
*
*/
@GlobalTransactional
public R doTransactionCommit(TransferInfoEntity transferInfoEntity){
Map<String, Object> map = new HashMap<>();
//kay是字段名 value是字段值
map.put("acc_id", transferInfoEntity.getAccIdFrom());
//3.1.数据初始化和检查(获取转账金额)
Double amount = transferInfoEntity.getAmount();
//3.2.业务防重控制
String uuid = "";
while (true){
uuid = UUID.randomUUID().toString().substring(0,30);
Map<String, Object> bizmap = new HashMap<>();
bizmap.put("txn_id",uuid); //txn_id:业务唯一编号
List<BizRegTableEntity> bizRegTableEntityList = bizRegTableService.listByMap(bizmap);
System.out.printf("业务编号的个数为:%d\n",bizRegTableEntityList.size());
System.out.println(bizRegTableEntityList.toString());
if(bizRegTableEntityList.size()>0){
System.out.println( "业务编号重复");
}else if(bizRegTableEntityList.size() ==0){
break;
}
}
//3.3.新增业务登记簿(将前端表单输入的信息新增到数据库的业务登记表)
BizRegTableEntity bizRegTableEntity = new BizRegTableEntity();
bizRegTableEntity.setSnId(idWorker.nextId());//记录序号
bizRegTableEntity.setTxnDate(transferInfoEntity.getExecDate());//交易日期
bizRegTableEntity.setTxnId(uuid);//业务唯一编码
bizRegTableEntity.setChannelType(1);//渠道种类
bizRegTableEntity.setExecOrganno(transferInfoEntity.getExecOrganno());//操作机构
bizRegTableEntity.setExecTellerno(transferInfoEntity.getExecTellerno());//操作柜员
bizRegTableEntity.setTxnCode(111);//交易代码
bizRegTableEntity.setTxnType(111);//交易类型
bizRegTableEntity.setCashTransferFlag(1);//现金转账标志
bizRegTableEntity.setDebitAccId(String.valueOf(transferInfoEntity.getAccIdFrom()));//借方账号(发起账号)
bizRegTableEntity.setDebitAccName(transferInfoEntity.getAccTitleFrom());//借方名称
bizRegTableEntity.setDebitCurrType(1);//借方币种 transferInfoEntity.getCurrType()
bizRegTableEntity.setDebitAmount(amount);//借方发生额(转账金额)
bizRegTableEntity.setCreditAccId(String.valueOf(transferInfoEntity.getAccIdTo()));//贷方账号
bizRegTableEntity.setCreditAccName(transferInfoEntity.getAccTitleTo());//贷方币种
bizRegTableEntity.setCreditCurrType(1);//贷方币种 transferInfoEntity.getCurrType()
bizRegTableEntity.setCreditAmount(transferInfoEntity.getAmount());//贷方发生额
bizRegTableEntity.setStatus(1);//状态1-处理中2-交易
bizRegTableEntity.setLastModifyDate(transferInfoEntity.getExecDate());//最后更新日期
bizRegTableEntity.setRegionId(1);//地区编号
System.out.println(bizRegTableEntity.toString());
bizRegTableService.save(bizRegTableEntity);
//3.4.查询账户信息表
//3.4.1.判断付方账号ID是否存在
List<AccInfoTableEntity> accInfoTableEntityFromList = accInfoTableService.listByMap(map);
if(accInfoTableEntityFromList.size()<1){
return R.error("账号错误, 账号不存在");
}
//3.4.2.判断付方账号名字是否异常
AccInfoTableEntity accInfoTableEntityFrom = accInfoTableEntityFromList.get(0);
if(!accInfoTableEntityFrom.getAccTitle().equals(transferInfoEntity.getAccTitleFrom())){
return R.error("付方账号与用户名对不上");
}
if(accInfoTableEntityFrom.getAccStatus()==-1){
return R.error("账号错误, 账号存在异常");
}
//3.4.3.判断收方账号ID是否存在
map.put("acc_id", transferInfoEntity.getAccIdTo());
List<AccInfoTableEntity> accInfoTableEntityToList = accInfoTableService.listByMap(map);
if(accInfoTableEntityToList.size()<1){
return R.error("信息错误, 付方账号不存在");
}
//3.4.4.判断收方账号名字是否存在异常
AccInfoTableEntity accInfoTableEntityTo = accInfoTableEntityToList.get(0);
if(!accInfoTableEntityTo.getAccTitle().equals(transferInfoEntity.getAccTitleTo())){
return R.error("收方账号与用户名对不上");
}
if(accInfoTableEntityTo.getAccStatus()==-1){
return R.error("账号错误,付方账号存在异常");
}
//3.5.查询账户余额表
//3.5.1.判断付方账户余额信息是否正常
map.put("acc_id", transferInfoEntity.getAccIdFrom());
List<AccBalanceTableEntity> accBalanceEntityListFrom = accBalanceTableService.listByMap(map);
if(accBalanceEntityListFrom.size()==0){
return R.error("账户余额信息缺失");
}else if(accBalanceEntityListFrom.size()>1){
return R.error("余额信息重复");
}
//3.5.2.判断付方账户余额信息是否正常
map.put("acc_id", transferInfoEntity.getAccIdTo());
List<AccBalanceTableEntity> accBalanceEntityListTo = accBalanceTableService.listByMap(map);
if(accBalanceEntityListTo.size()==0){
return R.error("收方账户余额信息缺失");
}else if(accBalanceEntityListTo.size()>1){
return R.error("收方余额重复");
}
//3.6.判断付方余额是否充足
AccBalanceTableEntity accBalanceTableEntityFrom = accBalanceEntityListFrom.get(0);
if (accBalanceTableEntityFrom.getCurBalance() < amount || accBalanceTableEntityFrom.getCurBalance()<0) {
return R.error("余额不足");
}
boolean result = false;
//3.7. 扣款事务(tccActionOne.prepare方法具体请看4.9节)
//第一个TCC 事务参与者
//accBalanceTableEntityFrom:付方账户余额
//amount:前端输入的转账金额
result = tccActionOne.prepare(null, transferInfoEntity,uuid,amount,accBalanceTableEntityFrom);
if(!result){
throw new RuntimeException("付方扣款失败.");
}
//3.8.收款事务(tccActionTwo.prepare方法具体请看4.9节)
//第二个事务参与者
//收方增加余额
//增加收方明细
System.out.println("开始收款账");
AccBalanceTableEntity accBalanceTableEntityTo = accBalanceEntityListTo.get(0);
result = tccActionTwo.prepare(null, transferInfoEntity,uuid,amount,accBalanceTableEntityTo);
if(!result){
throw new RuntimeException("收方收款失败.");
}
return R.ok("转账正在进行中");
}
}
完整代码:
package com.icbc.distributed.transfer.tccservice;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.icbc.common.utils.R;
import com.icbc.distributed.common.IdWorker;
import com.icbc.distributed.transfer.entity.AccBalanceTableEntity;
import com.icbc.distributed.transfer.entity.AccInfoTableEntity;
import com.icbc.distributed.transfer.entity.BizRegTableEntity;
import com.icbc.distributed.transfer.entity.TransferInfoEntity;
import com.icbc.distributed.transfer.action.TccActionOne;
import com.icbc.distributed.transfer.action.TccActionTwo;
import com.icbc.distributed.transfer.service.*;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* The type Tcc transaction service.
*
* @author xuzexi
*/
@Service("tccTransaction")
public class TccTransactionService {
@Autowired
private TccActionOne tccActionOne;
@Autowired
private TccActionTwo tccActionTwo;
@Autowired
private AccBalanceTableService accBalanceTableService;
@Autowired
private AccAgrtTableService accAgrtTableService;
@Autowired
private AccDetailsTableService accDetailsTableService;
@Autowired
private BizRegTableService bizRegTableService;
@Autowired
private AccInfoTableService accInfoTableService;
@Autowired
IdWorker idWorker;// 分布式自增长ID
// @GlobalTransactional
// public String tansfer(TransferInfoEntity transferInfoEntity){
// String ret = "转账失败";
// try {
// ret = doTransactionCommit(transferInfoEntity);
// }catch (Throwable t){
// t.printStackTrace();
// return "转账失败";
// }
// return ret;
// }
public R checkAcc(TransferInfoEntity transferInfoEntity){
Map<String, Object> map = new HashMap<>();
//kay是字段名 value是字段值
map.put("acc_id", transferInfoEntity.getAccIdFrom());
//1数据初始化和检查(获取前端转账表单填写的转账金额)
Double amount = transferInfoEntity.getAmount();
// 1.1.核对付方的账户ID和名字,判断付方账号是否存在
// 将数据库中的付方账号ID和前端表单输入的付方ID进行核对(拿前端输入的付款账号去数据库表中查询该账号是否存在于数据库表中)
List<AccInfoTableEntity> accInfoTableEntityFromList = accInfoTableService.listByMap(map);
if(accInfoTableEntityFromList.size()<1){
return R.error("账号错误, 账号不存在");
}
// 判断账号是否异常
// 1.2.将数据库中的付方账号名字和前端表单输入的付方户名进行核对
AccInfoTableEntity accInfoTableEntityFrom = accInfoTableEntityFromList.get(0);
if(!accInfoTableEntityFrom.getAccTitle().equals(transferInfoEntity.getAccTitleFrom())){
return R.error("付方账号与用户名对不上");
}
if(accInfoTableEntityFrom.getAccStatus()==-1){
return R.error("账号错误, 账号存在异常");
}
// 1.3.核对收方的账号ID和名字
// 将数据库中的收方账号ID和前端表单输入的收方ID进行核对
map.put("acc_id", transferInfoEntity.getAccIdTo());
List<AccInfoTableEntity> accInfoTableEntityToList = accInfoTableService.listByMap(map);
if(accInfoTableEntityToList.size()<1){
return R.error("信息错误, 付方账号不存在");
}
//判断收方账号是否存在异常
//1.4.将数据库中的收方账号名字和前端表单输入的收方户名进行核对
AccInfoTableEntity accInfoTableEntityTo = accInfoTableEntityToList.get(0);
if(!accInfoTableEntityTo.getAccTitle().equals(transferInfoEntity.getAccTitleTo())){
return R.error("收方账号与用户名对不上");
}
if(accInfoTableEntityTo.getAccStatus()==-1){
return R.error("账号错误,付方账号存在异常");
}
//1.5.查询付方账户余额信息是否被正常记录
map.put("acc_id", transferInfoEntity.getAccIdFrom());
List<AccBalanceTableEntity> accBalanceEntityListFrom = accBalanceTableService.listByMap(map);
if(accBalanceEntityListFrom.size()==0){
return R.error("账户余额信息缺失");
}else if(accBalanceEntityListFrom.size()>1){
return R.error("余额信息重复");
}
//1.6.查询收方账户余额信息是否被正常记录
map.put("acc_id", transferInfoEntity.getAccIdTo());
List<AccBalanceTableEntity> accBalanceEntityListTo = accBalanceTableService.listByMap(map);
if(accBalanceEntityListTo.size()==0){
return R.error("收方账户余额信息缺失");
}else if(accBalanceEntityListTo.size()>1){
return R.error("收方余额重复");
}
//1.7.查询付方余额是否充足
AccBalanceTableEntity accBalanceTableEntityFrom = accBalanceEntityListFrom.get(0);
if (accBalanceTableEntityFrom.getCurBalance() < amount || accBalanceTableEntityFrom.getCurBalance()<0) {
return R.error("余额不足");
}
return R.ok("请输入支付密码");
}
/**
* 发起转账事务
*
* @return string string
*
*/
@GlobalTransactional
public R doTransactionCommit(TransferInfoEntity transferInfoEntity){
Map<String, Object> map = new HashMap<>();
//kay是字段名 value是字段值
map.put("acc_id", transferInfoEntity.getAccIdFrom());
//3.1.数据初始化和检查(获取转账金额)
Double amount = transferInfoEntity.getAmount();
//3.2.业务防重控制
String uuid = "";
while (true){
uuid = UUID.randomUUID().toString().substring(0,30);
Map<String, Object> bizmap = new HashMap<>();
bizmap.put("txn_id",uuid); //txn_id:业务唯一编号
List<BizRegTableEntity> bizRegTableEntityList = bizRegTableService.listByMap(bizmap);
System.out.printf("业务编号的个数为:%d\n",bizRegTableEntityList.size());
System.out.println(bizRegTableEntityList.toString());
if(bizRegTableEntityList.size()>0){
System.out.println( "业务编号重复");
}else if(bizRegTableEntityList.size() ==0){
break;
}
}
//3.3.新增业务登记簿(将前端表单输入的信息新增到数据库的业务登记表)
BizRegTableEntity bizRegTableEntity = new BizRegTableEntity();
bizRegTableEntity.setSnId(idWorker.nextId());//记录序号
bizRegTableEntity.setTxnDate(transferInfoEntity.getExecDate());//交易日期
bizRegTableEntity.setTxnId(uuid);//业务唯一编码
bizRegTableEntity.setChannelType(1);//渠道种类
bizRegTableEntity.setExecOrganno(transferInfoEntity.getExecOrganno());//操作机构
bizRegTableEntity.setExecTellerno(transferInfoEntity.getExecTellerno());//操作柜员
bizRegTableEntity.setTxnCode(111);//交易代码
bizRegTableEntity.setTxnType(111);//交易类型
bizRegTableEntity.setCashTransferFlag(1);//现金转账标志
bizRegTableEntity.setDebitAccId(String.valueOf(transferInfoEntity.getAccIdFrom()));//借方账号(发起账号)
bizRegTableEntity.setDebitAccName(transferInfoEntity.getAccTitleFrom());//借方名称
bizRegTableEntity.setDebitCurrType(1);//借方币种 transferInfoEntity.getCurrType()
bizRegTableEntity.setDebitAmount(amount);//借方发生额(转账金额)
bizRegTableEntity.setCreditAccId(String.valueOf(transferInfoEntity.getAccIdTo()));//贷方账号
bizRegTableEntity.setCreditAccName(transferInfoEntity.getAccTitleTo());//贷方币种
bizRegTableEntity.setCreditCurrType(1);//贷方币种 transferInfoEntity.getCurrType()
bizRegTableEntity.setCreditAmount(transferInfoEntity.getAmount());//贷方发生额
bizRegTableEntity.setStatus(1);//状态1-处理中2-交易
bizRegTableEntity.setLastModifyDate(transferInfoEntity.getExecDate());//最后更新日期
bizRegTableEntity.setRegionId(1);//地区编号
System.out.println(bizRegTableEntity.toString());
bizRegTableService.save(bizRegTableEntity);
//3.4.查询账户信息表
//3.4.1.判断付方账号ID是否存在
List<AccInfoTableEntity> accInfoTableEntityFromList = accInfoTableService.listByMap(map);
if(accInfoTableEntityFromList.size()<1){
return R.error("账号错误, 账号不存在");
}
//3.4.2.判断付方账号名字是否异常
AccInfoTableEntity accInfoTableEntityFrom = accInfoTableEntityFromList.get(0);
if(!accInfoTableEntityFrom.getAccTitle().equals(transferInfoEntity.getAccTitleFrom())){
return R.error("付方账号与用户名对不上");
}
if(accInfoTableEntityFrom.getAccStatus()==-1){
return R.error("账号错误, 账号存在异常");
}
//3.4.3.判断收方账号ID是否存在
map.put("acc_id", transferInfoEntity.getAccIdTo());
List<AccInfoTableEntity> accInfoTableEntityToList = accInfoTableService.listByMap(map);
if(accInfoTableEntityToList.size()<1){
return R.error("信息错误, 付方账号不存在");
}
//3.4.4.判断收方账号名字是否存在异常
AccInfoTableEntity accInfoTableEntityTo = accInfoTableEntityToList.get(0);
if(!accInfoTableEntityTo.getAccTitle().equals(transferInfoEntity.getAccTitleTo())){
return R.error("收方账号与用户名对不上");
}
if(accInfoTableEntityTo.getAccStatus()==-1){
return R.error("账号错误,付方账号存在异常");
}
//3.5.查询账户余额表
//3.5.1.判断付方账户余额信息是否正常
map.put("acc_id", transferInfoEntity.getAccIdFrom());
List<AccBalanceTableEntity> accBalanceEntityListFrom = accBalanceTableService.listByMap(map);
if(accBalanceEntityListFrom.size()==0){
return R.error("账户余额信息缺失");
}else if(accBalanceEntityListFrom.size()>1){
return R.error("余额信息重复");
}
//3.5.2.判断付方账户余额信息是否正常
map.put("acc_id", transferInfoEntity.getAccIdTo());
List<AccBalanceTableEntity> accBalanceEntityListTo = accBalanceTableService.listByMap(map);
if(accBalanceEntityListTo.size()==0){
return R.error("收方账户余额信息缺失");
}else if(accBalanceEntityListTo.size()>1){
return R.error("收方余额重复");
}
//3.6.判断付方余额是否充足
AccBalanceTableEntity accBalanceTableEntityFrom = accBalanceEntityListFrom.get(0);
if (accBalanceTableEntityFrom.getCurBalance() < amount || accBalanceTableEntityFrom.getCurBalance()<0) {
return R.error("余额不足");
}
boolean result = false;
//3.7. 扣款事务
//第一个TCC 事务参与者
//accBalanceTableEntityFrom:付方账户余额
//amount:前端输入的转账金额
result = tccActionOne.prepare(null, transferInfoEntity,uuid,amount,accBalanceTableEntityFrom);
if(!result){
throw new RuntimeException("付方扣款失败.");
}
//3.8.收款事务
//第二个事务参与者
//收方增加余额
//增加收方明细
System.out.println("开始收款账");
AccBalanceTableEntity accBalanceTableEntityTo = accBalanceEntityListTo.get(0);
result = tccActionTwo.prepare(null, transferInfoEntity,uuid,amount,accBalanceTableEntityTo);
if(!result){
throw new RuntimeException("收方收款失败.");
}
return R.ok("转账正在进行中");
}
}
分布式事务XID是如何将所有微服务串联起来的?
https://juejin.cn/post/6894516787481608205