binarywang/WxJava · error · WxErrorException

X-APPID不能为空

Error message

X-APPID不能为空

What it means

Thrown by `executeDialogPost` when a dialog call is made with `withOpenToken=false` and neither the per-call `appid` argument nor `configStorage.appid` is set. The resolved appid (`defaultIfBlank(appid, configStorage.appid)`) is required for the `X-APPID` header. It is a WxErrorException.

Source

Thrown at weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceImpl.java:111

    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) {
            builder.addParameter(entry.getKey(), entry.getValue());
          }
        }
      }
      HttpGet request = new HttpGet(builder.build());

View on GitHub (pinned to 1c43293a3c)

Solutions

  1. Set appid on the config storage at init: `configStorage.setAppid(...)`.
  2. Or pass a non-blank appid to the dialog method call.
  3. Validate configuration completeness before issuing requests.

Example fix

// before
String r = service.executeDialogPost(path, body, false, null);  // throws
// after
configStorage.setAppid("your-appid");
String r = service.executeDialogPost(path, body, false, null);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure an appid is resolvable for non-OpenAI-token dialog calls
String resolvedAppid = StringUtils.defaultIfBlank(appid, configStorage.getAppid());
if (!withOpenToken && StringUtils.isBlank(resolvedAppid)) {
    throw new IllegalStateException("No appid available for X-APPID header");
}

Prevention

When it happens

Trigger: Calling a non-OpenAI-token dialog path while the global appid was never configured and no appid was passed to the method.

Common situations: Config storage created without setting appid; relying on a per-call appid but passing null; partial config after migrating setup code.

Related errors


AI-assisted analysis of binarywang/WxJava@1c43293a3c (2026-08-14). Data as JSON: /api/errors/f7113c5272fb0857. Report an issue: GitHub.