Skip to content

Commit 742e757

Browse files
Copilotbinarywang
andcommitted
添加直接获取配置的方法,改进多商户配置管理
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
1 parent 8512374 commit 742e757

File tree

4 files changed

+271
-6
lines changed

4 files changed

+271
-6
lines changed

weixin-java-pay/MULTI_APPID_USAGE.md

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,50 @@ configMap.put(mchId + "_" + config3.getAppId(), config3);
5353
payService.setMultiConfig(configMap);
5454
```
5555

56-
### 2. 切换配置的方式
56+
### 2. 获取配置的方式
57+
58+
#### 方式一:直接获取配置(推荐,新功能)
59+
60+
直接通过商户号和 appId 获取配置,**不依赖 ThreadLocal**,适用于多商户管理场景:
61+
62+
```java
63+
// 精确获取指定商户号和 appId 的配置
64+
WxPayConfig config1 = payService.getConfig("1234567890", "wx1111111111111111");
65+
66+
// 仅使用商户号获取配置(会返回该商户号的任意一个配置)
67+
WxPayConfig config = payService.getConfig("1234567890");
68+
69+
// 使用获取的配置进行支付操作
70+
if (config != null) {
71+
String appId = config.getAppId();
72+
String mchKey = config.getMchKey();
73+
// ... 使用配置信息
74+
}
75+
```
76+
77+
**优势**
78+
- 不依赖 ThreadLocal,可以在任何上下文中使用
79+
- 适合在异步场景、线程池等环境中使用
80+
- 线程安全,不会因为线程切换导致配置丢失
81+
- 可以同时获取多个不同的配置
82+
83+
#### 方式二:切换配置后使用(原有方式)
84+
85+
通过切换配置,然后调用 `getConfig()` 获取当前配置:
86+
87+
```java
88+
// 精确切换到指定的配置
89+
payService.switchover("1234567890", "wx1111111111111111");
90+
WxPayConfig config = payService.getConfig(); // 获取当前切换的配置
91+
92+
// 仅使用商户号切换
93+
payService.switchover("1234567890");
94+
config = payService.getConfig(); // 获取切换后的配置
95+
```
96+
97+
**注意**:此方式依赖 ThreadLocal,需要注意线程上下文的问题。
98+
99+
### 3. 切换配置的方式
57100

58101
#### 方式一:精确切换(原有方式,向后兼容)
59102

@@ -92,7 +135,7 @@ WxPayUnifiedOrderResult result = payService
92135
.unifiedOrder(request);
93136
```
94137

95-
### 3. 动态添加配置
138+
### 4. 动态添加配置
96139

97140
```java
98141
// 运行时动态添加新的 appId 配置
@@ -107,7 +150,7 @@ payService.addConfig("1234567890", "wx4444444444444444", newConfig);
107150
payService.switchover("1234567890", "wx4444444444444444");
108151
```
109152

110-
### 4. 移除配置
153+
### 5. 移除配置
111154

112155
```java
113156
// 移除特定的 appId 配置
@@ -174,24 +217,78 @@ WxPayRefundRequest refundRequest = new WxPayRefundRequest();
174217
WxPayRefundResult refundResult = payService.refund(refundRequest);
175218
```
176219

