Skip to content

Commit

Permalink
🆕 #3372【视频号】增加支持多视频号账号的spring-boot-starter组件
Browse files Browse the repository at this point in the history
  • Loading branch information
Winnie-by996 authored Sep 16, 2024
1 parent b0b487f commit 0ac68ee
Show file tree
Hide file tree
Showing 19 changed files with 897 additions and 0 deletions.
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

0 comments on commit 0ac68ee

Please sign in to comment.