{"record":{"id":"5af829feef4c4d31","repo":"binarywang/WxJava","slug":"error-5af829","errorCode":null,"errorMessage":"错误代码：{}, 错误信息：{}","messagePattern":"错误代码：(.+?), 错误信息：(.+?)","errorType":"exception","errorClass":"WxErrorException","httpStatus":null,"severity":"error","filePath":"weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java","lineNumber":97,"sourceCode":"        return this.configStorage.getContactAccessToken();\n      }\n      // 使用通讯录同步secret获取access_token\n      String contactSecret = this.configStorage.getContactSecret();\n      if (contactSecret == null || contactSecret.trim().isEmpty()) {\n        throw new WxErrorException(\"通讯录同步secret未配置\");\n      }\n      HttpRequest request = HttpRequest.get(String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN),\n        this.configStorage.getCorpId(), contactSecret));\n      if (this.httpProxy != null) {\n        httpClient.useProxy(this.httpProxy);\n      }\n      request.withConnectionProvider(httpClient);\n      HttpResponse response = request.send();\n\n      String resultContent = response.bodyText();\n      WxError error = WxError.fromJson(resultContent, WxType.CP);\n      if (error.getErrorCode() != 0) {\n        throw new WxErrorException(error);\n      }\n      WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);\n      this.configStorage.updateContactAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());\n    } finally {\n      lock.unlock();\n    }\n    return this.configStorage.getContactAccessToken();\n  }\n\n  @Override\n  public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {\n    if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {\n      return this.configStorage.getMsgAuditAccessToken();\n    }\n\n    Lock lock = this.configStorage.getMsgAuditAccessTokenLock();\n    lock.lock();\n    try {","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/binarywang/WxJava/blob/1c43293a3c2c9d7e91304b6d037fb017f680d0c6/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java#L79-L115","documentation":"Thrown (as checked WxErrorException wrapping a WxError) when the WeChat API returns a non-zero error code during contact access_token retrieval via the Jodd HTTP implementation. Unlike the secret-not-configured error (which fires before the HTTP call), this fires after the API responds with an error. The message is formatted by WxError.toString() as '错误代码：{code}, 错误信息：{msg}'.","triggerScenarios":"The HTTP GET to the token endpoint succeeds but the response JSON contains errcode != 0. Common causes: invalid corpId or contactSecret (errcode 40001), IP not in the whitelist (errcode 60020), the contact secret was revoked in the admin console, or rate limiting on token refresh.","commonSituations":"The contact secret was rotated in the WeChat admin console but the app still uses the old value; the server IP is not in the API whitelist; corpId and secret are mismatched (from different corps); excessive token refresh calls hitting the rate limit; the contact-sync app was disabled in the admin console.","solutions":["Check the errcode in the WxError: 40001 = invalid credential (secret wrong or revoked), 60020 = IP not whitelisted, 40013 = invalid corpId","Verify the server's outbound IP is added to the WeChat Work admin console trusted IP list","Ensure corpId and contactSecret are from the same corp and the secret has not been rotated","Reduce token refresh frequency — the SDK caches tokens; avoid calling getContactAccessToken(true) unless necessary","Log the full WxError.getJson() for the raw WeChat response to diagnose edge cases"],"exampleFix":"// before — calling force refresh unnecessarily, or with stale secret\nString token = service.getContactAccessToken(true); // may hit rate limit or invalid credential\n\n// after — let the SDK manage token lifecycle, and handle errors with diagnostics\ntry {\n  String token = service.getContactAccessToken(false); // use cached token\n} catch (WxErrorException e) {\n  WxError err = e.getError();\n  log.error(\"获取通讯录 token 失败 errcode={}, errmsg={}, raw={}\",\n    err.getErrorCode(), err.getErrorMsg(), err.getJson());\n  if (err.getErrorCode() == 60020) {\n    log.error(\"当前服务器 IP 未加入企业微信可信 IP 列表\");\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Pre-validate that secrets are set and IP is whitelisted before requesting token\nString contactSecret = configStorage.getContactSecret();\nif (StringUtils.isBlank(contactSecret)) {\n  throw new IllegalStateException(\"通讯录同步 secret 未配置\");\n}\n// Ensure the server IP is in the WeChat admin trusted IP list (manual check)","typeGuard":null,"tryCatchPattern":"try {\n  String token = service.getContactAccessToken(false);\n} catch (WxErrorException e) {\n  WxError error = e.getError();\n  switch (error.getErrorCode()) {\n    case 40001:\n      log.error(\"凭证无效：contactSecret 可能已失效或被更换\");\n      break;\n    case 60020:\n      log.error(\"当前 IP 不在企业微信可信 IP 列表中，请添加: {}\", serverIp);\n      break;\n    case 40013:\n      log.error(\"corpId 无效\");\n      break;\n    default:\n      log.error(\"获取通讯录 token 失败: {}\", error.toString());\n  }\n  throw e;\n}","preventionTips":["Add the server's outbound IP to the WeChat Work admin console trusted IP list before deploying","Avoid calling getContactAccessToken(true) unless the cached token is confirmed expired — let the SDK manage refresh","Monitor for secret rotation in the WeChat admin console and update config promptly","Log the full WxError (including raw JSON) for diagnostics when token retrieval fails","Ensure corpId, corpSecret, and contactSecret are all from the same WeChat Work corp"],"tags":["wechat-cp","access-token","contact-sync","api-error","jodd","credential"],"backgroundTag":null,"analyzedSha":"1c43293a3c2c9d7e91304b6d037fb017f680d0c6","analyzedAt":"2026-08-14T02:29:11.060Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}