适配器模式(Adapter Pattern)是一种结构型设计模式,用于解决接口不兼容问题。它充当两个不兼容接口之间的桥梁,通过包装已有接口(被适配者),使其符合目标接口规范。
角色组成
目标接口(Target):客户端期望使用的接口。
被适配者(Adaptee):需要被适配的已有组件。
适配器(Adapter):实现目标接口,内部持有被适配者的引用,进行接口转换。
什么乱七八糟的名词,看不懂
简单来说就是当我们遇见很多类似的接口,例如支付的时候有微信支付,xx支付,网银支付,支付宝支付(被适配者),这些支付(目标接口:支付)他们都有他们自己的实现,如果我们在客户端要调用的时候是不是还有自己去一个一个适配这个支付接口
那么通过设计适配器模式,就可以让客户端无感的调用不同的支付方式,
首先对于对账而且,都是第三方和本地资金进行比较, 通过定时任务接收对账文件,第三方对账分为交易对账,资金对账,而交易对账又分为(代收,代付,退款,提现),资金对账(待分账账户对账,收入账户对账)因此可以设计一个适配器模式,然客户端无感
什么是客户端无感,下面就是定时任务调用的执行对账,这里相当于客户端,只是通过传入的ReconTypeEnum 枚举类型,来改变具体的执行
//下面代码是分开在不同模块的,这里只是为了方便观看
public enum ReconTypeEnum {
TX_RECON(0,"交易对账",1,".txt","tx","txReconciliationAdapter"),
FUND_RECON(1,"资金对账",10,".csv","fund","fundReconciliationAdapter");
}
//执行交易对账项目
reconciliationFacade.reconcile(beginDate, endDate, ChannelTypeEnum.ALLINPAY,ReconTypeEnum.TX_RECON, reconciliationTxProjectCO);
//执行交易对账项目
reconciliationFacade.reconcile(beginDate, endDate, ChannelTypeEnum.ALLINPAY, ReconTypeEnum.FUND_RECON, reconciliationFundProjectCO);
门面,用来获取适配器,并执行具体对账
/**
* 对账门面类
* 提供一个简化的接口来使用适配器模式实现的对账系统
*/
@Component
@Slf4j
public class ReconciliationFacade {
@Resource
private ReconciliationAdapterFactory adapterFactory;
/**
* 执行交易和资金对账
* @param startDate 对账开始日期
* @param endDate 对账结束日期
* @param channel 渠道,如果为空则使用默认渠道
* @return 对账结果
*/
public void reconcile(Date startDate, Date endDate, ChannelTypeEnum channel, ReconTypeEnum reconTypeEnum, ReconciliationProjectCO projectCO) {
if (Objects.isNull(channel)) {
channel = ChannelTypeEnum.ALLINPAY;
}
ReconciliationAdapter adapter = adapterFactory.getAdapter(reconTypeEnum);
adapter.reconcile(startDate,endDate, channel,projectCO);
}
}
适配器工厂
@Component
@Slf4j
public class ReconciliationAdapterFactory {
/**
* 根据对账类型获取对应的适配器
* @param reconTypeEnum 对账类型
* @return 对账适配器
*/
public ReconciliationAdapter getAdapter(ReconTypeEnum reconTypeEnum) {
if (reconTypeEnum == null) {
throw new IllegalArgumentException("Reconciliation type cannot be null");
}
try {
// 尝试获取Bean实例
ReconciliationAdapter adapter = SpringUtil.getBean(reconTypeEnum.getAdapterBeanName(), ReconciliationAdapter.class);
if (adapter == null) {
throw new IllegalStateException("Failed to get adapter bean for type: " + reconTypeEnum);
}
log.info("成功获取对账适配器: {},类型: {}", reconTypeEnum.getAdapterBeanName(), reconTypeEnum.getDesc());
return adapter;
} catch (BeansException e) {
log.error("获取对账适配器失败: {}", e.getMessage(), e);
throw new IllegalStateException("Failed to get adapter bean for reconciliation type: " + reconTypeEnum.getDesc(), e);
}
}
}
适配器接口
/**
* 对账适配器接口
* 定义了交易和资金对账的通用方法
*/
public interface ReconciliationAdapter {
/**
* 执行对账逻辑
* @param startDate 对账开始日期
* @param endDate 对账结束日期
* @param channel 渠道
* @param projectCO 对账项目
*/
void reconcile(Date startDate, Date endDate, ChannelTypeEnum channel, ReconciliationProjectCO projectCO);
}
适配器
/**
* 交易对账适配器
* 将交易对账的特定实现适配到通用的对账接口
*/
@Component
@Slf4j
public class TxReconciliationAdapter implements ReconciliationAdapter {
@Resource
private ReconciliationStrategyFactory strategyFactory;
@Override
public void reconcile(Date startDate, Date endDate, ChannelTypeEnum channel, ReconciliationProjectCO projectCO) {
ReconSubTypeEnum reconSubTypeEnum = projectCO.getReconcileType();
log.info("执行交易对账,开始日期:{},结束日期:{},渠道:{},对账类型:{}",
startDate, endDate, channel, reconSubTypeEnum.getDesc());
try {
// 获取对应的对账策略
ReconciliationStrategy<?, ?> strategy = strategyFactory.getStrategy(reconSubTypeEnum);
// 使用模板方法执行对账流程
if (strategy instanceof AbstractReconciliationTemplate) {
((AbstractReconciliationTemplate<?, ?>) strategy).doReconcile(startDate, endDate, channel,projectCO);
} else {
log.error("对账策略不是AbstractReconciliationTemplate的实例,无法执行doReconcile方法");
}
} catch (IllegalArgumentException e) {
log.warn("不支持的交易对账类型:{}", reconSubTypeEnum.getDesc());
} catch (Exception e) {
log.error("执行交易对账异常", e);
throw new BizException(e.getMessage());
}
}
}
那么被适配类在哪里,这里就是通过策略方法,具体去执行被适配类,这里如果具体执行的方法没有多个也不需要策略模式,只是在这里资金对账,交易对账还分为了很多类型的对账
策略模式可以参考这篇策略模式
/**
* 交易对账类型
*/
@Getter
public enum ReconSubTypeEnum {
// 交易对账
AGENT_COLLECT(0,"通联代收","代收","tx"),
AGENT_PAY(1,"通联代付","代付","tx"),
REFUND(2,"通联退款","退款","tx"),
WITHDRAW(3,"通联提现","提现","tx"),
// 资金对账
PENDING_SPLIT_ACCOUNT(4,"通联待分账账户资金对账","通联待分账账户资金对账","fund"),
PLATFORM_INCOME_ACCOUNT(5,"通联平台收入账户资金对账","通联平台收入账户资金对账","fund");
}
因此具体的执行类在
策略模板,相当于一个执行流程,然后具体的实现在不同的策略类中
package com.htyc.biz.payment.job.reconcile.template;
import cn.hutool.core.date.DateUtil;
import com.alibaba.cola.exception.BizException;
import com.htyc.biz.payment.common.enums.ChannelTypeEnum;
import com.htyc.biz.payment.common.enums.ReconSubTypeEnum;
import com.htyc.biz.payment.dto.reconciliation.ReconciliationProjectCO;
import com.htyc.biz.payment.job.reconcile.strategy.ReconciliationStrategy;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 对账模板类
* 定义对账的通用流程和步骤
*/
@Slf4j
public abstract class AbstractReconciliationTemplate<P, C> implements ReconciliationStrategy<P, C> {
@Resource
private TransactionTemplate transactionTemplate;
/**
* 执行对账
* 模板方法定义了对账的标准流程
*
* @param startDate 开始日期
* @param endDate 结束日期
* @param channel 渠道
*/
public void doReconcile(Date startDate, Date endDate, ChannelTypeEnum channel, ReconciliationProjectCO projectCO) {
Date currentDate = startDate;
ReconSubTypeEnum reconType = getReconSubType();
// 按日期区间逐天对账
while (!currentDate.after(endDate)) {
log.info("执行{}日{}对账,渠道:{}",
DateUtil.format(currentDate, "yyyy-MM-dd"),
reconType.getDesc(),
channel.getDesc());
try {
Date finalCurrentDate = currentDate;
transactionTemplate.executeWithoutResult(transactionStatus -> {
verify(finalCurrentDate, channel,projectCO);
// 步骤1: 查询平台数据
List<P> platformData = queryPlatformData(finalCurrentDate, channel);
log.info("获取到平台{}数据条数:{}", reconType.getDesc(), platformData.size());
// 步骤2: 查询渠道数据
List<C> channelData = queryChannelData(finalCurrentDate, channel);
log.info("获取到渠道{}数据条数:{}", reconType.getDesc(), channelData.size());
// 步骤3: 比较数据差异
Map<String, Object> compareResult = compareData(platformData, channelData, finalCurrentDate,projectCO);
// 步骤4: 处理对账结果
processResult(compareResult, finalCurrentDate);
});
// 日期递增,继续下一天
currentDate = DateUtil.offsetDay(currentDate, 1).toJdkDate();
} catch (Exception e) {
log.error("执行{}日{}对账异常,渠道:{}",
DateUtil.format(currentDate, "yyyy-MM-dd"),
reconType.getDesc(),
channel.getDesc(), e);
// 处理对账异常
handleReconciliationException(currentDate,reconType,channel, e);
}
}
}
/**
* 处理渠道数据为空的情况
* 子类可以覆盖此方法实现自定义处理
*
* @param txDate 交易日期
* @param channel 渠道
*/
protected void handleChannelDataEmpty(Date txDate, ChannelTypeEnum channel) {
log.warn("渠道数据为空,无法完成对账");
}
/**
* 处理对账异常情况
* 子类可以覆盖此方法实现自定义异常处理
*
* @param txDate 交易日期
* @param channel 渠道
* @param e 异常
*/
protected void handleReconciliationException(Date txDate, ReconSubTypeEnum reconSubTypeEnum,ChannelTypeEnum channel, Exception e) {
log.error("{}日{}对账过程发生异常{}",txDate,reconSubTypeEnum.getDesc(),e.getMessage());
throw new BizException(DateUtil.format(txDate, "yyyy-MM-dd")+"日"+reconSubTypeEnum.getDesc()+"对账异常:"+e.getMessage());
}
}
待更新。。。。