Skip to content

Commit b79206d

Browse files
authored
📝 明确说明 wx-java-cp-multi-spring-boot-starter 中 corp-secret 的配置方式
1 parent 24703be commit b79206d

6 files changed

Lines changed: 168 additions & 82 deletions

File tree

solon-plugins/wx-java-cp-multi-solon-plugin/README.md

Lines changed: 48 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,25 @@
66
- 未实现 WxCpTpService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
77
- 未实现 WxCpCgService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
88

9+
## 关于 corp-secret 的说明
10+
11+
企业微信中不同功能模块对应不同的 `corp-secret`,每种 Secret 只对对应模块的接口具有调用权限:
12+
13+
| Secret 类型 | 获取位置 | 可调用的接口 | 是否需要 agent-id |
14+
|---|---|---|---|
15+
| 自建应用 Secret | 应用管理 → 自建应用 → 选择应用 → 查看 Secret | 该应用有权限的接口 | **必填** |
16+
| 通讯录同步 Secret | 管理工具 → 通讯录同步 → 查看 Secret | 部门/成员增删改查等通讯录接口 | **不填** |
17+
| 客户联系 Secret | 客户联系 → API → Secret | 客户联系相关接口 | 不填 |
18+
19+
> **常见问题**
20+
> - 使用自建应用 Secret + agent-id 可以获取部门列表,但**无法更新部门**(因为写接口需要通讯录同步权限)
21+
> - 使用通讯录同步 Secret 可以同步部门,但**调用某些需要 agent-id 的应用接口会报错**
22+
23+
如需同时使用多种权限范围,可在 `wx.cp.corps` 下配置多个条目,每个条目使用对应权限的 Secret,通过不同的 `tenantId` 区分后使用。
24+
25+
> **注意**
26+
> 当前插件实现会校验同一 `corp-id` 下的 `agent-id` **必须唯一**,并且 **只能有一个条目不填写 `agent-id`**
27+
> 如果在同一 `corp-id` 下同时配置多个未填写 `agent-id` 的条目,会因 token/ticket 缓存 key 冲突而在启动时直接抛异常。
928
## 快速开始
1029

1130
1. 引入依赖
@@ -18,25 +37,21 @@
1837
```
1938
2. 添加配置(app.properties)
2039
```properties
21-
# 应用 1 配置
22-
wx.cp.corps.tenantId1.corp-id = @corp-id
23-
wx.cp.corps.tenantId1.corp-secret = @corp-secret
40+
# 自建应用 1 配置(使用自建应用 Secret,需填写 agent-id)
41+
wx.cp.corps.app1.corp-id = @corp-id
42+
wx.cp.corps.app1.corp-secret = @自建应用的Secret(在"应用管理-自建应用"中查看)
43+
wx.cp.corps.app1.agent-id = @自建应用的AgentId
2444
## 选填
25-
wx.cp.corps.tenantId1.agent-id = @agent-id
26-
wx.cp.corps.tenantId1.token = @token
27-
wx.cp.corps.tenantId1.aes-key = @aes-key
28-
wx.cp.corps.tenantId1.msg-audit-priKey = @msg-audit-priKey
29-
wx.cp.corps.tenantId1.msg-audit-lib-path = @msg-audit-lib-path
30-
31-
# 应用 2 配置
32-
wx.cp.corps.tenantId2.corp-id = @corp-id
33-
wx.cp.corps.tenantId2.corp-secret = @corp-secret
34-
## 选填
35-
wx.cp.corps.tenantId2.agent-id = @agent-id
36-
wx.cp.corps.tenantId2.token = @token
37-
wx.cp.corps.tenantId2.aes-key = @aes-key
38-
wx.cp.corps.tenantId2.msg-audit-priKey = @msg-audit-priKey
39-
wx.cp.corps.tenantId2.msg-audit-lib-path = @msg-audit-lib-path
45+
wx.cp.corps.app1.token = @token
46+
wx.cp.corps.app1.aes-key = @aes-key
47+
wx.cp.corps.app1.msg-audit-priKey = @msg-audit-priKey
48+
wx.cp.corps.app1.msg-audit-lib-path = @msg-audit-lib-path
49+
50+
# 通讯录同步配置(使用通讯录同步 Secret,不需要填写 agent-id)
51+
# 此配置用于部门、成员的增删改查等通讯录管理操作
52+
wx.cp.corps.contact.corp-id = @corp-id
53+
wx.cp.corps.contact.corp-secret = @通讯录同步的Secret(在"管理工具-通讯录同步"中查看)
54+
## agent-id 不填,通讯录同步不需要 agentId
4055

