添加直接获取配置的方法,改进多商户配置管理
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
This commit is contained in:
@@ -53,7 +53,50 @@ configMap.put(mchId + "_" + config3.getAppId(), config3);
|
||||
payService.setMultiConfig(configMap);
|
||||
```
|
||||
|
||||
### 2. 切换配置的方式
|
||||
### 2. 获取配置的方式
|
||||
|
||||
#### 方式一:直接获取配置(推荐,新功能)
|
||||
|
||||
直接通过商户号和 appId 获取配置,**不依赖 ThreadLocal**,适用于多商户管理场景:
|
||||
|
||||
```java
|
||||
// 精确获取指定商户号和 appId 的配置
|
||||
WxPayConfig config1 = payService.getConfig("1234567890", "wx1111111111111111");
|
||||
|
||||
// 仅使用商户号获取配置(会返回该商户号的任意一个配置)
|
||||
WxPayConfig config = payService.getConfig("1234567890");
|
||||
|
||||
// 使用获取的配置进行支付操作
|
||||
if (config != null) {
|
||||
String appId = config.getAppId();
|
||||
String mchKey = config.getMchKey();
|
||||
// ... 使用配置信息
|
||||
}
|
||||
```
|
||||
|
||||
**优势**:
|
||||
- 不依赖 ThreadLocal,可以在任何上下文中使用
|
||||
- 适合在异步场景、线程池等环境中使用
|
||||
- 线程安全,不会因为线程切换导致配置丢失
|
||||
- 可以同时获取多个不同的配置
|
||||
|
||||
#### 方式二:切换配置后使用(原有方式)
|
||||
|
||||
通过切换配置,然后调用 `getConfig()` 获取当前配置:
|
||||
|
||||
```java
|
||||
// 精确切换到指定的配置
|
||||
payService.switchover("1234567890", "wx1111111111111111");
|
||||
WxPayConfig config = payService.getConfig(); // 获取当前切换的配置
|
||||
|
||||
// 仅使用商户号切换
|
||||
payService.switchover("1234567890");
|
||||
config = payService.getConfig(); // 获取切换后的配置
|
||||
```
|
||||
|
||||
**注意**:此方式依赖 ThreadLocal,需要注意线程上下文的问题。
|
||||
|
||||
### 3. 切换配置的方式
|
||||
|
||||
#### 方式一:精确切换(原有方式,向后兼容)
|
||||
|
||||
@@ -92,7 +135,7 @@ WxPayUnifiedOrderResult result = payService
|
||||
.unifiedOrder(request);
|
||||
```
|
||||
|
||||
### 3. 动态添加配置
|
||||
### 4. 动态添加配置
|
||||
|
||||
```java
|
||||
// 运行时动态添加新的 appId 配置
|
||||
@@ -107,7 +150,7 @@ payService.addConfig("1234567890", "wx4444444444444444", newConfig);
|
||||
payService.switchover("1234567890", "wx4444444444444444");
|
||||
```
|
||||
|
||||
### 4. 移除配置
|
||||
### 5. 移除配置
|
||||
|
||||
```java
|
||||
// 移除特定的 appId 配置
|
||||
@@ -174,24 +217,78 @@ WxPayRefundRequest refundRequest = new WxPayRefundRequest();
|
||||
WxPayRefundResult refundResult = payService.refund(refundRequest);
|
||||
```
|
||||
|
||||
### 场景4:多商户管理(推荐使用直接获取配置)
|
||||
|
||||
```java
|
||||
// 在多商户管理系统中,可以直接获取指定商户的配置
|
||||
// 这种方式不依赖 ThreadLocal,适合异步场景和线程池环境
|
||||
|
||||
public void processMerchantOrder(String mchId, String appId, Order order) {
|
||||
// 直接获取配置,无需切换
|
||||
WxPayConfig config = payService.getConfig(mchId, appId);
|
||||
|
||||
if (config == null) {
|
||||
log.error("找不到商户配置:mchId={}, appId={}", mchId, appId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用配置信息
|
||||
String merchantKey = config.getMchKey();
|
||||
String apiV3Key = config.getApiV3Key();
|
||||
|
||||
// ... 处理订单逻辑
|
||||
}
|
||||
|
||||
// 或者在不确定 appId 的情况下
|
||||
public void processRefund(String mchId, String outTradeNo) {
|
||||
// 获取该商户号的任意一个配置
|
||||
WxPayConfig config = payService.getConfig(mchId);
|
||||
|
||||
if (config == null) {
|
||||
log.error("找不到商户配置:mchId={}", mchId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 先切换到该配置,然后进行退款
|
||||
payService.switchover(mchId, config.getAppId());
|
||||
// ... 执行退款操作
|
||||
}
|
||||
```
|
||||
|
||||
## 新增方法对比
|
||||
|
||||
| 方法 | 说明 | 是否依赖 ThreadLocal | 适用场景 |
|
||||
|-----|------|---------------------|---------|
|
||||
| `getConfig()` | 获取当前配置 | 是 | 单线程同步场景 |
|
||||
| `getConfig(String mchId, String appId)` | 直接获取指定配置 | **否** | 多商户管理、异步场景、线程池 |
|
||||
| `getConfig(String mchId)` | 根据商户号获取配置 | **否** | 不确定 appId 的场景 |
|
||||
| `switchover(String mchId, String appId)` | 精确切换配置 | 是 | 需要切换上下文的场景 |
|
||||
| `switchover(String mchId)` | 根据商户号切换 | 是 | 不关心 appId 的切换场景 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **向后兼容**:所有原有的使用方式继续有效,不需要修改现有代码。
|
||||
|
||||
2. **配置隔离**:每个 `mchId + appId` 组合都是独立的配置,修改一个配置不会影响其他配置。
|
||||
|
||||
3. **线程安全**:配置切换使用 `WxPayConfigHolder`(基于 `ThreadLocal`),是线程安全的。
|
||||
3. **线程安全**:
|
||||
- 配置切换使用 `WxPayConfigHolder`(基于 `ThreadLocal`),是线程安全的
|
||||
- 直接获取配置方法(`getConfig(mchId, appId)`)不依赖 ThreadLocal,可以在任何上下文中安全使用
|
||||
|
||||
4. **自动切换**:在处理支付回调时,SDK 会自动根据回调中的 `mchId` 和 `appId` 切换到正确的配置。
|
||||
|
||||
5. **推荐实践**:
|
||||
- 如果知道具体的 appId,建议使用精确切换方式,避免歧义
|
||||
- 如果使用仅商户号切换,确保该商户号下至少有一个可用的配置
|
||||
- 如果知道具体的 appId,建议使用精确切换或获取方式,避免歧义
|
||||
- 在多商户管理、异步场景、线程池等环境中,建议使用 `getConfig(mchId, appId)` 直接获取配置
|
||||
- 如果使用仅商户号切换或获取,确保该商户号下至少有一个可用的配置
|
||||
|
||||
## 相关 API
|
||||
|
||||
| 方法 | 参数 | 返回值 | 说明 |
|
||||
|-----|------|--------|------|
|
||||
| `getConfig()` | 无 | WxPayConfig | 获取当前配置(依赖 ThreadLocal) |
|
||||
| `getConfig(String mchId, String appId)` | 商户号, appId | WxPayConfig | 直接获取指定配置(不依赖 ThreadLocal) |
|
||||
| `getConfig(String mchId)` | 商户号 | WxPayConfig | 根据商户号获取配置(不依赖 ThreadLocal) |
|
||||
| `switchover(String mchId, String appId)` | 商户号, appId | boolean | 精确切换到指定配置 |
|
||||
| `switchover(String mchId)` | 商户号 | boolean | 仅使用商户号切换 |
|
||||
| `switchoverTo(String mchId, String appId)` | 商户号, appId | WxPayService | 精确切换,支持链式调用 |
|
||||
|
||||
@@ -785,11 +785,33 @@ public interface WxPayService {
|
||||
|
||||
/**
|
||||
* 获取配置.
|
||||
* 在多商户配置场景下,会根据 WxPayConfigHolder 中的值获取对应的配置.
|
||||
*
|
||||
* @return the config
|
||||
*/
|
||||
WxPayConfig getConfig();
|
||||
|
||||
/**
|
||||
* 根据商户号和 appId 直接获取配置.
|
||||
* 此方法不依赖 ThreadLocal,可以在任何上下文中使用,适用于多商户管理场景.
|
||||
*
|
||||
* @param mchId 商户号
|
||||
* @param appId 微信应用 id
|
||||
* @return 对应的配置对象,如果不存在则返回 null
|
||||
*/
|
||||
WxPayConfig getConfig(String mchId, String appId);
|
||||
|
||||
/**
|
||||
* 根据商户号直接获取配置.
|
||||
* 此方法不依赖 ThreadLocal,可以在任何上下文中使用.
|
||||
* 适用于一个商户号对应多个 appId 的场景,会返回该商户号的任意一个配置.
|
||||
* 注意:当存在多个匹配项时返回的配置是不可预测的,建议使用精确匹配方式.
|
||||
*
|
||||
* @param mchId 商户号
|
||||
* @return 对应的配置对象,如果不存在则返回 null
|
||||
*/
|
||||
WxPayConfig getConfig(String mchId);
|
||||
|
||||
/**
|
||||
* 设置配置对象.
|
||||
*
|
||||
|
||||
@@ -154,6 +154,45 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
|
||||
return this.configMap.get(WxPayConfigHolder.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxPayConfig getConfig(String mchId, String appId) {
|
||||
if (StringUtils.isBlank(mchId)) {
|
||||
log.warn("商户号mchId不能为空");
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.isBlank(appId)) {
|
||||
log.warn("应用ID appId不能为空");
|
||||
return null;
|
||||
}
|
||||
String configKey = this.getConfigKey(mchId, appId);
|
||||
return this.configMap.get(configKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxPayConfig getConfig(String mchId) {
|
||||
if (StringUtils.isBlank(mchId)) {
|
||||
log.warn("商户号mchId不能为空");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 先尝试精确匹配(针对只有mchId没有appId的配置)
|
||||
if (this.configMap.containsKey(mchId)) {
|
||||
return this.configMap.get(mchId);
|
||||
}
|
||||
|
||||
// 尝试前缀匹配(查找以 mchId_ 开头的配置)
|
||||
String prefix = mchId + "_";
|
||||
for (Map.Entry<String, WxPayConfig> entry : this.configMap.entrySet()) {
|
||||
if (entry.getKey().startsWith(prefix)) {
|
||||
log.debug("根据mchId=【{}】找到配置key=【{}】", mchId, entry.getKey());
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
log.warn("无法找到对应mchId=【{}】的商户号配置信息", mchId);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfig(WxPayConfig config) {
|
||||
final String defaultKey = this.getConfigKey(config.getMchId(), config.getAppId());
|
||||
|
||||
@@ -52,6 +52,113 @@ public class MultiAppIdSwitchoverTest {
|
||||
payService.setMultiConfig(configMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试直接通过 mchId 和 appId 获取配置(新功能)
|
||||
*/
|
||||
@Test
|
||||
public void testGetConfigWithMchIdAndAppId() {
|
||||
// 测试获取第一个配置
|
||||
WxPayConfig config1 = payService.getConfig(testMchId, testAppId1);
|
||||
assertNotNull(config1, "应该能够获取到配置");
|
||||
assertEquals(config1.getMchId(), testMchId);
|
||||
assertEquals(config1.getAppId(), testAppId1);
|
||||
assertEquals(config1.getMchKey(), "test_key_1");
|
||||
|
||||
// 测试获取第二个配置
|
||||
WxPayConfig config2 = payService.getConfig(testMchId, testAppId2);
|
||||
assertNotNull(config2);
|
||||
assertEquals(config2.getAppId(), testAppId2);
|
||||
assertEquals(config2.getMchKey(), "test_key_2");
|
||||
|
||||
// 测试获取第三个配置
|
||||
WxPayConfig config3 = payService.getConfig(testMchId, testAppId3);
|
||||
assertNotNull(config3);
|
||||
assertEquals(config3.getAppId(), testAppId3);
|
||||
assertEquals(config3.getMchKey(), "test_key_3");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试直接通过 mchId 获取配置(新功能)
|
||||
*/
|
||||
@Test
|
||||
public void testGetConfigWithMchIdOnly() {
|
||||
WxPayConfig config = payService.getConfig(testMchId);
|
||||
assertNotNull(config, "应该能够通过mchId获取配置");
|
||||
assertEquals(config.getMchId(), testMchId);
|
||||
|
||||
// appId应该是三个中的一个
|
||||
String currentAppId = config.getAppId();
|
||||
assertTrue(
|
||||
testAppId1.equals(currentAppId) || testAppId2.equals(currentAppId) || testAppId3.equals(currentAppId),
|
||||
"获取的配置的appId应该是配置的appId之一"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 getConfig 方法不依赖 ThreadLocal
|
||||
* 在不切换配置的情况下也能直接获取
|
||||
*/
|
||||
@Test
|
||||
public void testGetConfigWithoutSwitchover() {
|
||||
// 不进行任何switchover操作,直接通过参数获取配置
|
||||
WxPayConfig config1 = payService.getConfig(testMchId, testAppId1);
|
||||
WxPayConfig config2 = payService.getConfig(testMchId, testAppId2);
|
||||
WxPayConfig config3 = payService.getConfig(testMchId, testAppId3);
|
||||
|
||||
// 验证可以同时获取到所有配置,不受 ThreadLocal 影响
|
||||
assertNotNull(config1);
|
||||
assertNotNull(config2);
|
||||
assertNotNull(config3);
|
||||
|
||||
assertEquals(config1.getAppId(), testAppId1);
|
||||
assertEquals(config2.getAppId(), testAppId2);
|
||||
assertEquals(config3.getAppId(), testAppId3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 getConfig 方法处理不存在的配置
|
||||
*/
|
||||
@Test
|
||||
public void testGetConfigWithNonexistentConfig() {
|
||||
// 测试不存在的商户号和appId组合
|
||||
WxPayConfig config = payService.getConfig("nonexistent_mch_id", "nonexistent_app_id");
|
||||
assertNull(config, "获取不存在的配置应该返回null");
|
||||
|
||||
// 测试存在商户号但不存在的appId
|
||||
config = payService.getConfig(testMchId, "wx9999999999999999");
|
||||
assertNull(config, "获取不存在的appId配置应该返回null");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 getConfig 方法处理空参数或null参数
|
||||
*/
|
||||
@Test
|
||||
public void testGetConfigWithNullOrEmptyParameters() {
|
||||
// 测试 null 商户号
|
||||
WxPayConfig config = payService.getConfig(null, testAppId1);
|
||||
assertNull(config, "商户号为null时应该返回null");
|
||||
|
||||
// 测试空商户号
|
||||
config = payService.getConfig("", testAppId1);
|
||||
assertNull(config, "商户号为空字符串时应该返回null");
|
||||
|
||||
// 测试 null appId
|
||||
config = payService.getConfig(testMchId, null);
|
||||
assertNull(config, "appId为null时应该返回null");
|
||||
|
||||
// 测试空 appId
|
||||
config = payService.getConfig(testMchId, "");
|
||||
assertNull(config, "appId为空字符串时应该返回null");
|
||||
|
||||
// 测试仅mchId方法的null参数
|
||||
config = payService.getConfig((String) null);
|
||||
assertNull(config, "商户号为null时应该返回null");
|
||||
|
||||
// 测试仅mchId方法的空字符串
|
||||
config = payService.getConfig("");
|
||||
assertNull(config, "商户号为空字符串时应该返回null");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试使用 mchId + appId 精确切换(原有功能,确保向后兼容)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user