xbank示例项目
通过xBank项目来介绍用Biz-SIP业务中台开发一个系统的全流程
xBank项目版本库:https://gitee.com/szhengye/xbank.git
一、概述
xBank是一家商业银行,面向个人客户和公司客户,其中个人客户业务包括存款、贷款、缴费等业务;银行业务渠道除了传统柜面以外,还有网上银行、手机银行、ATM、POS等,最近准备上一个针对银行合作伙伴的基于OPENAPI网关的开放平台渠道。
本示例项目是以个人客户中的存款查询和缴费业务为例子,后台系统对接个人客户存款系统和个人客户信息系统,第三方对接缴费平台,来演示如何打造基于Biz-SIP中间件的银行业务中台。
xBank项目版本库:[https://gitee.com/szhengye/xbank.git]
二、体验Biz-SIP示例项目xbank
1. 示例项目xbank简介
xBank系统的逻辑架构如下:
按DDD分层架构,应用架构图如下所示:
3. xbank示例项目模板介绍
打开示例项目,如下图:
xbank项目分为以下模块:
- xbank-source:适配层模块,存放外部适配接入的适配层实现。
- xbank-openapi-source:OPENAPI接入的适配层子模块
- xbank-app:应用层模块,存放应用层的服务实现。
- xbank-sink:领域层模块,存放按领域划分的领域服务实现。
- xbank-account-sink:账户域子模块
- xbank-customer-sink:客户域子模块
- xbank-payment1-sink:缴费域子模块(存储转发)
- xbank-payment1-sink:缴费域子模块(交易补偿)
- xbank-infrastructure:基础设施层模块,存放对数据库、内容存储、HSM等基础设施的访问能力实现。
- xbank-account-db:账户数据库访问子模块
- xbank-customre-db:客户数据库访问子模块
- xbank-client:接口模块,存放各层之间的服务接口,以及相关数据DTO。
- xbank-account-sink-client:账户域服务接口子模块
- xbank-customer-sink-client:客户域服务接口子模块
- xbank-app-client:应用服务接口子模块
各层之间调用关系如下所示: ## 4. 创建数据库 执行项目中xbank-infrastructure/xbank-sql/xbank.sql脚本,以建立xbank演示库。
5. 启动xbank项目
- 启动应用层服务整合器
- 执行xbank-app模块中的XbankAppApplication
- 启动领域层领域服务
- 执行xbank-customer-sink子模块中的CustomerSinkApplication
- 执行xbank-account-sink子模块中的AccountSinkAppliction
- 启动适配层OPENAPI接入服务
- 执行xbank-openapi-source子模块中的OpenapiSourceApplication
4. 测试接口
4.1 查询客户信息
$ curl -H "Content-Type:application/json" -H "Biz-Service-Id:/sink/customer" -X POST --data '{"methodName":"getCustomer","params":["001"]}' http://localhost:8888/api|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"appServiceId": "/sink/customer",
"traceId": "92a7422ffdff4d10942db56108e86048",
"parentTraceId": null,
"timestamp": 1640692635697,
"data": {
"result": {
"sex": "1",
"customerName": "张三",
"customerId": "001",
"age": 30
}
}
}
4.2 查询账户列表
$ curl -H "Content-Type:application/json" -H "Biz-Service-Id:/sink/account" -X POST --data '{"methodName":"getAccountListByCustomerId","params":["001"]}' http://localhost:8888/api|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"appServiceId": "/sink/account",
"traceId": "54d52aac25ef4f86b693cedf592ad56c",
"parentTraceId": null,
"timestamp": 1640692642485,
"data": {
"result": [
{
"accountId": "0001",
"balance": -34,
"customerId": "001"
},
{
"accountId": "0002",
"balance": 200,
"customerId": "001"
}
]
}
}
4.3 查询客户信息和账户列表
$ curl -H "Content-Type:application/json" -H "Biz-Service-Id:/app/personal" -X POST --data '{"methodName":"getCustomerAndAccountList","params":["001"]}' http://localhost:8888/api|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"appServiceId": "/app/personal",
"traceId": "69c3800c50cb41a682d32bd1774328ee",
"parentTraceId": null,
"timestamp": 1640692648956,
"data": {
"result": {
"accountList": [
{
"accountId": "0001",
"balance": -34,
"customerId": "001"
},
{
"accountId": "0002",
"balance": 200,
"customerId": "001"
}
],
"customer": {
"sex": "1",
"customerName": "张三",
"customerId": "001",
"age": 30
}
}
}
}
三、各层间调用和依赖关系
xBank系统各层间的调用和依赖关系如下图所示:
可以看到,整个xBank系统从上到下分别是:
- Source层(对应于DDD架构中的适配接入层):负责聚合服务的接入,包括通讯协议适配和消息格式转换,并统一接入App层的App服务。
- App层(对应于DDD架构中的应用层):负责服务的整合和编排,对Source层接入的服务,进行服务编排,并通过Sink模块接入要编排的服务。
- Sink层(对应于DDD架构中的领域层):统一被App服务所调用,实现业务逻辑的处理,或接入外部第三方服务,包括和外部服务对接时的通讯协议适配和消息格式转换。
在下图中标出了在具体开发中的要点,下面会逐一进行讲解:
主要调用流程如下:
- 调用方是通过HTTP POST方式,发送XML格式的报文给Source层的XmlController应用模块❶进行处理;
- XmlController服务模块会把报文格式转换成平台内部标准报文,并调用❷ App层的App服务PersonalAppService❹;
- PersonalAppService服务会进行服务编排,分别调用❺Sink层的CustomerSinkService服务❼、AccountSinkService服务、Payment1SinkService服务、Payment2SinkService服务。
1、Source层的XmlController
XmlController是一个RestController应用,主要完成以下处理:
- 接收调用者发来的HTTP POST请求;
- 通过消息转换器,把XML报文解包成平台标准格式报文;
- 调用App层封装好的App服务;
- 把App服务返回的平台标准格式报文,打包成XML报文并返回请求方。
代码如下:
@RestController
@RequestMapping("/personal")
public class XmlController {
// 定义一个接口为PersonalAppInterface的,app层服务名为app/personal的服务调用句柄
private PersonalAppInterface personalAppInterface = SourceClientFactory
.getAppServiceClient(PersonalAppInterface.class,"/app/personal");
// 定义一个接口为平台标准JSON接口(类型固定为BizMessageInterface),app层服务名为sink/payment1的服务调用句柄
private BizMessageInterface payment1SinkInterface = SourceClientFactory
.getAppServiceClient(BizMessageInterface.class,"/sink/payment1");
// 获取source层消息格式转换器
private Converter converter = Converter.getSourceConverter("xml-source");
@PostMapping(value = "/getCustomerAndAccountList", consumes = "application/xml", produces = "application/xml")
public String getCustomerAndAccountList(@RequestBody String inMessage) throws BizException {
// 消息解包操作
JSONObject jsonObject = this.converter.unpack(inMessage.getBytes());
String customerId = (String)jsonObject.get("customerId");
// 调用app层服务
CustomerAndAccountList customerAndAccountList = this.personalAppInterface.getCustomerAndAccountList(customerId);
jsonObject = JSONUtil.parseObj(customerAndAccountList);
// 消息打包并返回
return new String(this.converter.pack(jsonObject));
}
......
}
进阶要点
- Source模块可以采用可复用的代码框架,把通用的Source通讯适配应用和个性化的Source服务分开,通用的Source通讯适配应用可以参见Biz-SIP源码中source模块中的子模块,目前有rest-source和netty-source,前一个是简单的HTTP RESTful POST接入的模块,后一个是用Netty封装的支持TCP同步短连接的模块。个性化的Source服务是继承并实现SourceServiceInterface接口,在其中的doService()方法实现通讯适配以外的工作,包括格式转换、调用App层服务等。
2、XmlController调用App服务
XmlController是通过申请App服务的调用接口,在申请调用接口时,会采用App层封装好的App服务Interface接口类:
public class XmlController {
// 定义一个接口为PersonalAppInterface的,app层服务名为app/personal的服务调用接口
private PersonalAppInterface personalAppInterface = SourceClientFactory
.getAppServiceClient(PersonalAppInterface.class,"/app/personal");
......
// 调用app层服务
CustomerAndAccountList customerAndAccountList = this.personalAppInterface.getCustomerAndAccountList(customerId);
......
}
进阶要点
- 调用App服务除了使用约定好的自定义接口调用外,还支持非接口约定调用(即采用平台标准JSON消息),Interface接口类统一采用BizMessageInterface接口类,并统一用call()来调用app层服务。
3、App层的PersonalAppInterface接口类
在上面的XmlController代码中,App服务调用接口在申请时,会要求填入App层的服务接口类PersonalAppInterface,这是在App层定义的,约定了能被Source层调用的App服务接口,如下所示:
public interface PersonalAppInterface {
public CustomerAndAccountList getCustomerAndAccountList(String customerId);
public List<Account> getAccountListByCustomerId(String customerId);
public Customer getCustomer(String customerId);
public BizMessage send2Payment1(Object message) throws BizException;
public BizMessage send2Payment2(String tranMode, String tranCode, Object message) throws BizException;
public Customer getCustomerAndSaf2Payment2(String tranMode, String customerId) throws BizException;
public Account payout(String accountId,long amount);
public void payoutForward(String tranMode,String accountId,long amount) throws BizException;
public void payoutForwardCompensate(JSONObject jsonObject) throws BizException;
public void payoutBackward(String tranMode,String accountId,long amount) throws BizException;
public void payoutBackwardCompensate(JSONObject jsonObject) throws BizException;
}
进阶要点
- App层可以设置Source层调用服务的消息校验,支持域级和服务级的消息校验定义。
4、App层的PersonalAppService类
PersonalAppService服务类是上面PersonalAppInterface的具体实现类,服务接口是在类中具体实现的,主要是对Sink层的服务进行服务编排,如下所示:
@Service
public class PersonalAppService implements PersonalAppInterface {
private AccountSinkInterface accountSinkInterface = AppClientFactory
.getSinkClient(AccountSinkInterface.class,"account-sink");
private CustomerSinkInterface customerSinkInterface = AppClientFactory
.getSinkClient(CustomerSinkInterface.class,"customer-sink");
private BizMessageInterface payment1SinkInterface = AppClientFactory
.getSinkClient(BizMessageInterface.class,"payment1-sink");
private BizMessageInterface payment2SinkInterface = AppClientFactory
.getSinkClient(BizMessageInterface.class,"payment2-sink");
private PersonalAppInterface personalAppDelayInterface = AppClientFactory
.getDelayAppServiceClient(PersonalAppInterface.class,"/app/personal",
0,1000,2000,4000,8000,16000,32000);
@Override
public CustomerAndAccountList getCustomerAndAccountList(String customerId) {
Customer customer = this.customerSinkInterface.getCustomer(customerId);
List<Account> accountList = this.accountSinkInterface.getAccountListByCustomerId(customerId);
CustomerAndAccountList customerAndAccountList = new CustomerAndAccountList();
customerAndAccountList.setCustomer(customer);
customerAndAccountList.setAccountList(accountList);
return customerAndAccountList;
}
......
}
PersonalAppService服务类是在app.yml文件中进行设置的:
app.yml
- app-service-id: /app/personal
type: bean-service
class-name: com.xbank.app.service.PersonalAppService
进阶要点
- App服务的接口,除了本节介绍的采用由开发者自己定义的Java接口类以外,还可以统一采用AppBeanInterface接口类,AppBeanInterface接口类是采用平台标准JSON消息作为调用和返回参数,开发者继承AppBeanInterface接口后实现process()接口即可。
- 在App服务中可以调用延迟服务,延迟服务能实现服务调用的自定义调用次数和重试间隔时间。
- App层可以打开交易日志服务,根据设置的日志级别,会将交易成功、交易延迟调用、交易失败的状态日志,通过RabbitMQ队列发送给开发者应用,进行各种事后处理。
5、PersonalAppService调用Sink层服务
PersonalAppService类在代码中会申请Sink层服务的调用接口,在申请调用接口时,会采用Sink层封装好的Sink服务Interface接口类:
@Service
public class PersonalAppService implements PersonalAppInterface {
private CustomerSinkInterface customerSinkInterface = AppClientFactory
.getSinkClient(CustomerSinkInterface.class,"customer-sink");
......
Customer customer = this.customerSinkInterface.getCustomer(customerId);
......
}
进阶要点
- 调用Sink层服务,除了支持普通的rest同步调用外,还支持rabbitmq异步调用(App服务无需等待返回结果)。
- Sink层服务除了支持基于自定义接口类的调用外,还支持基于平台标准报文格式的调用接口,。
- 可以在App层定义sink-service类型的App服务,直接绑定并调用Sink层的Sink服务。
6、Sink层的CustomerSinkInterface接口类
在上面的PersonalAppService类代码中,Sink服务调用接口在申请时,会要求填入Sink层的服务接口类CustomerSinkInterface,这是在Sink层定义的,约定了能被App层调用的Sink服务接口,如下所示:
public interface CustomerSinkInterface {
public Customer getCustomer(String customerId);
}
进阶要点
- Sink层的内置标准服务接口,除了本节介绍的采用由开发者定义Java接口类以外,还可以统一采用SinkBeanInterface接口,开发者继承SinkBeanInterface接口后实现process()接口即可。SinkBeanInterface接口是采用平台标准JSON对象作为调用和返回参数。
7、Sink层的CustomerSinkService类
CustomerSinkService服务类是上面的CustomerSinkInterface接口的实现类,具体的Sink服务业务逻辑是在这个类中实现的:
@Service
public class CustomerSinkService implements CustomerSinkInterface {
@Autowired
private CustomerService custmerService;
@Override
public Customer getCustomer(String customerId) {
Customer customer = this.custmerService.getById(customerId);
return customer;
}
}
CustomerSinkService服务类是在sink.yml文件中进行设置的:
sink.yml
- id: customer-sink
type: rest
processor: bean
url: http://customer-sink/sink
class-name: com.xbank.sink.customer.service.CustomerSinkService
并在application.yml(application-local.yml)文件中,在bizsip.sink-id属性中添加customer-sink,以打开注册的customer-sink接口:
bizsip:
config-path: /var/bizsip/config
sink-id: customer-sink
进阶要点:
- 除了本节介绍的采用基于开发者自定义Java接口类开发的bean类型sink服务以外,还可以统一采用SinkBeanInterface接口类,SinkBeanInterface接口类是采用平台标准JSON消息作为调用和返回参数,开发者实现SinkBeanInterface的process()接口即可。
- bean服务类和sink-bean服务类如果继承AbstractSinkService类,当前sink的connector和converter是会自动注入的。
四、项目实践:客户域服务的开发
本章节以客户域服务为例,展现开发者从数据库建库到Sink服务、App服务、Source服务开发的大致步骤。
1. 创建数据库
执行项目中xbank-infrastructure/xbank-sql/xbank.sql脚本,以建立xbank演示库。
2. 自动生成数据访问层代码
数据库创建后,用MybatisX插件自动生成数据访问层代码:
3. Customer领域服务接口和实现类的开发
先在xbank-customer-domain-client子模块中约定customer领域服务接口,这个接口是共享给领域层和应用层的:
然后在xbank-customer-sink子模块中完成以下工作:
- 基于此接口完成CustomerSinkService类的编写;
- 完成CustomerSinkApplication启动类的编写;
- 配置application.yml相关文件。
以上就完成了客户领域服务微服务的开发,接下来需要把这个客户领域服务微服务,通过sink接入Biz-SIP平台,配置xbank-app/config/sink.yml,就把CustomerSinkService类挂接到了sink(customer-sink)上:
sink.yml:
- id: customer-sink
type: rest
processor: bean
url: http://customer-sink/sink
class-name: com.xbank.sink.customer.service.CustomerSinkService
4. Customer领域服务的快速发布
Customer领域服务接入Biz-SIP平台后,能实现在Biz-SIP开放平台接口的快速发布,这需要在xbank-app/config/app.yml中配置一个类型为“sink-service”的App服务:
app.yml:
- app-service-id: /sink/customer
type: sink-service
sink-id: customer-sink
注:这类“sink-service”的App服务,命名规范为“/sink/xxx”,xxx为sink名称。
启动Biz-SIP平台和Customer领域服务,就可以直接在Biz-SIP开放平台接口进行访问:
$ curl -H "Content-Type:application/json" -H "Biz-Service-Id:/sink/customer" -X POST --data '{"methodName":"getCustomer","params":["001"]}' http://localhost:8888/api|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"appServiceId": "/sink/customer",
"traceId": "e130a4ffc2894d3a98393971a256246e",
"parentTraceId": null,
"timestamp": 1640692649234,
"data": {
"result": {
"sex": "1",
"customerName": "张三",
"customerId": "001",
"age": 30
}
}
}
在上传的数据体中,“methodName”为方法名,“params”为输入参数。
5. Customer领域服务在应用层和适配层的定制
通过在app.yml中配置sink-service类型的App服务,能实现已经挂接到Sink的领域服务的快速发布;但是,客户针对应用层和适配层,还是有个性化定制要求,这就涉及到应用层和适配层的定制。
同样以已经挂接到customer-sink上的Customer领域服务为例,先在xbank-app-client子模块中约定应用层接口,这个接口是共享给应用层和适配层的:
然后在xbank-app模块中基于此接口完成PersonalAppService类的编写:
在xbank-app/config/app.yml中配置一个类型为“bean-service”的App服务,并类名指定为上面实现的PersonalAppService应用层服务类:
app.yml:
- app-service-id: /app/personal
type: bean-service
class-name: com.xbank.app.service.PersonalAppService
最后在适配层中,定制一个controller来对外发布服务:
开发者可以在适配层的controller类中通过personalAppInterface,直接调用应用层App服务”/app/personal“:
$ curl http://localhost:9001/personal/getCustomer\?customerId=001|jq
{
"customerId": "001",
"customerName": "张三",
"age": 30,
"sex": "1"
}
五、项目实践:账户域服务的开发
1. Account领域服务的封装
Account领域服务是和Customer领域服务并列的,Account领域服务的封装,依次有以下步骤:
- 第1步:领域服务接口的约定:在xbank-account-sink-client中编写AccountSinkInterface接口;
- 第2步:领域服务的实现:创建xbank-account-sink子模块,基于第1步约定的接口,实现AccountSinkService服务类;
- 第3步:配置sink.yml文件,把AccountSinkService服务类作为Sink连接到Biz-SIP平台;
另外,Account领域Sink服务在封装时,涉及到多个接口调用,对每个接口采用命令执行器(Command Executor),所有的接口实现继承AbstractBeanCmdExe类来实现,这样可以避免把所有的业务逻辑代码写在一个Account服务类中:
2. Account服务在适配层和应用层的开发和对外暴露
把sink服务,暴露给适配层接口调用,有2种方案:
2.1 方案1:Sink服务透传
通过sink-service类型,打通适配层和应用层:直接把account领域服务所对应的sink,直接暴露给Biz-SIP开放平台接口。
- 第4步:配置app.yml文件,把account-sink直接作为sink-service暴露给Biz-SIP开放平台接口。
接口测试如下:
2.2 方案2:开发Bean服务类来聚合转发
通过bean-service,打通适配层和应用层:
- 第4步:应用服务接口的约定:在xbank-personal-app-client中编写PersonalAppInterface接口和CustomerAndAccountList DTO类;
- 第5步:应用服务的实现:在xbank-app模块中,基于第4步约定的接口实现PersonalAppService服务;
- 第6步:配置app.yml文件,把第5步开发PersonalAppService服务,通过bean-service的account-sink暴露给适配层;
- 第7步:在适配层创建xbank-openapi-source子模块,开发PersonalController类,暴露个性化的RESTful接口,并通过SourceClientFactory.getAppServiceClient(PersonalAppInterface.class,"/app/personal")获取应用层服务的调用接口,在适配层中调用应用层约定接口。
接口测试如下:
$ curl http://localhost:9001/personal/getAccountListByCustomerId\?customerId=001|jq
[
{
"accountId": "0001",
"balance": 100,
"customerId": "001"
},
{
"accountId": "0002",
"balance": 200,
"customerId": "001"
}
]
六、项目实践:客户域和账户域服务在应用层的编排
上面已经分别实现了客户域和账户域服务的开发和部署,在应用层中,能方便地对域服务进行服务编排。
1. App服务类的开发和部署
在xbank-app模块中,我们可以在PersonalAppService类中,方便地进行服务编排,代码如下:
@Service
public class PersonalAppService implements PersonalAppInterface {
private AccountSinkInterface accountSinkInterface = AppClientFactory
.getSinkClient(AccountSinkInterface.class,"account-sink");
private CustomerSinkInterface customerSinkInterface = AppClientFactory
.getSinkClient(CustomerSinkInterface.class,"customer-sink");
private BizMessageInterface payment1SinkInterface = AppClientFactory
.getSinkClient(BizMessageInterface.class,"payment1-sink");
private BizMessageInterface payment2SinkInterface = AppClientFactory
.getSinkClient(BizMessageInterface.class,"payment2-sink");
private PersonalAppInterface personalAppDelayInterface = AppClientFactory
.getDelayAppServiceClient(PersonalAppInterface.class,"/app/personal",
0,1000,2000,4000,8000,16000,32000);
@Override
public CustomerAndAccountList getCustomerAndAccountList(String customerId) {
Customer customer = this.customerSinkInterface.getCustomer(customerId);
List<Account> accountList = this.accountSinkInterface.getAccountListByCustomerId(customerId);
CustomerAndAccountList customerAndAccountList = new CustomerAndAccountList();
customerAndAccountList.setCustomer(customer);
customerAndAccountList.setAccountList(accountList);
return customerAndAccountList;
}
...
}
在getCustomerAndAccountList()方法中,我们可以同时调用客户域的接口customerSinkInterface,也可以调用账户域的接口accountSinkInterface,从而实现多个域服务的混合编排。
PersonalAppService服务类通过bean-service进行挂接到App服务,并进行部署:
app.yml:
- app-service-id: /app/personal
type: bean-service
class-name: com.xbank.app.service.PersonalAppService
2. App服务被调用的方式
分别支持Biz-SIP开放平台接口访问和个性化的PersonController实现的REST接口:
2.1 通过Biz-SIP开放平台接口
通过开放平台接口进行调用的测试:
$ curl -H "Content-Type:application/json" -H "Biz-Service-Id:/app/personal" -X POST --data '{"methodName":"getCustomerAndAccountList","params":["001"]}' http://localhost:8888/api|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"appServiceId": "/app/personal",
"traceId": "45c12fecc1b141f68266ffd61fe4d4f3",
"parentTraceId": null,
"timestamp": 1640692649888,
"data": {
"result": {
"accountList": [
{
"accountId": "0001",
"balance": -34,
"customerId": "001"
},
{
"accountId": "0002",
"balance": 200,
"customerId": "001"
}
],
"customer": {
"sex": "1",
"customerName": "张三",
"customerId": "001",
"age": 30
}
}
}
}
2.2 通过个性化开发定制的Controller
通过个性化PersonController实现的RESTful接口,接口测试如下:
$ curl http://localhost:9001/personal/getCustomerAndAccountList\?customerId=001|jq
{
"customer": {
"customerId": "001",
"customerName": "张三",
"age": 30,
"sex": "1"
},
"accountList": [
{
"accountId": "0001",
"customerId": "001",
"balance": 100
},
{
"accountId": "0002",
"customerId": "001",
"balance": 200
}
]
}
七、项目实践:支付域服务的开发
1. Payment领域服务的封装
payment领域服务是对接第三方缴费平台的,第三方缴费平台的接口是XML报文格式。
payment领域服务是属于对接第三方的领域服务,前面提到的customer领域服务和account领域服务,主要是内部交易处理的领域服务。
这二类领域服务在开发时有比较大的不同:
- 对接第三方的领域服务,一般涉及到复杂的通讯接口对接和报文格式转换;
- 内部交易处理领域服务,应用层和领域层之间,建议采用约定的Interface接口类的调用约定(底层Biz-SIP平台自动转换成RESTful协议和BizMessage标准消息进行交互)。
在开发对接第三方的领域服务时,建议采用基于SinkBeanInterface接口的Sink服务:
public interface SinkBeanInterface {
/**
* SinkBean服务调用接口
* @param jsonObject 传入的消息
* @return 返回值
* @throws BizException
*/
public JSONObject process(JSONObject jsonObject) throws BizException;
}
Payment领域服务的封装,依次有以下步骤:
- 第1步
领域服务的实现:创建xbank-payment1-sink子模块,Payment1SinkService类由于是继承AbstractSinkService类的,使得当前sink的connector和converter是会自动注入的,在process()中就可以直接调用Converter和Connector,实现代码中对报文的解包和打包::
@Service
public class Payment1SinkService extends AbstractSinkService implements SinkBeanInterface {
@Override
public JSONObject process(JSONObject jsonObject) throws BizException {
log.info("传入消息:\n{}", BizUtils.buildJsonLog(jsonObject));
byte[] inBytes = this.converter.pack(jsonObject);
log.info("打包后消息:\n{}", BizUtils.buildHexLog(inBytes));
JSONObject outJsonObject = this.converter.unpack(inBytes);
log.info("解包后消息:\n{}", BizUtils.buildJsonLog(outJsonObject));
return outJsonObject;
}
}
- 第2步
在sink.yml文件中,把Payment1SinkService微服务作为Sink连接到Biz-SIP平台:
sink.yml
- id: payment1-sink
type: rest
processor: sink-bean
url: http://payment1-sink/sink
class-name: com.xbank.sink.payment.service.Payment1SinkService
converter:
type: simple-xml
2. Payment服务的快速发布
配置app.yml文件,把payment-sink直接作为sink-service暴露给Biz-SIP开放平台接口:
app.yml
- app-service-id: /sink/payment1
type: sink-service
sink-id: payment1-sink
接口测试如下:
$ curl -H "Content-Type:application/json" -H "Biz-Service-Id:/sink/payment1" -X POST --data '{"message":"Hello world!"}' http://localhost:8888/api|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"appServiceId": "/sink/payment1",
"traceId": "93cde69b1b764b3096d6b9bd64119107",
"parentTraceId": null,
"timestamp": 1640692650220,
"data": {
"message": "Hello world!"
}
}
Payment1SinkApplication会打印出日志,收到了完成消息转换后的XML报文:
2021-09-09 10:05:13.265 INFO 10756 --- [nio-8003-exec-1] c.x.d.p.service.Payment1DomainService : 传入消息:
====+ 01-02-03-04-05-06-07-08-09-10-11-12-13-14-15-16-17-18-19-20 + ====== ASCII ====== +
0000: 3C 3F 78 6D 6C 20 76 65 72 73 69 6F 6E 3D 22 31 2E 30 22 20 | <?xml version="1.0" |
0020: 65 6E 63 6F 64 69 6E 67 3D 22 55 54 46 2D 38 22 20 73 74 61 | encoding="UTF-8" sta |
0040: 6E 64 61 6C 6F 6E 65 3D 22 6E 6F 22 3F 3E 3C 72 6F 6F 74 3E | ndalone="no"?><root> |
0060: 3C 6D 65 73 73 61 67 65 3E 48 65 6C 6C 6F 20 77 6F 72 6C 64 | <message>Hello world |
0080: 21 3C 2F 6D 65 73 73 61 67 65 3E 3C 2F 72 6F 6F 74 3E | !</message></root>.. |
3. payment领域服务在应用层和适配层的定制
通过在service.yml中配置sink-service聚合服务,能实现已经挂接到Sink的领域服务的快速发布。
但是,客户针对应用层和适配层,还是有个性化定制要求,这就涉及到应用层和适配层的定制。
例如我们要实现一个把消息发送给缴费平台的业务,输入的消息包括:交易码、传递的消息。
首先,需要在xbank-personal-app-client子模块中的PersonalAppInterface接口,增加一个消息发送接口send2payment1(),这个接口是共享给应用层和适配层的:
public interface PersonalAppInterface {
...
public BizMessage send2Payment1(Object message) throws BizException;
...
}
然后在xbank-app模块中基于此接口方法。在PersonalAppService类实现接口:
public class PersonalAppService implements PersonalAppInterface {
...
private BizMessageInterface payment1DomainInterface = AppClientFactory
.getSinkClient(BizMessageInterface.class,"payment1-sink");
...
@Override
public BizMessage<JSONObject> send2Payment1(Object message) throws BizException {
JSONObject jsonObject = new JSONObject();
jsonObject.set("message",message);
return this.payment1DomainInterface.call(jsonObject);
}
...
}
由于payment领域服务没有另外约定接口,是采用BizMessage标准消息接口,所以是采用BizMessageInterface接口来构建sink调用接口的,并统一通过call()方法来调用领域服务。
由于前面已经在service.yml中把PersonalAppService应用层服务类,配置成了“bean-service”的聚合服务,所以可以直接进行接口调用:
$ curl -H "Content-Type:application/json" -H "Biz-Service-Id:/app/personal" -X POST --data '{"methodName":"send2Payment1","params":["Hello world!"]}'
{
"code": 0,
"message": "success",
"extMessage": null,
"appServiceId": "/app/personal",
"traceId": "e84221a881b8486f88ac51e56b0dd4b5",
"parentTraceId": null,
"timestamp": 1640692651199,
"data": {
"result": {
"traceId": "e84221a881b8486f88ac51e56b0dd4b5",
"code": 0,
"data": {
"message": "Hello world!"
},
"message": "success",
"appServiceId": "/app/personal",
"timestamp": 1640692651199
}
}
}
Payment1SinkApplication应用会收到消息:
2021-09-09 10:13:44.911 INFO 10756 --- [nio-8003-exec-2] c.x.d.p.service.Payment1DomainService : 传入消息:
====+ 01-02-03-04-05-06-07-08-09-10-11-12-13-14-15-16-17-18-19-20 + ====== ASCII ====== +
0000: 3C 3F 78 6D 6C 20 76 65 72 73 69 6F 6E 3D 22 31 2E 30 22 20 | <?xml version="1.0" |
0020: 65 6E 63 6F 64 69 6E 67 3D 22 55 54 46 2D 38 22 20 73 74 61 | encoding="UTF-8" sta |
0040: 6E 64 61 6C 6F 6E 65 3D 22 6E 6F 22 3F 3E 3C 72 6F 6F 74 3E | ndalone="no"?><root> |
0060: 3C 6D 65 73 73 61 67 65 3E 48 65 6C 6C 6F 20 77 6F 72 6C 64 | <message>Hello world |
0080: 21 3C 2F 6D 65 73 73 61 67 65 3E 3C 2F 72 6F 6F 74 3E | !</message></root>.. |
在适配层中,在原有的PersonalController类中,增加对“/send2Payment1"请求的实现:
@GetMapping(value ="/send2Payment1")
public BizMessage<JSONObject> send2Payment1(String message) throws BizException {
return this.personalAppInterface.send2Payment1(message);
}
开发者可以在适配层的PersonalController类中,通过personalAppInterface,把消息发送给缴费平台:
$ curl http://localhost:9001/personal/send2Payment1\?message=hello|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"appServiceId": "/app/personal",
"traceId": "96138053c11a41faa86acb286ee6d976",
"parentTraceId": null,
"timestamp": 1640700400374,
"data": {
"message": "hello"
}
}
在适配层中,还可以通过配置sink-service类型的聚合服务,直接调用领域层的Sink服务。例如可以在原有的PersonalController类中,直接向应用层发起JSONObject类型的平台报文:
@RestController
@RequestMapping("/personal")
public class OpenapiController {
...
private BizMessageInterface payment1SinkInterface = SourceClientFactory
.getAppServiceClient(BizMessageInterface.class,"/sink/payment1");
...
@GetMapping(value ="/send2Payment")
public BizMessage<JSONObject> send2Payment(String message) throws BizException {
JSONObject jsonObject = new JSONObject();
jsonObject.set("message",message);
return this.payment1SinkInterface.call(jsonObject);
}
}
开发者可以在适配层的OpenapiController类中,通过payment1SinkInterface,把消息发送给缴费平台:
$ curl http://localhost:9001/personal/send2Payment\?message=hello|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"appServiceId": "/sink/payment1",
"traceId": "13503e8193604d8b88eb48615964a148",
"parentTraceId": null,
"timestamp": 1640700717374,
"data": {
"message": "hello"
}
}
八、项目实践:应用服务对延迟服务的组装
1. SAF存储转发的实现
在领域层中,开发Payment2SinkService,根据tranMode交易模式,实现处理超时、处理失败和处理成功的各种异常情况:
@Service
public class Payment2SinkService implements JSONObjectSinkBeanInterface {
@Autowired
private TimeoutCmdExe timeoutCmdExe;
@Autowired
private TimeoutAndFailCmdExe timeoutAndFailCmdExe;
@Autowired
private TimeoutAndSuccessCmdExe timeoutAndSuccessCmdExe;
@Autowired
private SuccessCmdExe successCmdExe;
@Override
public JSONObject process(JSONObject jsonObject) throws BizException {
log.info("传入消息:\n{}", jsonObject.toString());
AbstractSinkBeanCmdExe sinkBeanCmdExe;
String tranMode = (String)jsonObject.get("tranMode");
switch (tranMode) {
case "timeout":
// 收到交易后,永远返回超时
return timeoutCmdExe.execute(jsonObject);
case "3timeout-fail":
// 收到交易后,前3次返回超时,第4次返回失败码
return timeoutAndFailCmdExe.execute(jsonObject);
case "3timeout-success":
// 收到交易后,前3次返回超时,第4次成功返回原报文
return timeoutAndSuccessCmdExe.execute(jsonObject);
default:
//其它情况,成功返回原报文
return successCmdExe.execute(jsonObject);
}
}
}
注意,Payment2领域服务在封装时,涉及到多个接口调用,对每个接口采用命令执行器(Command Executor),这里是传入标准的JSONObject数据,所以所有的接口实现继承AbstractSinkBeanCmdExe类来实现:
在应用层中,在原有的PersonalAppInterface应用层接口、PersonalAppService服务类中,增加send2Payment2()、getCustomerAndSaf2Payment2()这2个方法接口和实现,通过personalAppDelayInterface延迟调用接口,实现对原有send2Payment2()方法的多次SAF调用:
@Service
public class PersonalAppService implements PersonalAppInterface {
...
private BizMessageInterface payment2SinkInterface = AppClientFactory
.getSinkClient(BizMessageInterface.class,"payment2-sink");
private PersonalAppInterface personalAppDelayInterface = AppClientFactory
.getDelayAppServiceClient(PersonalAppInterface.class,"/app/personal",
0,1000,2000,4000,8000,16000,32000);
...
@Override
public BizMessage send2Payment2(String tranMode, String tranCode, Object message) throws BizException {
JSONObject jsonObject = new JSONObject();
jsonObject.set("tranCode",tranCode);
jsonObject.set("tranMode",tranMode);
jsonObject.set("message",message);
return this.payment2SinkInterface.call(jsonObject);
}
@Override
public Customer getCustomerAndSaf2Payment2(String tranMode,String customerId) throws BizException {
Customer customer = this.customerSinkInterface.getCustomer(customerId);
this.personalAppDelayInterface.send2Payment2(tranMode,"send-customer", customer);
return customer;
}
...
}
传入交易模式为”timeout“(收到交易后,永远返回超时),这将导致发送7次,最后交易状态为失败:
$ curl -H "Content-Type:application/json" -H "Biz-Service-Id:/app/personal" -X POST --data '{"methodName":"getCustomerAndSaf2Payment2","params":["timeout","001"]}' http://localhost:8888/api|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"traceId": "c1d2ffb7f0b44bf4b0cb7e13e5c0c0fa",
"parentTraceId": null,
"timestamp": 1631274896174,
"data": {
"result": {
"sex": "1",
"customerName": "张三",
"customerId": "001",
"age": 30
}
}
}
Payment2SinkApplication日志:
2021-09-10 19:54:56.650 INFO 14993 --- [nio-8004-exec-3] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"timeout","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:54:56.651 INFO 14993 --- [nio-8004-exec-3] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回超时异常!
2021-09-10 19:54:57.753 INFO 14993 --- [nio-8004-exec-4] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"timeout","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:54:57.754 INFO 14993 --- [nio-8004-exec-4] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回超时异常!
2021-09-10 19:54:59.920 INFO 14993 --- [nio-8004-exec-5] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"timeout","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:54:59.920 INFO 14993 --- [nio-8004-exec-5] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回超时异常!
2021-09-10 19:55:04.015 INFO 14993 --- [nio-8004-exec-6] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"timeout","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:55:04.015 INFO 14993 --- [nio-8004-exec-6] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回超时异常!
2021-09-10 19:55:12.041 INFO 14993 --- [nio-8004-exec-7] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"timeout","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:55:12.042 INFO 14993 --- [nio-8004-exec-7] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回超时异常!
2021-09-10 19:55:28.080 INFO 14993 --- [nio-8004-exec-8] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"timeout","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:55:28.080 INFO 14993 --- [nio-8004-exec-8] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回超时异常!
2021-09-10 19:56:00.228 INFO 14993 --- [nio-8004-exec-9] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"timeout","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:56:00.228 INFO 14993 --- [nio-8004-exec-9] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回超时异常!
传入交易码为”3timeout-fail“(收到交易后,前3次返回超时,第4次返回失败),这将导致发送4次,最后交易状态为失败:
$ curl -H "Content-Type:application/json" -H "Biz-Service-Id:/app/personal" -X POST --data '{"methodName":"getCustomerAndSaf2Payment2","params":["3timeout-fail","001"]}' http://localhost:8888/api|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"traceId": "d3589b169d654a409c2f7d15f83f5113",
"parentTraceId": null,
"timestamp": 1631275051123,
"data": {
"result": {
"sex": "1",
"customerName": "张三",
"customerId": "001",
"age": 30
}
}
}
Payment2SinkApplication日志:
2021-09-10 19:57:31.379 INFO 14993 --- [nio-8004-exec-1] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"3timeout-fail","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:57:31.379 INFO 14993 --- [nio-8004-exec-1] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回超时异常!
2021-09-10 19:57:32.647 INFO 14993 --- [nio-8004-exec-2] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"3timeout-fail","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:57:32.647 INFO 14993 --- [nio-8004-exec-2] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回超时异常!
2021-09-10 19:57:34.672 INFO 14993 --- [nio-8004-exec-3] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"3timeout-fail","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:57:34.673 INFO 14993 --- [nio-8004-exec-3] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回超时异常!
2021-09-10 19:57:38.733 INFO 14993 --- [nio-8004-exec-4] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"3timeout-fail","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:57:38.733 INFO 14993 --- [nio-8004-exec-4] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回交易处理失败!
传入交易码为”3timeout-success“(收到交易后,前3次返回超时,第4次成功返回原报文),这将导致发送4次,最后交易状态为成功:
$ curl -H "Content-Type:application/json" -H "Biz-Service-Id:/app/personal" -X POST --data '{"methodName":"getCustomerAndSaf2Payment2","params":["3timeout-success","001"]}' http://localhost:8888/api|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"traceId": "75b76cf518164964bedf477ea488698c",
"parentTraceId": null,
"timestamp": 1631275117934,
"data": {
"result": {
"sex": "1",
"customerName": "张三",
"customerId": "001",
"age": 30
}
}
}
Payment2SinkApplication日志:
2021-09-10 19:58:38.146 INFO 14993 --- [nio-8004-exec-5] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"3timeout-success","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:58:38.146 INFO 14993 --- [nio-8004-exec-5] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回超时异常!
2021-09-10 19:58:39.200 INFO 14993 --- [nio-8004-exec-6] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"3timeout-success","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:58:39.200 INFO 14993 --- [nio-8004-exec-6] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回超时异常!
2021-09-10 19:58:41.409 INFO 14993 --- [nio-8004-exec-7] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"3timeout-success","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:58:41.409 INFO 14993 --- [nio-8004-exec-7] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回超时异常!
2021-09-10 19:58:45.539 INFO 14993 --- [nio-8004-exec-8] c.x.d.p.service.Payment2DomainService : 传入消息:
{"tranMode":"3timeout-success","message":{"sex":"1","customerName":"张三","customerId":"001","age":30},"tranCode":"send-customer"}
2021-09-10 19:58:45.539 INFO 14993 --- [nio-8004-exec-8] c.x.d.p.service.Payment2DomainService : 交易:send-customer,返回交易成功!
2. 向前补偿的实现
向前补偿机制是指当交易无应答时,系统会对原交易进行查询,如查询超时会重试多次。如原交易成功则继续后续交易,如原交易失败则对前续交易进行交易补偿,向前补偿一般是针对第三方接入方无法提供补偿交易时的解决方案。
在金融领域,这类交易补偿动作常被称为“冲正”。
在应用层中,在原有的PersonalAppInterface应用层接口、PersonalAppService服务类中,增加payoutForward()、payoutForwardCompensate()这2个方法接口和实现,通过personalAppDelayInterface延迟调用接口,实现对原有payoutForwardCompensate()方法的多次重复延迟调用:
@Service
public class PersonalAppService implements PersonalAppInterface {
private AccountSinkInterface accountSinkInterface = AppClientFactory
.getSinkClient(AccountSinkInterface.class,"account-sink");
private CustomerSinkInterface customerSinkInterface = AppClientFactory
.getSinkClient(CustomerSinkInterface.class,"customer-sink");
private BizMessageInterface payment1SinkInterface = AppClientFactory
.getSinkClient(BizMessageInterface.class,"payment1-sink");
private BizMessageInterface payment2SinkInterface = AppClientFactory
.getSinkClient(BizMessageInterface.class,"payment2-sink");
private PersonalAppInterface personalAppDelayInterface = AppClientFactory
.getDelayAppServiceClient(PersonalAppInterface.class,"/app/personal",
0,1000,2000,4000,8000,16000,32000);
......
@Override
public void payoutForward(String tranMode,String accountId, long amount) throws BizException {
log.info("account出金:{},{}",accountId,amount);
this.accountDomainInterface.payout(accountId,amount);
JSONObject jsonObject = new JSONObject();
jsonObject.set("tranCode","pay");
jsonObject.set("tranMode",tranMode);
jsonObject.set("accountId",accountId);
jsonObject.set("tranAmount",amount);
BizMessage<JSONObject> bizMessage = null;
try {
log.info("payment缴费...");
bizMessage = this.payment2SinkInterface.call(jsonObject);
} catch (BizException e) {
if (e.isTimeOutException()) {
log.info("payment交易超时,开始payout补偿...");
this.personalAppDelayInterface.payoutForwardCompensate(jsonObject);
return;
}
else {
throw e;
}
}
log.info("payment缴费成功!");
log.info("payout成功!");
}
@Override
public void payoutForwardCompensate(JSONObject jsonObject) throws BizException{
jsonObject.set("tranCode","pay-query");
BizMessage<JSONObject> bizMessage = null;
try {
log.info("payment查询缴费订单...");
bizMessage = this.payment2SinkInterface.call(jsonObject);
} catch (BizException e) {
if (e.isTimeOutException()) {
log.info("payment交易超时...");
throw e;
}
else {
log.info("payment查询缴费订单返回错误(表示对方订单没执行)...");
String accountId = (String)jsonObject.get("accountId");
long amount = (Integer) jsonObject.get("tranAmount");
log.info("account出金补偿:{},{}",accountId,amount);
this.accountDomainInterface.payoutCompensation(accountId,amount);
return;
}
}
log.info("payment查询缴费订单成功(表示对方订单已执行)");
log.info("payout成功!");
}
传入交易模式为”3timeout-success“(收到订单交易后,前3次返回超时,第4次成功返回模拟订单已执行),这将导致发送4次,最后交易状态为成功:
$ curl -H "Content-Type:application/json" -H "Biz-Service-Id:/app/personal" -X POST --data '{"methodName":"payoutForward","params":["3timeout-success","0001",1]}' http://localhost:8888/api|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"traceId": "e0127a25e2514da682ad77042917087a",
"parentTraceId": null,
"timestamp": 1631275840559,
"data": {}
}
XbankAppApplication日志:
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:10:40.560 INFO 16503 [11034350FE38437BB459F1E8DC6CCE17] [http-nio-8888-exec-5] c.xbank.app.service.PersonalAppService account出金:0001,1
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:10:40.822 INFO 16503 [11034350FE38437BB459F1E8DC6CCE17] [http-nio-8888-exec-5] c.xbank.app.service.PersonalAppService payment缴费...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:10:40.831 INFO 16503 [11034350FE38437BB459F1E8DC6CCE17] [http-nio-8888-exec-5] c.xbank.app.service.PersonalAppService payment交易超时,开始payout补偿...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:10:42.086 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-4] c.xbank.app.service.PersonalAppService payment查询缴费订单...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:10:42.095 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-4] c.xbank.app.service.PersonalAppService payment交易超时...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:10:43.268 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-3] c.xbank.app.service.PersonalAppService payment查询缴费订单...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:10:43.277 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-3] c.xbank.app.service.PersonalAppService payment交易超时...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:10:45.296 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-2] c.xbank.app.service.PersonalAppService payment查询缴费订单...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:10:45.306 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-2] c.xbank.app.service.PersonalAppService payment查询缴费订单成功(表示对方订单已执行)
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:10:45.306 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-2] c.xbank.app.service.PersonalAppService payout成功!
传入交易模式为”3timeout-success“(收到订单交易后,前3次返回超时,第4次成功返回模拟订单已执行),这将导致发送4次,最后交易状态为成功:
$ curl -H "Content-Type:application/json" -H "Biz-Service-Id:/app/personal" -X POST --data '{"methodName":"payoutForward","params":["3timeout-fail","0001",1]}' http://localhost:8888/api|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"traceId": "a04163bf05484fa28071650a085d4124",
"parentTraceId": null,
"timestamp": 1631275997342,
"data": {}
}
XbankAppApplication日志:
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:13:17.342 INFO 16503 [5E5951618A27430684C5552E1F782D8B] [http-nio-8888-exec-7] c.xbank.app.service.PersonalAppService account出金:0001,1
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:13:17.568 INFO 16503 [5E5951618A27430684C5552E1F782D8B] [http-nio-8888-exec-7] c.xbank.app.service.PersonalAppService payment缴费...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:13:17.579 INFO 16503 [5E5951618A27430684C5552E1F782D8B] [http-nio-8888-exec-7] c.xbank.app.service.PersonalAppService payment交易超时,开始payout补偿...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:13:17.775 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-5] c.xbank.app.service.PersonalAppService payment查询缴费订单...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:13:17.788 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-5] c.xbank.app.service.PersonalAppService payment交易超时...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:13:18.827 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.xbank.app.service.PersonalAppService payment查询缴费订单...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:13:18.836 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.xbank.app.service.PersonalAppService payment交易超时...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:13:20.890 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-4] c.xbank.app.service.PersonalAppService payment查询缴费订单...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:13:20.900 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-4] c.xbank.app.service.PersonalAppService payment查询缴费订单返回错误(表示对方订单没执行)...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:13:20.901 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-4] c.xbank.app.service.PersonalAppService account出金补偿:0001,1
3. 向后补偿的实现
向后补偿机制是指当交易无应答时,系统会对原交易发起多次交易补偿重试,如补偿交易成功则对前续交易进行补偿。
在应用层中,在原有的PersonalAppInterface应用层接口、PersonalAppService服务类中,增加payoutBackward()、payoutBackwardCompensate()这2个方法接口和实现,通过personalAppDelayInterface延迟调用接口,实现对原有payoutForwardCompensate()方法的多次重复延迟调用:
@Service
public class PersonalAppService implements PersonalAppInterface {
private AccountSinkInterface accountSinkInterface = AppClientFactory
.getSinkClient(AccountSinkInterface.class,"account-sink");
private CustomerSinkInterface customerSinkInterface = AppClientFactory
.getSinkClient(CustomerSinkInterface.class,"customer-sink");
private BizMessageInterface payment1SinkInterface = AppClientFactory
.getSinkClient(BizMessageInterface.class,"payment1-sink");
private BizMessageInterface payment2SinkInterface = AppClientFactory
.getSinkClient(BizMessageInterface.class,"payment2-sink");
private PersonalAppInterface personalAppDelayInterface = AppClientFactory
.getDelayAppServiceClient(PersonalAppInterface.class,"/app/personal",
0,1000,2000,4000,8000,16000,32000);
......
@Override
public void payoutBackward(String tranMode, String accountId, long amount) throws BizException {
log.info("account出金:{},{}",accountId,amount);
this.accountDomainInterface.payout(accountId,amount);
JSONObject jsonObject = new JSONObject();
jsonObject.set("tranCode","pay");
jsonObject.set("tranMode",tranMode);
jsonObject.set("accountId",accountId);
jsonObject.set("tranAmount",amount);
BizMessage<JSONObject> bizMessage = null;
try {
log.info("payment缴费...");
bizMessage = this.payment2SinkInterface.call(jsonObject);
} catch (BizException e) {
if (e.isTimeOutException()) {
log.info("payment交易超时,开始payout冲正...");
this.personalAppDelayInterface.payoutBackwardCompensate(jsonObject);
return;
}
else {
log.info("payment缴费交易返回错误");
log.info("account出金补偿:{},{}",accountId,amount);
this.accountDomainInterface.payoutCompensation(accountId,amount);
log.info("payout交易补偿成功!");
return;
}
}
log.info("payment缴费成功");
log.info("payout成功!");
}
@Override
public void payoutBackwardCompensate(JSONObject jsonObject) throws BizException {
jsonObject.set("tranCode","pay-reversal");
BizMessage<JSONObject> bizMessage;
try {
log.info("payment缴费补偿...");
bizMessage = this.payment2SinkInterface.call(jsonObject);
log.info("payment缴费补偿成功");
} catch (BizException e) {
if (e.isTimeOutException()) {
log.info("payment缴费补偿交易超时...");
throw e;
}
log.info("payment缴费补偿交易失败,需要人工干预调整!");
return;
}
String accountId = (String)jsonObject.get("accountId");
long amount = (Integer)jsonObject.get("tranAmount");
log.info("account出金补偿:{},{}",accountId,amount);
this.accountDomainInterface.payoutCompensation(accountId,amount);
log.info("payout补偿成功!");
}
传入交易模式为”3timeout-success“(收到订单交易后,前3次返回补偿交易超时,第4次成功返回补偿交易执行成功),这将导致发送4次,最后交易状态为成功:
$ curl -H "Content-Type:application/json" -H "Biz-Service-Id:/app/personal" -X POST --data '{"methodName":"payoutBackward","params":["3timeout-success","0001",1]}' http://localhost:8888/api|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"traceId": "26c58fe44b3f4ceba462762494fa5981",
"parentTraceId": null,
"timestamp": 1631276405513,
"data": {}
}
XbankAppApplication日志:
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:20:05.513 INFO 16503 [84936F44451449C5A92183EF93C3690C] [http-nio-8888-exec-2] c.xbank.app.service.PersonalAppService account出金:0001,1
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:20:05.952 INFO 16503 [84936F44451449C5A92183EF93C3690C] [http-nio-8888-exec-2] c.xbank.app.service.PersonalAppService payment缴费...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:20:05.961 INFO 16503 [84936F44451449C5A92183EF93C3690C] [http-nio-8888-exec-2] c.xbank.app.service.PersonalAppService payment交易超时,开始payout冲正...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:20:06.396 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.xbank.app.service.PersonalAppService payment缴费补偿...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:20:06.405 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.xbank.app.service.PersonalAppService payment缴费补偿交易超时...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:20:07.468 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-4] c.xbank.app.service.PersonalAppService payment缴费补偿...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:20:07.475 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-4] c.xbank.app.service.PersonalAppService payment缴费补偿交易超时...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:20:09.560 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-3] c.xbank.app.service.PersonalAppService payment缴费补偿...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:20:09.570 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-3] c.xbank.app.service.PersonalAppService payment缴费补偿成功
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:20:09.571 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-3] c.xbank.app.service.PersonalAppService account出金补偿:0001,1
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:20:09.726 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-3] c.xbank.app.service.PersonalAppService payout补偿成功!
传入交易模式为”3timeout-fail“(收到订单交易后,前3次返回超时,第4次成功返回模拟补偿交易失败),这将导致发送4次,最后交易状态为失败:
$ curl -H "Content-Type:application/json" -H "Biz-Service-Id:/app/personal" -X POST --data '{"methodName":"payoutBackward","params":["3timeout-fail","0001",1]}' http://localhost:8888/api|jq
{
"code": 0,
"message": "success",
"extMessage": null,
"traceId": "40aed8f06bba436ba67ff93e769d5a33",
"parentTraceId": null,
"timestamp": 1631276555599,
"data": {}
}
XbankAppApplication日志:
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:22:35.599 INFO 16503 [B54A535614A64492A7A00D987F12B529] [http-nio-8888-exec-4] c.xbank.app.service.PersonalAppService account出金:0001,1
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:22:35.760 INFO 16503 [B54A535614A64492A7A00D987F12B529] [http-nio-8888-exec-4] c.xbank.app.service.PersonalAppService payment缴费...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:22:35.767 INFO 16503 [B54A535614A64492A7A00D987F12B529] [http-nio-8888-exec-4] c.xbank.app.service.PersonalAppService payment交易超时,开始payout冲正...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:22:35.979 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-2] c.xbank.app.service.PersonalAppService payment缴费补偿...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:22:35.993 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-2] c.xbank.app.service.PersonalAppService payment缴费补偿交易超时...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:22:37.073 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-5] c.xbank.app.service.PersonalAppService payment缴费补偿...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:22:37.083 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-5] c.xbank.app.service.PersonalAppService payment缴费补偿交易超时...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:22:39.111 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.xbank.app.service.PersonalAppService payment缴费补偿...
[bizsip-integrator:192.169.1.101:8888] 2021-09-10 20:22:39.121 INFO 16503 [] [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.xbank.app.service.PersonalAppService payment缴费补偿交易失败,需要人工干预调整!