220+
### 场景4:多商户管理(推荐使用直接获取配置)
221+
222+
```java
223+
// 在多商户管理系统中,可以直接获取指定商户的配置
224+
// 这种方式不依赖 ThreadLocal,适合异步场景和线程池环境
225+
226+
public void processMerchantOrder(String mchId, String appId, Order order) {
227+
// 直接获取配置,无需切换
228+
WxPayConfig config = payService.getConfig(mchId, appId);
229+
230+
if (config == null) {
231+
log.error("找不到商户配置:mchId={}, appId={}", mchId, appId);
232+
return;
233+
}
234+
235+
// 使用配置信息
236+
String merchantKey = config.getMchKey();
237+
String apiV3Key = config.getApiV3Key();
238+
239+
// ... 处理订单逻辑
240+
}
241+
242+
// 或者在不确定 appId 的情况下
243+
public void processRefund(String mchId, String outTradeNo) {
244+
// 获取该商户号的任意一个配置
245+
WxPayConfig config = payService.getConfig(mchId);
246+
247+
if (config == null) {
248+
log.error("找不到商户配置:mchId={}", mchId);
249+
return;
250+
}
251+
252+
// 先切换到该配置,然后进行退款
253+
payService.switchover(mchId, config.getAppId());
254+
// ... 执行退款操作
255+
}
256+
```
257+
258+
## 新增方法对比
259+
260+
| 方法 | 说明 | 是否依赖 ThreadLocal | 适用场景 |
261+
|-----|------|---------------------|---------|
262+
| `getConfig()` | 获取当前配置 || 单线程同步场景 |
263+
| `getConfig(String mchId, String appId)` | 直接获取指定配置 | **** | 多商户管理、异步场景、线程池 |
264+
| `getConfig(String mchId)` | 根据商户号获取配置 | **** | 不确定 appId 的场景 |
265+
| `switchover(String mchId, String appId)` | 精确切换配置 || 需要切换上下文的场景 |
266+
| `switchover(String mchId)` | 根据商户号切换 || 不关心 appId 的切换场景 |
267+
177268
## 注意事项
178269

179270
1. **向后兼容**:所有原有的使用方式继续有效,不需要修改现有代码。
180271

181272
2. **配置隔离**:每个 `mchId + appId` 组合都是独立的配置,修改一个配置不会影响其他配置。
182273

183-
3. **线程安全**:配置切换使用 `WxPayConfigHolder`(基于 `ThreadLocal`),是线程安全的。
274+
3. **线程安全**
275+
- 配置切换使用 `WxPayConfigHolder`(基于 `ThreadLocal`),是线程安全的
276+
- 直接获取配置方法(`getConfig(mchId, appId)`)不依赖 ThreadLocal,可以在任何上下文中安全使用
184277

185278
4. **自动切换**:在处理支付回调时,SDK 会自动根据回调中的 `mchId``appId` 切换到正确的配置。
186279

187280
5. **推荐实践**
188-
- 如果知道具体的 appId,建议使用精确切换方式,避免歧义
189-
- 如果使用仅商户号切换,确保该商户号下至少有一个可用的配置
281+
- 如果知道具体的 appId,建议使用精确切换或获取方式,避免歧义
282+
- 在多商户管理、异步场景、线程池等环境中,建议使用 `getConfig(mchId, appId)` 直接获取配置
283+
- 如果使用仅商户号切换或获取,确保该商户号下至少有一个可用的配置
190284

191285
## 相关 API
192286

