Skip to content

[企业微信] 企业微信多账号配置 starter #3149

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 18, 2023
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
5 changes: 4 additions & 1 deletion spring-boot-starters/pom.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?xml version="1.0"?>
<project 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" xmlns="http://maven.apache.org/POM/4.0.0">
<project 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"
xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.binarywang</groupId>
Expand All @@ -21,6 +23,7 @@
<module>wx-java-pay-spring-boot-starter</module>
<module>wx-java-open-spring-boot-starter</module>
<module>wx-java-qidian-spring-boot-starter</module>
<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>
</modules>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# wx-java-cp-multi-spring-boot-starter

企业微信多账号配置

- 实现多 WxCpService 初始化。
- 未实现 WxCpTpService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
- 未实现 WxCpCgService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。

## 快速开始

1. 引入依赖
```xml
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-cp-multi-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
```
2. 添加配置(application.properties)
```properties
# 应用 1 配置
wx.cp.corps.tenantId1.corp-id = @corp-id
wx.cp.corps.tenantId1.corp-secret = @corp-secret
## 选填
wx.cp.corps.tenantId1.agent-id = @agent-id
wx.cp.corps.tenantId1.token = @token
wx.cp.corps.tenantId1.aes-key = @aes-key
wx.cp.corps.tenantId1.msg-audit-priKey = @msg-audit-priKey
wx.cp.corps.tenantId1.msg-audit-lib-path = @msg-audit-lib-path

# 应用 2 配置
wx.cp.corps.tenantId2.corp-id = @corp-id
wx.cp.corps.tenantId2.corp-secret = @corp-secret
## 选填
wx.cp.corps.tenantId2.agent-id = @agent-id
wx.cp.corps.tenantId2.token = @token
wx.cp.corps.tenantId2.aes-key = @aes-key
wx.cp.corps.tenantId2.msg-audit-priKey = @msg-audit-priKey
wx.cp.corps.tenantId2.msg-audit-lib-path = @msg-audit-lib-path

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

4. 使用样例

```java
import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices;
import com.binarywang.spring.starter.wxjava.cp.service.WxCpServices;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.WxCpUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DemoService {
@Autowired
private WxCpMultiServices wxCpMultiServices;

public void test() {
// 应用 1 的 WxCpService
WxCpService wxCpService1 = wxCpMultiServices.getWxCpService("tenantId1");
WxCpUserService userService1 = wxCpService1.getUserService();
userService1.getUserId("xxx");
// todo ...

// 应用 2 的 WxCpService
WxCpService wxCpService2 = wxCpMultiServices.getWxCpService("tenantId2");
WxCpUserService userService2 = wxCpService2.getUserService();
userService2.getUserId("xxx");
// todo ...
}
}
```
60 changes: 60 additions & 0 deletions spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?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.5.5.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>wx-java-cp-multi-spring-boot-starter</artifactId>
<name>WxJava - Spring Boot Starter for WxCp::支持多账号配置</name>
<description>微信企业号开发的 Spring Boot Starter::支持多账号配置</description>

<dependencies>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-cp</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>
</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,20 @@
package com.binarywang.spring.starter.wxjava.cp.autoconfigure;

import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
* 企业微信自动注册
*
* @author yl
* created on 2023/10/16
*/
@Configuration
@EnableConfigurationProperties(WxCpMultiProperties.class)
@Import({
WxCpMultiServicesAutoConfiguration.class
})
public class WxCpMultiAutoConfiguration {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.binarywang.spring.starter.wxjava.cp.autoconfigure;

import com.binarywang.spring.starter.wxjava.cp.autoconfigure.services.WxCpInJedisConfiguration;
import com.binarywang.spring.starter.wxjava.cp.autoconfigure.services.WxCpInMemoryConfiguration;
import com.binarywang.spring.starter.wxjava.cp.autoconfigure.services.WxCpInRedisTemplateConfiguration;
import com.binarywang.spring.starter.wxjava.cp.autoconfigure.services.WxCpInRedissonConfiguration;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
* 企业微信平台相关服务自动注册
*
* @author yl
* created on 2023/10/16
*/
@Configuration
@RequiredArgsConstructor
@Import({
WxCpInJedisConfiguration.class,
WxCpInMemoryConfiguration.class,
WxCpInRedissonConfiguration.class,
WxCpInRedisTemplateConfiguration.class
})
public class WxCpMultiServicesAutoConfiguration {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.binarywang.spring.starter.wxjava.cp.autoconfigure.services;

import com.binarywang.spring.starter.wxjava.cp.properties.CorpProperties;
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties;
import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices;
import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServicesImpl;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
import org.apache.commons.lang3.StringUtils;

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

/**
* WxCpConfigStorage 抽象配置类
*
* @author yl
* created on 2023/10/16
*/
@RequiredArgsConstructor
public abstract class AbstractWxCpConfiguration {

protected WxCpMultiServices configWxCpServices(WxCpMultiProperties wxCpMultiProperties) {
WxCpMultiServicesImpl wxCpServices = new WxCpMultiServicesImpl();
Map<String, CorpProperties> corps = wxCpMultiProperties.getCorps();
if (corps == null || corps.isEmpty()) {
throw new RuntimeException("企业微信配置为null");
}
/**
* 校验同一个企业下,agentId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。
*
* 查看 {@link me.chanjar.weixin.cp.config.impl.AbstractWxCpInRedisConfigImpl#setAgentId(Integer)}
*/
Collection<CorpProperties> corpList = corps.values();
if (corpList.size() > 1) {
// 先按 corpId 分组统计
Map<String, List<CorpProperties>> corpsMap = corpList.stream()
.collect(Collectors.groupingBy(CorpProperties::getCorpId));
Set<Map.Entry<String, List<CorpProperties>>> entries = corpsMap.entrySet();
for (Map.Entry<String, List<CorpProperties>> entry : entries) {
String corpId = entry.getKey();
// 校验每个企业下,agentId 是否唯一
boolean multi = entry.getValue().stream()
// 通讯录没有 agentId,如果不判断是否为空,这里会报 NPE 异常
.collect(Collectors.groupingBy(c -> c.getAgentId() == null ? 0 : c.getAgentId(), Collectors.counting()))
.entrySet().stream().anyMatch(e -> e.getValue() > 1);
if (multi) {
throw new RuntimeException("请确保企业微信配置唯一性[" + corpId + "]");
}
}
}

Set<Map.Entry<String, CorpProperties>> entries = corps.entrySet();
for (Map.Entry<String, CorpProperties> entry : entries) {
String tenantId = entry.getKey();
CorpProperties corpProperties = entry.getValue();
WxCpDefaultConfigImpl storage = this.configWxCpDefaultConfigImpl(wxCpMultiProperties);
this.configCorp(storage, corpProperties);
this.configHttp(storage, wxCpMultiProperties.getConfigStorage());
WxCpService wxCpService = this.configWxCpService(storage, wxCpMultiProperties.getConfigStorage());
wxCpServices.addWxCpService(tenantId, wxCpService);
}
return wxCpServices;
}

/**
* 配置 WxCpDefaultConfigImpl
*
* @param wxCpMultiProperties 参数
* @return WxCpDefaultConfigImpl
*/
protected abstract WxCpDefaultConfigImpl configWxCpDefaultConfigImpl(WxCpMultiProperties wxCpMultiProperties);

private WxCpService configWxCpService(WxCpConfigStorage wxCpConfigStorage, WxCpMultiProperties.ConfigStorage storage) {
WxCpService wxCpService = new WxCpServiceImpl();
wxCpService.setWxCpConfigStorage(wxCpConfigStorage);

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

private void configCorp(WxCpDefaultConfigImpl config, CorpProperties corpProperties) {
String corpId = corpProperties.getCorpId();
String corpSecret = corpProperties.getCorpSecret();
Integer agentId = corpProperties.getAgentId();
String token = corpProperties.getToken();
String aesKey = corpProperties.getAesKey();
// 企业微信,私钥,会话存档路径
String msgAuditPriKey = corpProperties.getMsgAuditPriKey();
String msgAuditLibPath = corpProperties.getMsgAuditLibPath();

config.setCorpId(corpId);
config.setCorpSecret(corpSecret);
config.setAgentId(agentId);
if (StringUtils.isNotBlank(token)) {
config.setToken(token);
}
if (StringUtils.isNotBlank(aesKey)) {
config.setAesKey(aesKey);
}
if (StringUtils.isNotBlank(msgAuditPriKey)) {
config.setMsgAuditPriKey(msgAuditPriKey);
}
if (StringUtils.isNotBlank(msgAuditLibPath)) {
config.setMsgAuditLibPath(msgAuditLibPath);
}
}

private void configHttp(WxCpDefaultConfigImpl config, WxCpMultiProperties.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