{"record":{"id":"6966108138173bd6","repo":"jeecgboot/JeecgBoot","slug":"message","errorCode":null,"errorMessage":"语音生成异常: {message}","messagePattern":"语音生成异常: (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/voice/service/impl/VoiceServiceImpl.java","lineNumber":100,"sourceCode":"\n            // 调用公共TTS API生成音频\n            ttsApiHelper.generateAudio(vo.getContent(), audioFile, voice, speed);\n\n            // 返回结果\n            String voiceUrl = bizPath + \"/\" + fileName;\n            VoiceResultVo result = new VoiceResultVo();\n            result.setVoiceUrl(voiceUrl);\n            result.setFileName(fileName);\n\n            // 存入Redis\n            saveToRedis(vo, fileName, voiceUrl, loginUser);\n\n            return result;\n        } catch (RuntimeException e) {\n            throw e;\n        } catch (Exception e) {\n            log.error(\"语音生成异常\", e);\n            throw new RuntimeException(\"语音生成异常: \" + e.getMessage(), e);\n        }\n    }\n\n    //update-begin---author:wangshuai ---date:2026-04-15  for：【QQYUN-14568】语音生成改为异步，支持切换菜单后重新获取结果-----------\n    @Override\n    public String generateAsync(VoiceGenerateVo vo) {\n        String taskId = UUID.randomUUID().toString().replace(\"-\", \"\");\n        String taskKey = VOICE_TASK_PREFIX + taskId;\n\n        JSONObject pending = new JSONObject();\n        pending.put(\"status\", \"pending\");\n        redisUtil.set(taskKey, pending.toJSONString(), VOICE_TASK_TTL);\n\n        // 在异步线程执行前先获取登录用户，避免子线程中 Shiro 上下文丢失\n        LoginUser loginUser = null;\n        try {\n            loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();\n        } catch (Exception e) {","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/jeecgboot/JeecgBoot/blob/96fb33f5ec68516da0b0147da06b2eb0419e063a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/voice/service/impl/VoiceServiceImpl.java#L82-L118","documentation":"A generic catch-all at the bottom of VoiceServiceImpl.textToSpeechWithUser(). It rethrows RuntimeExceptions unchanged (e.g. the TTS API failure in error 123) and wraps any other checked exception -- IOException/InterruptedException from the HTTP call or FileSystemException writing the wav -- into a RuntimeException carrying the cause message.","triggerScenarios":"ttsApiHelper.generateAudio() raises a non-RuntimeException: HttpClient.send() hits a network/connect timeout (config.getTimeout()); the upload/voice output dir cannot be created or the wav written; the worker thread is interrupted during the blocking HTTP send.","commonSituations":"TTS provider unreachable from the server; upload path on a read-only/full volume; thread interrupted (async cancellation); connectTimeout (30s) exceeded.","solutions":["Inspect the cause via the logged '语音生成异常' stacktrace (e.getMessage() is in the thrown text).","Verify TTS apiHost/apiKey/model config (the underlying error 123 will surface for HTTP failures).","Confirm jeecg.path.upload + /voice is writable and the volume has space.","Raise config.getTimeout() if the error is a client-side timeout."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// pre-flight the upload/voice directory before TTS\nPath dir = Paths.get(jeecgBaseConfig.getPath().getUpload(), \"voice\");\nFiles.createDirectories(dir);\nif (!Files.isWritable(dir)) { throw new IOException(\"voice 目录不可写: \" + dir); }","typeGuard":null,"tryCatchPattern":"// The method already separates RuntimeException (rethrown) from checked (wrapped).\n// Callers should catch RuntimeException and surface it to the user as a TTS failure.\ntry {\n    return voiceService.textToSpeech(vo);\n} catch (RuntimeException e) {\n    log.error(\"语音生成失败\", e);\n    return Result.error(\"语音生成失败，请稍后重试\");\n}","preventionTips":["Validate the TTS provider config (apiHost/apiKey/model) on startup with a tiny request.","Ensure the upload/voice directory is writable and on a volume with free space.","Set a sane config.getTimeout() so HttpClient.send doesn't hang the thread."],"tags":["tts","voice","io","network","wrapper"],"backgroundTag":null,"analyzedSha":"96fb33f5ec68516da0b0147da06b2eb0419e063a","analyzedAt":"2026-08-14T00:04:16.786Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}