Skip to content

Commit 6a9852f

Browse files
authored
🆕 #3847 【开放平台】新增 wx-java-open-multi-spring-boot-starter 支持多开放平台配置
1 parent 777f4e5 commit 6a9852f

17 files changed

+873
-0
lines changed

spring-boot-starters/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<module>wx-java-mp-multi-spring-boot-starter</module>
2424
<module>wx-java-mp-spring-boot-starter</module>
2525
<module>wx-java-pay-spring-boot-starter</module>
26+
<module>wx-java-open-multi-spring-boot-starter</module>
2627
<module>wx-java-open-spring-boot-starter</module>
2728
<module>wx-java-qidian-spring-boot-starter</module>
2829
<module>wx-java-cp-multi-spring-boot-starter</module>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# wx-java-open-multi-spring-boot-starter
2+
3+
## 快速开始
4+
5+
1. 引入依赖
6+
```xml
7+
<dependency>
8+
<groupId>com.github.binarywang</groupId>
9+
<artifactId>wx-java-open-multi-spring-boot-starter</artifactId>
10+
<version>${version}</version>
11+
</dependency>
12+
```
13+
2. 添加配置(application.properties)
14+
```properties
15+
# 开放平台配置
16+
## 应用 1 配置(必填)
17+
wx.open.apps.tenantId1.app-id=appId
18+
wx.open.apps.tenantId1.secret=@secret
19+
## 选填
20+
wx.open.apps.tenantId1.token=@token
21+
wx.open.apps.tenantId1.aes-key=@aesKey
22+
wx.open.apps.tenantId1.api-host-url=@apiHostUrl
23+
wx.open.apps.tenantId1.access-token-url=@accessTokenUrl
24+
## 应用 2 配置(必填)
25+
wx.open.apps.tenantId2.app-id=@appId
26+
wx.open.apps.tenantId2.secret=@secret
27+
## 选填
28+
wx.open.apps.tenantId2.token=@token
29+
wx.open.apps.tenantId2.aes-key=@aesKey
30+
wx.open.apps.tenantId2.api-host-url=@apiHostUrl
31+
wx.open.apps.tenantId2.access-token-url=@accessTokenUrl
32+
33+
# ConfigStorage 配置(选填)
34+
## 配置类型: memory(默认), jedis, redisson, redistemplate
35+
wx.open.config-storage.type=memory
36+
## 相关redis前缀配置: wx:open:multi(默认)
37+
wx.open.config-storage.key-prefix=wx:open:multi
38+
wx.open.config-storage.redis.host=127.0.0.1
39+
wx.open.config-storage.redis.port=6379
40+
## 注意:当前版本暂不支持 sentinel 配置,以下配置仅作为预留
41+
# wx.open.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379
42+
# wx.open.config-storage.redis.sentinel-name=mymaster
43+
44+
# http 客户端配置(选填)
45+
wx.open.config-storage.http-proxy-host=
46+
wx.open.config-storage.http-proxy-port=
47+
wx.open.config-storage.http-proxy-username=
48+
wx.open.config-storage.http-proxy-password=
49+
## 最大重试次数,默认:5 次,如果小于 0,则为 0
50+
wx.open.config-storage.max-retry-times=5
51+
## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000
52+
wx.open.config-storage.retry-sleep-millis=1000
53+
## 连接超时时间,单位毫秒,默认:5000
54+
wx.open.config-storage.connection-timeout=5000
55+
## 读数据超时时间,即socketTimeout,单位毫秒,默认:5000
56+
wx.open.config-storage.so-timeout=5000
57+
## 从连接池获取链接的超时时间,单位毫秒,默认:5000
58+
wx.open.config-storage.connection-request-timeout=5000
59+
```
60+
3. 自动注入的类型:`WxOpenMultiServices`
61+
62+
4. 使用样例
63+
64+
```java
65+
import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServices;
66+
import me.chanjar.weixin.open.api.WxOpenService;
67+
import me.chanjar.weixin.open.api.WxOpenComponentService;
68+
import org.springframework.beans.factory.annotation.Autowired;
69+
import org.springframework.stereotype.Service;
70+
71+
@Service
72+
public class DemoService {
73+
@Autowired
74+
private WxOpenMultiServices wxOpenMultiServices;
75+
76+
public void test() {
77+
// 应用 1 的 WxOpenService
78+
WxOpenService wxOpenService1 = wxOpenMultiServices.getWxOpenService("tenantId1");
79+
WxOpenComponentService componentService1 = wxOpenService1.getWxOpenComponentService();
80+
// todo ...
81+
82+
// 应用 2 的 WxOpenService
83+
WxOpenService wxOpenService2 = wxOpenMultiServices.getWxOpenService("tenantId2");
84+
WxOpenComponentService componentService2 = wxOpenService2.getWxOpenComponentService();
85+
// todo ...
86+
87+
// 应用 3 的 WxOpenService
88+
WxOpenService wxOpenService3 = wxOpenMultiServices.getWxOpenService("tenantId3");
89+
// 判断是否为空
90+
if (wxOpenService3 == null) {
91+
// todo wxOpenService3 为空,请先配置 tenantId3 微信开放平台应用参数
92+
return;
93+
}
94+
WxOpenComponentService componentService3 = wxOpenService3.getWxOpenComponentService();
95+
// todo ...
96+
}
97+
}
98+
```
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>wx-java-spring-boot-starters</artifactId>
7+
<groupId>com.github.binarywang</groupId>
8+
<version>4.8.0</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>wx-java-open-multi-spring-boot-starter</artifactId>
13+
<name>WxJava - Spring Boot Starter for WxOpen::支持多账号配置</name>
14+
<description>微信开放平台开发的 Spring Boot Starter::支持多账号配置</description>
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>com.github.binarywang</groupId>
19+
<artifactId>weixin-java-open</artifactId>
20+
<version>${project.version}</version>
21+
</dependency>
22+
<dependency>
23+
<groupId>redis.clients</groupId>
24+
<artifactId>jedis</artifactId>
25+
<scope>provided</scope>
26+
</dependency>
27+
<dependency>
28+
<groupId>org.redisson</groupId>
29+
<artifactId>redisson</artifactId>
30+
<scope>provided</scope>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.springframework.data</groupId>
34+
<artifactId>spring-data-redis</artifactId>
35+
<scope>provided</scope>
36+
</dependency>
37+
</dependencies>
38+
39+
<build>
40+
<plugins>
41+
<plugin>
42+
<groupId>org.springframework.boot</groupId>
43+
<artifactId>spring-boot-maven-plugin</artifactId>
44+
<version>${spring.boot.version}</version>
45+
</plugin>
46+
<plugin>
47+
<groupId>org.apache.maven.plugins</groupId>
48+
<artifactId>maven-source-plugin</artifactId>
49+
<version>2.2.1</version>
50+
<executions>
51+
<execution>
52+
<id>attach-sources</id>
53+
<goals>
54+
<goal>jar-no-fork</goal>
55+
</goals>
56+
</execution>
57+
</executions>
58+
</plugin>
59+
</plugins>
60+
</build>
61+
62+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.binarywang.spring.starter.wxjava.open.autoconfigure;
2+
3+
import com.binarywang.spring.starter.wxjava.open.configuration.WxOpenMultiServiceConfiguration;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.context.annotation.Import;
6+
7+
/**
8+
* 微信开放平台多账号自动配置
9+
*
10+
* @author Binary Wang
11+
*/
12+
@Configuration
13+
@Import(WxOpenMultiServiceConfiguration.class)
14+
public class WxOpenMultiAutoConfiguration {
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.binarywang.spring.starter.wxjava.open.configuration;
2+
3+
import com.binarywang.spring.starter.wxjava.open.configuration.services.WxOpenInJedisConfiguration;
4+
import com.binarywang.spring.starter.wxjava.open.configuration.services.WxOpenInMemoryConfiguration;
5+
import com.binarywang.spring.starter.wxjava.open.configuration.services.WxOpenInRedisTemplateConfiguration;
6+
import com.binarywang.spring.starter.wxjava.open.configuration.services.WxOpenInRedissonConfiguration;
7+
import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiProperties;
8+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
9+
import org.springframework.context.annotation.Configuration;
10+
import org.springframework.context.annotation.Import;
11+
12+
/**
13+
* 微信开放平台相关服务自动注册
14+
*
15+
* @author Binary Wang
16+
*/
17+
@Configuration
18+
@EnableConfigurationProperties(WxOpenMultiProperties.class)
19+
@Import({
20+
WxOpenInJedisConfiguration.class,
21+
WxOpenInMemoryConfiguration.class,
22+
WxOpenInRedissonConfiguration.class,
23+
WxOpenInRedisTemplateConfiguration.class
24+
})
25+
public class WxOpenMultiServiceConfiguration {
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package com.binarywang.spring.starter.wxjava.open.configuration.services;
2+
3+
import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiProperties;
4+
import com.binarywang.spring.starter.wxjava.open.properties.WxOpenSingleProperties;
5+
import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServices;
6+
import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServicesImpl;
7+
import lombok.RequiredArgsConstructor;
8+
import lombok.extern.slf4j.Slf4j;
9+
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
10+
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
11+
import me.chanjar.weixin.open.api.WxOpenConfigStorage;
12+
import me.chanjar.weixin.open.api.WxOpenService;
13+
import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage;
14+
import me.chanjar.weixin.open.api.impl.WxOpenServiceImpl;
15+
import org.apache.commons.lang3.StringUtils;
16+
17+
import java.util.Collection;
18+
import java.util.Map;
19+
import java.util.Set;
20+
import java.util.stream.Collectors;
21+
22+
/**
23+
* WxOpenConfigStorage 抽象配置类
24+
*
25+
* @author Binary Wang
26+
*/
27+
@RequiredArgsConstructor
28+
@Slf4j
29+
public abstract class AbstractWxOpenConfiguration {
30+
31+
protected WxOpenMultiServices wxOpenMultiServices(WxOpenMultiProperties wxOpenMultiProperties) {
32+
Map<String, WxOpenSingleProperties> appsMap = wxOpenMultiProperties.getApps();
33+
if (appsMap == null || appsMap.isEmpty()) {
34+
log.warn("微信开放平台应用参数未配置,通过 WxOpenMultiServices#getWxOpenService(\"tenantId\")获取实例将返回空");
35+
return new WxOpenMultiServicesImpl();
36+
}
37+
/**
38+
* 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。
39+
*/
40+
Collection<WxOpenSingleProperties> apps = appsMap.values();
41+
if (apps.size() > 1) {
42+
// 校验 appId 是否唯一
43+
String nullAppIdPlaceholder = "__NULL_APP_ID__";
44+
boolean multi = apps.stream()
45+
// 没有 appId,如果不判断是否为空,这里会报 NPE 异常
46+
.collect(Collectors.groupingBy(c -> c.getAppId() == null ? nullAppIdPlaceholder : c.getAppId(), Collectors.counting()))
47+
.entrySet().stream().anyMatch(e -> e.getValue() > 1);
48+
if (multi) {
49+
throw new RuntimeException("请确保微信开放平台配置 appId 的唯一性");
50+
}
51+
}
52+
WxOpenMultiServicesImpl services = new WxOpenMultiServicesImpl();
53+
54+
Set<Map.Entry<String, WxOpenSingleProperties>> entries = appsMap.entrySet();
55+
for (Map.Entry<String, WxOpenSingleProperties> entry : entries) {
56+
String tenantId = entry.getKey();
57+
WxOpenSingleProperties wxOpenSingleProperties = entry.getValue();
58+
WxOpenInMemoryConfigStorage storage = this.wxOpenConfigStorage(wxOpenMultiProperties);
59+
this.configApp(storage, wxOpenSingleProperties);
60+
this.configHttp(storage, wxOpenMultiProperties.getConfigStorage());
61+
WxOpenService wxOpenService = this.wxOpenService(storage, wxOpenMultiProperties);
62+
services.addWxOpenService(tenantId, wxOpenService);
63+
}
64+
return services;
65+
}
66+
67+
/**
68+
* 配置 WxOpenInMemoryConfigStorage
69+
*
70+
* @param wxOpenMultiProperties 参数
71+
* @return WxOpenInMemoryConfigStorage
72+
*/
73+
protected abstract WxOpenInMemoryConfigStorage wxOpenConfigStorage(WxOpenMultiProperties wxOpenMultiProperties);
74+
75+
public WxOpenService wxOpenService(WxOpenConfigStorage configStorage, WxOpenMultiProperties wxOpenMultiProperties) {
76+
WxOpenService wxOpenService = new WxOpenServiceImpl();
77+
wxOpenService.setWxOpenConfigStorage(configStorage);
78+
return wxOpenService;
79+
}
80+
81+
private void configApp(WxOpenInMemoryConfigStorage config, WxOpenSingleProperties appProperties) {
82+
String appId = appProperties.getAppId();
83+
String secret = appProperties.getSecret();
84+
String token = appProperties.getToken();
85+
String aesKey = appProperties.getAesKey();
86+
String apiHostUrl = appProperties.getApiHostUrl();
87+
String accessTokenUrl = appProperties.getAccessTokenUrl();
88+
89+
// appId 和 secret 是必需的
90+
if (StringUtils.isBlank(appId)) {
91+
throw new IllegalArgumentException("微信开放平台 appId 不能为空");
92+
}
93+
if (StringUtils.isBlank(secret)) {
94+
throw new IllegalArgumentException("微信开放平台 secret 不能为空");
95+
}
96+
97+
config.setComponentAppId(appId);
98+
config.setComponentAppSecret(secret);
99+
if (StringUtils.isNotBlank(token)) {
100+
config.setComponentToken(token);
101+
}
102+
if (StringUtils.isNotBlank(aesKey)) {
103+
config.setComponentAesKey(aesKey);
104+
}
105+
// 设置URL配置
106+
config.setApiHostUrl(StringUtils.trimToNull(apiHostUrl));
107+
config.setAccessTokenUrl(StringUtils.trimToNull(accessTokenUrl));
108+
}
109+
110+
private void configHttp(WxOpenInMemoryConfigStorage config, WxOpenMultiProperties.ConfigStorage storage) {
111+
String httpProxyHost = storage.getHttpProxyHost();
112+
Integer httpProxyPort = storage.getHttpProxyPort();
113+
String httpProxyUsername = storage.getHttpProxyUsername();
114+
String httpProxyPassword = storage.getHttpProxyPassword();
115+
if (StringUtils.isNotBlank(httpProxyHost)) {
116+
config.setHttpProxyHost(httpProxyHost);
117+
if (httpProxyPort != null) {
118+
config.setHttpProxyPort(httpProxyPort);
119+
}
120+
if (StringUtils.isNotBlank(httpProxyUsername)) {
121+
config.setHttpProxyUsername(httpProxyUsername);
122+
}
123+
if (StringUtils.isNotBlank(httpProxyPassword)) {
124+
config.setHttpProxyPassword(httpProxyPassword);
125+
}
126+
}
127+
128+
// 设置重试配置
129+
int maxRetryTimes = storage.getMaxRetryTimes();
130+
if (maxRetryTimes < 0) {
131+
maxRetryTimes = 0;
132+
}
133+
int retrySleepMillis = storage.getRetrySleepMillis();
134+
if (retrySleepMillis < 0) {
135+
retrySleepMillis = 1000;
136+
}
137+
config.setRetrySleepMillis(retrySleepMillis);
138+
config.setMaxRetryTimes(maxRetryTimes);
139+
140+
// 设置自定义的HttpClient超时配置
141+
ApacheHttpClientBuilder clientBuilder = config.getApacheHttpClientBuilder();
142+
if (clientBuilder == null) {
143+
clientBuilder = DefaultApacheHttpClientBuilder.get();
144+
}
145+
if (clientBuilder instanceof DefaultApacheHttpClientBuilder) {
146+
DefaultApacheHttpClientBuilder defaultBuilder = (DefaultApacheHttpClientBuilder) clientBuilder;
147+
defaultBuilder.setConnectionTimeout(storage.getConnectionTimeout());
148+
defaultBuilder.setSoTimeout(storage.getSoTimeout());
149+
defaultBuilder.setConnectionRequestTimeout(storage.getConnectionRequestTimeout());
150+
config.setApacheHttpClientBuilder(defaultBuilder);
151+
}
152+
}
153+
}

0 commit comments

Comments
 (0)