{"record":{"id":"7f346f84d5ed886e","repo":"binarywang/WxJava","slug":"secret-7f346f","errorCode":null,"errorMessage":"通讯录同步secret未配置","messagePattern":"通讯录同步secret未配置","errorType":"validation","errorClass":"WxErrorException","httpStatus":null,"severity":"error","filePath":"weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java","lineNumber":89,"sourceCode":"  }\n\n  @Override\n  public String getContactAccessToken(boolean forceRefresh) throws WxErrorException {\n    final WxCpConfigStorage configStorage = getWxCpConfigStorage();\n    if (!configStorage.isContactAccessTokenExpired() && !forceRefresh) {\n      return configStorage.getContactAccessToken();\n    }\n    Lock lock = configStorage.getContactAccessTokenLock();\n    lock.lock();\n    try {\n      // 拿到锁之后，再次判断一下最新的token是否过期，避免重刷\n      if (!configStorage.isContactAccessTokenExpired() && !forceRefresh) {\n        return configStorage.getContactAccessToken();\n      }\n      // 使用通讯录同步secret获取access_token\n      String contactSecret = configStorage.getContactSecret();\n      if (contactSecret == null || contactSecret.trim().isEmpty()) {\n        throw new WxErrorException(\"通讯录同步secret未配置\");\n      }\n      String url = String.format(configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN),\n        this.configStorage.getCorpId(), contactSecret);\n      try {\n        HttpGet httpGet = new HttpGet(url);\n        if (getRequestHttpProxy() != null) {\n          RequestConfig config = RequestConfig.custom().setProxy(getRequestHttpProxy()).build();\n          httpGet.setConfig(config);\n        }\n        String resultContent = getRequestHttpClient().execute(httpGet, ApacheBasicResponseHandler.INSTANCE);\n        WxError error = WxError.fromJson(resultContent, WxType.CP);\n        if (error.getErrorCode() != 0) {\n          throw new WxErrorException(error);\n        }\n\n        WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);\n        configStorage.updateContactAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());\n      } catch (IOException e) {","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/binarywang/WxJava/blob/1c43293a3c2c9d7e91304b6d037fb017f680d0c6/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java#L71-L107","documentation":"Thrown (as checked WxErrorException) by getContactAccessToken() in the default WxCpServiceImpl when the contact-sync secret is null or blank. This is the base/default HTTP client implementation. The contact secret is a separate credential from the main corp secret, used exclusively for contact-sync APIs.","triggerScenarios":"Any contact-sync API call (user/department/tag sync) through the default WxCpServiceImpl when configStorage.getContactSecret() returns null or empty.","commonSituations":"Config initialized with only corpId + corpSecret but no contactSecret; the contact secret env var is undefined; using a Redis-backed config storage that lost the contact secret after a flush; the WeChat Work admin console was reconfigured and the old secret is no longer valid (though this would cause a different error — this specific error is only about the value being absent).","solutions":["Call configStorage.setContactSecret(\"...\") during application startup before any contact-sync API is used","In Spring Boot starter, set wx.cp.contact-secret in application.yml or application.properties","If using a custom config storage (e.g., Redis), ensure the contact secret is persisted and reloaded correctly","Differentiate the contact-sync secret from the application secret — they are separate values in the WeChat admin console"],"exampleFix":"// before\nWxCpDefaultConfigImpl config = new WxCpDefaultConfigImpl();\nconfig.setCorpId(corpId);\nconfig.setCorpSecret(corpSecret);\n// missing: config.setContactSecret(...)\n\n// after\nconfig.setContactSecret(System.getenv(\"WX_CP_CONTACT_SECRET\"));","handlingStrategy":"validation","validationCode":"// Validate contact secret at startup for default impl\nString contactSecret = configStorage.getContactSecret();\nif (StringUtils.isBlank(contactSecret)) {\n  log.error(\"通讯录同步 secret 未配置，通讯录相关接口将不可用\");\n  // Optionally throw to fail-fast\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Set contactSecret via configStorage.setContactSecret() during initialization","For Spring Boot starter, use wx.cp.contact-secret in application.yml","The contact secret is different from corpSecret — obtain it from 管理工具 → 通讯录同步 in the admin console","If using Redis-backed config storage, ensure the secret survives restarts"],"tags":["wechat-cp","config","contact-sync","access-token","secret","default-httpclient"],"backgroundTag":null,"analyzedSha":"1c43293a3c2c9d7e91304b6d037fb017f680d0c6","analyzedAt":"2026-08-14T02:29:11.060Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}