From ea9db98e136bb159771462cda3ee0f3565b9266c Mon Sep 17 00:00:00 2001 From: DaxPay Dev Date: Fri, 10 Jul 2026 18:12:11 +0800 Subject: [PATCH] =?UTF-8?q?refactor(tenant):=20=E8=BF=90=E8=A1=8C=E6=80=81?= =?UTF-8?q?/=E9=85=8D=E7=BD=AE=E6=80=81=E6=8B=86=E5=88=86=20IgnoreTenant?= =?UTF-8?q?=EF=BC=8C=E5=9B=9E=E8=B0=83=20path=20=E5=86=99=E5=85=A5=20mchNo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 CallbackMchContextFilter:/unipay/callback/{mchNo}/... 解析后走正常租户过滤 - Access 引导读统一 *NotTenant;initMch 后业务读(公钥等)走租户内 - 管理端路由/凭证配置去掉滥用的 NotTenant - 通道认证能力/授权配置补 *NotTenant 读路径;AuthStrategy 走 resolveAppNotTenant --- .../AlipayDirectAppCapabilityManager.java | 9 ++- .../direct/AlipayDirectConfigAssembler.java | 3 +- .../DouyinDirectAppCapabilityManager.java | 9 ++- .../WechatDirectAppAuthConfigManager.java | 9 +++ .../WechatDirectAppCapabilityManager.java | 9 ++- .../isv/WechatIsvChannelMerchantManager.java | 9 ++- .../isv/WechatIsvMchAppAuthConfigManager.java | 9 +++ .../isv/WechatIsvMchAppCapabilityManager.java | 9 ++- .../direct/WechatDirectConfigAssembler.java | 2 +- .../service/isv/WechatIsvConfigAssembler.java | 3 +- .../direct/WechatDirectAuthStrategy.java | 2 +- .../strategy/isv/WechatIsvAuthStrategy.java | 2 +- .../runtime/CallbackMchContextFilter.java | 67 +++++++++++++++++++ .../common/service/MerchantContextLoader.java | 2 + .../dao/appinfo/MchAppInfoManager.java | 20 ++++-- .../config/MerchantCredentialService.java | 8 +-- .../query/MerchantAccessQueryService.java | 26 ++++--- .../basic/PayRouteBasicConfigService.java | 4 +- .../route/facade/PayRouteConfigService.java | 7 +- .../scene/PayRouteSceneConfigService.java | 8 +-- 20 files changed, 181 insertions(+), 36 deletions(-) create mode 100644 daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/common/runtime/CallbackMchContextFilter.java diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dao/direct/AlipayDirectAppCapabilityManager.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dao/direct/AlipayDirectAppCapabilityManager.java index f61a34360..8fb8aec4e 100644 --- a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dao/direct/AlipayDirectAppCapabilityManager.java +++ b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dao/direct/AlipayDirectAppCapabilityManager.java @@ -2,6 +2,7 @@ package cn.daxpay.open.channel.alipay.dao.direct; import cn.daxpay.open.channel.alipay.entity.direct.AlipayDirectAppCapability; import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager; +import cn.daxpay.open.platform.core.annotation.IgnoreTenant; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -27,7 +28,7 @@ public class AlipayDirectAppCapabilityManager extends BaseManager findOne(String channelMchNo, String capability) { return lambdaQuery() .eq(AlipayDirectAppCapability::getChannelMchNo, channelMchNo) @@ -35,6 +36,12 @@ public class AlipayDirectAppCapabilityManager extends BaseManager findOneNotTenant(String channelMchNo, String capability) { + return findOne(channelMchNo, capability); + } + /// 根据通道商户号删除全部关联(批量保存时先清后插) public void deleteByChannelMchNo(String channelMchNo) { lambdaUpdate() diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/direct/AlipayDirectConfigAssembler.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/direct/AlipayDirectConfigAssembler.java index b68427af3..654dc22a2 100644 --- a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/direct/AlipayDirectConfigAssembler.java +++ b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/direct/AlipayDirectConfigAssembler.java @@ -73,7 +73,8 @@ public class AlipayDirectConfigAssembler { return credential; } - /// 解析支付使用的应用,优先级:能力关联 > 通道商户首个 > 商户号首个兜底 + /// 解析支付使用的应用(须已装载 mchNo,租户内) + /// 优先级:能力关联 > 通道商户首个 > 商户号首个兜底 private AlipayDirectApp resolveApp(String mchNo, String channelMchNo, String capability) { // 1. 能力关联解析(显式配置 > appType自动推导) Optional resolved = alipayDirectAppCapabilityService.resolveApp(channelMchNo, capability); diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/dao/direct/DouyinDirectAppCapabilityManager.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/dao/direct/DouyinDirectAppCapabilityManager.java index ed7525033..a4280b437 100644 --- a/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/dao/direct/DouyinDirectAppCapabilityManager.java +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/dao/direct/DouyinDirectAppCapabilityManager.java @@ -2,6 +2,7 @@ package cn.daxpay.open.channel.douyin.dao.direct; import cn.daxpay.open.channel.douyin.entity.direct.DouyinDirectAppCapability; import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager; +import cn.daxpay.open.platform.core.annotation.IgnoreTenant; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -27,7 +28,7 @@ public class DouyinDirectAppCapabilityManager extends BaseManager findOne(String channelMchNo, String capability) { return lambdaQuery() .eq(DouyinDirectAppCapability::getChannelMchNo, channelMchNo) @@ -35,6 +36,12 @@ public class DouyinDirectAppCapabilityManager extends BaseManager findOneNotTenant(String channelMchNo, String capability) { + return findOne(channelMchNo, capability); + } + /// 根据通道商户号删除全部关联(批量保存时先清后插) public void deleteByChannelMchNo(String channelMchNo) { lambdaUpdate() diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/direct/WechatDirectAppAuthConfigManager.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/direct/WechatDirectAppAuthConfigManager.java index e760be5f2..2662880ea 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/direct/WechatDirectAppAuthConfigManager.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/direct/WechatDirectAppAuthConfigManager.java @@ -2,6 +2,7 @@ package cn.daxpay.open.channel.wechat.dao.direct; import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectAppAuthConfig; import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager; +import cn.daxpay.open.platform.core.annotation.IgnoreTenant; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -20,6 +21,14 @@ public class WechatDirectAppAuthConfigManager extends BaseManager findByWechatDirectAppIdNotTenant(Long wechatDirectAppId) { + return lambdaQuery() + .eq(WechatDirectAppAuthConfig::getWechatDirectAppId, wechatDirectAppId) + .oneOpt(); + } + /// 根据应用ID删除授权认证配置 public void deleteByWechatDirectAppId(Long wechatDirectAppId) { lambdaUpdate() diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/direct/WechatDirectAppCapabilityManager.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/direct/WechatDirectAppCapabilityManager.java index 7876febab..4af147aa4 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/direct/WechatDirectAppCapabilityManager.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/direct/WechatDirectAppCapabilityManager.java @@ -2,6 +2,7 @@ package cn.daxpay.open.channel.wechat.dao.direct; import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectAppCapability; import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager; +import cn.daxpay.open.platform.core.annotation.IgnoreTenant; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -27,7 +28,7 @@ public class WechatDirectAppCapabilityManager extends BaseManager findOne(String channelMchNo, String capability) { return lambdaQuery() .eq(WechatDirectAppCapability::getChannelMchNo, channelMchNo) @@ -35,6 +36,12 @@ public class WechatDirectAppCapabilityManager extends BaseManager findOneNotTenant(String channelMchNo, String capability) { + return findOne(channelMchNo, capability); + } + /// 根据通道商户号删除全部关联(批量保存时先清后插) public void deleteByChannelMchNo(String channelMchNo) { lambdaUpdate() diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvChannelMerchantManager.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvChannelMerchantManager.java index a724f93ef..b22e77cef 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvChannelMerchantManager.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvChannelMerchantManager.java @@ -2,6 +2,7 @@ package cn.daxpay.open.channel.wechat.dao.isv; import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvChannelMerchant; import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager; +import cn.daxpay.open.platform.core.annotation.IgnoreTenant; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -19,10 +20,16 @@ public class WechatIsvChannelMerchantManager extends BaseManager findByChannelMchNo(String channelMchNo) { return lambdaQuery() .eq(WechatIsvChannelMerchant::getChannelMchNo, channelMchNo) .oneOpt(); } + + /// 根据通道商户号查询(认证引导,忽略租户) + @IgnoreTenant + public Optional findByChannelMchNoNotTenant(String channelMchNo) { + return findByChannelMchNo(channelMchNo); + } } diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppAuthConfigManager.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppAuthConfigManager.java index 2c565c71f..5e064da5c 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppAuthConfigManager.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppAuthConfigManager.java @@ -2,6 +2,7 @@ package cn.daxpay.open.channel.wechat.dao.isv; import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchAppAuthConfig; import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager; +import cn.daxpay.open.platform.core.annotation.IgnoreTenant; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -20,6 +21,14 @@ public class WechatIsvMchAppAuthConfigManager extends BaseManager findByWechatIsvMchAppIdNotTenant(Long wechatIsvMchAppId) { + return lambdaQuery() + .eq(WechatIsvMchAppAuthConfig::getWechatIsvMchAppId, wechatIsvMchAppId) + .oneOpt(); + } + /// 根据应用ID删除授权认证配置 public void deleteByWechatIsvMchAppId(Long wechatIsvMchAppId) { lambdaUpdate() diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppCapabilityManager.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppCapabilityManager.java index 3df3d6738..8ab0c2cd0 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppCapabilityManager.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppCapabilityManager.java @@ -2,6 +2,7 @@ package cn.daxpay.open.channel.wechat.dao.isv; import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchAppCapability; import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager; +import cn.daxpay.open.platform.core.annotation.IgnoreTenant; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -27,7 +28,7 @@ public class WechatIsvMchAppCapabilityManager extends BaseManager findOne(String channelMchNo, String capability) { return lambdaQuery() .eq(WechatIsvMchAppCapability::getChannelMchNo, channelMchNo) @@ -35,6 +36,12 @@ public class WechatIsvMchAppCapabilityManager extends BaseManager findOneNotTenant(String channelMchNo, String capability) { + return findOne(channelMchNo, capability); + } + /// 根据通道商户号删除全部关联(批量保存时先清后插) public void deleteByChannelMchNo(String channelMchNo) { lambdaUpdate() diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectConfigAssembler.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectConfigAssembler.java index 056fc38f6..81f253ef4 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectConfigAssembler.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectConfigAssembler.java @@ -60,7 +60,7 @@ public class WechatDirectConfigAssembler { return credential; } - /// 解析支付使用的应用 + /// 解析支付使用的应用(须已装载 mchNo,租户内) /// /// 优先级(委托 [WechatDirectAppCapabilityService.resolveApp]): /// 能力关联(显式配置 > appType自动推导) > 通道商户首个应用 diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/isv/WechatIsvConfigAssembler.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/isv/WechatIsvConfigAssembler.java index 6869ebc95..887093970 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/isv/WechatIsvConfigAssembler.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/isv/WechatIsvConfigAssembler.java @@ -50,10 +50,11 @@ public class WechatIsvConfigAssembler { // 服务商密钥(全局唯一, 含 sp_mchid 与证书; 缺失或关键字段为空时 fail-fast) WechatIsvKeyConfig keyConfig = wechatIsvKeyConfigService.getByProductForPay(ProductEnum.WECHAT_ISV.getCode()); // 特约商户绑定(取 sub_mchid) + // 支付/回调须已装载 mchNo,通道商户走租户内查询 WechatIsvChannelMerchant channelMerchant = wechatIsvChannelMerchantManager.findByChannelMchNo(channelMchNo) // 微信: 通道商户配置不存在 .orElseThrow(() -> new DataNotExistException("error.payment.channel.channelMerchantNotExist")); - // 服务商应用(取 sp_appid, 必须存在) + // 服务商应用为平台级(非 MchBaseEntity),无租户过滤 WechatIsvApp isvApp = wechatIsvAppCapabilityService.resolveApp(capability) // 微信: 服务商应用不存在 .orElseThrow(() -> new DataNotExistException("error.channel.wechat.appNotFound")); diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/direct/WechatDirectAuthStrategy.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/direct/WechatDirectAuthStrategy.java index a1434b7e0..0363968c7 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/direct/WechatDirectAuthStrategy.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/direct/WechatDirectAuthStrategy.java @@ -96,7 +96,7 @@ public class WechatDirectAuthStrategy extends AbsChannelAuthStrategy { .orElseThrow(() -> new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.wechat.opAppIdNotFound", opAppId)); } - return wechatDirectAppCapabilityService.resolveApp(channelMchNo, capability) + return wechatDirectAppCapabilityService.resolveAppNotTenant(channelMchNo, capability) // 微信: 直连商户应用不存在 .orElseThrow(() -> new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.wechat.mchAppNotFound")); diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/isv/WechatIsvAuthStrategy.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/isv/WechatIsvAuthStrategy.java index 2bc83b38c..e5077bc1e 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/isv/WechatIsvAuthStrategy.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/isv/WechatIsvAuthStrategy.java @@ -127,7 +127,7 @@ public class WechatIsvAuthStrategy extends AbsChannelAuthStrategy { return new ResolvedAuthApp(app.getWxAppId(), cfg.getAppSecret()); } // SUB_APP: 子商户应用(仅显式配置, 未配置返回empty) - WechatIsvMchApp app = wechatIsvMchAppCapabilityService.resolveApp(channelMchNo, capability) + WechatIsvMchApp app = wechatIsvMchAppCapabilityService.resolveAppNotTenant(channelMchNo, capability) // 微信: 服务商通道商户应用不存在 .orElseThrow(() -> new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.wechat.mchAppNotFound")); diff --git a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/common/runtime/CallbackMchContextFilter.java b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/common/runtime/CallbackMchContextFilter.java new file mode 100644 index 000000000..d8e28ec33 --- /dev/null +++ b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/common/runtime/CallbackMchContextFilter.java @@ -0,0 +1,67 @@ +package cn.daxpay.open.payment.common.runtime; + +import cn.hutool.core.util.StrUtil; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/// # 通道回调商户上下文过滤器 +/// +/// 所有通道回调路径约定为 `/unipay/callback/{mchNo}/{appId}/...`。 +/// 在 [PaymentContextFilter] 开启作用域后,从 path 解析 mchNo 写入 [PaymentContext], +/// 使回调链路在验签/查单/组装凭证时走**正常租户过滤**,无需再依赖 `@IgnoreTenant`。 +/// +/// 须晚于 [PaymentContextFilter](open 之后)执行。 +@Slf4j +@Component +@RequiredArgsConstructor +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +public class CallbackMchContextFilter extends OncePerRequestFilter implements Ordered { + + /// 匹配 .../unipay/callback/{mchNo}/... + private static final Pattern CALLBACK_MCH_PATH = + Pattern.compile(".*/unipay/callback/([^/]+)/.*"); + + private final PaymentContext paymentContext; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + if (paymentContext.isOpen()) { + String mchNo = extractMchNo(request.getRequestURI()); + if (StrUtil.isNotBlank(mchNo)) { + paymentContext.setMchNo(mchNo); + } + } + filterChain.doFilter(request, response); + } + + /// 从回调 URI 解析商户号;非回调路径返回 null + private static String extractMchNo(String requestUri) { + if (StrUtil.isBlank(requestUri)) { + return null; + } + Matcher matcher = CALLBACK_MCH_PATH.matcher(requestUri); + if (!matcher.matches()) { + return null; + } + return matcher.group(1); + } + + @Override + public int getOrder() { + // PaymentContextFilter = HIGHEST_PRECEDENCE + 100 + return Ordered.HIGHEST_PRECEDENCE + 101; + } +} diff --git a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/common/service/MerchantContextLoader.java b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/common/service/MerchantContextLoader.java index b5afc1a07..91f60c747 100644 --- a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/common/service/MerchantContextLoader.java +++ b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/common/service/MerchantContextLoader.java @@ -27,6 +27,8 @@ import static java.util.Optional.ofNullable; /// - `initMch`:商户身份进上下文(所有交易场景必调) /// - `resolveApp`:应用解析(推导默认/校验启用),仅需要应用归属时调,返回应用信息供调用方显式 setAppId /// +/// 引导阶段通过 [MerchantAccessQueryService] 的 `*NotTenant` 读路径装载身份; +/// **initMch 成功之后**业务链路应走正常租户过滤,勿再散落 `@IgnoreTenant`。 /// appId 不进线程上下文:它是可空、可推导的业务属性,由调用方按需显式赋值到实体。 @Slf4j @Service diff --git a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/dao/appinfo/MchAppInfoManager.java b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/dao/appinfo/MchAppInfoManager.java index 83526b743..20576ddf1 100644 --- a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/dao/appinfo/MchAppInfoManager.java +++ b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/dao/appinfo/MchAppInfoManager.java @@ -25,18 +25,30 @@ import java.util.Optional; @RequiredArgsConstructor public class MchAppInfoManager extends BaseManager { - /// 根据应用AppId查询, 商户端使用会过滤租户 + /// 根据应用AppId查询(配置态,租户内) public Optional findByAppId(String appId) { return this.findByField(MchAppInfo::getAppId, appId); } + + /// 按应用号加载商户应用,不存在则抛配置异常(配置态,租户内;运营端 ignoreTable) + public MchAppInfo requireByAppId(String appId) { + return findByAppId(appId) + // 商户: 未找到指定应用的配置 + .orElseThrow(() -> new ConfigNotExistException("error.payment.merchant.specifiedAppConfigNotFound")); + } + + /// 按应用号解析商户号(配置态,租户内) + public String requireMchNoByAppId(String appId) { + return requireByAppId(appId).getMchNo(); + } - /// 根据应用AppId查询, 忽略租户拦截 + /// 根据应用AppId查询(运行态引导,忽略租户) @IgnoreTenant public Optional findByAppIdNotTenant(String appId) { return this.findByField(MchAppInfo::getAppId, appId); } - /// 按应用号加载商户应用,不存在则抛配置异常(忽略租户) + /// 按应用号加载商户应用(运行态引导,忽略租户) @IgnoreTenant public MchAppInfo requireByAppIdNotTenant(String appId) { return findByAppIdNotTenant(appId) @@ -44,7 +56,7 @@ public class MchAppInfoManager extends BaseManager .orElseThrow(() -> new ConfigNotExistException("error.payment.merchant.specifiedAppConfigNotFound")); } - /// 按应用号解析商户号(忽略租户) + /// 按应用号解析商户号(运行态引导,忽略租户) @IgnoreTenant public String requireMchNoByAppIdNotTenant(String appId) { return requireByAppIdNotTenant(appId).getMchNo(); diff --git a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/config/MerchantCredentialService.java b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/config/MerchantCredentialService.java index 01527b15b..d963166b4 100644 --- a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/config/MerchantCredentialService.java +++ b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/config/MerchantCredentialService.java @@ -1,6 +1,5 @@ package cn.daxpay.open.payment.merchant.service.config; -import cn.daxpay.open.platform.core.annotation.IgnoreTenant; import cn.daxpay.open.platform.core.exception.DataNotExistException; import cn.daxpay.open.platform.common.config.properties.PlatformConfigProperties; import cn.daxpay.open.platform.core.util.RsaSignUtil; @@ -15,8 +14,10 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -/// # 商户API配置服务 +/// # 商户API配置服务(配置态) /// +/// 管理端/商户端凭证查询与更新。须在已有租户上下文(登录 Filter / 运营端 ignoreTable)下调用。 +/// 网关验签取公钥请走 [cn.daxpay.open.payment.merchant.service.query.MerchantAccessQueryService]。 @Slf4j @Service @RequiredArgsConstructor @@ -25,8 +26,7 @@ public class MerchantCredentialService { private final MerchantInfoManager merchantInfoManager; private final PlatformConfigProperties platformConfigProperties; - /// 根据商户号查询 - @IgnoreTenant + /// 根据商户号查询(配置态,租户内;运营端由 ignoreTable 放开) public MerchantCredentialResult findByMchNo(String mchNo) { String publicKey = platformConfigProperties.getKeyConfig().getPublicKey(); var credentialOptional = credentialManager.findByMchNo(mchNo); diff --git a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/query/MerchantAccessQueryService.java b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/query/MerchantAccessQueryService.java index 3abad4a56..2a9b9e536 100644 --- a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/query/MerchantAccessQueryService.java +++ b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/query/MerchantAccessQueryService.java @@ -3,38 +3,42 @@ package cn.daxpay.open.payment.merchant.service.query; import cn.daxpay.open.payment.merchant.dto.MchAppInfoAccessInfo; import cn.daxpay.open.payment.merchant.dto.MerchantAccessInfo; import cn.daxpay.open.payment.merchant.dao.appinfo.MchAppInfoManager; +import cn.daxpay.open.payment.merchant.dao.config.MerchantCredentialManager; import cn.daxpay.open.payment.merchant.dao.info.MerchantInfoManager; import cn.daxpay.open.payment.merchant.entity.appinfo.MchAppInfo; +import cn.daxpay.open.payment.merchant.entity.config.MerchantCredential; import cn.daxpay.open.payment.merchant.entity.info.MerchantInfo; -import cn.daxpay.open.payment.merchant.service.config.MerchantCredentialService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -/// # 商户接入信息查询服务 +/// # 商户接入信息查询服务(运行态 Access) /// -/// 供支付总线层查询商户/应用的接入信息,返回精简 DTO,不暴露 JPA 实体。 +/// 供支付总线层在**商户上下文尚未装载**时查询商户/应用接入信息,返回精简 DTO。 +/// 引导类读路径统一走 `*NotTenant`;`initMch` 之后的业务读(如公钥验签)走租户内查询。 +/// +/// 配置态 CRUD 请使用各业务 Service,勿在此扩展管理端能力。 @Service @RequiredArgsConstructor public class MerchantAccessQueryService { private final MerchantInfoManager merchantInfoManager; private final MchAppInfoManager mchAppInfoManager; - private final MerchantCredentialService merchantCredentialService; + private final MerchantCredentialManager merchantCredentialManager; - /// 根据商户号查询商户接入信息 + /// 根据商户号查询商户接入信息(引导用,忽略租户) public MerchantAccessInfo getMerchantByMchNo(String mchNo) { - return merchantInfoManager.findByMchNo(mchNo) + return merchantInfoManager.findByMchNoNotTenant(mchNo) .map(this::toMerchantAccessInfo) .orElse(null); } - /// 根据商户号查询默认应用 + /// 根据商户号查询默认应用(引导用,忽略租户) public MchAppInfoAccessInfo getDefaultAppByMchNo(String mchNo) { return mchAppInfoManager.findDefaultByMchNoNotTenant(mchNo) .map(this::toMchAppInfoAccessInfo) .orElse(null); } - /// 根据应用号查询应用 + /// 根据应用号查询应用(引导用,忽略租户) public MchAppInfoAccessInfo getAppByAppId(String appId) { return mchAppInfoManager.findByAppIdNotTenant(appId) .map(this::toMchAppInfoAccessInfo) @@ -42,8 +46,12 @@ public class MerchantAccessQueryService { } /// 根据商户号查询商户公钥 + /// + /// 调用方须已完成 `initMch`(或回调 Filter 已写入 mchNo),走**租户内**查询。 public String findMerchantPublicKey(String mchNo) { - return merchantCredentialService.findByMchNo(mchNo).getPublicKey(); + return merchantCredentialManager.findByMchNo(mchNo) + .map(MerchantCredential::getPublicKey) + .orElse(null); } private MerchantAccessInfo toMerchantAccessInfo(MerchantInfo merchantInfo) { diff --git a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/route/basic/PayRouteBasicConfigService.java b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/route/basic/PayRouteBasicConfigService.java index d9002ff44..d0be0dfd4 100644 --- a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/route/basic/PayRouteBasicConfigService.java +++ b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/route/basic/PayRouteBasicConfigService.java @@ -41,7 +41,7 @@ public class PayRouteBasicConfigService { /// 查询基础模式面板数据(已保存通道商户号 + 各渠道可选通道商户列表) public List listBasicByAppId(String appId) { - String mchNo = mchAppInfoManager.requireMchNoByAppIdNotTenant(appId); + String mchNo = mchAppInfoManager.requireMchNoByAppId(appId); PayRouteStrategy strategy = requireStrategy(appId); Map mchMap = basicConfigManager.findByStrategyId(strategy.getId()).stream() .filter(config -> StrUtil.isNotBlank(config.getProvider())) @@ -55,7 +55,7 @@ public class PayRouteBasicConfigService { /// 批量保存基础模式配置(先删后插) @Transactional(rollbackFor = Exception.class) public void saveBasicBatch(PayRouteBasicConfigBatchParam param) { - String mchNo = mchAppInfoManager.requireMchNoByAppIdNotTenant(param.getAppId()); + String mchNo = mchAppInfoManager.requireMchNoByAppId(param.getAppId()); PayRouteStrategy strategy = requireStrategy(param.getAppId()); basicConfigManager.deleteByStrategyId(strategy.getId()); for (PayRouteBasicConfigItem item : param.getItems()) { diff --git a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/route/facade/PayRouteConfigService.java b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/route/facade/PayRouteConfigService.java index 757fed099..04ed9f5a8 100644 --- a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/route/facade/PayRouteConfigService.java +++ b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/route/facade/PayRouteConfigService.java @@ -39,7 +39,8 @@ public class PayRouteConfigService { /// 按应用号获取路由策略,不存在则创建默认基础模式策略 @Transactional(rollbackFor = Exception.class) public PayRouteStrategyResult getOrInitByAppId(String appId) { - mchAppInfoManager.requireByAppIdNotTenant(appId); + // 配置态:运营端 ignoreTable / 租户内查应用 + mchAppInfoManager.requireByAppId(appId); return strategyManager.findByAppId(appId) .map(PayRouteStrategy::toResult) .orElseGet(() -> createDefaultStrategy(appId)); @@ -49,7 +50,7 @@ public class PayRouteConfigService { private PayRouteStrategyResult createDefaultStrategy(String appId) { PayRouteStrategy strategy = new PayRouteStrategy(); strategy.setAppId(appId); - strategy.setMchNo(mchAppInfoManager.requireMchNoByAppIdNotTenant(appId)); + strategy.setMchNo(mchAppInfoManager.requireMchNoByAppId(appId)); strategy.setMode(PayRouteModeEnum.BASIC.getCode()); strategyManager.save(strategy); return strategy.toResult(); @@ -58,7 +59,7 @@ public class PayRouteConfigService { /// 更新应用路由策略(模式、支付渠道、启用状态、名称) @Transactional(rollbackFor = Exception.class) public PayRouteStrategyResult updateStrategy(PayRouteStrategyParam param) { - mchAppInfoManager.requireByAppIdNotTenant(param.getAppId()); + mchAppInfoManager.requireByAppId(param.getAppId()); PayRouteStrategy strategy = strategyManager.findByAppId(param.getAppId()) .orElseThrow(() -> new DataNotExistException("pay.route.error.routeStrategyNotExist")); PayRouteStrategyConvert.CONVERT.copy(param, strategy); diff --git a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/route/scene/PayRouteSceneConfigService.java b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/route/scene/PayRouteSceneConfigService.java index 9bd8ca333..8bb580ef4 100644 --- a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/route/scene/PayRouteSceneConfigService.java +++ b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/merchant/service/route/scene/PayRouteSceneConfigService.java @@ -59,7 +59,7 @@ public class PayRouteSceneConfigService { /// 批量保存场景模式配置(全量覆盖:method 唯一、通道商户/能力校验) @Transactional(rollbackFor = Exception.class) public void saveSceneBatch(PayRouteSceneConfigBatchParam param) { - String mchNo = mchAppInfoManager.requireMchNoByAppIdNotTenant(param.getAppId()); + String mchNo = mchAppInfoManager.requireMchNoByAppId(param.getAppId()); PayRouteStrategy strategy = requireStrategy(param.getAppId()); validateSceneConfigUnique(param.getItems()); validateSceneChannelMchCapabilityPairing(param.getItems()); @@ -114,13 +114,13 @@ public class PayRouteSceneConfigService { /// 通道路由白名单目录下全部 (provider,method) 的通道商户候选(批量) public Map> listSceneChannelMchCandidatesBatch(String appId) { - String mchNo = mchAppInfoManager.requireMchNoByAppIdNotTenant(appId); + String mchNo = mchAppInfoManager.requireMchNoByAppId(appId); return payRouteStrategyCapabilitySupport.listSceneChannelMchCandidatesBatch(mchNo); } /// 按目录项+通道商户批量返回支付能力候选 public Map> listSceneCapabilityCandidatesBatch(PayRouteSceneCapabilityBatchParam param) { - mchAppInfoManager.requireMchNoByAppIdNotTenant(param.getAppId()); + mchAppInfoManager.requireMchNoByAppId(param.getAppId()); return payRouteStrategyCapabilitySupport.listSceneCapabilityCandidatesBatch(param.getItems()); } @@ -129,7 +129,7 @@ public class PayRouteSceneConfigService { if (StrUtil.isBlank(method) || !payProviderMethodService.contains(provider, method)) { return List.of(); } - String mchNo = mchAppInfoManager.requireMchNoByAppIdNotTenant(appId); + String mchNo = mchAppInfoManager.requireMchNoByAppId(appId); return payRouteStrategyCapabilitySupport.listSceneChannelMchCandidates(mchNo, provider, method); }