🎨 #3898 【微信支付】支持单参数 switchover 自定义键及通知回调空 appId 降级处理
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# 支持一个商户号对应多个 appId 的使用说明
|
# 支持一个商户号对应多个 appId 及自定义配置键的使用说明
|
||||||
|
|
||||||
## 背景
|
## 背景
|
||||||
|
|
||||||
@@ -7,13 +7,19 @@
|
|||||||
- 所有小程序共用同一个支付商户号
|
- 所有小程序共用同一个支付商户号
|
||||||
- 支付配置(商户号、密钥、证书等)完全相同,只有 appId 不同
|
- 支付配置(商户号、密钥、证书等)完全相同,只有 appId 不同
|
||||||
|
|
||||||
|
此外,也存在多租户(SaaS)场景,需要以自定义唯一键(如租户ID)来管理多个不同商户的配置。
|
||||||
|
|
||||||
## 解决方案
|
## 解决方案
|
||||||
|
|
||||||
WxJava 支持在配置多个相同商户号、不同 appId 的情况下,**可以仅通过商户号进行配置切换**,无需每次都指定 appId。
|
WxJava 支持以下几种多配置管理方式:
|
||||||
|
|
||||||
|
1. **mchId + appId 精确匹配**:适用于一个商户号对应多个 appId 的场景
|
||||||
|
2. **仅使用商户号(mchId)匹配**:适用于一个商户号对应单个 appId 或不关心具体 appId 的场景
|
||||||
|
3. **自定义唯一键**:适用于多租户场景,使用任意字符串(如租户ID)作为配置键
|
||||||
|
|
||||||
## 使用方式
|
## 使用方式
|
||||||
|
|
||||||
### 1. 配置多个 appId
|
### 1. 配置多个 appId(按 mchId + appId 格式)
|
||||||
|
|
||||||
```java
|
```java
|
||||||
WxPayService payService = new WxPayServiceImpl();
|
WxPayService payService = new WxPayServiceImpl();
|
||||||
@@ -53,7 +59,47 @@ configMap.put(mchId + "_" + config3.getAppId(), config3);
|
|||||||
payService.setMultiConfig(configMap);
|
payService.setMultiConfig(configMap);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 获取配置的方式
|
### 2. 使用自定义唯一键配置(多租户场景)
|
||||||
|
|
||||||
|
适用于多租户 SaaS 系统,使用任意唯一标识符(如租户ID)管理不同商户的配置:
|
||||||
|
|
||||||
|
```java
|
||||||
|
WxPayService payService = new WxPayServiceImpl();
|
||||||
|
|
||||||
|
// 使用租户ID作为配置键
|
||||||
|
WxPayConfig tenant1Config = new WxPayConfig();
|
||||||
|
tenant1Config.setMchId("1234567890");
|
||||||
|
tenant1Config.setAppId("wx1111111111111111");
|
||||||
|
tenant1Config.setMchKey("tenant1_mch_key");
|
||||||
|
// ... 其他配置
|
||||||
|
|
||||||
|
WxPayConfig tenant2Config = new WxPayConfig();
|
||||||
|
tenant2Config.setMchId("9876543210");
|
||||||
|
tenant2Config.setAppId("wx2222222222222222");
|
||||||
|
tenant2Config.setMchKey("tenant2_mch_key");
|
||||||
|
// ... 其他配置
|
||||||
|
|
||||||
|
// 方式一:通过 setMultiConfig 批量设置
|
||||||
|
Map<String, WxPayConfig> configMap = new HashMap<>();
|
||||||
|
configMap.put("tenant_001", tenant1Config);
|
||||||
|
configMap.put("tenant_002", tenant2Config);
|
||||||
|
payService.setMultiConfig(configMap);
|
||||||
|
|
||||||
|
// 方式二:通过 addConfig(key, config) 逐个添加(推荐,兼容单参数 switchover 方式)
|
||||||
|
payService.addConfig("tenant_001", tenant1Config);
|
||||||
|
payService.addConfig("tenant_002", tenant2Config);
|
||||||
|
|
||||||
|
// 切换配置:直接使用自定义键
|
||||||
|
payService.switchover("tenant_001");
|
||||||
|
WxPayConfig config = payService.getConfig(); // 获取 tenant_001 对应的配置
|
||||||
|
|
||||||
|
// 链式调用
|
||||||
|
WxPayUnifiedOrderResult result = payService
|
||||||
|
.switchoverTo("tenant_001")
|
||||||
|
.unifiedOrder(request);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 获取配置的方式
|
||||||
|
|
||||||
#### 方式一:直接获取配置(推荐,新功能)
|
#### 方式一:直接获取配置(推荐,新功能)
|
||||||
|
|
||||||
@@ -122,7 +168,7 @@ payService.switchover("1234567890", "wx1111111111111111");
|
|||||||
payService.switchover("1234567890", "wx2222222222222222");
|
payService.switchover("1234567890", "wx2222222222222222");
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 方式二:仅使用商户号切换(新功能)
|
#### 方式二:仅使用商户号切换
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// 仅使用商户号切换,会自动匹配该商户号的某个配置
|
// 仅使用商户号切换,会自动匹配该商户号的某个配置
|
||||||
@@ -135,7 +181,19 @@ boolean success = payService.switchover("1234567890");
|
|||||||
2. 如果未找到,则尝试前缀匹配(查找以 `商户号_` 开头的配置)
|
2. 如果未找到,则尝试前缀匹配(查找以 `商户号_` 开头的配置)
|
||||||
3. 如果有多个匹配项,将返回其中任意一个匹配项,具体选择结果不保证稳定或可预测,如需确定性行为请使用精确匹配方式(同时指定商户号和 appId)
|
3. 如果有多个匹配项,将返回其中任意一个匹配项,具体选择结果不保证稳定或可预测,如需确定性行为请使用精确匹配方式(同时指定商户号和 appId)
|
||||||
|
|
||||||
#### 方式三:链式调用
|
#### 方式三:使用自定义唯一键切换(多租户场景)
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 使用通过 addConfig(key, config) 或 setMultiConfig(map) 注册的自定义键切换
|
||||||
|
boolean success = payService.switchover("tenant_001");
|
||||||
|
|
||||||
|
// 链式调用
|
||||||
|
WxPayUnifiedOrderResult result = payService
|
||||||
|
.switchoverTo("tenant_001")
|
||||||
|
.unifiedOrder(request);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 方式四:链式调用
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// 精确切换,支持链式调用
|
// 精确切换,支持链式调用
|
||||||
@@ -152,7 +210,7 @@ WxPayUnifiedOrderResult result = payService
|
|||||||
### 4. 动态添加配置
|
### 4. 动态添加配置
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// 运行时动态添加新的 appId 配置
|
// 方式一:使用 mchId + appId 添加配置
|
||||||
WxPayConfig newConfig = new WxPayConfig();
|
WxPayConfig newConfig = new WxPayConfig();
|
||||||
newConfig.setMchId("1234567890");
|
newConfig.setMchId("1234567890");
|
||||||
newConfig.setAppId("wx4444444444444444");
|
newConfig.setAppId("wx4444444444444444");
|
||||||
@@ -162,13 +220,27 @@ payService.addConfig("1234567890", "wx4444444444444444", newConfig);
|
|||||||
|
|
||||||
// 切换到新添加的配置
|
// 切换到新添加的配置
|
||||||
payService.switchover("1234567890", "wx4444444444444444");
|
payService.switchover("1234567890", "wx4444444444444444");
|
||||||
|
|
||||||
|
// 方式二:使用自定义唯一键添加配置(多租户场景)
|
||||||
|
WxPayConfig tenantConfig = new WxPayConfig();
|
||||||
|
tenantConfig.setMchId("1234567890");
|
||||||
|
tenantConfig.setAppId("wx4444444444444444");
|
||||||
|
// ... 其他配置
|
||||||
|
|
||||||
|
payService.addConfig("tenant_003", tenantConfig);
|
||||||
|
|
||||||
|
// 使用自定义键切换
|
||||||
|
payService.switchover("tenant_003");
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. 移除配置
|
### 5. 移除配置
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// 移除特定的 appId 配置
|
// 方式一:使用 mchId + appId 移除配置
|
||||||
payService.removeConfig("1234567890", "wx1111111111111111");
|
payService.removeConfig("1234567890", "wx1111111111111111");
|
||||||
|
|
||||||
|
// 方式二:使用自定义唯一键移除配置(多租户场景)
|
||||||
|
payService.removeConfig("tenant_001");
|
||||||
```
|
```
|
||||||
|
|
||||||
## 实际应用场景
|
## 实际应用场景
|
||||||
@@ -201,6 +273,7 @@ public String handlePayNotify(@RequestBody String xmlData) {
|
|||||||
// 注意:parseOrderNotifyResult 方法内部会自动调用
|
// 注意:parseOrderNotifyResult 方法内部会自动调用
|
||||||
// switchover(notifyResult.getMchId(), notifyResult.getAppid())
|
// switchover(notifyResult.getMchId(), notifyResult.getAppid())
|
||||||
// 切换到正确的配置进行签名验证
|
// 切换到正确的配置进行签名验证
|
||||||
|
// 若回调中 appId 为空,会自动降级为仅使用 mchId 匹配
|
||||||
|
|
||||||
// 处理业务逻辑
|
// 处理业务逻辑
|
||||||
processOrder(notifyResult);
|
processOrder(notifyResult);
|
||||||
@@ -277,24 +350,30 @@ public void processRefund(String mchId, String outTradeNo) {
|
|||||||
| `getConfig(String mchId, String appId)` | 直接获取指定配置 | **否** | 多商户管理、异步场景、线程池 |
|
| `getConfig(String mchId, String appId)` | 直接获取指定配置 | **否** | 多商户管理、异步场景、线程池 |
|
||||||
| `getConfig(String mchId)` | 根据商户号获取配置 | **否** | 不确定 appId 的场景 |
|
| `getConfig(String mchId)` | 根据商户号获取配置 | **否** | 不确定 appId 的场景 |
|
||||||
| `switchover(String mchId, String appId)` | 精确切换配置 | 是 | 需要切换上下文的场景 |
|
| `switchover(String mchId, String appId)` | 精确切换配置 | 是 | 需要切换上下文的场景 |
|
||||||
| `switchover(String mchId)` | 根据商户号切换 | 是 | 不关心 appId 的切换场景 |
|
| `switchover(String mchIdOrKey)` | 根据商户号或自定义键切换 | 是 | 不关心 appId 或多租户场景 |
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. **向后兼容**:所有原有的使用方式继续有效,不需要修改现有代码。
|
1. **向后兼容**:所有原有的使用方式继续有效,不需要修改现有代码。
|
||||||
|
|
||||||
2. **配置隔离**:每个 `mchId + appId` 组合都是独立的配置,修改一个配置不会影响其他配置。
|
2. **自定义键支持**:可以通过 `addConfig(String configKey, WxPayConfig)` 或 `setMultiConfig(Map)` 注册任意键,
|
||||||
|
然后直接用 `switchover(key)` 进行精确匹配切换,无需关心 mchId 或 appId 的格式。
|
||||||
|
|
||||||
3. **线程安全**:
|
3. **通知回调兼容**:当 `switchover(mchId, appId)` 的 appId 为空(通知回调中可能出现此情况)时,
|
||||||
|
SDK 会自动降级为仅使用 mchId 进行匹配,避免因 appId 缺失导致的切换失败。
|
||||||
|
|
||||||
|
4. **配置隔离**:每个配置键对应独立的配置,修改一个配置不会影响其他配置。
|
||||||
|
|
||||||
|
5. **线程安全**:
|
||||||
- 配置切换使用 `WxPayConfigHolder`(基于 `ThreadLocal`),是线程安全的
|
- 配置切换使用 `WxPayConfigHolder`(基于 `ThreadLocal`),是线程安全的
|
||||||
- 直接获取配置方法(`getConfig(mchId, appId)`)不依赖 ThreadLocal,可以在任何上下文中安全使用
|
- 直接获取配置方法(`getConfig(mchId, appId)`)不依赖 ThreadLocal,可以在任何上下文中安全使用
|
||||||
|
|
||||||
4. **自动切换**:在处理支付回调时,SDK 会自动根据回调中的 `mchId` 和 `appId` 切换到正确的配置。
|
6. **自动切换**:在处理支付回调时,SDK 会自动根据回调中的 `mchId` 和 `appId` 切换到正确的配置。
|
||||||
|
|
||||||
5. **推荐实践**:
|
7. **推荐实践**:
|
||||||
- 如果知道具体的 appId,建议使用精确切换或获取方式,避免歧义
|
- 多租户 SaaS 场景:使用自定义键(如租户ID)管理配置,通过 `switchover(tenantId)` 切换
|
||||||
|
- 一商户多 appId 场景:使用 `mchId_appId` 格式的键,通过 `switchover(mchId, appId)` 精确切换
|
||||||
- 在多商户管理、异步场景、线程池等环境中,建议使用 `getConfig(mchId, appId)` 直接获取配置
|
- 在多商户管理、异步场景、线程池等环境中,建议使用 `getConfig(mchId, appId)` 直接获取配置
|
||||||
- 如果使用仅商户号切换或获取,确保该商户号下至少有一个可用的配置
|
|
||||||
|
|
||||||
## 相关 API
|
## 相关 API
|
||||||
|
|
||||||
@@ -303,9 +382,11 @@ public void processRefund(String mchId, String outTradeNo) {
|
|||||||
| `getConfig()` | 无 | WxPayConfig | 获取当前配置(依赖 ThreadLocal) |
|
| `getConfig()` | 无 | WxPayConfig | 获取当前配置(依赖 ThreadLocal) |
|
||||||
| `getConfig(String mchId, String appId)` | 商户号, appId | WxPayConfig | 直接获取指定配置(不依赖 ThreadLocal) |
|
| `getConfig(String mchId, String appId)` | 商户号, appId | WxPayConfig | 直接获取指定配置(不依赖 ThreadLocal) |
|
||||||
| `getConfig(String mchId)` | 商户号 | WxPayConfig | 根据商户号获取配置(不依赖 ThreadLocal) |
|
| `getConfig(String mchId)` | 商户号 | WxPayConfig | 根据商户号获取配置(不依赖 ThreadLocal) |
|
||||||
| `switchover(String mchId, String appId)` | 商户号, appId | boolean | 精确切换到指定配置 |
|
| `switchover(String mchId, String appId)` | 商户号, appId | boolean | 精确切换到指定配置;appId 为空时自动降级为仅 mchId 匹配 |
|
||||||
| `switchover(String mchId)` | 商户号 | boolean | 仅使用商户号切换 |
|
| `switchover(String mchIdOrKey)` | 商户号或自定义键 | boolean | 根据商户号或自定义键切换 |
|
||||||
| `switchoverTo(String mchId, String appId)` | 商户号, appId | WxPayService | 精确切换,支持链式调用 |
|
| `switchoverTo(String mchId, String appId)` | 商户号, appId | WxPayService | 精确切换,支持链式调用 |
|
||||||
| `switchoverTo(String mchId)` | 商户号 | WxPayService | 仅商户号切换,支持链式调用 |
|
| `switchoverTo(String mchIdOrKey)` | 商户号或自定义键 | WxPayService | 根据商户号或自定义键切换,支持链式调用 |
|
||||||
| `addConfig(String mchId, String appId, WxPayConfig)` | 商户号, appId, 配置 | void | 动态添加配置 |
|
| `addConfig(String mchId, String appId, WxPayConfig)` | 商户号, appId, 配置 | void | 动态添加配置(键为 mchId_appId 格式) |
|
||||||
| `removeConfig(String mchId, String appId)` | 商户号, appId | void | 移除指定配置 |
|
| `addConfig(String configKey, WxPayConfig)` | 自定义键, 配置 | void | 动态添加配置(使用自定义键) |
|
||||||
|
| `removeConfig(String mchId, String appId)` | 商户号, appId | void | 移除指定配置(键为 mchId_appId 格式) |
|
||||||
|
| `removeConfig(String configKey)` | 自定义键 | void | 移除指定配置(使用自定义键) |
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public interface WxPayService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Map里 加入新的 {@link WxPayConfig},适用于动态添加新的微信商户配置.
|
* Map里 加入新的 {@link WxPayConfig},适用于动态添加新的微信商户配置.
|
||||||
|
* 配置键将使用 mchId + "_" + appId 的格式.
|
||||||
*
|
*
|
||||||
* @param mchId 商户id
|
* @param mchId 商户id
|
||||||
* @param appId 微信应用id
|
* @param appId 微信应用id
|
||||||
@@ -45,6 +46,15 @@ public interface WxPayService {
|
|||||||
*/
|
*/
|
||||||
void addConfig(String mchId, String appId, WxPayConfig wxPayConfig);
|
void addConfig(String mchId, String appId, WxPayConfig wxPayConfig);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map里 加入新的 {@link WxPayConfig},使用自定义配置键,适用于动态添加新的微信商户配置.
|
||||||
|
* 此方法允许使用任意唯一标识符(如租户ID)作为配置键,兼容单参数 switchover 使用方式.
|
||||||
|
*
|
||||||
|
* @param configKey 自定义的配置键(全局唯一标识符,如租户ID)
|
||||||
|
* @param wxPayConfig 新的微信配置
|
||||||
|
*/
|
||||||
|
void addConfig(String configKey, WxPayConfig wxPayConfig);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从 Map中 移除 {@link String mchId} 和 {@link String appId} 所对应的 {@link WxPayConfig},适用于动态移除微信商户配置.
|
* 从 Map中 移除 {@link String mchId} 和 {@link String appId} 所对应的 {@link WxPayConfig},适用于动态移除微信商户配置.
|
||||||
*
|
*
|
||||||
@@ -53,6 +63,14 @@ public interface WxPayService {
|
|||||||
*/
|
*/
|
||||||
void removeConfig(String mchId, String appId);
|
void removeConfig(String mchId, String appId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Map中 移除指定配置键所对应的 {@link WxPayConfig},适用于动态移除微信商户配置.
|
||||||
|
* 此方法允许使用任意唯一标识符(如租户ID)删除配置,兼容单参数 switchover 使用方式.
|
||||||
|
*
|
||||||
|
* @param configKey 自定义的配置键(全局唯一标识符,如租户ID)
|
||||||
|
*/
|
||||||
|
void removeConfig(String configKey);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注入多个 {@link WxPayConfig} 的实现. 并为每个 {@link WxPayConfig} 赋予不同的 {@link String mchId} 值
|
* 注入多个 {@link WxPayConfig} 的实现. 并为每个 {@link WxPayConfig} 赋予不同的 {@link String mchId} 值
|
||||||
* 随机采用一个{@link String mchId}进行Http初始化操作
|
* 随机采用一个{@link String mchId}进行Http初始化操作
|
||||||
@@ -79,14 +97,17 @@ public interface WxPayService {
|
|||||||
boolean switchover(String mchId, String appId);
|
boolean switchover(String mchId, String appId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仅根据商户号进行切换.
|
* 根据商户号或自定义配置键进行切换.
|
||||||
* 适用于一个商户号对应多个appId的场景,切换时会匹配符合该商户号的配置.
|
* <ul>
|
||||||
* 注意:由于HashMap迭代顺序不确定,当存在多个匹配项时返回的配置是不可预测的,建议使用精确匹配方式.
|
* <li>当传入商户号(mchId)时,会先尝试精确匹配,若未找到则前缀匹配(mchId_*)。</li>
|
||||||
|
* <li>也可传入通过 {@link #addConfig(String, WxPayConfig)} 或 {@link #setMultiConfig(Map)} 注册的任意自定义配置键,此时直接精确匹配。</li>
|
||||||
|
* </ul>
|
||||||
|
* 注意:当存在多个前缀匹配项时返回的配置是不可预测的,建议使用精确匹配方式.
|
||||||
*
|
*
|
||||||
* @param mchId 商户标识
|
* @param mchIdOrConfigKey 商户标识或自定义配置键
|
||||||
* @return 切换是否成功,如果找不到匹配的配置则返回false
|
* @return 切换是否成功,如果找不到匹配的配置则返回false
|
||||||
*/
|
*/
|
||||||
default boolean switchover(String mchId) {
|
default boolean switchover(String mchIdOrConfigKey) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,15 +121,18 @@ public interface WxPayService {
|
|||||||
WxPayService switchoverTo(String mchId, String appId);
|
WxPayService switchoverTo(String mchId, String appId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仅根据商户号进行切换.
|
* 根据商户号或自定义配置键进行切换,支持链式调用.
|
||||||
* 适用于一个商户号对应多个appId的场景,切换时会匹配符合该商户号的配置.
|
* <ul>
|
||||||
* 注意:由于HashMap迭代顺序不确定,当存在多个匹配项时返回的配置是不可预测的,建议使用精确匹配方式.
|
* <li>当传入商户号(mchId)时,会先尝试精确匹配,若未找到则前缀匹配(mchId_*)。</li>
|
||||||
|
* <li>也可传入通过 {@link #addConfig(String, WxPayConfig)} 或 {@link #setMultiConfig(Map)} 注册的任意自定义配置键,此时直接精确匹配。</li>
|
||||||
|
* </ul>
|
||||||
|
* 注意:当存在多个前缀匹配项时返回的配置是不可预测的,建议使用精确匹配方式.
|
||||||
*
|
*
|
||||||
* @param mchId 商户标识
|
* @param mchIdOrConfigKey 商户标识或自定义配置键
|
||||||
* @return 切换成功,则返回当前对象,方便链式调用
|
* @return 切换成功,则返回当前对象,方便链式调用
|
||||||
* @throws me.chanjar.weixin.common.error.WxRuntimeException 如果找不到匹配的配置
|
* @throws me.chanjar.weixin.common.error.WxRuntimeException 如果找不到匹配的配置
|
||||||
*/
|
*/
|
||||||
default WxPayService switchoverTo(String mchId) {
|
default WxPayService switchoverTo(String mchIdOrConfigKey) {
|
||||||
throw new me.chanjar.weixin.common.error.WxRuntimeException("子类需要实现此方法");
|
throw new me.chanjar.weixin.common.error.WxRuntimeException("子类需要实现此方法");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -214,6 +214,18 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addConfig(String configKey, WxPayConfig wxPayConfig) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (this.configMap == null) {
|
||||||
|
this.setMultiConfig(ImmutableMap.of(configKey, wxPayConfig), configKey);
|
||||||
|
} else {
|
||||||
|
WxPayConfigHolder.set(configKey);
|
||||||
|
this.configMap.put(configKey, wxPayConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeConfig(String mchId, String appId) {
|
public void removeConfig(String mchId, String appId) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
@@ -231,6 +243,22 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeConfig(String configKey) {
|
||||||
|
synchronized (this) {
|
||||||
|
this.configMap.remove(configKey);
|
||||||
|
if (this.configMap.isEmpty()) {
|
||||||
|
log.warn("已删除最后一个商户号配置:configKey[{}],须立即使用setConfig或setMultiConfig添加配置", configKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (WxPayConfigHolder.get().equals(configKey)) {
|
||||||
|
final String nextConfigKey = this.configMap.keySet().iterator().next();
|
||||||
|
WxPayConfigHolder.set(nextConfigKey);
|
||||||
|
log.warn("已删除默认商户号配置,商户号【{}】被设为默认配置", nextConfigKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMultiConfig(Map<String, WxPayConfig> wxPayConfigs) {
|
public void setMultiConfig(Map<String, WxPayConfig> wxPayConfigs) {
|
||||||
this.setMultiConfig(wxPayConfigs, wxPayConfigs.keySet().iterator().next());
|
this.setMultiConfig(wxPayConfigs, wxPayConfigs.keySet().iterator().next());
|
||||||
@@ -244,6 +272,10 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean switchover(String mchId, String appId) {
|
public boolean switchover(String mchId, String appId) {
|
||||||
|
// 如果appId为空,则降级为仅使用mchId进行切换
|
||||||
|
if (StringUtils.isBlank(appId)) {
|
||||||
|
return this.switchover(mchId);
|
||||||
|
}
|
||||||
String configKey = this.getConfigKey(mchId, appId);
|
String configKey = this.getConfigKey(mchId, appId);
|
||||||
if (this.configMap.containsKey(configKey)) {
|
if (this.configMap.containsKey(configKey)) {
|
||||||
WxPayConfigHolder.set(configKey);
|
WxPayConfigHolder.set(configKey);
|
||||||
@@ -283,6 +315,10 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WxPayService switchoverTo(String mchId, String appId) {
|
public WxPayService switchoverTo(String mchId, String appId) {
|
||||||
|
// 如果appId为空,则降级为仅使用mchId进行切换
|
||||||
|
if (StringUtils.isBlank(appId)) {
|
||||||
|
return this.switchoverTo(mchId);
|
||||||
|
}
|
||||||
String configKey = this.getConfigKey(mchId, appId);
|
String configKey = this.getConfigKey(mchId, appId);
|
||||||
if (this.configMap.containsKey(configKey)) {
|
if (this.configMap.containsKey(configKey)) {
|
||||||
WxPayConfigHolder.set(configKey);
|
WxPayConfigHolder.set(configKey);
|
||||||
|
|||||||
@@ -414,4 +414,133 @@ public class MultiAppIdSwitchoverTest {
|
|||||||
assertTrue(success);
|
assertTrue(success);
|
||||||
assertEquals(testService.getConfig().getAppId(), appId2);
|
assertEquals(testService.getConfig().getAppId(), appId2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试使用自定义唯一键(非mchId格式)添加配置并切换.
|
||||||
|
* 验证向后兼容性:支持使用任意唯一标识符(如租户ID)管理配置
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAddConfigWithCustomKey() {
|
||||||
|
WxPayService testService = new WxPayServiceImpl();
|
||||||
|
|
||||||
|
String customKey1 = "tenant_001";
|
||||||
|
String customKey2 = "tenant_002";
|
||||||
|
|
||||||
|
WxPayConfig config1 = new WxPayConfig();
|
||||||
|
config1.setMchId("mch001");
|
||||||
|
config1.setAppId("wxabc");
|
||||||
|
config1.setMchKey("key_tenant_001");
|
||||||
|
|
||||||
|
WxPayConfig config2 = new WxPayConfig();
|
||||||
|
config2.setMchId("mch002");
|
||||||
|
config2.setAppId("wxdef");
|
||||||
|
config2.setMchKey("key_tenant_002");
|
||||||
|
|
||||||
|
// 使用自定义键添加配置
|
||||||
|
testService.addConfig(customKey1, config1);
|
||||||
|
testService.addConfig(customKey2, config2);
|
||||||
|
|
||||||
|
// 使用自定义键切换配置
|
||||||
|
boolean success = testService.switchover(customKey1);
|
||||||
|
assertTrue(success, "应该能够使用自定义键切换配置");
|
||||||
|
assertEquals(testService.getConfig().getMchKey(), "key_tenant_001");
|
||||||
|
|
||||||
|
success = testService.switchover(customKey2);
|
||||||
|
assertTrue(success, "应该能够切换到第二个自定义键配置");
|
||||||
|
assertEquals(testService.getConfig().getMchKey(), "key_tenant_002");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试使用自定义唯一键删除配置.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRemoveConfigWithCustomKey() {
|
||||||
|
WxPayService testService = new WxPayServiceImpl();
|
||||||
|
|
||||||
|
String customKey1 = "tenant_A";
|
||||||
|
String customKey2 = "tenant_B";
|
||||||
|
|
||||||
|
WxPayConfig config1 = new WxPayConfig();
|
||||||
|
config1.setMchId("mchA");
|
||||||
|
config1.setAppId("wxA");
|
||||||
|
config1.setMchKey("key_A");
|
||||||
|
|
||||||
|
WxPayConfig config2 = new WxPayConfig();
|
||||||
|
config2.setMchId("mchB");
|
||||||
|
config2.setAppId("wxB");
|
||||||
|
config2.setMchKey("key_B");
|
||||||
|
|
||||||
|
Map<String, WxPayConfig> configMap = new HashMap<>();
|
||||||
|
configMap.put(customKey1, config1);
|
||||||
|
configMap.put(customKey2, config2);
|
||||||
|
testService.setMultiConfig(configMap);
|
||||||
|
|
||||||
|
// 删除第一个自定义键配置
|
||||||
|
testService.removeConfig(customKey1);
|
||||||
|
|
||||||
|
// 尝试切换到已删除的配置应该失败
|
||||||
|
boolean success = testService.switchover(customKey1);
|
||||||
|
assertFalse(success, "切换到已删除的配置应该失败");
|
||||||
|
|
||||||
|
// 但仍然能够切换到第二个配置
|
||||||
|
success = testService.switchover(customKey2);
|
||||||
|
assertTrue(success, "切换到未删除的配置应该成功");
|
||||||
|
assertEquals(testService.getConfig().getMchKey(), "key_B");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试 switchover(mchId, appId) 当 appId 为 null 时降级为 switchover(mchId).
|
||||||
|
* 模拟通知回调中 appId 可能为空的场景
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSwitchoverWithNullAppIdFallsBackToMchId() {
|
||||||
|
// 切换到 appId 为 null 时,应该降级为只使用 mchId 匹配
|
||||||
|
boolean success = payService.switchover(testMchId, null);
|
||||||
|
assertTrue(success, "appId为null时应该降级为仅mchId匹配");
|
||||||
|
assertEquals(payService.getConfig().getMchId(), testMchId);
|
||||||
|
|
||||||
|
// appId 为空字符串时同样应该降级
|
||||||
|
success = payService.switchover(testMchId, "");
|
||||||
|
assertTrue(success, "appId为空字符串时应该降级为仅mchId匹配");
|
||||||
|
assertEquals(payService.getConfig().getMchId(), testMchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试 switchoverTo(mchId, appId) 当 appId 为 null 时降级为 switchoverTo(mchId).
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSwitchoverToWithNullAppIdFallsBackToMchId() {
|
||||||
|
WxPayService result = payService.switchoverTo(testMchId, null);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(result, payService);
|
||||||
|
assertEquals(payService.getConfig().getMchId(), testMchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试使用自定义键通过 setMultiConfig 注册后可以直接 switchover.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSwitchoverWithCustomKeyViaSetMultiConfig() {
|
||||||
|
WxPayService testService = new WxPayServiceImpl();
|
||||||
|
|
||||||
|
String tenantId = "my-unique-tenant-id";
|
||||||
|
WxPayConfig config = new WxPayConfig();
|
||||||
|
config.setMchId("mchTenant");
|
||||||
|
config.setAppId("wxTenant");
|
||||||
|
config.setMchKey("key_tenant");
|
||||||
|
|
||||||
|
Map<String, WxPayConfig> configMap = new HashMap<>();
|
||||||
|
configMap.put(tenantId, config);
|
||||||
|
testService.setMultiConfig(configMap);
|
||||||
|
|
||||||
|
// 使用自定义租户ID切换
|
||||||
|
boolean success = testService.switchover(tenantId);
|
||||||
|
assertTrue(success, "应该能够使用自定义租户ID切换配置");
|
||||||
|
assertEquals(testService.getConfig().getMchKey(), "key_tenant");
|
||||||
|
|
||||||
|
// switchoverTo 链式调用也应该支持
|
||||||
|
WxPayService result = testService.switchoverTo(tenantId);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(result, testService);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user