4156
# 公共配置
4257
## ConfigStorage 配置(选填)
@@ -59,8 +74,10 @@
5974

6075
```java
6176
import com.binarywang.solon.wxjava.cp_multi.service.WxCpMultiServices;
77+
import me.chanjar.weixin.cp.api.WxCpDepartmentService;
6278
import me.chanjar.weixin.cp.api.WxCpService;
6379
import me.chanjar.weixin.cp.api.WxCpUserService;
80+
import me.chanjar.weixin.cp.bean.WxCpDepart;
6481
import org.noear.solon.annotation.Component;
6582
import org.noear.solon.annotation.Inject;
6683

@@ -70,27 +87,21 @@ public class DemoService {
7087
private WxCpMultiServices wxCpMultiServices;
7188

7289
public void test() {
73-
// 应用 1 的 WxCpService
74-
WxCpService wxCpService1 = wxCpMultiServices.getWxCpService("tenantId1");
75-
WxCpUserService userService1 = wxCpService1.getUserService();
76-
userService1.getUserId("xxx");
77-
// todo ...
78-
79-
// 应用 2 的 WxCpService
80-
WxCpService wxCpService2 = wxCpMultiServices.getWxCpService("tenantId2");
81-
WxCpUserService userService2 = wxCpService2.getUserService();
82-
userService2.getUserId("xxx");
90+
// 使用自建应用的 WxCpService(对应 corp-secret 为自建应用 Secret)
91+
WxCpService appService = wxCpMultiServices.getWxCpService("app1");
92+
WxCpUserService userService = appService.getUserService();
93+
userService.getUserId("xxx");
8394
// todo ...
8495

85-
// 应用 3 的 WxCpService
86-
WxCpService wxCpService3 = wxCpMultiServices.getWxCpService("tenantId3");
87-
// 判断是否为空
88-
if (wxCpService3 == null) {
89-
// todo wxCpService3 为空,请先配置 tenantId3 企业微信应用参数
90-
return;
91-
}
92-
WxCpUserService userService3 = wxCpService3.getUserService();
93-
userService3.getUserId("xxx");
96+
// 使用通讯录同步的 WxCpService(对应 corp-secret 为通讯录同步 Secret)
97+
// 通讯录同步 Secret 具有部门/成员增删改查等权限
98+
WxCpService contactService = wxCpMultiServices.getWxCpService("contact");
99+
WxCpDepartmentService departmentService = contactService.getDepartmentService();
100+
// 更新部门示例(WxCpDepart 包含 id、name、parentId 等字段)
101+
WxCpDepart depart = new WxCpDepart();
102+
depart.setId(100L);
103+
depart.setName("新部门名称");
104+
departmentService.update(depart);
94105
// todo ...
95106
}
96107
}

solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/AbstractWxCpConfiguration.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.Collection;
1616
import java.util.List;
1717
import java.util.Map;
18+
import java.util.Objects;
1819
import java.util.Set;
1920
import java.util.stream.Collectors;
2021

@@ -37,6 +38,13 @@ protected WxCpMultiServices wxCpMultiServices(WxCpMultiProperties wxCpMultiPrope
3738
/**
3839
* 校验同一个企业下,agentId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。
3940
*
41+
* <p>同一企业(corpId 相同)下可配置多个条目以使用不同的权限 Secret,例如:</p>
42+
* <ul>
43+
* <li>自建应用条目:填写应用对应的 corpSecret 和 agentId</li>
44+
* <li>通讯录同步条目:填写通讯录同步 Secret,agentId 可不填(null)</li>
45+
* </ul>
46+
* <p>但同一 corpId 下不允许出现重复的 agentId(包括多个 null)。</p>
47+
*
4048
* 查看 {@link me.chanjar.weixin.cp.config.impl.AbstractWxCpInRedisConfigImpl#setAgentId(Integer)}
4149
*/
4250
Collection<WxCpSingleProperties> corpList = corps.values();
@@ -49,8 +57,8 @@ protected WxCpMultiServices wxCpMultiServices(WxCpMultiProperties wxCpMultiPrope
4957
String corpId = entry.getKey();
5058
// 校验每个企业下,agentId 是否唯一
5159
boolean multi = entry.getValue().stream()
52-
// 通讯录没有 agentId,如果不判断是否为空,这里会报 NPE 异常
53-
.collect(Collectors.groupingBy(c -> c.getAgentId() == null ? 0 : c.getAgentId(), Collectors.counting()))
60+
// 通讯录没有 agentId,使用字符串转换避免 null 与 agentId=0 冲突
61+
.collect(Collectors.groupingBy(c -> Objects.toString(c.getAgentId(), "null"), Collectors.counting()))
5462
.entrySet().stream().anyMatch(e -> e.getValue() > 1);
5563
if (multi) {
5664
throw new RuntimeException("请确保企业微信配置唯一性[" + corpId + "]");

solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/properties/WxCpSingleProperties.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88
/**
99
* 企业微信企业相关配置属性
1010
*
11+
* <p>企业微信中不同的 corpSecret 对应不同的权限范围,常见的有:</p>
12+
* <ul>
13+
* <li>自建应用 Secret:在"应用管理 - 自建应用"中查看,只能调用该应用有权限的接口</li>
14+
* <li>通讯录同步 Secret:在"管理工具 - 通讯录同步"中查看,用于管理部门和成员(增删改查)</li>
15+
* <li>客户联系 Secret:在"客户联系"中查看,用于客户联系相关接口</li>
16+
* </ul>
17+
* <p>如需同时使用多种权限范围(例如:既要操作通讯录,又要调用自建应用接口),
18+
* 可在 {@code wx.cp.corps} 下配置多个条目,每个条目使用对应权限的 {@code corpSecret},
19+
* 其中通讯录同步的条目无需填写 {@code agentId}。</p>
20+
*
1121
* @author yl
1222
* created on 2023/10/16
1323
*/
@@ -20,15 +30,27 @@ public class WxCpSingleProperties implements Serializable {
2030
*/
2131
private String corpId;
2232
/**
23-
* 微信企业号 corpSecret
33+
* 微信企业号 corpSecret(权限密钥)
34+
*
35+
* <p>企业微信针对不同的功能模块提供了不同的 Secret,每种 Secret 只对对应模块的接口有调用权限:</p>
36+
* <ul>
37+
* <li>自建应用 Secret:在"应用管理 - 自建应用"中找到对应应用,查看其 Secret,
38+
* 使用时需同时配置对应的 {@code agentId}</li>
39+
* <li>通讯录同步 Secret:在"管理工具 - 通讯录同步"中查看,
40+
* 使用此 Secret 可管理部门、成员,无需配置 {@code agentId}</li>
41+
* <li>其他 Secret(客户联系等):根据需要在企业微信后台查看对应 Secret</li>
42+
* </ul>
2443
*/
2544
private String corpSecret;
2645
/**
2746
* 微信企业号应用 token
2847
*/
2948
private String token;
3049
/**
31-
* 微信企业号应用 ID
50+
* 微信企业号应用 ID(AgentId)
51+
*
52+
* <p>使用自建应用 Secret 时,需要填写对应应用的 AgentId。</p>
53+
* <p>使用通讯录同步 Secret 时,无需填写此字段。</p>
3254
*/
3355
private Integer agentId;
3456
/**

spring-boot-starters/wx-java-cp-multi-spring-boot-starter/README.md

Lines changed: 52 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@
66
- 未实现 WxCpTpService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
77
- 未实现 WxCpCgService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
88

9+
## 关于 corp-secret 的说明
10+
11+
企业微信中不同功能模块对应不同的 `corp-secret`,每种 Secret 只对对应模块的接口具有调用权限:
12+
13+
| Secret 类型 | 获取位置 | 可调用的接口 | 是否需要 agent-id |
14+
|---|---|---|---|
15+
| 自建应用 Secret | 应用管理 → 自建应用 → 选择应用 → 查看 Secret | 该应用有权限的接口 | **必填** |
16+
| 通讯录同步 Secret | 管理工具 → 通讯录同步 → 查看 Secret | 部门/成员增删改查等通讯录接口 | **不填** |
17+
| 客户联系 Secret | 客户联系 → API → Secret | 客户联系相关接口 | 不填 |
18+
19+
> **常见问题**
20+
> - 使用自建应用 Secret + agent-id 可以获取部门列表,但**无法更新部门**(因为写接口需要通讯录同步权限)
21+
> - 使用通讯录同步 Secret 可以同步部门,但**调用某些需要 agent-id 的应用接口会报错**
22+
23+
如需同时使用多种权限范围,可在 `wx.cp.corps` 下配置多个条目,每个条目使用对应权限的 Secret,通过不同的 `tenantId` 区分后使用。
24+
25+
> **配置限制说明**
26+
> - 当前 starter 实现会校验:同一 `corp-id` 下,`agent-id` **必须唯一**
27+
> - 同一 `corp-id` 下,**只能有一个条目不填 `agent-id`**
28+
> - 否则会因为 token/ticket 缓存 key 冲突而在启动时直接抛异常
29+
>
30+
> 因此,像"通讯录同步 Secret""客户联系 Secret"这类通常不填写 `agent-id` 的配置,**不能**在同一个 `corp-id` 下同时配置多个 `agent-id` 均为空的条目;如确有多个条目,请确保其中最多只有一个未填写 `agent-id`
31+
932
## 快速开始
1033

1134
1. 引入依赖
@@ -18,25 +41,21 @@
1841
```
1942
2. 添加配置(application.properties)
2043
```properties
21-
# 应用 1 配置
22-
wx.cp.corps.tenantId1.corp-id = @corp-id
23-
wx.cp.corps.tenantId1.corp-secret = @corp-secret
24-
## 选填
25-
wx.cp.corps.tenantId1.agent-id = @agent-id
26-
wx.cp.corps.tenantId1.token = @token
27-
wx.cp.corps.tenantId1.aes-key = @aes-key
28-
wx.cp.corps.tenantId1.msg-audit-priKey = @msg-audit-priKey
29-
wx.cp.corps.tenantId1.msg-audit-lib-path = @msg-audit-lib-path
30-
31-
# 应用 2 配置
32-
wx.cp.corps.tenantId2.corp-id = @corp-id
33-
wx.cp.corps.tenantId2.corp-secret = @corp-secret
44+
# 自建应用 1 配置(使用自建应用 Secret,需填写 agent-id)
45+
wx.cp.corps.app1.corp-id = @corp-id
46+
wx.cp.corps.app1.corp-secret = @自建应用的Secret(在"应用管理-自建应用"中查看)
47+
wx.cp.corps.app1.agent-id = @自建应用的AgentId
3448
## 选填
35-
wx.cp.corps.tenantId2.agent-id = @agent-id
36-
wx.cp.corps.tenantId2.token = @token
37-
wx.cp.corps.tenantId2.aes-key = @aes-key
38-
wx.cp.corps.tenantId2.msg-audit-priKey = @msg-audit-priKey
39-
wx.cp.corps.tenantId2.msg-audit-lib-path = @msg-audit-lib-path
49+
wx.cp.corps.app1.token = @token
50+
wx.cp.corps.app1.aes-key = @aes-key
51+
wx.cp.corps.app1.msg-audit-priKey = @msg-audit-priKey
52+
wx.cp.corps.app1.msg-audit-lib-path = @msg-audit-lib-path
53+
54+
# 通讯录同步配置(使用通讯录同步 Secret,不需要填写 agent-id)
55+
# 此配置用于部门、成员的增删改查等通讯录管理操作
56+
wx.cp.corps.contact.corp-id = @corp-id
57+
wx.cp.corps.contact.corp-secret = @通讯录同步的Secret(在"管理工具-通讯录同步"中查看)
58+
## agent-id 不填,通讯录同步不需要 agentId
4059

4160
# 公共配置
4261
## ConfigStorage 配置(选填)
@@ -59,8 +78,10 @@
5978

6079
```java
6180
import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices;
81+
import me.chanjar.weixin.cp.api.WxCpDepartmentService;
6282
import me.chanjar.weixin.cp.api.WxCpService;
6383
import me.chanjar.weixin.cp.api.WxCpUserService;
84+
import me.chanjar.weixin.cp.bean.WxCpDepart;
6485
import org.springframework.beans.factory.annotation.Autowired;
6586
import org.springframework.stereotype.Service;
6687

