Skip to content

微信官方要求通讯录同步相关 API(成员/部门 CRUD)仅支持通过「通讯录同步 secret」获取的独立 access token 调用,现有实现未支持此场景#4047

Open
Copilot wants to merge 5 commits into
developfrom
copilot/fix-address-book-sync-secret
Open

微信官方要求通讯录同步相关 API(成员/部门 CRUD)仅支持通过「通讯录同步 secret」获取的独立 access token 调用,现有实现未支持此场景#4047
Copilot wants to merge 5 commits into
developfrom
copilot/fix-address-book-sync-secret

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jun 3, 2026

微信官方要求通讯录同步相关 API(成员/部门 CRUD)仅支持通过「通讯录同步 secret」获取的独立 access token 调用,现有实现未支持此场景。

核心变更

  • WxCpConfigStorage:新增 getContactSecret()getContactAccessToken()getContactAccessTokenLock()isContactAccessTokenExpired()expireContactAccessToken()updateContactAccessToken() 6 个接口方法
  • WxCpDefaultConfigImpl:新增 contactSecret/contactAccessToken/过期时间/锁字段,实现上述接口方法,提供 fluent setter setContactSecret()
  • WxCpRedisConfigImpl:补充实现新接口方法(与 msgAudit 保持一致,不持久化到 Redis)
  • WxCpService:新增 getContactAccessToken(boolean forceRefresh)getForContact(url, param)postForContact(url, data) 方法声明
  • BaseWxCpServiceImpl:实现 getForContact/postForContact,自动拼接通讯录 token 并通过 executeNormal 绕过常规 token 注入
  • 5 个 HTTP 客户端实现(Apache/OkHttp/Jodd/HttpComponents/WxCpServiceImpl):各自实现 getContactAccessToken,含缓存、双重检查锁、自动刷新逻辑

整体设计与已有的 msgAuditSecret 模式完全对齐。

用法示例

WxCpDefaultConfigImpl config = new WxCpDefaultConfigImpl();
config.setCorpId("corpId");
config.setCorpSecret("appSecret");
config.setContactSecret("contactSyncSecret"); // 通讯录同步专用 secret

// 通讯录 API 调用,内部自动使用独立 token
String result = wxCpService.postForContact(url, postData);

Copilot AI linked an issue Jun 3, 2026 that may be closed by this pull request
Copilot AI changed the title [WIP] Fix sync using separate address book secret 企业微信通讯录同步支持独立 secret 及 access token Jun 3, 2026
Copilot AI requested a review from binarywang June 3, 2026 13:35
@binarywang binarywang marked this pull request as ready for review June 4, 2026 02:10
Copilot AI review requested due to automatic review settings June 4, 2026 02:10
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

本 PR 为企业微信「通讯录同步」相关接口补齐“独立 secret + 独立 access token”的调用通道,避免通讯录成员/部门 CRUD 继续误用应用级 access token,从而满足微信官方对通讯录同步 API 的鉴权要求。

Changes:

  • 扩展 WxCpConfigStorage / WxCpDefaultConfigImpl:新增并实现通讯录同步 secret、token、过期与锁相关接口。
  • 扩展 WxCpService / BaseWxCpServiceImpl:新增 getContactAccessToken 以及 getForContact / postForContact 调用入口。
  • 在多种 HTTP 客户端实现中增加通讯录同步 token 的获取与缓存刷新逻辑,并补充相应单测桩代码与新增测试类。

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetMsgAuditAccessTokenTest.java 为适配新增接口补充测试桩 getContactAccessToken 实现
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetContactAccessTokenTest.java 新增通讯录同步 token 缓存/刷新/锁/缺少 secret 的测试用例
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java 为适配新增接口补充测试桩 getContactAccessToken 实现
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java 配置存储接口新增通讯录同步 secret/token/锁/过期/更新方法
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java 补充通讯录同步相关接口的占位实现
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java 增加通讯录同步 secret/token/过期时间/锁字段及实现与 fluent setter
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java WxCpService 新增通讯录同步 token 获取及基于该 token 的 GET/POST 方法声明
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java OkHttp 客户端实现通讯录同步 token 获取与缓存刷新
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java JoddHttp 客户端实现通讯录同步 token 获取与缓存刷新
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java 默认 Apache HttpClient 实现补齐通讯录同步 token 获取逻辑
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceHttpComponentsImpl.java HttpComponents 客户端实现通讯录同步 token 获取与缓存刷新
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java Apache HttpClient 客户端实现通讯录同步 token 获取与缓存刷新
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java 增加 getForContact / postForContact,使用通讯录同步 token 直连请求执行器

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0a9e488b78

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

