Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions spring-boot-starters/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<module>wx-java-cp-multi-spring-boot-starter</module>
<module>wx-java-cp-spring-boot-starter</module>
<module>wx-java-channel-spring-boot-starter</module>
<module>wx-java-channel-multi-spring-boot-starter</module>
</modules>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# wx-java-channel-multi-spring-boot-starter

## 快速开始

1. 引入依赖
```xml
<dependencies>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-channel-multi-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>

<!-- 配置存储方式为jedis 则引入jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>

<!-- 配置存储方式为redisson 则引入redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>

<!-- 配置存储方式为redis_template 则引入spring data redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
```
2. 添加配置(application.properties)
```properties
# 视频号配置
## 应用 1 配置(必填)
wx.channel.apps.tenantId1.app-id=@appId
wx.channel.apps.tenantId1.secret=@secret
## 选填
wx.channel.apps.tenantId1.use-stable-access-token=false
wx.channel.apps.tenantId1.token=
wx.channel.apps.tenantId1.aes-key=
## 应用 2 配置(必填)
wx.channel.apps.tenantId2.app-id=@appId
wx.channel.apps.tenantId2.secret=@secret
## 选填
wx.channel.apps.tenantId2.use-stable-access-token=false
wx.channel.apps.tenantId2.token=
wx.channel.apps.tenantId2.aes-key=

# ConfigStorage 配置(选填)
## 配置类型: memory(默认), jedis, redisson, redis_template
wx.channel.config-storage.type=memory
## 相关redis前缀配置: wx:channel:multi(默认)
wx.channel.config-storage.key-prefix=wx:channel:multi
wx.channel.config-storage.redis.host=127.0.0.1
wx.channel.config-storage.redis.port=6379
wx.channel.config-storage.redis.password=123456

# redis_template 方式使用spring data redis配置
spring.data.redis.database=0
spring.data.redis.host=127.0.0.1
spring.data.redis.password=123456
spring.data.redis.port=6379

# http 客户端配置(选填)
## # http客户端类型: http_client(默认)
wx.channel.config-storage.http-client-type=http_client
wx.channel.config-storage.http-proxy-host=
wx.channel.config-storage.http-proxy-port=
wx.channel.config-storage.http-proxy-username=
wx.channel.config-storage.http-proxy-password=
## 最大重试次数,默认:5 次,如果小于 0,则为 0
wx.channel.config-storage.max-retry-times=5
## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000
wx.channel.config-storage.retry-sleep-millis=1000
```
3. 自动注入的类型:`WxChannelMultiServices`

4. 使用样例