@@ -70,27 +91,21 @@ public class DemoService {
7091
private WxCpMultiServices wxCpMultiServices;
7192

7293
public void test() {
73-
// 应用 1 的 WxCpService
74-
WxCpService wxCpService1 = wxCpMultiServices.getWxCpService("tenantId1");
75-
WxCpUserService userService1 = wxCpService1.getUserService();
76-
userService1.getUserId("xxx");
77-
// todo ...
78-
79-
// 应用 2 的 WxCpService
80-
WxCpService wxCpService2 = wxCpMultiServices.getWxCpService("tenantId2");
81-
WxCpUserService userService2 = wxCpService2.getUserService();
82-
userService2.getUserId("xxx");
94+
// 使用自建应用的 WxCpService(对应 corp-secret 为自建应用 Secret)
95+
WxCpService appService = wxCpMultiServices.getWxCpService("app1");
96+
WxCpUserService userService = appService.getUserService();
97+
userService.getUserId("xxx");
8398
// todo ...
8499

85-
// 应用 3 的 WxCpService
86-
WxCpService wxCpService3 = wxCpMultiServices.getWxCpService("tenantId3");
87-
// 判断是否为空
88-
if (wxCpService3 == null) {
89-
// todo wxCpService3 为空,请先配置 tenantId3 企业微信应用参数
90-
return;
91-
}
92-
WxCpUserService userService3 = wxCpService3.getUserService();
93-
userService3.getUserId("xxx");
100+
// 使用通讯录同步的 WxCpService(对应 corp-secret 为通讯录同步 Secret)
101+
// 通讯录同步 Secret 具有部门/成员增删改查等权限
102+
WxCpService contactService = wxCpMultiServices.getWxCpService("contact");
103+
WxCpDepartmentService departmentService = contactService.getDepartmentService();
104+
// 更新部门示例(WxCpDepart 包含 id、name、parentId 等字段)
105+
WxCpDepart depart = new WxCpDepart();
106+
depart.setId(100L);
107+
depart.setName("新部门名称");
108+
departmentService.update(depart);
94109
// todo ...
95110
}
96111
}

spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/AbstractWxCpConfiguration.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.Collection;
1919
import java.util.List;
2020
import java.util.Map;
21+
import java.util.Objects;
2122
import java.util.Set;
2223
import java.util.stream.Collectors;
2324

@@ -40,6 +41,13 @@ protected WxCpMultiServices wxCpMultiServices(WxCpMultiProperties wxCpMultiPrope
4041
/**
4142
* 校验同一个企业下,agentId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。
4243
*
44+
* <p>同一企业(corpId 相同)下可配置多个条目以使用不同的权限 Secret,例如:</p>
45+
* <ul>
46+
* <li>自建应用条目:填写应用对应的 corpSecret 和 agentId</li>
47+
* <li>通讯录同步条目:填写通讯录同步 Secret,agentId 可不填(null)</li>
48+
* </ul>
49+
* <p>但同一 corpId 下不允许出现重复的 agentId(包括多个 null)。</p>
50+
*
4351
* 查看 {@link me.chanjar.weixin.cp.config.impl.AbstractWxCpInRedisConfigImpl#setAgentId(Integer)}
4452
*/
4553
Collection<WxCpSingleProperties> corpList = corps.values();
@@ -52,8 +60,8 @@ protected WxCpMultiServices wxCpMultiServices(WxCpMultiProperties wxCpMultiPrope
5260
String corpId = entry.getKey();
5361
// 校验每个企业下,agentId 是否唯一
5462
boolean multi = entry.getValue().stream()
55-
// 通讯录没有 agentId,如果不判断是否为空,这里会报 NPE 异常
56-
.collect(Collectors.groupingBy(c -> c.getAgentId() == null ? 0 : c.getAgentId(), Collectors.counting()))
63+
// 通讯录没有 agentId,使用字符串转换避免 null 与 agentId=0 冲突
64+
.collect(Collectors.groupingBy(c -> Objects.toString(c.getAgentId(), "null"), Collectors.counting()))
5765
.entrySet().stream().anyMatch(e -> e.getValue() > 1);
5866
if (multi) {
5967
throw new RuntimeException("请确保企业微信配置唯一性[" + corpId + "]");

spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpSingleProperties.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88
/**
99
* 企业微信企业相关配置属性
1010
*
11+
* <p>企业微信中不同的 corpSecret 对应不同的权限范围,常见的有:</p>
12+
* <ul>
13+
* <li>自建应用 Secret:在"应用管理 - 自建应用"中查看,只能调用该应用有权限的接口</li>
14+
* <li>通讯录同步 Secret:在"管理工具 - 通讯录同步"中查看,用于管理部门和成员(增删改查)</li>
15+
* <li>客户联系 Secret:在"客户联系"中查看,用于客户联系相关接口</li>
16+
* </ul>
17+
* <p>如需同时使用多种权限范围(例如:既要操作通讯录,又要调用自建应用接口),
18+
* 可在 {@code wx.cp.corps} 下配置多个条目,每个条目使用对应权限的 {@code corpSecret},
19+
* 其中通讯录同步的条目无需填写 {@code agentId}。</p>
20+
*
1121
* @author yl
1222
* created on 2023/10/16
1323
*/
@@ -20,15 +30,27 @@ public class WxCpSingleProperties implements Serializable {
2030
*/
2131
private String corpId;
2232
/**
23-
* 微信企业号 corpSecret
33+
* 微信企业号 corpSecret(权限密钥)
34+
*
35+
* <p>企业微信针对不同的功能模块提供了不同的 Secret,每种 Secret 只对对应模块的接口有调用权限:</p>
36+
* <ul>
37+
* <li>自建应用 Secret:在"应用管理 - 自建应用"中找到对应应用,查看其 Secret,
38+
* 使用时需同时配置对应的 {@code agentId}</li>
39+
* <li>通讯录同步 Secret:在"管理工具 - 通讯录同步"中查看,
40+
* 使用此 Secret 可管理部门、成员,无需配置 {@code agentId}</li>
41+
* <li>其他 Secret(客户联系等):根据需要在企业微信后台查看对应 Secret</li>
42+
* </ul>
2443
*/
2544
private String corpSecret;
2645
/**
2746
* 微信企业号应用 token
2847
*/
2948
private String token;
3049
/**
31-
* 微信企业号应用 ID
50+
* 微信企业号应用 ID(AgentId)
51+
*
52+
* <p>使用自建应用 Secret 时,需要填写对应应用的 AgentId。</p>
53+
* <p>使用通讯录同步 Secret 时,无需填写此字段。</p>
3254
*/
3355
private Integer agentId;
3456
/**

0 commit comments

Comments
 (0)