diff --git a/_config/sql/update-tables.sql b/_config/sql/update-tables.sql index e69de29bb..909668007 100644 --- a/_config/sql/update-tables.sql +++ b/_config/sql/update-tables.sql @@ -0,0 +1,3 @@ +-- 支付通道路由策略表:移除开源版不需要的 enable/name 字段 +ALTER TABLE pay_route_strategy DROP COLUMN IF EXISTS enable; +ALTER TABLE pay_route_strategy DROP COLUMN IF EXISTS name; diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/convert/route/strategy/PayRouteStrategyConvert.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/convert/route/strategy/PayRouteStrategyConvert.java index b6074fcb3..28331ce66 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/convert/route/strategy/PayRouteStrategyConvert.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/convert/route/strategy/PayRouteStrategyConvert.java @@ -1,7 +1,5 @@ package cn.daxpay.open.payment.merchant.convert.route.strategy; -import cn.daxpay.open.payment.merchant.entity.route.strategy.PayRouteStrategy; -import cn.daxpay.open.payment.merchant.result.route.strategy.PayRouteStrategyResult; import cn.daxpay.open.payment.merchant.entity.route.strategy.PayRouteStrategy; import cn.daxpay.open.payment.merchant.param.route.strategy.PayRouteStrategyParam; import cn.daxpay.open.payment.merchant.result.route.strategy.PayRouteStrategyResult; diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/entity/route/strategy/PayRouteStrategy.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/entity/route/strategy/PayRouteStrategy.java index a47285780..1d89435d2 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/entity/route/strategy/PayRouteStrategy.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/entity/route/strategy/PayRouteStrategy.java @@ -29,12 +29,6 @@ public class PayRouteStrategy extends MchBaseEntity implements ToResult> listSceneCapabilityCandidatesBatch(PayRouteSceneCapabilityBatchParam param) { - String mchNo = mchAppInfoManager.requireMchNoByAppIdNotTenant(param.getAppId()); - return payRouteStrategyCapabilitySupport.listSceneCapabilityCandidatesBatch(mchNo, param.getItems()); + mchAppInfoManager.requireMchNoByAppIdNotTenant(param.getAppId()); + return payRouteStrategyCapabilitySupport.listSceneCapabilityCandidatesBatch(param.getItems()); } /// 按目录项(支付渠道+支付方式)筛选商户已开通的通道商户候选 diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/support/PayRouteStrategyCapabilitySupport.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/support/PayRouteStrategyCapabilitySupport.java index 00e5c9bc5..448a536df 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/support/PayRouteStrategyCapabilitySupport.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/support/PayRouteStrategyCapabilitySupport.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; /// # 通道路由:策略方式→通道商户与能力候选 /// @@ -67,22 +68,64 @@ public class PayRouteStrategyCapabilitySupport { } /// 按目录项+通道商户批量返回支付能力候选 - public Map> listSceneCapabilityCandidatesBatch( - String mchNo, List items) { + /// + /// 优化:目录、策略、产品能力及 channelMchNo→product 映射在循环外一次性预加载, + /// 循环内仅做内存集合求交,避免逐 item 重复查库与扫描 Spring 容器。 + public Map> listSceneCapabilityCandidatesBatch(List items) { Map> index = new LinkedHashMap<>(); if (CollUtil.isEmpty(items)) { return index; } + RouteBatchContext ctx = buildRouteBatchContext(payProviderMethodService.listDirectoryEntries()); + // 批量预加载 channelMchNo → product,替代逐次 productOfChannelMchNo 查库 + Set channelMchNos = new HashSet<>(); + for (PayRouteSceneCapabilityBatchItem item : items) { + if (item != null && StrUtil.isNotBlank(item.getChannelMchNo())) { + channelMchNos.add(item.getChannelMchNo()); + } + } + Map productByChannelMchNo = channelMerchantManager.lambdaQuery() + .select(ChannelMerchant::getChannelMchNo, ChannelMerchant::getProduct) + .in(ChannelMerchant::getChannelMchNo, channelMchNos) + .list() + .stream() + .collect(Collectors.toMap(ChannelMerchant::getChannelMchNo, ChannelMerchant::getProduct, (a, b) -> a)); for (PayRouteSceneCapabilityBatchItem item : items) { if (item == null || StrUtil.hasBlank(item.getProvider(), item.getMethod(), item.getChannelMchNo())) { continue; } String key = capabilityBatchKey(item.getProvider(), item.getMethod(), item.getChannelMchNo()); - index.put(key, listSceneCapabilityCandidates(item.getProvider(), item.getMethod(), item.getChannelMchNo())); + if (!ctx.directoryPairKeys().contains(PayProviderMethodManager.pairKey(item.getProvider(), item.getMethod()))) { + index.put(key, List.of()); + continue; + } + String product = productByChannelMchNo.get(item.getChannelMchNo()); + if (StrUtil.isBlank(product)) { + index.put(key, List.of()); + continue; + } + index.put(key, capabilitiesForMethodWithCtx(ctx, product, PayMethodEnum.findByCode(item.getMethod()))); } return index; } + /// 用预加载上下文计算支付能力候选(策略声明能力 ∩ DB 挂载启用),零查库 + private List capabilitiesForMethodWithCtx(RouteBatchContext ctx, String product, PayMethodEnum method) { + AbsProductStrategy strategy = ctx.strategyByProduct().get(product); + if (strategy == null) { + return List.of(); + } + List declared = ProductStrategySupport.capabilitiesForMethod(strategy, method); + if (CollUtil.isEmpty(declared)) { + return List.of(); + } + Set mounted = ctx.capabilityCodesByProduct().getOrDefault(product, Set.of()); + return declared.stream() + .filter(capability -> mounted.contains(capability.getCode())) + .map(capability -> new LabelValue(I18nUtil.getEnumName(capability), capability.getCode())) + .toList(); + } + /// 能力批量候选 Map 的 key:provider|method|channelMchNo public static String capabilityBatchKey(String provider, String method, String channelMchNo) { return provider + "|" + method + "|" + channelMchNo; @@ -206,7 +249,7 @@ public class PayRouteStrategyCapabilitySupport { } /// 通道路由:产品是否支持目录支付方式(策略 Map ∧ DB) - public boolean routeProductSupportsMethod(String product, PayMethodEnum method) { + private boolean routeProductSupportsMethod(String product, PayMethodEnum method) { if (!PaymentStrategyFactory.existsByProduct(product, AbsProductStrategy.class)) { return false; } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/pay/route/error.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/pay/route/error.json index f9feda313..67d6d3885 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/pay/route/error.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/pay/route/error.json @@ -1,37 +1,18 @@ { "strategyNotFound": "Payment route strategy not configured for this app", - "strategyDisabled": "Payment route strategy is disabled", "noMatch": "No available payment product matched", "providerRequired": "Pay brand is required when product is not specified", "methodRequiredWhenProductSet": "Pay method is required when product is specified", "basicProviderInvalid": "Invalid pay brand", "basicProductNotAvailable": "No available product for pay brand [{0}]; check merchant product, channel merchant and app channel config", - "basicProductNotConfigured": "No payment product configured for pay brand [{0}]; configure it in basic route mode", - "basicProductAmbiguous": "Multiple available products for pay brand [{0}]; specify product explicitly", - "methodNotSupportedForProduct": "Product \"{0}\" has no available payment methods", - "duplicateSceneConfig": "Duplicate pay brand configuration in scene mode", - "duplicatePayProvider": "Duplicate configuration for pay brand [{0}]", - "duplicatePayProviderMethod": "Duplicate configuration for pay brand [{0}] and method [{1}]", - "sceneDirectoryIncomplete": "Payment product is required for pay brand [{0}] and method [{1}]", "scenePayProviderRequiredForMethod": "Pay brand is required for method [{0}]", - "sceneGenericMethodNoPayProvider": "Generic pay method [{0}] cannot be bound to a pay brand", - "sceneMethodPayProviderMismatch": "Pay method [{0}] does not match pay brand [{1}]", - "sceneProductRequired": "Payment product is required in scene routing mode", "sceneMethodRequired": "Pay method is required in scene routing mode", "sceneMethodProductMismatch": "Pay method [{0}] is not supported by product [{1}]", - "sceneProductCapabilityPair": "For pay brand [{0}] and method [{1}], select both payment product and capability, or leave both empty", "sceneCapabilityRequired": "Payment capability is required in scene routing mode", "sceneCapabilityProductMismatch": "Payment capability [{0}] does not match product [{1}] and method [{2}]", - "sceneChannelMethodRequired": "Generic pay route requires channel and pay method", - "providerRequiredForScene": "Pay brand is required in scene routing mode", - "productNotResolved": "Cannot resolve product for channel [{0}] and method [{1}]", - "productMismatch": "Specified product [{0}] does not match resolved [{1}]", - "productInvalid": "Invalid payment product [{0}]", "productStrategyMissing": "Payment product strategy not found", "routeStrategyNotExist": "Route strategy not found", "routeModeNotExist": "Route mode not found: {0}", - "businessSceneNotExist": "Business scene not found: {0}", - "terminalSceneNotExist": "Terminal scene not found: {0}", "channelMchNoRequired": "Channel merchant no cannot be blank", "channelMchNotExist": "Channel merchant [{0}] not found", "channelMchDisabled": "Channel merchant [{0}] is disabled", diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/pay/route/error.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/pay/route/error.json index f043445a8..6fcf92530 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/pay/route/error.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/pay/route/error.json @@ -1,37 +1,18 @@ { "strategyNotFound": "应用未配置通道路由策略", - "strategyDisabled": "通道路由策略已禁用", "noMatch": "未匹配到可用支付产品", "providerRequired": "未指定支付产品时,支付渠道不能为空", "methodRequiredWhenProductSet": "指定支付产品时,支付方式不能为空", "basicProviderInvalid": "支付渠道无效", "basicProductNotAvailable": "支付渠道[{0}]下无可用支付产品,请检查商户产品、通道商户及应用通道配置", - "basicProductNotConfigured": "支付渠道[{0}]未配置支付产品,请在通道路由基础模式中完成配置", - "basicProductAmbiguous": "支付渠道[{0}]下存在多个可用支付产品,请指定 product 参数", - "methodNotSupportedForProduct": "产品「{0}」无可用支付方式", - "duplicateSceneConfig": "场景模式下同一支付渠道存在多条配置", - "duplicatePayProvider": "支付渠道[{0}]配置重复", - "duplicatePayProviderMethod": "支付渠道[{0}]下支付方式[{1}]配置重复", - "sceneDirectoryIncomplete": "须为支付渠道[{0}]的支付方式[{1}]配置支付产品", "scenePayProviderRequiredForMethod": "非通用支付方式[{0}]须指定支付渠道", - "sceneGenericMethodNoPayProvider": "通用支付方式[{0}]不能绑定支付渠道", - "sceneMethodPayProviderMismatch": "支付方式[{0}]与支付渠道[{1}]不匹配", - "sceneProductRequired": "场景模式下须选择支付产品", "sceneMethodRequired": "场景模式下须选择支付方式", "sceneMethodProductMismatch": "支付方式[{0}]与产品[{1}]不匹配", - "sceneProductCapabilityPair": "支付渠道[{0}]支付方式[{1}]须同时选择支付产品与支付能力,或同时留空", "sceneCapabilityRequired": "场景模式下须选择支付能力", "sceneCapabilityProductMismatch": "支付能力[{0}]与产品[{1}]、支付方式[{2}]不匹配", - "sceneChannelMethodRequired": "通用支付配置须指定通道与支付方式", - "providerRequiredForScene": "场景模式下,支付渠道不能为空", - "productNotResolved": "通道[{0}]与支付方式[{1}]无法解析支付产品", - "productMismatch": "指定产品[{0}]与解析结果[{1}]不一致", - "productInvalid": "支付产品[{0}]无效", "productStrategyMissing": "支付产品策略不存在", "routeStrategyNotExist": "路由策略不存在", "routeModeNotExist": "路由模式不存在: {0}", - "businessSceneNotExist": "业务场景不存在: {0}", - "terminalSceneNotExist": "终端场景不存在: {0}", "channelMchNoRequired": "通道商户号不能为空", "channelMchNotExist": "通道商户号[{0}]不存在", "channelMchDisabled": "通道商户号[{0}]未启用",