```java
import com.binarywang.spring.starter.wxjava.channel.service.WxChannelMultiServices;
import me.chanjar.weixin.channel.api.WxChannelService;
import me.chanjar.weixin.channel.api.WxFinderLiveService;
import me.chanjar.weixin.channel.bean.lead.component.response.FinderAttrResponse;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DemoService {
@Autowired
private WxChannelMultiServices wxChannelMultiServices;

public void test() throws WxErrorException {
// 应用 1 的 WxChannelService
WxChannelService wxChannelService1 = wxChannelMultiServices.getWxChannelService("tenantId1");
WxFinderLiveService finderLiveService = wxChannelService1.getFinderLiveService();
FinderAttrResponse response1 = finderLiveService.getFinderAttrByAppid();
// todo ...

// 应用 2 的 WxChannelService
WxChannelService wxChannelService2 = wxChannelMultiServices.getWxChannelService("tenantId2");
WxFinderLiveService finderLiveService2 = wxChannelService2.getFinderLiveService();
FinderAttrResponse response2 = finderLiveService2.getFinderAttrByAppid();
// todo ...

// 应用 3 的 WxChannelService
WxChannelService wxChannelService3 = wxChannelMultiServices.getWxChannelService("tenantId3");
// 判断是否为空
if (wxChannelService3 == null) {
// todo wxChannelService3 为空,请先配置 tenantId3 微信视频号应用参数
return;
}
WxFinderLiveService finderLiveService3 = wxChannelService3.getFinderLiveService();
FinderAttrResponse response3 = finderLiveService3.getFinderAttrByAppid();
// todo ...
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.6.5.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>wx-java-channel-multi-spring-boot-starter</artifactId>
<name>WxJava - Spring Boot Starter for Channel::支持多账号配置</name>
<description>微信视频号开发的 Spring Boot Starter::支持多账号配置</description>

<dependencies>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-channel</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jodd</groupId>
<artifactId>jodd-http</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.binarywang.spring.starter.wxjava.channel.autoconfigure;

import com.binarywang.spring.starter.wxjava.channel.configuration.WxChannelMultiServiceConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
* 微信视频号自动注册
*
* @author <a href="https://github.com/Winnie-by996">Winnie</a>
* @date 2024/9/13
*/
@Configuration
@Import(WxChannelMultiServiceConfiguration.class)
public class WxChannelMultiAutoConfiguration {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.binarywang.spring.starter.wxjava.channel.configuration;

import com.binarywang.spring.starter.wxjava.channel.configuration.services.WxChannelInJedisConfiguration;
import com.binarywang.spring.starter.wxjava.channel.configuration.services.WxChannelInMemoryConfiguration;
import com.binarywang.spring.starter.wxjava.channel.configuration.services.WxChannelInRedisTemplateConfiguration;
import com.binarywang.spring.starter.wxjava.channel.configuration.services.WxChannelInRedissonConfiguration;
import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelMultiProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
* 微信视频号相关服务自动注册
*
* @author <a href="https://github.com/Winnie-by996">Winnie</a>
* @date 2024/9/13
*/
@Configuration
@EnableConfigurationProperties(WxChannelMultiProperties.class)
@Import({WxChannelInJedisConfiguration.class, WxChannelInMemoryConfiguration.class, WxChannelInRedissonConfiguration.class, WxChannelInRedisTemplateConfiguration.class})
public class WxChannelMultiServiceConfiguration {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package com.binarywang.spring.starter.wxjava.channel.configuration.services;

import com.binarywang.spring.starter.wxjava.channel.enums.HttpClientType;
import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelMultiProperties;
import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelSingleProperties;
import com.binarywang.spring.starter.wxjava.channel.service.WxChannelMultiServices;
import com.binarywang.spring.starter.wxjava.channel.service.WxChannelMultiServicesImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.channel.api.WxChannelService;
import me.chanjar.weixin.channel.api.impl.WxChannelServiceHttpClientImpl;
import me.chanjar.weixin.channel.api.impl.WxChannelServiceImpl;
import me.chanjar.weixin.channel.config.WxChannelConfig;
import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl;
import org.apache.commons.lang3.StringUtils;

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
* WxChannelConfigStorage 抽象配置类
*
* @author <a href="https://github.com/Winnie-by996">Winnie</a>
* @date 2024/9/13
*/
@RequiredArgsConstructor
@Slf4j
public abstract class AbstractWxChannelConfiguration {
protected WxChannelMultiServices wxChannelMultiServices(WxChannelMultiProperties wxChannelMultiProperties) {
Map<String, WxChannelSingleProperties> appsMap = wxChannelMultiProperties.getApps();
if (appsMap == null || appsMap.isEmpty()) {
log.warn("微信视频号应用参数未配置,通过 WxChannelMultiServices#getWxChannelService(\"tenantId\")获取实例将返回空");
return new WxChannelMultiServicesImpl();
}
/**
* 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。
*
* 查看 {@link me.chanjar.weixin.channel.config.impl.WxChannelRedisConfigImpl#setAppid(String)}
*/
Collection<WxChannelSingleProperties> apps = appsMap.values();
if (apps.size() > 1) {
// 校验 appId 是否唯一
boolean multi = apps.stream()
// 没有 appId,如果不判断是否为空,这里会报 NPE 异常
.collect(Collectors.groupingBy(c -> c.getAppId() == null ? 0 : c.getAppId(), Collectors.counting()))
.entrySet().stream().anyMatch(e -> e.getValue() > 1);
if (multi) {
throw new RuntimeException("请确保微信视频号配置 appId 的唯一性");
}
}
WxChannelMultiServicesImpl services = new WxChannelMultiServicesImpl();

Set<Map.Entry<String, WxChannelSingleProperties>> entries = appsMap.entrySet();
for (Map.Entry<String, WxChannelSingleProperties> entry : entries) {
String tenantId = entry.getKey();
WxChannelSingleProperties wxChannelSingleProperties = entry.getValue();
WxChannelDefaultConfigImpl storage = this.wxChannelConfigStorage(wxChannelMultiProperties);
this.configApp(storage, wxChannelSingleProperties);
this.configHttp(storage, wxChannelMultiProperties.getConfigStorage());
WxChannelService wxChannelService = this.wxChannelService(storage, wxChannelMultiProperties, wxChannelSingleProperties.isUseStableAccessToken());
services.addWxChannelService(tenantId, wxChannelService);
}
return services;
}

/**
* 配置 WxChannelDefaultConfigImpl
*
* @param wxChannelMultiProperties 参数
* @return WxChannelDefaultConfigImpl
*/
protected abstract WxChannelDefaultConfigImpl wxChannelConfigStorage(WxChannelMultiProperties wxChannelMultiProperties);

public WxChannelService wxChannelService(WxChannelConfig wxChannelConfig, WxChannelMultiProperties wxChannelMultiProperties, boolean useStableAccessToken) {
WxChannelMultiProperties.ConfigStorage storage = wxChannelMultiProperties.getConfigStorage();
HttpClientType httpClientType = storage.getHttpClientType();
WxChannelService wxChannelService;
switch (httpClientType) {
// case OK_HTTP:
// wxChannelService = new WxChannelServiceOkHttpImpl(false, false);
// break;
case HTTP_CLIENT:
wxChannelService = new WxChannelServiceHttpClientImpl(useStableAccessToken, false);
break;
default:
wxChannelService = new WxChannelServiceImpl();
break;
}

wxChannelService.setConfig(wxChannelConfig);
int maxRetryTimes = storage.getMaxRetryTimes();
if (maxRetryTimes < 0) {
maxRetryTimes = 0;
}
int retrySleepMillis = storage.getRetrySleepMillis();
if (retrySleepMillis < 0) {
retrySleepMillis = 1000;
}
wxChannelService.setRetrySleepMillis(retrySleepMillis);
wxChannelService.setMaxRetryTimes(maxRetryTimes);
return wxChannelService;
}

private void configApp(WxChannelDefaultConfigImpl config, WxChannelSingleProperties wxChannelSingleProperties) {
String appId = wxChannelSingleProperties.getAppId();
String appSecret = wxChannelSingleProperties.getSecret();
String token = wxChannelSingleProperties.getToken();
String aesKey = wxChannelSingleProperties.getAesKey();

config.setAppid(appId);
config.setSecret(appSecret);
if (StringUtils.isNotBlank(token)) {
config.setToken(token);
}
if (StringUtils.isNotBlank(aesKey)) {
config.setAesKey(aesKey);
}
}

private void configHttp(WxChannelDefaultConfigImpl config, WxChannelMultiProperties.ConfigStorage storage) {
String httpProxyHost = storage.getHttpProxyHost();
Integer httpProxyPort = storage.getHttpProxyPort();
String httpProxyUsername = storage.getHttpProxyUsername();
String httpProxyPassword = storage.getHttpProxyPassword();
if (StringUtils.isNotBlank(httpProxyHost)) {
config.setHttpProxyHost(httpProxyHost);
if (httpProxyPort != null) {
config.setHttpProxyPort(httpProxyPort);
}
if (StringUtils.isNotBlank(httpProxyUsername)) {
config.setHttpProxyUsername(httpProxyUsername);
}
if (StringUtils.isNotBlank(httpProxyPassword)) {
config.setHttpProxyPassword(httpProxyPassword);
}
}
}
}
Loading