{"record":{"id":"52080e135acd3de2","repo":"binarywang/WxJava","slug":"x-openai-token-getaccesstoken","errorCode":null,"errorMessage":"X-OPENAI-TOKEN不能为空，请先调用getAccessToken或手动设置","messagePattern":"X-OPENAI-TOKEN不能为空，请先调用getAccessToken或手动设置","errorType":"validation","errorClass":"WxErrorException","httpStatus":null,"severity":"error","filePath":"weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceImpl.java","lineNumber":106,"sourceCode":"\n  protected String executeDialogPost(String path, Object requestBody, boolean withOpenToken, String appid)\n    throws WxErrorException {\n    String body = toBody(requestBody);\n    String requestId = UUID.randomUUID().toString();\n    long timestamp = System.currentTimeMillis() / 1000;\n    String nonce = randomNonce();\n    String sign = WxAispeechSignUtil.calcDialogSign(configStorage.getToken(), timestamp, nonce, body);\n    String resolvedAppid = StringUtils.defaultIfBlank(appid, configStorage.getAppid());\n\n    HttpPost request = new HttpPost(configStorage.getDialogApiBaseUrl() + path);\n    request.setHeader(\"request_id\", requestId);\n    request.setHeader(\"timestamp\", String.valueOf(timestamp));\n    request.setHeader(\"nonce\", nonce);\n    request.setHeader(\"sign\", sign);\n    request.setHeader(\"Content-Type\", ContentType.APPLICATION_JSON.getMimeType());\n    if (withOpenToken) {\n      if (StringUtils.isBlank(configStorage.getOpenAiToken())) {\n        throw new WxErrorException(\"X-OPENAI-TOKEN不能为空，请先调用getAccessToken或手动设置\");\n      }\n      request.setHeader(\"X-OPENAI-TOKEN\", configStorage.getOpenAiToken());\n    } else {\n      if (StringUtils.isBlank(resolvedAppid)) {\n        throw new WxErrorException(\"X-APPID不能为空\");\n      }\n      request.setHeader(\"X-APPID\", resolvedAppid);\n    }\n    request.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON));\n    return executeRequest(request);\n  }\n\n  protected String executeKnowledgeGet(String path, Map<String, String> queryParams) throws WxErrorException {\n    try {\n      URIBuilder builder = new URIBuilder(configStorage.getKnowledgeApiBaseUrl() + path);\n      if (queryParams != null) {\n        for (Map.Entry<String, String> entry : queryParams.entrySet()) {\n          if (entry.getValue() != null) {","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/binarywang/WxJava/blob/1c43293a3c2c9d7e91304b6d037fb017f680d0c6/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceImpl.java#L88-L124","documentation":"Thrown by `executeDialogPost` in the AI Speech service when a dialog call is made with `withOpenToken=true` but `configStorage.openAiToken` is blank. The OpenAI token must be obtained beforehand via `getAccessToken` or set manually, then sent as the `X-OPENAI-TOKEN` header. It is a WxErrorException.","triggerScenarios":"Calling a dialog method that requires the OpenAI token before ever calling `getAccessToken(...)`, or after the token was cleared/expired and never refreshed, with no manual `setOpenAiToken` either.","commonSituations":"First dialog call after startup; token expired and not refreshed; misconfigured credentials causing getAccessToken to silently leave the field blank.","solutions":["Call `service.getAccessToken(...)` before the dialog call to populate openAiToken.","Or manually set the token on the config storage: `configStorage.setOpenAiToken(token)`.","Refresh the token on its documented expiry cadence.","Verify the credentials used for getAccessToken are correct."],"exampleFix":"// before\nString r = service.dialog(...);  // withOpenToken path -> throws\n// after\nservice.getAccessToken(appid, secretKey);  // populates openAiToken\nString r = service.dialog(...);","handlingStrategy":"validation","validationCode":"// Ensure openAiToken is present before any OpenAI-token dialog call\nif (withOpenToken && StringUtils.isBlank(configStorage.getOpenAiToken())) {\n    service.getAccessToken(appid, secretKey); // populate token\n    if (StringUtils.isBlank(configStorage.getOpenAiToken())) {\n        throw new IllegalStateException(\"Unable to obtain OpenAI token; check credentials\");\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Call getAccessToken at startup and refresh before expiry.","Surface a clear error if getAccessToken does not populate the token.","Centralize token lifecycle so callers never hit a blank token."],"tags":["aispeech","auth","token","runtime"],"backgroundTag":null,"analyzedSha":"1c43293a3c2c9d7e91304b6d037fb017f680d0c6","analyzedAt":"2026-08-14T02:29:11.060Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}