{"record":{"id":"81af448f38b31677","repo":"chinabugotech/hutool","slug":"tts-81af44","errorCode":null,"errorMessage":"TTS处理失败: ","messagePattern":"TTS处理失败: ","errorType":"exception","errorClass":"AIException","httpStatus":null,"severity":"error","filePath":"hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolServiceImpl.java","lineNumber":129,"sourceCode":"\t}\n\n\t@Override\n\tpublic InputStream tts(String input, final HutoolCommon.HutoolSpeech voice) {\n\t\ttry {\n\t\t\tString paramJson = buildTTSRequestBody(input, voice.getVoice());\n\t\t\tfinal HttpResponse response = sendPost(TTS, paramJson);\n\n\t\t\t// 检查响应内容类型\n\t\t\tString contentType = response.header(\"Content-Type\");\n\t\t\tif (contentType != null && contentType.startsWith(\"application/json\")) {\n\t\t\t\t// 如果是JSON响应，说明有错误\n\t\t\t\tString errorBody = response.body();\n\t\t\t\tthrow new AIException(\"TTS请求失败: \" + errorBody);\n\t\t\t}\n\t\t\t// 默认返回音频流\n\t\t\treturn response.bodyStream();\n\t\t} catch (Exception e) {\n\t\t\tthrow new AIException(\"TTS处理失败: \" + e.getMessage(), e);\n\t\t}\n\t}\n\n\t@Override\n\tpublic String stt(final File file) {\n\t\tfinal Map<String, Object> paramMap = buildSTTRequestBody(file);\n\t\tfinal HttpResponse response = sendFormData(STT, paramMap);\n\t\treturn response.body();\n\t}\n\n\n\t@Override\n\tpublic String videoTasks(String text, String image, final List<HutoolCommon.HutoolVideo> videoParams) {\n\t\tString paramJson = buildGenerationsTasksRequestBody(text, image, videoParams);\n\t\tfinal HttpResponse response = sendPost(CREATE_VIDEO, paramJson);\n\t\treturn response.body();\n\t}\n","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/chinabugotech/hutool/blob/8870454b2a0c29cc6ffd31dcf5667c8ceb2fc442/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolServiceImpl.java#L111-L147","documentation":"The outer catch-all in HutoolServiceImpl.tts: any Exception (including the inner 'TTS请求失败' throw from error 15, plus any transport failure from sendPost) is re-wrapped as AIException(\"TTS处理失败: \" + e.getMessage(), e). The cause is preserved. When the inner check threw, the visible message is the doubled form 'TTS处理失败: TTS请求失败: <json body>'.","triggerScenarios":"Any failure during tts(): the upstream returned a JSON error (inner throw), or sendPost raised a transport exception (network/timeout/SSL), or bodyStream() failed. This is the single exception callers of tts() actually catch.","commonSituations":"Network/timeout talking to the hutool TTS endpoint; invalid key returning a JSON error; unsupported voice; transient upstream failure.","solutions":["Catch AIException around tts() and inspect getMessage() (strip the 'TTS处理失败: ' / 'TTS请求失败: ' prefixes) and getCause().","If getCause() is a transport exception, fix network/timeout via config setTimeout/setReadTimeout and setApiUrl.","If the message contains a JSON body, parse it for the upstream error code.","Retry with backoff for transient transport errors."],"exampleFix":"// before\nInputStream audio = service.tts(text, voice); // any failure -> TTS处理失败: ...\n\n// after\ntry {\n    return service.tts(text, voice);\n} catch (AIException e) {\n    Throwable root = e.getCause() != null ? e.getCause() : e;\n    log.error(\"tts failed: {} | root: {}\", e.getMessage(), root.toString());\n    throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight network + key\nif (StrUtil.isBlank(config.getApiKey())) throw new IllegalStateException(\"key missing\");\nif (StrUtil.isBlank(config.getApiUrl())) throw new IllegalStateException(\"apiUrl missing\");\nif (config.getReadTimeout() < 30_000) config.setReadTimeout(60_000);","typeGuard":null,"tryCatchPattern":"try {\n    return service.tts(text, voice);\n} catch (AIException e) {\n    String m = e.getMessage();\n    Throwable root = e.getCause();\n    if (m.startsWith(\"TTS处理失败\")) {\n        if (m.contains(\"TTS请求失败\")) {\n            // upstream JSON error body; parse it\n            String body = m.substring(m.lastIndexOf(\"TTS请求失败:\") + \"TTS请求失败:\".length()).trim();\n        } else {\n            // transport failure -> root is an IOException\n        }\n    }\n    throw e;\n}","preventionTips":["Catch AIException once around tts(); the cause distinguishes JSON-error vs transport.","Strip the doubled 'TTS处理失败: TTS请求失败: ' prefix to reach the body.","Log getCause() to separate network issues from upstream rejections."],"tags":["hutool","tts","network","error-masking","catch-all"],"backgroundTag":null,"analyzedSha":"8870454b2a0c29cc6ffd31dcf5667c8ceb2fc442","analyzedAt":"2026-08-14T04:01:12.892Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}