package com.xly.web; import com.xly.runner.AppStartupRunner; import com.xly.service.UserSceneSessionService; import com.xly.tool.DynamicToolProvider; import com.xly.tts.bean.*; import com.xly.tts.service.PythonTtsProxyService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.InputStreamResource; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.InputStream; import java.util.List; import java.util.concurrent.CompletableFuture; @Slf4j @RestController @RequestMapping("/api/tts") @RequiredArgsConstructor public class TTSStreamController { private final PythonTtsProxyService pythonTtsProxyService; private final DynamicToolProvider dynamicToolProvider; private final UserSceneSessionService userSceneSessionService; private final AppStartupRunner appStartupRunner; /*** * @Author 钱豹 * @Date 14:32 2026/2/10 * @Param [request] * @return org.springframework.http.ResponseEntity * @Description 初始化AI所有变量 热启动 **/ @PostMapping("/initTool") public ResponseEntity initTool(@RequestBody TTSRequestDTO request) { appStartupRunner.cleanAllInit(); userSceneSessionService.cleanAllSession(); dynamicToolProvider.cleanAllToolProvider(); //方法重新初始化 dynamicToolProvider.init(); return pythonTtsProxyService.initTool(request); } /** * 提取报修结构化信息 */ @PostMapping("/init") public ResponseEntity init(@RequestBody TTSRequestDTO request) { return pythonTtsProxyService.init(request); } @PostConstruct public void init() { log.info("TTS Stream Controller initialized"); log.info("Python TTS Service URL: http://localhost:8000"); } @PreDestroy public void cleanup() { log.info("TTS Stream Controller shutting down"); pythonTtsProxyService.shutdown(); } /** * 流式合成语音(代理到Python服务) */ @PostMapping("/stream/query") public ResponseEntity stream(@RequestBody TTSRequestDTO request) { return pythonTtsProxyService.synthesizeStreamAi(request); } /** * 流式合成语音(代理到Python服务) */ @PostMapping("/cleanMemory") public ResponseEntity cleanMemory(@RequestBody TTSRequestDTO request) { return pythonTtsProxyService.cleanMemory(request); } /** * 流式合成语音(异步) */ @PostMapping("/async-stream") public CompletableFuture> asyncStreamSynthesize( @RequestBody TTSRequestDTO request) { log.info("收到异步流式合成请求"); return pythonTtsProxyService.synthesizeStreamAsync(request); } /** * 直接调用Python服务合成 */ @PostMapping("/direct-stream") public ResponseEntity directStreamSynthesize(@RequestBody TTSRequestDTO request) { log.info("收到直接合成请求"); return pythonTtsProxyService.synthesizeDirect(request); } /** * 简化的流式接口 */ @PostMapping("/quick-stream") public ResponseEntity quickStream( @RequestParam String text, @RequestParam(defaultValue = "zh-CN-XiaoxiaoNeural") String voice) { log.info("收到快速合成请求: voice={}, text={}", voice, text.length() > 50 ? text.substring(0, 50) + "..." : text); return pythonTtsProxyService.quickSynthesize(text, voice); } /** * GET方式的快速合成接口 */ @GetMapping("/quick-stream") public ResponseEntity quickStreamGet( @RequestParam String text, @RequestParam(defaultValue = "zh-CN-XiaoxiaoNeural") String voice) { return quickStream(text, voice); } /** * 获取所有可用语音 */ @GetMapping("/voices") public ResponseEntity> getVoices() { log.info("收到获取语音列表请求"); List voices = pythonTtsProxyService.getAvailableVoices(); return ResponseEntity.ok(voices); } /** * 获取特定语音详情 */ @GetMapping("/voices/{name}") public ResponseEntity getVoiceDetail(@PathVariable String name) { log.info("收到获取语音详情请求: {}", name); VoiceInfoDTO voice = pythonTtsProxyService.getVoiceDetail(name); if (voice != null) { return ResponseEntity.ok(voice); } else { return ResponseEntity.notFound().build(); } } /** * 健康检查(同时检查Java服务和Python服务) */ @GetMapping("/health") public ResponseEntity healthCheck() { boolean pythonServiceHealthy = pythonTtsProxyService.healthCheck(); HealthStatus healthStatus = new HealthStatus(); healthStatus.setJavaService("healthy"); healthStatus.setPythonService(pythonServiceHealthy ? "healthy" : "unhealthy"); healthStatus.setTimestamp(System.currentTimeMillis()); healthStatus.setMessage(pythonServiceHealthy ? "所有服务运行正常" : "Python TTS服务不可用"); if (pythonServiceHealthy) { return ResponseEntity.ok(healthStatus); } else { return ResponseEntity.status(503).body(healthStatus); } } /** * 简单健康检查(仅返回状态) */ @GetMapping("/health/simple") public ResponseEntity healthCheckSimple() { boolean pythonServiceHealthy = pythonTtsProxyService.healthCheck(); if (pythonServiceHealthy) { return ResponseEntity.ok("TTS服务运行正常"); } else { return ResponseEntity.status(503).body("Python TTS服务不可用"); } } /** * 批处理合成 */ @PostMapping("/batch") public ResponseEntity>> batchSynthesize( @RequestBody List requests) { log.info("收到批量合成请求,数量: {}", requests.size()); List> results = pythonTtsProxyService.batchSynthesize(requests); return ResponseEntity.ok(results); } /** * SSE流式输出(Server-Sent Events) */ @GetMapping(value = "/sse-stream", produces = "text/event-stream") public ResponseEntity sseStream( @RequestParam String text, @RequestParam(defaultValue = "zh-CN-XiaoxiaoNeural") String voice) { log.info("收到SSE流式请求: voice={}", voice); TTSRequestDTO request = new TTSRequestDTO(); request.setText(text); request.setVoice(voice); StreamingResponseBody responseBody = outputStream -> { try { outputStream.write(("event: audio-start\ndata: \n\n").getBytes()); outputStream.flush(); // 调用Python服务获取音频 ResponseEntity response = pythonTtsProxyService.synthesizeStream(request); if (response.getBody() != null) { InputStream inputStream = response.getBody().getInputStream(); byte[] buffer = new byte[1024]; int bytesRead; int totalBytes = 0; while ((bytesRead = inputStream.read(buffer)) != -1) { totalBytes += bytesRead; // 发送进度事件 String progressEvent = String.format( "event: progress\ndata: {\"bytes\":%d}\n\n", totalBytes); outputStream.write(progressEvent.getBytes()); outputStream.flush(); // 发送音频数据(base64编码) String base64Data = java.util.Base64.getEncoder().encodeToString( java.util.Arrays.copyOfRange(buffer, 0, bytesRead)); String audioEvent = String.format( "event: audio-data\ndata: {\"chunk\":\"%s\"}\n\n", base64Data); outputStream.write(audioEvent.getBytes()); outputStream.flush(); } // 发送完成事件 String completeEvent = String.format( "event: audio-complete\ndata: {\"total_bytes\":%d}\n\n", totalBytes); outputStream.write(completeEvent.getBytes()); outputStream.flush(); } else { outputStream.write(("event: error\ndata: {\"message\":\"合成失败\"}\n\n").getBytes()); outputStream.flush(); } } catch (Exception e) { // log.error("SSE流式输出异常: {}", e.getMessage(), e); try { outputStream.write(("event: error\ndata: {\"message\":\"" + e.getMessage().replace("\"", "\\\"") + "\"}\n\n").getBytes()); outputStream.flush(); } catch (Exception ex) { // 忽略关闭错误 } } }; return ResponseEntity.ok() .header("Content-Type", "text/event-stream") .header("Cache-Control", "no-cache") .header("X-Accel-Buffering", "no") // 禁用Nginx缓冲 .body(responseBody); } /** * 测试接口 */ @GetMapping("/test") public ResponseEntity testSynthesis() { log.info("收到测试请求"); TTSRequestDTO request = new TTSRequestDTO(); request.setText("这是一个测试语音,用于验证Edge-TTS服务是否正常工作。"); request.setVoice("zh-CN-XiaoxiaoNeural"); return pythonTtsProxyService.synthesizeStream(request); } /** * 状态接口 */ @GetMapping("/status") public ResponseEntity getStatus() { ServiceStatus status = new ServiceStatus(); status.setJavaService(true); status.setPythonService(pythonTtsProxyService.healthCheck()); status.setServiceUrl("http://localhost:8000"); status.setJavaApiUrl("/api/tts"); status.setTimestamp(new java.util.Date()); return ResponseEntity.ok(status); } }