package com.rj.controller; import com.rj.dto.SiliconFlowTtsRequest; import com.rj.dto.SiliconFlowTtsResponse; import com.rj.entity.TtsRequestLog; import com.rj.service.ITtsRequestLogService; import com.rj.service.MinIOService; import com.rj.service.SiliconFlowTtsService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.UUID; /** * SiliconFlow TTS 控制器 * * @author rj * @date 2025-01-02 */ @Slf4j @RestController @RequestMapping("/api/tts") @Tag(name = "文本转语音TTS", description = "SiliconFlow文本转语音接口") public class SiliconFlowTtsController { @Autowired private SiliconFlowTtsService ttsService; @Autowired private MinIOService minIOService; @Autowired private ITtsRequestLogService ttsRequestLogService; @PostMapping("/speech") @Operation(summary = "文本转语音", description = "将文本转换为语音音频") public ResponseEntity> textToSpeech( @Parameter(description = "TTS请求参数") @RequestBody SiliconFlowTtsRequest request) { Map result = new HashMap<>(); try { if (!ttsService.isServiceAvailable()) { result.put("success", false); result.put("message", "TTS服务不可用,请检查配置"); return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(result); } SiliconFlowTtsResponse response = ttsService.textToSpeech(request); if (response.isSuccess()) { result.put("success", true); result.put("message", "文本转语音成功"); result.put("audio", response.getAudio()); result.put("format", response.getFormat()); result.put("sampleRate", response.getSampleRate()); result.put("duration", response.getDuration()); result.put("requestId", response.getRequestId()); result.put("minioUrl", response.getMinioUrl()); // 添加MinIO访问URL result.put("shortUrl", response.getShortUrl()); // 添加短链URL result.put("shortUrlExpireTime", response.getShortUrlExpireTime()); // 添加短链过期时间 // 添加音频数据长度信息 if (response.getAudio() != null) { result.put("audioLength", response.getAudio().length()); log.info("TTS转换成功,音频格式: {}, 采样率: {}, 时长: {:.2f}秒, Base64长度: {}, MinIO URL: {}, 临时URL: {}", response.getFormat(), response.getSampleRate(), response.getDuration(), response.getAudio().length(), response.getMinioUrl(), response.getShortUrl()); } else { log.warn("TTS转换成功但音频数据为空"); } return ResponseEntity.ok(result); } else { result.put("success", false); result.put("message", response.getMessage()); result.put("error", response.getError()); result.put("errorCode", response.getErrorCode()); log.error("TTS转换失败: {}", response.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } catch (Exception e) { log.error("TTS请求异常: {}", e.getMessage(), e); result.put("success", false); result.put("message", "TTS请求异常: " + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } @PostMapping("/speech/simple") @Operation(summary = "简单文本转语音", description = "使用默认模型将文本转换为语音") public ResponseEntity> simpleTextToSpeech( @Parameter(description = "要转换的文本") @RequestParam String text) { Map result = new HashMap<>(); try { if (text == null || text.trim().isEmpty()) { result.put("success", false); result.put("message", "输入文本不能为空"); return ResponseEntity.badRequest().body(result); } if (!ttsService.isServiceAvailable()) { result.put("success", false); result.put("message", "TTS服务不可用,请检查配置"); return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(result); } SiliconFlowTtsResponse response = ttsService.textToSpeech(text); if (response.isSuccess()) { result.put("success", true); result.put("message", "文本转语音成功"); result.put("audio", response.getAudio()); result.put("format", response.getFormat()); result.put("sampleRate", response.getSampleRate()); result.put("duration", response.getDuration()); result.put("inputText", text); log.info("简单TTS转换成功,输入文本长度: {}, 音频格式: {}", text.length(), response.getFormat()); return ResponseEntity.ok(result); } else { result.put("success", false); result.put("message", response.getMessage()); result.put("error", response.getError()); result.put("errorCode", response.getErrorCode()); log.error("简单TTS转换失败: {}", response.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } catch (Exception e) { log.error("简单TTS请求异常: {}", e.getMessage(), e); result.put("success", false); result.put("message", "TTS请求异常: " + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } @PostMapping("/speech/download") @Operation(summary = "文本转语音并下载", description = "将文本转换为语音并直接下载音频文件") public ResponseEntity textToSpeechDownload( @Parameter(description = "TTS请求参数") @RequestBody SiliconFlowTtsRequest request) { try { if (!ttsService.isServiceAvailable()) { return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build(); } byte[] audioBytes = ttsService.textToSpeechBytes(request); if (audioBytes != null && audioBytes.length > 0) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.setContentDispositionFormData("attachment", "speech.wav"); headers.setContentLength(audioBytes.length); log.info("TTS音频下载成功,文件大小: {} bytes", audioBytes.length); return ResponseEntity.ok() .headers(headers) .body(audioBytes); } else { log.error("TTS音频生成失败"); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } catch (Exception e) { log.error("TTS音频下载异常: {}", e.getMessage(), e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @PostMapping("/speech/download/simple") @Operation(summary = "简单文本转语音并下载", description = "使用默认模型将文本转换为语音并直接下载") public ResponseEntity simpleTextToSpeechDownload( @Parameter(description = "要转换的文本") @RequestParam String text) { try { if (text == null || text.trim().isEmpty()) { return ResponseEntity.badRequest().build(); } if (!ttsService.isServiceAvailable()) { return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build(); } byte[] audioBytes = ttsService.textToSpeechBytes(text); if (audioBytes != null && audioBytes.length > 0) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.setContentDispositionFormData("attachment", "speech.wav"); headers.setContentLength(audioBytes.length); log.info("简单TTS音频下载成功,输入文本长度: {}, 文件大小: {} bytes", text.length(), audioBytes.length); return ResponseEntity.ok() .headers(headers) .body(audioBytes); } else { log.error("简单TTS音频生成失败"); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } catch (Exception e) { log.error("简单TTS音频下载异常: {}", e.getMessage(), e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @GetMapping("/status") @Operation(summary = "检查TTS服务状态", description = "检查SiliconFlow TTS服务是否可用") public ResponseEntity> checkServiceStatus() { Map result = new HashMap<>(); try { boolean available = ttsService.isServiceAvailable(); result.put("success", true); result.put("available", available); result.put("message", available ? "TTS服务可用" : "TTS服务不可用"); return ResponseEntity.ok(result); } catch (Exception e) { log.error("检查TTS服务状态异常: {}", e.getMessage(), e); result.put("success", false); result.put("available", false); result.put("message", "检查服务状态异常: " + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } @PostMapping("/speech/preview") @Operation(summary = "文本转语音预览", description = "将文本转换为语音并返回可直接播放的音频URL") public ResponseEntity> textToSpeechPreview( @Parameter(description = "要转换的文本") @RequestParam String text) { Map result = new HashMap<>(); try { if (text == null || text.trim().isEmpty()) { result.put("success", false); result.put("message", "输入文本不能为空"); return ResponseEntity.badRequest().body(result); } if (!ttsService.isServiceAvailable()) { result.put("success", false); result.put("message", "TTS服务不可用,请检查配置"); return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(result); } SiliconFlowTtsResponse response = ttsService.textToSpeech(text); if (response.isSuccess()) { result.put("success", true); result.put("message", "文本转语音成功"); result.put("audioBase64", response.getAudio()); result.put("format", response.getFormat()); result.put("sampleRate", response.getSampleRate()); result.put("duration", response.getDuration()); result.put("inputText", text); // 生成音频播放URL String audioUrl = "/api/tts/audio/play?text=" + java.net.URLEncoder.encode(text, "UTF-8"); result.put("audioUrl", audioUrl); result.put("playUrl", "data:audio/mp3;base64," + response.getAudio()); // 同时提供解码后的字节数组长度信息 if (response.getAudio() != null) { try { byte[] audioBytes = Base64.getDecoder().decode(response.getAudio()); result.put("audioSizeBytes", audioBytes.length); } catch (Exception e) { log.warn("Base64解码失败: {}", e.getMessage()); } } log.info("TTS预览转换成功,输入文本长度: {}, 音频格式: {}, 时长: {:.2f}秒", text.length(), response.getFormat(), response.getDuration()); return ResponseEntity.ok(result); } else { result.put("success", false); result.put("message", response.getMessage()); result.put("error", response.getError()); result.put("errorCode", response.getErrorCode()); log.error("TTS预览转换失败: {}", response.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } catch (Exception e) { log.error("TTS预览请求异常: {}", e.getMessage(), e); result.put("success", false); result.put("message", "TTS请求异常: " + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } @GetMapping("/audio/info") @Operation(summary = "获取音频信息", description = "获取音频的详细信息") public ResponseEntity> getAudioInfo( @Parameter(description = "要转换的文本") @RequestParam String text) { Map result = new HashMap<>(); try { if (text == null || text.trim().isEmpty()) { result.put("success", false); result.put("message", "输入文本不能为空"); return ResponseEntity.badRequest().body(result); } if (!ttsService.isServiceAvailable()) { result.put("success", false); result.put("message", "TTS服务不可用,请检查配置"); return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(result); } SiliconFlowTtsResponse response = ttsService.textToSpeech(text); if (response.isSuccess()) { result.put("success", true); result.put("message", "音频信息获取成功"); result.put("format", response.getFormat()); result.put("sampleRate", response.getSampleRate()); result.put("duration", response.getDuration()); result.put("inputText", text); result.put("inputLength", text.length()); if (response.getAudio() != null) { byte[] audioBytes = Base64.getDecoder().decode(response.getAudio()); result.put("audioSizeBytes", audioBytes.length); result.put("audioSizeKB", String.format("%.2f", audioBytes.length / 1024.0)); result.put("audioSizeMB", String.format("%.2f", audioBytes.length / (1024.0 * 1024.0))); } // 提供播放链接 result.put("playUrl", "/api/tts/audio/play?text=" + java.net.URLEncoder.encode(text, "UTF-8")); result.put("downloadUrl", "/api/tts/speech/download/simple?text=" + java.net.URLEncoder.encode(text, "UTF-8")); log.info("音频信息获取成功,输入文本长度: {}, 音频格式: {}, 时长: {:.2f}秒", text.length(), response.getFormat(), response.getDuration()); return ResponseEntity.ok(result); } else { result.put("success", false); result.put("message", response.getMessage()); result.put("error", response.getError()); result.put("errorCode", response.getErrorCode()); log.error("音频信息获取失败: {}", response.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } catch (Exception e) { log.error("音频信息获取异常: {}", e.getMessage(), e); result.put("success", false); result.put("message", "获取音频信息异常: " + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } @PostMapping(value = "/upload-audio", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @Operation(summary = "音频文件上传", description = "上传音频文件到MinIO并生成临时访问链接") public ResponseEntity> uploadAudio( @Parameter(description = "音频文件") @RequestParam("file") MultipartFile file, @Parameter(description = "音频名称") @RequestParam("audioName") String audioName, @Parameter(description = "创建人姓名") @RequestParam("creatorName") String creatorName, @Parameter(description = "创建人电话") @RequestParam("creatorPhone") String creatorPhone) { Map result = new HashMap<>(); try { log.info("开始上传音频文件: {}, 创建人: {}, 电话: {}", audioName, creatorName, creatorPhone); // 验证文件 if (file.isEmpty()) { log.error("上传文件为空"); result.put("success", false); result.put("message", "上传文件为空"); return ResponseEntity.badRequest().body(result); } // 验证文件类型 String contentType = file.getContentType(); if (contentType == null || !contentType.startsWith("audio/")) { log.error("文件类型不是音频文件: {}", contentType); result.put("success", false); result.put("message", "文件类型不是音频文件,请上传音频文件"); return ResponseEntity.badRequest().body(result); } // 生成唯一文件名 String originalFilename = file.getOriginalFilename(); String fileExtension = originalFilename != null && originalFilename.contains(".") ? originalFilename.substring(originalFilename.lastIndexOf(".")) : ".mp3"; String uniqueFileName = "/" + UUID.randomUUID().toString() + fileExtension; // 上传文件到MinIO String minioUrl = minIOService.uploadFileWithName(file, uniqueFileName); if (minioUrl == null) { log.error("文件上传到MinIO失败"); result.put("success", false); result.put("message", "文件上传到MinIO失败"); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } log.info("文件上传成功,MinIO URL: {}", minioUrl); // 生成7天临时访问链接 int expiresInSeconds = 7 * 24 * 60 * 60; // 7天 String shortUrl = minIOService.getPresignedUrl(uniqueFileName, expiresInSeconds); if (shortUrl == null) { log.error("生成临时访问链接失败"); result.put("success", false); result.put("message", "生成临时访问链接失败"); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } LocalDateTime expireTime = LocalDateTime.now().plusSeconds(expiresInSeconds); log.info("生成临时访问链接成功,过期时间: {}", expireTime); // 创建TTS请求日志记录 TtsRequestLog ttsLog = new TtsRequestLog(); ttsLog.setId(UUID.randomUUID().toString()); ttsLog.setRequestTime(LocalDateTime.now()); ttsLog.setModel("AUDIO_UPLOAD"); ttsLog.setInputText("音频文件上传"); ttsLog.setInputLength(0); ttsLog.setStatus("SUCCESS"); ttsLog.setAudioSizeBytes(file.getSize()); ttsLog.setMinioUrl(minioUrl); ttsLog.setAudioName(audioName); ttsLog.setShortUrl(shortUrl); ttsLog.setShortUrlExpireTime(expireTime); ttsLog.setCreatorName(creatorName); ttsLog.setCreatorPhone(creatorPhone); ttsLog.setCreateTime(LocalDateTime.now()); ttsLog.setUpdateTime(LocalDateTime.now()); // 保存到数据库 boolean saveResult = ttsRequestLogService.saveTtsRequestLog(ttsLog); if (!saveResult) { log.error("保存TTS请求日志失败"); result.put("success", false); result.put("message", "保存TTS请求日志失败"); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } // 构建成功响应 result.put("success", true); result.put("message", "音频文件上传成功"); result.put("id", ttsLog.getId()); result.put("audioName", audioName); result.put("minioUrl", minioUrl); result.put("shortUrl", shortUrl); result.put("shortUrlExpireTime", expireTime); result.put("creatorName", creatorName); result.put("creatorPhone", creatorPhone); result.put("audioSizeBytes", file.getSize()); result.put("status", "SUCCESS"); result.put("createTime", LocalDateTime.now()); log.info("音频文件上传完成,记录ID: {}, MinIO URL: {}, 临时URL: {}", ttsLog.getId(), minioUrl, shortUrl); return ResponseEntity.ok(result); } catch (Exception e) { log.error("音频文件上传异常: {}", e.getMessage(), e); result.put("success", false); result.put("message", "音频文件上传异常: " + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } }