From eff2875e23ed16113cfaf438ee34d05cb2370b3c Mon Sep 17 00:00:00 2001 From: DaxPay Dev Date: Mon, 27 Jul 2026 23:29:30 +0800 Subject: [PATCH] =?UTF-8?q?refactor(wx):=20=E5=BE=AE=E4=BF=A1=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E8=A7=A3=E6=9E=90/=E8=83=BD=E5=8A=9B=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=87=8D=E6=9E=84,=20WxAppResolveService=20=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E8=A7=A3=E6=9E=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WxPlatformAppManager 新增查询方法, WxAppResolveService 重构应用解析流程, WxChannelAppCapabilityBatchParam 新增字段, admin/merchant 两端 Controller 适配, PaymentCommonParam 同步补充字段。 --- .../wx/WxChannelAppCapabilityController.java | 7 +-- .../unipay/param/PaymentCommonParam.java | 5 ++ .../payment/wx/dao/WxPlatformAppManager.java | 9 ++++ .../WxChannelAppCapabilityBatchParam.java | 11 ++++ .../wx/service/WxAppResolveService.java | 51 ++++++++++--------- .../WxChannelAppCapabilityService.java | 5 ++ .../MchWxChannelAppCapabilityController.java | 8 ++- 7 files changed, 63 insertions(+), 33 deletions(-) diff --git a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/wx/WxChannelAppCapabilityController.java b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/wx/WxChannelAppCapabilityController.java index 15ac6e976..d8a4428e4 100644 --- a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/wx/WxChannelAppCapabilityController.java +++ b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/wx/WxChannelAppCapabilityController.java @@ -39,11 +39,8 @@ public class WxChannelAppCapabilityController { @PermCode(code = PermCodes.Action.MANAGE) @Operation(summary = "全量保存能力绑定") @PostMapping("/save-batch") - public Result saveBatch( - @NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo, - @NotBlank(message = "{validation.field.channelMchNo.notBlank}") String channelMchNo, - @RequestBody @Validated WxChannelAppCapabilityBatchParam param) { - wxChannelAppCapabilityService.saveBatch(mchNo, channelMchNo, param.getItems()); + public Result saveBatch(@RequestBody @Validated WxChannelAppCapabilityBatchParam param) { + wxChannelAppCapabilityService.saveBatch(param.getMchNo(), param.getChannelMchNo(), param.getItems()); return Res.ok(); } } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/PaymentCommonParam.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/PaymentCommonParam.java index ab7252c1b..8c5dbc5de 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/PaymentCommonParam.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/PaymentCommonParam.java @@ -8,6 +8,7 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Data; import lombok.experimental.Accessors; +import org.springframework.format.annotation.DateTimeFormat; import java.time.OffsetDateTime; @@ -41,9 +42,13 @@ public abstract class PaymentCommonParam { private String sign; /// 请求时间(北京时间,格式 yyyy-MM-dd HH:mm:ss) + /// + /// 双注解支撑两条绑定路径:`@JsonFormat` 供 POST JSON body(Jackson)使用, + /// `@DateTimeFormat` 供 GET query / form(Spring ConversionService)使用。 @Schema(description = "请求时间(北京时间,yyyy-MM-dd HH:mm:ss)") @NotNull(message = "{validation.field.reqTime.notNull}") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private OffsetDateTime reqTime; } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/dao/WxPlatformAppManager.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/dao/WxPlatformAppManager.java index 45be5a33b..a3708fa6d 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/dao/WxPlatformAppManager.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/dao/WxPlatformAppManager.java @@ -44,4 +44,13 @@ public class WxPlatformAppManager extends BaseManager listByAppType(String appType) { + return lambdaQuery() + .eq(WxPlatformApp::getAppType, appType) + .orderByAsc(WxPlatformApp::getCreateTime) + .orderByAsc(WxPlatformApp::getId) + .list(); + } } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/param/WxChannelAppCapabilityBatchParam.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/param/WxChannelAppCapabilityBatchParam.java index 60059047a..392ea5073 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/param/WxChannelAppCapabilityBatchParam.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/param/WxChannelAppCapabilityBatchParam.java @@ -2,6 +2,7 @@ package cn.daxpay.open.payment.wx.param; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; import lombok.Data; import lombok.experimental.Accessors; @@ -10,12 +11,22 @@ import java.util.List; /// # 通道商户微信应用能力绑定批量保存参数 /// /// 按通道商户全量覆盖绑定:先清后插;items 为空表示清空该通道商户下全部绑定。 +/// mchNo 由运营端请求体传入;商户端不传,由登录上下文注入。 /// @Data @Accessors(chain = true) @Schema(title = "通道商户微信应用能力绑定批量保存参数") public class WxChannelAppCapabilityBatchParam { + // 商户号(运营端必传;商户端由上下文注入,故不加 @NotBlank) + @Schema(description = "商户号") + private String mchNo; + + // 通道商户号 + @NotBlank(message = "{validation.field.channelMchNo.notBlank}") + @Schema(description = "通道商户号") + private String channelMchNo; + @Valid @Schema(description = "能力绑定列表") private List items; diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/WxAppResolveService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/WxAppResolveService.java index 4b8fb1301..4e30dcfb4 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/WxAppResolveService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/WxAppResolveService.java @@ -2,7 +2,6 @@ package cn.daxpay.open.payment.wx.service; import cn.daxpay.open.payment.wx.dao.WxChannelAppCapabilityManager; import cn.daxpay.open.payment.wx.dao.WxMchAppManager; -import cn.daxpay.open.payment.wx.dao.WxPlatformAppCapabilityManager; import cn.daxpay.open.payment.wx.dao.WxPlatformAppManager; import cn.daxpay.open.payment.wx.entity.WxMchApp; import cn.daxpay.open.payment.wx.entity.WxPlatformApp; @@ -19,6 +18,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.List; + /// # 微信开放应用解析服务 /// /// 实现 [WxAppFacade],供通道组装器统一解析 wxAppId + Secret。 @@ -33,7 +34,6 @@ public class WxAppResolveService implements WxAppFacade { private final WxPlatformAppAuthConfigService wxPlatformAppAuthConfigService; private final WxMchAppAuthConfigService wxMchAppAuthConfigService; private final WxChannelAppCapabilityManager wxChannelAppCapabilityManager; - private final WxPlatformAppCapabilityManager wxPlatformAppCapabilityManager; /// 按档位与主键加载应用视图(含 Auth) @Override @@ -60,7 +60,7 @@ public class WxAppResolveService implements WxAppFacade { throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.payment.wx.scopeNotExist"); } - /// 解析单应用:显式 channelAppId → 通道能力绑 → 产品级平台默认能力绑 → appType 推导 + /// 解析单应用:显式 channelAppId → 通道能力绑 → appType 推导(平台应用唯一命中) @Override public WxAppView resolve(String mchNo, String channelMchNo, String capability, String channelAppId, String product) { // 1. 显式 channelAppId @@ -80,13 +80,14 @@ public class WxAppResolveService implements WxAppFacade { return this.getById(WxAppScopeEnum.PLATFORM, platformBind.get().getWxAppRefId()); } } - // 3. 产品级平台默认能力绑 + appType 推导可选 - WxAppView platformFallback = this.resolvePlatformFallback(product, capability); + // 3. appType 推导:要求该类型平台应用唯一命中 + WxAppView platformFallback = this.resolvePlatformFallback(capability); if (platformFallback != null) { return platformFallback; } - // 微信: 未绑定应用 - throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE, "error.payment.wx.appNotBound"); + // 微信: 未配置该能力对应的应用 + throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE, + "error.payment.wx.appNotConfigured", capability); } /// 解析 ISV 双应用:platform(sp) 必填 + merchant(sub) 可选 @@ -108,9 +109,9 @@ public class WxAppResolveService implements WxAppFacade { merchant = this.getById(WxAppScopeEnum.MERCHANT, merchantBind.get().getWxAppRefId()); } } - // 平台侧回退:产品级平台默认能力绑 / appType 推导 + // 平台侧回退:appType 推导(平台应用唯一命中) if (platform == null) { - platform = this.resolvePlatformFallback(product, capability); + platform = this.resolvePlatformFallback(capability); } // channelAppId 命中某侧时按命中侧覆盖 if (StrUtil.isNotBlank(channelAppId)) { @@ -130,8 +131,9 @@ public class WxAppResolveService implements WxAppFacade { } } if (platform == null) { - // 微信: 未绑定平台应用 - throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE, "error.payment.wx.appNotBound"); + // 微信: 未配置该能力对应的平台应用(sp 必填) + throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE, + "error.payment.wx.appNotConfigured", capability); } return new WxIsvAppPair(platform, merchant); } @@ -150,23 +152,26 @@ public class WxAppResolveService implements WxAppFacade { throw new DataNotExistException("error.payment.wx.channelAppIdNotFound", channelAppId); } - /// 产品级平台默认能力绑 → appType 推导(可选) - private WxAppView resolvePlatformFallback(String product, String capability) { + /// appType 推导兜底:要求该类型平台应用唯一命中 + /// + /// - 唯一命中:返回该应用 + /// - 该类型存在多个平台应用:抛 notUnique,要求显式配置通道能力绑 + /// - 该类型无平台应用:返回 null,由调用方走最终报错 + private WxAppView resolvePlatformFallback(String capability) { if (StrUtil.isBlank(capability)) { return null; } - if (StrUtil.isNotBlank(product)) { - var rel = wxPlatformAppCapabilityManager.findByProductAndCapability(product, capability); - if (rel.isPresent()) { - return this.getById(WxAppScopeEnum.PLATFORM, rel.get().getWxPlatformAppId()); - } - } - // appType 推导可选 + // appType 推导:要求该类型平台应用唯一命中,>1 拒绝猜测 String appType = WxAppTypeEnum.resolveAppType(capability); if (appType != null) { - var byType = wxPlatformAppManager.findFirstByAppType(appType); - if (byType.isPresent()) { - return this.toPlatformView(byType.get()); + List apps = wxPlatformAppManager.listByAppType(appType); + if (apps.size() > 1) { + // 存在多个同类型平台应用,请显式配置通道能力绑定以明确选择 + throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE, + "error.payment.wx.appNotUnique", appType); + } + if (apps.size() == 1) { + return this.toPlatformView(apps.getFirst()); } } return null; diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/WxChannelAppCapabilityService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/WxChannelAppCapabilityService.java index b93c7974e..4b2ffb8aa 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/WxChannelAppCapabilityService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/WxChannelAppCapabilityService.java @@ -61,6 +61,11 @@ public class WxChannelAppCapabilityService { /// 按通道商户全量保存能力绑定(先清后插) @Transactional(rollbackFor = Exception.class) public void saveBatch(String mchNo, String channelMchNo, List items) { + // mchNo 缺失直接归入"通道商户与商户号不匹配",避免下方 equals 触发 NPE + if (mchNo == null || mchNo.isBlank()) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "error.payment.wx.channelMerchantMismatch"); + } // 校验通道商户存在且 mchNo 匹配 ChannelMerchant channelMerchant = channelMerchantManager.findByChannelMchNo(channelMchNo) // 微信: 通道商户不存在或商户号不匹配 diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/wx/MchWxChannelAppCapabilityController.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/wx/MchWxChannelAppCapabilityController.java index 81aad011e..5edd7c5e4 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/wx/MchWxChannelAppCapabilityController.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/wx/MchWxChannelAppCapabilityController.java @@ -86,12 +86,10 @@ public class MchWxChannelAppCapabilityController { @PermCode(code = PermCodes.Action.MANAGE) @Operation(summary = "全量保存能力绑定") @PostMapping("/save-batch") - public Result saveBatch( - @NotBlank(message = "{validation.field.channelMchNo.notBlank}") String channelMchNo, - @RequestBody @Validated WxChannelAppCapabilityBatchParam param) { + public Result saveBatch(@RequestBody @Validated WxChannelAppCapabilityBatchParam param) { String mchNo = this.requireMchNo(); - this.assertChannelMchOwned(channelMchNo); - wxChannelAppCapabilityService.saveBatch(mchNo, channelMchNo, param.getItems()); + this.assertChannelMchOwned(param.getChannelMchNo()); + wxChannelAppCapabilityService.saveBatch(mchNo, param.getChannelMchNo(), param.getItems()); return Res.ok(); } }