From be45cfd2018b0960d1490c93f46fd749f3ca68d9 Mon Sep 17 00:00:00 2001 From: DaxPay Dev Date: Sun, 28 Jun 2026 11:44:57 +0800 Subject: [PATCH] =?UTF-8?q?perf(route):=20=E5=9C=BA=E6=99=AF=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E9=80=9A=E9=81=93=E5=95=86=E6=88=B7=E5=80=99=E9=80=89?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=8E=A5=E5=8F=A3=E9=A2=84=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E4=B8=8E=E4=BA=A7=E5=93=81=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit listSceneChannelMchCandidatesBatch 在循环外一次性预加载目录键集合、产品策略索引与产品能力映射,循环内改为纯内存求交,避免逐(目录项×通道商户)重复查库与扫描 Spring 容器;单 method 版同步复用上下文。 --- .../PayRouteStrategyCapabilitySupport.java | 79 +++++++++++++++++-- 1 file changed, 71 insertions(+), 8 deletions(-) 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 d1b7d38f3..00e5c9bc5 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 @@ -24,10 +24,13 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; /// # 通道路由:策略方式→通道商户与能力候选 /// @@ -45,16 +48,20 @@ public class PayRouteStrategyCapabilitySupport { private final ChannelMerchantManager channelMerchantManager; /// 通道路由白名单目录下全部 (provider,method) 的通道商户候选(单次请求聚合) + /// + /// 优化:目录、策略、产品能力在循环外一次性预加载,循环内仅做内存集合求交, + /// 避免逐 (目录项 × 通道商户) 重复查库与扫描 Spring 容器。 public Map> listSceneChannelMchCandidatesBatch(String mchNo) { - Map> index = new LinkedHashMap<>(); - // 通道商户是商户级数据,目录循环外一次性加载 + List entries = payProviderMethodService.listDirectoryEntries(); + RouteBatchContext ctx = buildRouteBatchContext(entries); List mchants = channelMerchantManager.findAllByMchNo(mchNo); - for (PayProviderMethodEntry entry : payProviderMethodService.listDirectoryEntries()) { + Map> index = new LinkedHashMap<>(); + for (PayProviderMethodEntry entry : entries) { if (!PayRouteConfigProviders.contains(entry.getProviderCode())) { continue; } String key = PayProviderMethodManager.pairKey(entry.getProviderCode(), entry.getMethodCode()); - index.put(key, filterChannelMchForDirectory(mchants, entry.getProviderCode(), entry.getMethodCode())); + index.put(key, filterChannelMchForDirectory(ctx, mchants, entry.getProviderCode(), entry.getMethodCode())); } return index; } @@ -86,18 +93,26 @@ public class PayRouteStrategyCapabilitySupport { if (!payProviderMethodService.contains(provider, method)) { return List.of(); } - return filterChannelMchForDirectory(channelMerchantManager.findAllByMchNo(mchNo), provider, method); + // 单次请求也复用批量上下文,避免内层逐商户查库 / 扫容器 + RouteBatchContext ctx = buildRouteBatchContext(payProviderMethodService.listDirectoryEntries()); + return filterChannelMchForDirectory(ctx, channelMerchantManager.findAllByMchNo(mchNo), provider, method); } - /// 从商户全部通道商户中筛出启用且其产品支持该(provider,method)的候选 - private List filterChannelMchForDirectory(List mchants, String provider, String method) { + /// 从商户全部通道商户中筛出启用且其产品支持该(provider,method)的候选(用预加载上下文,零查库) + private List filterChannelMchForDirectory( + RouteBatchContext ctx, List mchants, String provider, String method) { List results = new ArrayList<>(); + // 目录键与支付方式枚举预解析,避免逐商户重复计算 + if (!ctx.directoryPairKeys().contains(PayProviderMethodManager.pairKey(provider, method))) { + return results; + } + PayMethodEnum methodEnum = PayMethodEnum.findByCode(method); for (ChannelMerchant mch : mchants) { if (!Boolean.TRUE.equals(mch.getEnable())) { continue; } String product = mch.getProduct(); - if (!routeProductSupportsMethod(product, provider, method)) { + if (!routeProductSupportsMethod(ctx, product, methodEnum)) { continue; } String label = StrUtil.isNotBlank(mch.getChannelMerchantName()) @@ -210,6 +225,54 @@ public class PayRouteStrategyCapabilitySupport { return routeProductSupportsMethod(product, PayMethodEnum.findByCode(methodCode)); } + /// 批量判断上下文:循环外一次性预加载目录 / 策略 / 产品能力,循环内零查库 + private record RouteBatchContext( + // 目录有效 (provider,method) 对键集合(替代逐次 contains 重查目录) + Set directoryPairKeys, + // product → 策略实例(替代逐次 SpringUtil.getBeansOfType 扫描容器) + Map strategyByProduct, + // product → 已挂载且启用能力编码集合(替代逐次 listByProduct + findByCode) + Map> capabilityCodesByProduct) { + } + + /// 构建批量判断上下文:目录键集合 + 策略索引 + 产品能力映射 + private RouteBatchContext buildRouteBatchContext(List entries) { + Set pairKeys = new HashSet<>(); + for (PayProviderMethodEntry entry : entries) { + pairKeys.add(PayProviderMethodManager.pairKey(entry.getProviderCode(), entry.getMethodCode())); + } + // 一次性获取全部产品策略 bean,建立 product 索引(替代逐次 getBeansOfType 扫描容器) + Map strategyByProduct = new HashMap<>(); + for (AbsProductStrategy strategy : PaymentStrategyFactory.createGroup(AbsProductStrategy.class)) { + strategyByProduct.put(strategy.getProduct().getCode(), strategy); + } + // 预加载「产品 → 能力编码」(替代逐次 listByProduct + 逐能力 findByCode) + Map> capabilityCodesByProduct = payProductCapabilityService.loadCapabilityCodesByProduct(); + return new RouteBatchContext(pairKeys, strategyByProduct, capabilityCodesByProduct); + } + + /// 用预加载上下文判断产品是否支持目录支付方式(策略声明能力 ∩ DB 挂载启用),零查库 + private boolean routeProductSupportsMethod(RouteBatchContext ctx, String product, PayMethodEnum method) { + if (method == null || StrUtil.isBlank(product)) { + return false; + } + AbsProductStrategy strategy = ctx.strategyByProduct().get(product); + if (strategy == null) { + return false; + } + List declared = ProductStrategySupport.capabilitiesForMethod(strategy, method); + if (CollUtil.isEmpty(declared)) { + return false; + } + Set mounted = ctx.capabilityCodesByProduct().getOrDefault(product, Set.of()); + for (PayCapabilityEnum capability : declared) { + if (mounted.contains(capability.getCode())) { + return true; + } + } + return false; + } + /// 通道商户号→产品编码(不存在返回 null) private String productOfChannelMchNo(String channelMchNo) { return channelMerchantManager.lambdaQuery()