perf(route): 场景模式通道商户候选批量接口预加载目录与产品能力

listSceneChannelMchCandidatesBatch 在循环外一次性预加载目录键集合、产品策略索引与产品能力映射,循环内改为纯内存求交,避免逐(目录项×通道商户)重复查库与扫描 Spring 容器;单 method 版同步复用上下文。
This commit is contained in:
DaxPay Dev
2026-06-28 11:44:57 +08:00
parent 8edf3bcbb2
commit be45cfd201

View File

@@ -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<String, List<LabelValue>> listSceneChannelMchCandidatesBatch(String mchNo) {
Map<String, List<LabelValue>> index = new LinkedHashMap<>();
// 通道商户是商户级数据,目录循环外一次性加载
List<PayProviderMethodEntry> entries = payProviderMethodService.listDirectoryEntries();
RouteBatchContext ctx = buildRouteBatchContext(entries);
List<ChannelMerchant> mchants = channelMerchantManager.findAllByMchNo(mchNo);
for (PayProviderMethodEntry entry : payProviderMethodService.listDirectoryEntries()) {
Map<String, List<LabelValue>> 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<LabelValue> filterChannelMchForDirectory(List<ChannelMerchant> mchants, String provider, String method) {
/// 从商户全部通道商户中筛出启用且其产品支持该(provider,method)的候选(用预加载上下文,零查库)
private List<LabelValue> filterChannelMchForDirectory(
RouteBatchContext ctx, List<ChannelMerchant> mchants, String provider, String method) {
List<LabelValue> 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<String> directoryPairKeys,
// product → 策略实例(替代逐次 SpringUtil.getBeansOfType 扫描容器)
Map<String, AbsProductStrategy> strategyByProduct,
// product → 已挂载且启用能力编码集合(替代逐次 listByProduct + findByCode)
Map<String, Set<String>> capabilityCodesByProduct) {
}
/// 构建批量判断上下文:目录键集合 + 策略索引 + 产品能力映射
private RouteBatchContext buildRouteBatchContext(List<PayProviderMethodEntry> entries) {
Set<String> pairKeys = new HashSet<>();
for (PayProviderMethodEntry entry : entries) {
pairKeys.add(PayProviderMethodManager.pairKey(entry.getProviderCode(), entry.getMethodCode()));
}
// 一次性获取全部产品策略 bean建立 product 索引(替代逐次 getBeansOfType 扫描容器)
Map<String, AbsProductStrategy> strategyByProduct = new HashMap<>();
for (AbsProductStrategy strategy : PaymentStrategyFactory.createGroup(AbsProductStrategy.class)) {
strategyByProduct.put(strategy.getProduct().getCode(), strategy);
}
// 预加载「产品 → 能力编码」(替代逐次 listByProduct + 逐能力 findByCode)
Map<String, Set<String>> 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<PayCapabilityEnum> declared = ProductStrategySupport.capabilitiesForMethod(strategy, method);
if (CollUtil.isEmpty(declared)) {
return false;
}
Set<String> 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()