@Override
public String getForContact(String url, String queryParam) throws WxErrorException {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 将通讯录服务切到专用 token

当前只新增了 getForContact/postForContact,但我检查到主代码里没有任何调用点(rg "postForContact|getForContact" weixin-java-cp/src/main/java 只命中这里和接口声明);现有通讯录 CRUD 仍在 WxCpUserServiceImpl#create/update/deleteWxCpDepartmentServiceImpl#create/get/update/delete 中调用 mainService.post/get,因此用户即使配置了 contactSecret,通过既有的成员/部门服务调用时仍会带普通应用 secret 换来的 access_token,微信要求通讯录同步 secret 的接口仍会失败。

Useful? React with 👍 / 👎.

Comment on lines +496 to +498
public String getContactSecret() {
return null;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 为 Redis 配置保留 contactSecret

在使用 WxCpRedisConfigImpl 的场景下,这里固定返回 null,而各个 getContactAccessToken 实现都会在刷新前读取 getContactSecret() 并在为空时抛出“通讯录同步secret未配置”;由于该类也没有新增对应字段或 setter,Redis 配置用户无法配置通讯录同步 secret,新增的通讯录 token 能力对该配置实现不可用。

Useful? React with 👍 / 👎.

// 拼接access_token参数
String urlWithToken = url + (url.contains("?") ? "&" : "?") + "access_token=" + contactAccessToken;
// 使用executeNormal方法,不自动添加token
return this.executeNormal(SimplePostRequestExecutor.create(this), urlWithToken, postData);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 在通讯录 token 失效时刷新后重试

这里走 executeNormal 后,微信返回 40014/42001 等 access_token 失效错误时只会直接抛出异常,不会像普通 executeInternal 那样将缓存 token 置过期并刷新重试;如果通讯录 token 被微信提前失效、secret 重置或本地缓存与服务端不一致,之后所有 postForContact 调用都会继续携带同一个坏的 contactAccessToken 直到本地过期时间到达,导致通讯录同步接口持续失败。

Useful? React with 👍 / 👎.

*
* @return contact secret
*/
String getContactSecret();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 为新增配置方法提供默认实现

WxCpConfigStorage 是公开的配置扩展点,很多用户会自定义实现来接入分布式缓存;这里一次新增多个抽象方法后,任何既有自定义实现升级到该版本都会因为未实现这些方法而编译失败,已编译的实现一旦走到通讯录 token 刷新也会出现 AbstractMethodError。建议将这些新方法做成有安全降级行为的 default 方法,或提供兼容适配层,再由内置实现覆盖。

Useful? React with 👍 / 👎.

binarywang and others added 2 commits June 4, 2026 10:18
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@binarywang
Copy link
Copy Markdown
Owner

@codex[agent] 根据最新代码提交再次给出新的review,必要时直接提交修改

Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
@Codex
Copy link
Copy Markdown

Codex AI commented Jun 4, 2026

@codex[agent] 根据最新代码提交再次给出新的review,必要时直接提交修改

已按最新提交复核:发现 WxCpServiceOkHttpImpl 在获取 access token / 会话存档 token 时仍存在 IOException 吞掉导致后续 JSON 解析空指针/丢失真实异常的问题,已在 57a9da7 统一改为 try-with-resources 关闭 Response、空响应体直接报错,并在 IOException 时抛出 WxErrorException。本分支已通过 mvn clean compile -DskipTests=truemvn checkstyle:check,以及 mvn -pl weixin-java-cp -am compile/checkstyle:check 验证。其余改动(独立 contactSecret + contactAccessToken + 锁)并发互斥逻辑与 msgAudit 模式一致,未发现新的回归风险点。

@Codex Codex AI changed the title 企业微信通讯录同步支持独立 secret 及 access token Addressing PR comments Jun 4, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 57a9da70b5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

* @return the string
* @throws WxErrorException the wx error exception
*/
String getForContact(String url, String queryParam) throws WxErrorException;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 改用通讯录 token 调用成员/部门服务

这里新增了通讯录专用请求入口,但我用 rg "getForContact|postForContact" 检查到它们只在接口和 Base 实现中出现,现有 WxCpUserServiceImplWxCpDepartmentServiceImpl 仍然调用 mainService.get/post(例如成员 create/list、部门 create/list)。因此用户按现有高层 API 调用成员/部门 CRUD 时,即使配置了 contactSecret,请求仍会带普通应用 access_token,在企业微信要求通讯录同步 secret 的环境下会继续失败;需要把这些通讯录相关服务切到 getForContact/postForContact

Useful? React with 👍 / 👎.

urlWithToken = urlWithToken + "&" + queryParam;
}
// 使用executeNormal方法,不自动添加token
return this.executeNormal(SimpleGetRequestExecutor.create(this), urlWithToken, null);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 在通讯录 token 失效时刷新重试

当微信端提前判定缓存的通讯录 access_token 无效(例如 secret 重置、token 被服务端回收或返回 40014/42001 等)时,这条路径走 executeNormal,只会把错误直接抛出,不会像普通 executeInternal 那样 expireAccessToken 后刷新并重试。结果是在本地过期时间到达前,所有 getForContact/postForContact 调用都会持续失败,除非调用方手动清理缓存;通讯录专用 token 也需要在 access-token 错误码下过期并重取。

Useful? React with 👍 / 👎.

*
* @return contact secret
*/
String getContactSecret();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 为新增配置方法提供兼容默认实现

这里在公开的 WxCpConfigStorage 接口上直接追加了新的抽象方法;下游项目里已有的自定义配置存储实现不会包含这些方法,升级后会在源码编译时报错,或在二进制兼容场景下调用通讯录 token 逻辑时触发 AbstractMethodError。如果这是非大版本发布,建议把这些新增入口改成带明确异常/默认值的 default 方法,避免破坏现有用户的自定义 WxCpConfigStorage

Useful? React with 👍 / 👎.

Comment on lines +496 to +497
public String getContactSecret() {
return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 让 Redis 配置返回可设置的通讯录 secret

使用内置 WxCpRedisConfigImpl 的场景下,新增的通讯录 token 获取逻辑会先读取 getContactSecret(),但这里固定返回 null 且类里没有对应 setter/字段,因此即使应用想配置通讯录同步 secret,也会始终抛出“通讯录同步secret未配置”。这会让采用 Redis 配置存储的用户完全无法使用本次新增的独立通讯录 token 能力,建议至少像默认配置一样保存本地 secret,或明确提供可覆盖/可配置的实现。

Useful? React with 👍 / 👎.

@binarywang binarywang changed the title Addressing PR comments 微信官方要求通讯录同步相关 API(成员/部门 CRUD)仅支持通过「通讯录同步 secret」获取的独立 access token 调用,现有实现未支持此场景 Jun 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

通讯录同步使用单独的secret

4 participants