binarywang/WxJava · error · WxErrorException
X-OPENAI-TOKEN不能为空,请先调用getAccessToken或手动设置
Error message
X-OPENAI-TOKEN不能为空,请先调用getAccessToken或手动设置
What it means
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.
Source
Thrown at weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceImpl.java:106
protected String executeDialogPost(String path, Object requestBody, boolean withOpenToken, String appid)
throws WxErrorException {
String body = toBody(requestBody);
String requestId = UUID.randomUUID().toString();
long timestamp = System.currentTimeMillis() / 1000;
String nonce = randomNonce();
String sign = WxAispeechSignUtil.calcDialogSign(configStorage.getToken(), timestamp, nonce, body);
String resolvedAppid = StringUtils.defaultIfBlank(appid, configStorage.getAppid());
HttpPost request = new HttpPost(configStorage.getDialogApiBaseUrl() + path);
request.setHeader("request_id", requestId);
request.setHeader("timestamp", String.valueOf(timestamp));
request.setHeader("nonce", nonce);
request.setHeader("sign", sign);
request.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType());
if (withOpenToken) {
if (StringUtils.isBlank(configStorage.getOpenAiToken())) {
throw new WxErrorException("X-OPENAI-TOKEN不能为空,请先调用getAccessToken或手动设置");
}
request.setHeader("X-OPENAI-TOKEN", configStorage.getOpenAiToken());
} else {
if (StringUtils.isBlank(resolvedAppid)) {
throw new WxErrorException("X-APPID不能为空");
}
request.setHeader("X-APPID", resolvedAppid);
}
request.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON));
return executeRequest(request);
}
protected String executeKnowledgeGet(String path, Map<String, String> queryParams) throws WxErrorException {
try {
URIBuilder builder = new URIBuilder(configStorage.getKnowledgeApiBaseUrl() + path);
if (queryParams != null) {
for (Map.Entry<String, String> entry : queryParams.entrySet()) {
if (entry.getValue() != null) {View on GitHub (pinned to 1c43293a3c)
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.
Example fix
// before String r = service.dialog(...); // withOpenToken path -> throws // after service.getAccessToken(appid, secretKey); // populates openAiToken String r = service.dialog(...);
Defensive patterns
Strategy: validation
Validate before calling
// Ensure openAiToken is present before any OpenAI-token dialog call
if (withOpenToken && StringUtils.isBlank(configStorage.getOpenAiToken())) {
service.getAccessToken(appid, secretKey); // populate token
if (StringUtils.isBlank(configStorage.getOpenAiToken())) {
throw new IllegalStateException("Unable to obtain OpenAI token; check credentials");
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: First dialog call after startup; token expired and not refreshed; misconfigured credentials causing getAccessToken to silently leave the field blank.
Related errors
AI-assisted analysis of binarywang/WxJava@1c43293a3c (2026-08-14).
Data as JSON: /api/errors/52080e135acd3de2.
Report an issue: GitHub.