TTSStreamController.java 8.72 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 jakarta.validation.Valid;
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 reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

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);
    }

    @PostMapping(value = "/stream/queryFlux",
            consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = {MediaType.APPLICATION_NDJSON_VALUE, MediaType.APPLICATION_JSON_VALUE})
    public Flux<TTSResponseDTO> streamFlux(@Valid @RequestBody TTSRequestDTO request) {
        return pythonTtsProxyService.synthesizeStreamAiStream(request)
                .doOnSubscribe(sub -> log.debug("流式订阅开始"))
                .doOnCancel(() -> log.debug("流式请求被取消"))
                .doOnComplete(() -> log.debug("流式响应完成"))
                .doOnError(e -> log.error("流式处理错误", e));
    }


    @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);
    }

    /**
     * 测试接口
     */
    @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);
    }


}