refactor(strategy): 策略无状态化, 显式PayContext传递替代实例字段

AbsPayStrategy/AbsPayCloseStrategy/AbsSyncPayOrderStrategy 去掉可变状态字段(trade/payParam/useCancel), 改为方法参数或PayContext显式传递; 新建PayContext承载payParam/trade/channelConfig, 配置由doBeforePay写入doPay读取; 策略全单例无状态, 消除并发线程安全隐患; 适配3个策略子类(AlipayDirect/AlipayIsv/Douyin)+新版调用方(NormalPayService/PayCloseService/PaySyncService)+old包调用方(PayService/PaySyncService/PayCloseService)。
This commit is contained in:
DaxPay Dev
2026-06-28 21:54:06 +08:00
parent 69c96c0579
commit 41f854681d
13 changed files with 86 additions and 85 deletions

View File

@@ -2,6 +2,7 @@ package cn.daxpay.open.channel.alipay.strategy.direct;
import cn.daxpay.open.channel.alipay.service.direct.AlipayDirectConfigAssembler;
import cn.daxpay.open.channel.alipay.service.pay.AlipayPayService;
import cn.daxpay.open.payment.common.context.PayContext;
import cn.daxpay.open.payment.pay.bo.PayTradeResultBo;
import cn.daxpay.open.payment.strategy.pay.AbsPayStrategy;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
@@ -28,8 +29,8 @@ public class AlipayDirectPayStrategy extends AbsPayStrategy {
}
@Override
public PayTradeResultBo doPayHandler() {
return alipayPayService.pay(getTrade(), getPayParam(),
alipayDirectConfigAssembler.buildConfig(getTrade().getMchNo()));
public PayTradeResultBo doPay(PayContext context) {
return alipayPayService.pay(context.getTrade(), context.getPayParam(),
alipayDirectConfigAssembler.buildConfig(context.getTrade().getMchNo()));
}
}

View File

