refactor(wx): 微信应用解析/能力管理重构, WxAppResolveService 调整解析逻辑

WxPlatformAppManager 新增查询方法, WxAppResolveService 重构应用解析流程,
WxChannelAppCapabilityBatchParam 新增字段, admin/merchant 两端 Controller 适配,
PaymentCommonParam 同步补充字段。
This commit is contained in:
DaxPay Dev
2026-07-27 23:29:30 +08:00
parent 06f11e2667
commit eff2875e23
7 changed files with 63 additions and 33 deletions

View File

@@ -39,11 +39,8 @@ public class WxChannelAppCapabilityController {
@PermCode(code = PermCodes.Action.MANAGE)
@Operation(summary = "全量保存能力绑定")
@PostMapping("/save-batch")
public Result<Void> 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<Void> saveBatch(@RequestBody @Validated WxChannelAppCapabilityBatchParam param) {
wxChannelAppCapabilityService.saveBatch(param.getMchNo(), param.getChannelMchNo(), param.getItems());
return Res.ok();
}
}

View File

@@ -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 bodyJackson使用
/// `@DateTimeFormat` 供 GET query / formSpring 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;
}

View File

@@ -44,4 +44,13 @@ public class WxPlatformAppManager extends BaseManager<WxPlatformAppMapper, WxPla
.orderByAsc(WxPlatformApp::getCreateTime)
.orderByAsc(WxPlatformApp::getId));
}
/// 按应用类型查询全部应用(按创建时间升序),供应用解析做唯一性判断
public List<WxPlatformApp> listByAppType(String appType) {
return lambdaQuery()
.eq(WxPlatformApp::getAppType, appType)
.orderByAsc(WxPlatformApp::getCreateTime)
.orderByAsc(WxPlatformApp::getId)
.list();
}
}

View File

@@ -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<WxChannelAppCapabilityParam> items;

View File

@@ -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<WxPlatformApp> 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;

View File

@@ -61,6 +61,11 @@ public class WxChannelAppCapabilityService {
/// 按通道商户全量保存能力绑定(先清后插)
@Transactional(rollbackFor = Exception.class)
public void saveBatch(String mchNo, String channelMchNo, List<WxChannelAppCapabilityParam> 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)
// 微信: 通道商户不存在或商户号不匹配

View File

@@ -86,12 +86,10 @@ public class MchWxChannelAppCapabilityController {
@PermCode(code = PermCodes.Action.MANAGE)
@Operation(summary = "全量保存能力绑定")
@PostMapping("/save-batch")
public Result<Void> saveBatch(
@NotBlank(message = "{validation.field.channelMchNo.notBlank}") String channelMchNo,
@RequestBody @Validated WxChannelAppCapabilityBatchParam param) {
public Result<Void> 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();
}
}