193287
| 方法 | 参数 | 返回值 | 说明 |
194288
|-----|------|--------|------|
289+
| `getConfig()` || WxPayConfig | 获取当前配置(依赖 ThreadLocal) |
290+
| `getConfig(String mchId, String appId)` | 商户号, appId | WxPayConfig | 直接获取指定配置(不依赖 ThreadLocal) |
291+
| `getConfig(String mchId)` | 商户号 | WxPayConfig | 根据商户号获取配置(不依赖 ThreadLocal) |
195292
| `switchover(String mchId, String appId)` | 商户号, appId | boolean | 精确切换到指定配置 |
196293
| `switchover(String mchId)` | 商户号 | boolean | 仅使用商户号切换 |
197294
| `switchoverTo(String mchId, String appId)` | 商户号, appId | WxPayService | 精确切换,支持链式调用 |

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,11 +785,33 @@ default WxPayService switchoverTo(String mchId) {
785785

786786
/**
787787
* 获取配置.
788+
* 在多商户配置场景下,会根据 WxPayConfigHolder 中的值获取对应的配置.
788789
*
789790
* @return the config
790791
*/
791792
WxPayConfig getConfig();
792793

794+
/**
795+
* 根据商户号和 appId 直接获取配置.
796+
* 此方法不依赖 ThreadLocal,可以在任何上下文中使用,适用于多商户管理场景.
797+
*
798+
* @param mchId 商户号
799+
* @param appId 微信应用 id
800+
* @return 对应的配置对象,如果不存在则返回 null
801+
*/
802+
WxPayConfig getConfig(String mchId, String appId);
803+
804+
/**
805+
* 根据商户号直接获取配置.
806+
* 此方法不依赖 ThreadLocal,可以在任何上下文中使用.
807+
* 适用于一个商户号对应多个 appId 的场景,会返回该商户号的任意一个配置.
808+
* 注意:当存在多个匹配项时返回的配置是不可预测的,建议使用精确匹配方式.
809+
*
810+
* @param mchId 商户号
811+
* @return 对应的配置对象,如果不存在则返回 null
812+
*/
813+
WxPayConfig getConfig(String mchId);
814+
793815
/**
794816
* 设置配置对象.
795817
*

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,45 @@ public WxPayConfig getConfig() {
154154
return this.configMap.get(WxPayConfigHolder.get());
155155
}
156156

157+
@Override
158+
public WxPayConfig getConfig(String mchId, String appId) {
159+
if (StringUtils.isBlank(mchId)) {
160+
log.warn("商户号mchId不能为空");
161+
return null;
162+
}
163+
if (StringUtils.isBlank(appId)) {
164+
log.warn("应用ID appId不能为空");
165+
return null;
166+
}
167+
String configKey = this.getConfigKey(mchId, appId);
168+
return this.configMap.get(configKey);
169+
}
170+
171+
@Override
172+
public WxPayConfig getConfig(String mchId) {
173+
if (StringUtils.isBlank(mchId)) {
174+
log.warn("商户号mchId不能为空");
175+
return null;
176+
}
177+
178+
// 先尝试精确匹配(针对只有mchId没有appId的配置)
179+
if (this.configMap.containsKey(mchId)) {
180+
return this.configMap.get(mchId);
181+
}
182+
183+
// 尝试前缀匹配(查找以 mchId_ 开头的配置)
184+
String prefix = mchId + "_";
185+
for (Map.Entry<String, WxPayConfig> entry : this.configMap.entrySet()) {
186+
if (entry.getKey().startsWith(prefix)) {
187+
log.debug("根据mchId=【{}】找到配置key=【{}】", mchId, entry.getKey());
188+
return entry.getValue();
189+
}
190+
}
191+
192+
log.warn("无法找到对应mchId=【{}】的商户号配置信息", mchId);
193+
return null;
194+
}
195+
157196
@Override
158197
public void setConfig(WxPayConfig config) {
159198
final String defaultKey = this.getConfigKey(config.getMchId(), config.getAppId());

weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MultiAppIdSwitchoverTest.java

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,113 @@ public void setup() {
5252
payService.setMultiConfig(configMap);
5353
}
5454

55+
/**
56+
* 测试直接通过 mchId 和 appId 获取配置(新功能)
57+
*/
58+
@Test
59+
public void testGetConfigWithMchIdAndAppId() {
60+
// 测试获取第一个配置
61+
WxPayConfig config1 = payService.getConfig(testMchId, testAppId1);
62+
assertNotNull(config1, "应该能够获取到配置");
63+
assertEquals(config1.getMchId(), testMchId);
64+
assertEquals(config1.getAppId(), testAppId1);
65+
assertEquals(config1.getMchKey(), "test_key_1");
66+
67+
// 测试获取第二个配置
68+
WxPayConfig config2 = payService.getConfig(testMchId, testAppId2);
69+
assertNotNull(config2);
70+
assertEquals(config2.getAppId(), testAppId2);
71+
assertEquals(config2.getMchKey(), "test_key_2");
72+
73+
// 测试获取第三个配置
74+
WxPayConfig config3 = payService.getConfig(testMchId, testAppId3);
75+
assertNotNull(config3);
76+
assertEquals(config3.getAppId(), testAppId3);
77+
assertEquals(config3.getMchKey(), "test_key_3");
78+
}
79+
80+
/**
81+
* 测试直接通过 mchId 获取配置(新功能)
82+
*/
83+
@Test
84+
public void testGetConfigWithMchIdOnly() {
85+
WxPayConfig config = payService.getConfig(testMchId);
86+
assertNotNull(config, "应该能够通过mchId获取配置");
87+
assertEquals(config.getMchId(), testMchId);
88+
89+
// appId应该是三个中的一个
90+
String currentAppId = config.getAppId();
91+
assertTrue(
92+
testAppId1.equals(currentAppId) || testAppId2.equals(currentAppId) || testAppId3.equals(currentAppId),
93+
"获取的配置的appId应该是配置的appId之一"
94+
);
95+
}
96+
97+
/**
98+
* 测试 getConfig 方法不依赖 ThreadLocal
99+
* 在不切换配置的情况下也能直接获取
100+
*/
101+
@Test
102+
public void testGetConfigWithoutSwitchover() {
103+
// 不进行任何switchover操作,直接通过参数获取配置
104+
WxPayConfig config1 = payService.getConfig(testMchId, testAppId1);
105+
WxPayConfig config2 = payService.getConfig(testMchId, testAppId2);
106+
WxPayConfig config3 = payService.getConfig(testMchId, testAppId3);
107+
108+
// 验证可以同时获取到所有配置,不受 ThreadLocal 影响
109+
assertNotNull(config1);
110+
assertNotNull(config2);
111+
assertNotNull(config3);
112+
113+
assertEquals(config1.getAppId(), testAppId1);
114+
assertEquals(config2.getAppId(), testAppId2);
115+
assertEquals(config3.getAppId(), testAppId3);
116+
}
117+
118+
/**
119+
* 测试 getConfig 方法处理不存在的配置
120+
*/
121+
@Test
122+
public void testGetConfigWithNonexistentConfig() {
123+
// 测试不存在的商户号和appId组合
124+
WxPayConfig config = payService.getConfig("nonexistent_mch_id", "nonexistent_app_id");
125+
assertNull(config, "获取不存在的配置应该返回null");
126+
127+
// 测试存在商户号但不存在的appId
128+
config = payService.getConfig(testMchId, "wx9999999999999999");
129+
assertNull(config, "获取不存在的appId配置应该返回null");
130+
}
131+
132+
/**
133+
* 测试 getConfig 方法处理空参数或null参数
134+
*/
135+
@Test
136+
public void testGetConfigWithNullOrEmptyParameters() {
137+
// 测试 null 商户号
138+
WxPayConfig config = payService.getConfig(null, testAppId1);
139+
assertNull(config, "商户号为null时应该返回null");
140+
141+
// 测试空商户号
142+
config = payService.getConfig("", testAppId1);
143+
assertNull(config, "商户号为空字符串时应该返回null");
144+
145+
// 测试 null appId
146+
config = payService.getConfig(testMchId, null);
147+
assertNull(config, "appId为null时应该返回null");
148+
149+
// 测试空 appId
150+
config = payService.getConfig(testMchId, "");
151+
assertNull(config, "appId为空字符串时应该返回null");
152+
153+
// 测试仅mchId方法的null参数
154+
config = payService.getConfig((String) null);
155+
assertNull(config, "商户号为null时应该返回null");
156+
157+
// 测试仅mchId方法的空字符串
158+
config = payService.getConfig("");
159+
assertNull(config, "商户号为空字符串时应该返回null");
160+
}
161+
55162
/**
56163
* 测试使用 mchId + appId 精确切换(原有功能,确保向后兼容)
57164
*/

0 commit comments

Comments
 (0)