@@ -7,6 +7,7 @@ import cn.daxpay.open.channel.alipay.entity.isv.AlipayIsvAppKeyConfig;
import cn.daxpay.open.channel.alipay.entity.isv.AlipayIsvChannelMerchant;
import cn.daxpay.open.channel.alipay.service.isv.AlipayIsvAppKeyConfigService;
import cn.daxpay.open.channel.alipay.service.pay.AlipayPayService;
import cn.daxpay.open.payment.common.context.PayContext;
import cn.daxpay.open.payment.pay.bo.PayTradeResultBo;
import cn.daxpay.open.payment.strategy.pay.AbsPayStrategy;
import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum;
@@ -40,17 +41,18 @@ public class AlipayIsvPayStrategy extends AbsPayStrategy {
}
@Override
public void doBeforePayHandler() {
mapMethod(getPayParam().getMethod());
public void doBeforePay(PayContext context) {
mapMethod(context.getPayParam().getMethod());
// 前置阶段构建配置,存入上下文供 doPay 使用
context.setChannelConfig(buildConfig(context.getTrade().getMchNo()));
}
@Override
public PayTradeResultBo doPayHandler() {
return alipayPayService.pay(getTrade(), getPayParam(), buildConfig());
public PayTradeResultBo doPay(PayContext context) {
return alipayPayService.pay(context.getTrade(), context.getPayParam(), context.getChannelConfig());
}
private Map<String, Object> buildConfig() {
String mchNo = getTrade().getMchNo();
private Map<String, Object> buildConfig(String mchNo) {
AlipayIsvChannelMerchant isvMerchant = alipayIsvChannelMerchantManager.findByMchNo(mchNo)
.orElseThrow(() -> new DataNotExistException("error.channel.alipay.mchAppNotFound"));
AlipayIsvApp isvApp = alipayIsvAppManager.findById(isvMerchant.getIsvAppId())

View File

@@ -3,6 +3,7 @@ package cn.daxpay.open.channel.douyin.strategy;
import cn.daxpay.open.channel.douyin.client.DouyinChannelClient;
import cn.daxpay.open.channel.douyin.dto.DouyinPayReq;
import cn.daxpay.open.channel.douyin.dto.DouyinPayResp;
import cn.daxpay.open.payment.common.context.PayContext;
import cn.daxpay.open.payment.common.result.DaxResult;
import cn.daxpay.open.payment.pay.bo.PayTradeResultBo;
import cn.daxpay.open.payment.strategy.pay.AbsPayStrategy;
@@ -33,14 +34,14 @@ public class DouyinDirectPayStrategy extends AbsPayStrategy {
}
@Override
public PayTradeResultBo doPayHandler() {
public PayTradeResultBo doPay(PayContext context) {
// 构建请求
DouyinPayReq req = new DouyinPayReq();
req.setChannel(ProductEnum.DOUYIN_PAY.getChannel());
req.setBizOrderNo(getPayParam().getBizOrderNo());
req.setAmount(getPayParam().getAmount().multiply(HUNDRED).longValue());
req.setSubject(getPayParam().getTitle());
req.setMethod(getPayParam().getMethod());
req.setBizOrderNo(context.getPayParam().getBizOrderNo());
req.setAmount(context.getPayParam().getAmount().multiply(HUNDRED).longValue());
req.setSubject(context.getPayParam().getTitle());
req.setMethod(context.getPayParam().getMethod());
// TODO 从数据库获取抖音通道配置
req.setConfig(Map.of());

View File

@@ -0,0 +1,38 @@
package cn.daxpay.open.payment.common.context;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import cn.daxpay.open.payment.unipay.param.trade.pay.PayParam;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/// # 支付策略执行上下文(请求级,每次支付创建新实例)
///
/// 显式传递,替代原 AbsPayStrategy 实例字段。通道配置由 doBeforePay 写入、doPay 读取。
@Getter
@Setter
@Accessors(chain = true)
public class PayContext {
/// 支付参数
private PayParam payParam;
/// 资金交易凭证
private PayTrade trade;
/// 通道配置doBeforePay 写入doPay 读取;类型各通道不同)
private Object channelConfig;
public PayContext() {
}
public PayContext(PayParam payParam) {
this.payParam = payParam;
}
/// 读取通道配置(各通道策略 cast 自己的配置类型)
@SuppressWarnings("unchecked")
public <T> T getChannelConfig() {
return (T) channelConfig;
}
}

View File

@@ -94,11 +94,10 @@ public class PayCloseService {
trade.setProduct(payOrder.getProduct());
trade.setChannel(payOrder.getChannel());
trade.setMethod(payOrder.getMethod());
strategy.init(trade, useCancel);
// 关闭前准备
strategy.doBeforeCloseHandler();
strategy.doBeforeClose(trade);
// 执行关闭策略, 返回关闭的方式
closeType = strategy.doCloseHandler();
closeType = strategy.doClose(trade, useCancel);
// 成功处理 关闭或撤销订单
payStatusEnum = useCancel ? PayStatusEnum.CANCEL : PayStatusEnum.CLOSE;
}

View File

@@ -6,6 +6,7 @@ import cn.daxpay.open.platform.core.exception.system.DataErrorException;
import cn.daxpay.open.platform.core.exception.PayFailureException;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.payment.common.context.PayContext;
import cn.daxpay.open.payment.common.util.PaymentStrategyFactory;
import cn.daxpay.open.payment.old.pay.bo.trade.PayResultBo;
import cn.daxpay.open.payment.old.pay.dao.order.pay.PayOrderExpandManager;
@@ -78,8 +79,7 @@ public class PayService {
public PayResult payHandle(PayParam payParam, PayOrder payOrder) {
// 获取支付策略类
var payStrategy = PaymentStrategyFactory.createByProduct(payParam.getProduct(), AbsPayStrategy.class);
// 初始化支付的参数
payStrategy.setPayParam(payParam);
PayContext context = new PayContext(payParam);
// 检测支付能力是否支持(产品已挂载能力覆盖该支付方式)
var methodEnum = PayMethodEnum.findByCode(payParam.getMethod());
if (!SpringUtil.getBean(PayProductCapabilityService.class).productSupportsMethod(
@@ -88,7 +88,7 @@ public class PayService {
I18nUtil.getEnumName(methodEnum) + "(" + methodEnum.getCode() + ")");
}
// 执行支付前处理动作, 进行各种校验, 校验通过才会进行下面的操作
payStrategy.doBeforePayHandler();
payStrategy.doBeforePay(context);
// 订单不存在执行支付前的保存动作, 保存支付订单默认状态为支付中
if (Objects.isNull(payOrder)){
payOrder = payAssistService.createPayOrder(payParam);
@@ -110,11 +110,11 @@ public class PayService {
trade.setChannel(payOrder.getChannel());
trade.setMethod(payOrder.getMethod());
trade.setAmount(payOrder.getAmount().multiply(java.math.BigDecimal.valueOf(100)).longValue());
payStrategy.setTrade(trade);
context.setTrade(trade);
PayResultBo result;
try {
// 支付操作
var newResult = payStrategy.doPayHandler();
var newResult = payStrategy.doPay(context);
result = new PayResultBo();
result.setOutOrderNo(newResult.getOutOrderNo());
result.setComplete(newResult.isComplete());

View File

@@ -103,10 +103,9 @@ public class PaySyncService {
trade.setChannel(payOrder.getChannel());
trade.setMethod(payOrder.getMethod());
trade.setAmount(payOrder.getAmount().multiply(BigDecimal.valueOf(100)).longValue());
syncPayStrategy.setTrade(trade);
try {
// 执行操作, 获取支付网关同步的结果
var newResult = syncPayStrategy.doSync();
var newResult = syncPayStrategy.doSync(trade);
PaySyncResultBo syncResult = new PaySyncResultBo();
syncResult.setSyncSuccess(newResult.isSyncSuccess());
if (newResult.getPayStatus() != null) {
@@ -253,10 +252,9 @@ public class PaySyncService {
closeTrade.setProduct(order.getProduct());
closeTrade.setChannel(order.getChannel());
closeTrade.setMethod(order.getMethod());
strategy.setTrade(closeTrade);
strategy.doBeforeCloseHandler();
strategy.doBeforeClose(closeTrade);
// 执行策略的关闭方法
strategy.doCloseHandler();
strategy.doClose(closeTrade, false);
// 关闭统一处理
tradeUniHandleService.payClose(order, PayStatusEnum.CLOSE);
}

View File

@@ -4,6 +4,7 @@ import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.daxpay.open.platform.core.exception.PayFailureException;
import cn.daxpay.open.payment.common.enums.PayFundStatusEnum;
import cn.daxpay.open.payment.common.context.PayContext;
import cn.daxpay.open.payment.common.util.PaymentStrategyFactory;
import cn.daxpay.open.payment.pay.bo.PayTradeResultBo;
import cn.daxpay.open.payment.pay.order.dao.PayTradeManager;
@@ -59,7 +60,6 @@ public class NormalPayService {
// 路由解析:直定模式(已传 channelMchNo)直接解析,否则按 appId+method 策略匹配
payRouteFacade.resolve(payParam);
var payStrategy = PaymentStrategyFactory.createByProduct(payParam.getProduct(), AbsPayStrategy.class);
payStrategy.setPayParam(payParam);
// 订单不存在,新建支付订单
if (Objects.isNull(trade)) {
trade = payAssistService.createOrder(payParam);
@@ -69,12 +69,12 @@ public class NormalPayService {
return payAssistService.buildResult(trade);
}
}
payStrategy.setTrade(trade);
payStrategy.initPayParam(trade, payParam);
payStrategy.doBeforePayHandler();
// 显式传递上下文(替代原策略实例字段)
PayContext context = new PayContext(payParam).setTrade(trade);
payStrategy.doBeforePay(context);
PayTradeResultBo result;
try {
result = payStrategy.doPayHandler();
result = payStrategy.doPay(context);
} catch (Exception e) {
log.error("支付出现异常", e);
trade.setStatus(PayFundStatusEnum.FAIL.getCode());

View File

@@ -76,10 +76,8 @@ public class PayCloseService {
} else {
AbsPayCloseStrategy strategy = PaymentStrategyFactory.createByProduct(
trade.getProduct(), AbsPayCloseStrategy.class);
strategy.setTrade(trade);
strategy.init(trade, useCancel);
strategy.doBeforeCloseHandler();
strategy.doCloseHandler();
strategy.doBeforeClose(trade);
strategy.doClose(trade, useCancel);
payUniHandleService.payClose(trade, normalOrder);
}
} catch (Exception e) {

View File

@@ -87,8 +87,7 @@ public class PaySyncService {
.orElse(null);
var syncStrategy = PaymentStrategyFactory.createByProduct(
trade.getProduct(), AbsSyncPayOrderStrategy.class);
syncStrategy.setTrade(trade);
PaySyncResultBo syncResult = syncStrategy.doSync();
PaySyncResultBo syncResult = syncStrategy.doSync(trade);
if (!Objects.equals(syncResult.getOutOrderNo(), trade.getOutOrderNo())) {
trade.setOutOrderNo(syncResult.getOutOrderNo());
payTradeManager.updateById(trade);

View File

@@ -3,32 +3,20 @@ package cn.daxpay.open.payment.strategy.pay;
import cn.daxpay.open.payment.common.strategy.PaymentStrategy;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import lombok.Getter;
import lombok.Setter;
/// # 支付关闭策略
///
@Getter
@Setter
/// 策略为单例无状态,运行时数据通过方法参数显式传递。
public abstract class AbsPayCloseStrategy implements PaymentStrategy {
/// 资金交易凭证
private PayTrade trade = null;
/// 是否使用撤销方式进行订单关闭
private boolean useCancel = false;
public void init(PayTrade trade, boolean useCancel){
this.trade = trade;
this.useCancel = useCancel;
/// 关闭前的处理方式
public void doBeforeClose(PayTrade trade) {
}
/// 关闭前的处理方式
public void doBeforeCloseHandler() {}
/// 关闭操作
/// @param trade 资金交易凭证
/// @param useCancel 是否使用撤销方式进行订单关闭
/// @return 如果执行成功, 返回使用何种方式关闭的支付订单
public abstract CloseTypeEnum doCloseHandler();
public abstract CloseTypeEnum doClose(PayTrade trade, boolean useCancel);
}

View File

@@ -1,38 +1,21 @@
package cn.daxpay.open.payment.strategy.pay;
import cn.daxpay.open.payment.common.context.PayContext;
import cn.daxpay.open.payment.common.strategy.PaymentStrategy;
import cn.daxpay.open.payment.pay.bo.PayTradeResultBo;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import cn.daxpay.open.payment.unipay.param.trade.pay.PayParam;
import lombok.Getter;
import lombok.Setter;
/// # 抽象支付策略基类
///
@Getter
@Setter
/// 策略为单例无状态,运行时数据通过 [PayContext] 显式传递。
public abstract class AbsPayStrategy implements PaymentStrategy {
/// 资金交易凭证
private PayTrade trade = null;
/// 支付参数
private PayParam payParam = null;
/// 初始化支付的参数
public void initPayParam(PayTrade trade, PayParam payParam) {
this.trade = trade;
this.payParam = payParam;
}
/// 支付前处理 包含必要的校验以及对当前通道支付配置信息的初始化
/// 出现错误不会保存相关信息
public void doBeforePayHandler(){
public void doBeforePay(PayContext context) {
}
/// 支付操作
/// 出现错误会保存相关信息
public abstract PayTradeResultBo doPayHandler();
public abstract PayTradeResultBo doPay(PayContext context);
}

View File

@@ -3,19 +3,13 @@ package cn.daxpay.open.payment.strategy.sync;
import cn.daxpay.open.payment.common.strategy.PaymentStrategy;
import cn.daxpay.open.payment.pay.bo.PaySyncResultBo;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import lombok.Getter;
import lombok.Setter;
/// # 支付同步抽象类
///
@Getter
@Setter
/// 策略为单例无状态,运行时数据通过方法参数显式传递。
public abstract class AbsSyncPayOrderStrategy implements PaymentStrategy {
/// 资金交易凭证
private PayTrade trade = null;
/// 查询通道网关方的退款订单状态信息
public abstract PaySyncResultBo doSync();
public abstract PaySyncResultBo doSync(PayTrade trade);
}