TTSStreamController.java 11.2 KB
package com.xly.web;

import cn.hutool.core.util.ObjectUtil;
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.LocalAudioCache;
import com.xly.tts.service.PythonTtsProxyService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType;
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.Map;
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<com.xly.tts.bean.TTSResponseDTO>
     * @Description 初始化AI所有变量 热启动
     **/
    @PostMapping("/initTool")
    public ResponseEntity<TTSResponseDTO> initTool(@RequestBody TTSRequestDTO request) {
        appStartupRunner.cleanAllInit();
        userSceneSessionService.cleanAllSession();
        dynamicToolProvider.cleanAllToolProvider();
        //方法重新初始化
        dynamicToolProvider.init();
        return pythonTtsProxyService.initTool(request);
    }

    /**
     * 提取报修结构化信息
     */
    @PostMapping(value="/init", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.ALL_VALUE})
    public ResponseEntity<TTSResponseDTO> 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(value = "/stream/query", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.ALL_VALUE})
    public ResponseEntity<TTSResponseDTO> stream(@RequestBody TTSRequestDTO request) {
        return pythonTtsProxyService.synthesizeStreamAi(request);
    }

    @GetMapping("/audio/piece")
    public ResponseEntity<Map<String, String>> getPiece(
            @RequestParam String cacheKey,
            @RequestParam int index) {
        return ResponseEntity.ok(LocalAudioCache.getPiece(cacheKey, index));
    }

    /**
     * 流式合成语音(代理到Python服务)
     */
    @PostMapping("/cleanMemory")
    public ResponseEntity<TTSResponseDTO> cleanMemory(@RequestBody TTSRequestDTO request) {
        return pythonTtsProxyService.cleanMemory(request);
    }

    /**
     * 流式合成语音(异步)
     */
    @PostMapping("/async-stream")
    public CompletableFuture<ResponseEntity<InputStreamResource>> asyncStreamSynthesize(
            @RequestBody TTSRequestDTO request) {
        log.info("收到异步流式合成请求");
        return pythonTtsProxyService.synthesizeStreamAsync(request);
    }

    /**
     * 直接调用Python服务合成
     */
    @PostMapping("/direct-stream")
    public ResponseEntity<InputStreamResource> directStreamSynthesize(@RequestBody TTSRequestDTO request) {
        log.info("收到直接合成请求");
        return pythonTtsProxyService.synthesizeDirect(request);
    }

    /**
     * 简化的流式接口
     */
    @PostMapping("/quick-stream")
    public ResponseEntity<InputStreamResource> 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<InputStreamResource> quickStreamGet(
            @RequestParam String text,
            @RequestParam(defaultValue = "zh-CN-XiaoxiaoNeural") String voice) {
        return quickStream(text, voice);
    }

    /**
     * 获取所有可用语音
     */
    @GetMapping("/voices")
    public ResponseEntity<List<VoiceInfoDTO>> getVoices() {
        log.info("收到获取语音列表请求");
        List<VoiceInfoDTO> voices = pythonTtsProxyService.getAvailableVoices();
        return ResponseEntity.ok(voices);
    }

    /**
     * 获取特定语音详情
     */
    @GetMapping("/voices/{name}")
    public ResponseEntity<VoiceInfoDTO> 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<Object> 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<String> healthCheckSimple() {
        boolean pythonServiceHealthy = pythonTtsProxyService.healthCheck();
        if (pythonServiceHealthy) {
            return ResponseEntity.ok("TTS服务运行正常");
        } else {
            return ResponseEntity.status(503).body("Python TTS服务不可用");
        }
    }

    /**
     * 批处理合成
     */
    @PostMapping("/batch")
    public ResponseEntity<List<ResponseEntity<InputStreamResource>>> batchSynthesize(
            @RequestBody List<TTSRequestDTO> requests) {
        log.info("收到批量合成请求,数量: {}", requests.size());
        List<ResponseEntity<InputStreamResource>> results = pythonTtsProxyService.batchSynthesize(requests);
        return ResponseEntity.ok(results);
    }

    /**
     * SSE流式输出(Server-Sent Events)
     */
    @GetMapping(value = "/sse-stream", produces = "text/event-stream")
    public ResponseEntity<StreamingResponseBody> 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<InputStreamResource> 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<InputStreamResource> testSynthesis() {
        log.info("收到测试请求");
        TTSRequestDTO request = new TTSRequestDTO();
        request.setText("这是一个测试语音,用于验证Edge-TTS服务是否正常工作。");
        request.setVoice("zh-CN-XiaoxiaoNeural");
        return pythonTtsProxyService.synthesizeStream(request);
    }

    /**
     * 状态接口
     */
    @GetMapping("/status")
    public ResponseEntity<Object> 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);
    }


}