501 lines
23 KiB
Java
501 lines
23 KiB
Java
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<Map<String, Object>> textToSpeech(
|
||
@Parameter(description = "TTS请求参数") @RequestBody SiliconFlowTtsRequest request) {
|
||
|
||
Map<String, Object> 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<Map<String, Object>> simpleTextToSpeech(
|
||
@Parameter(description = "要转换的文本") @RequestParam String text) {
|
||
|
||
Map<String, Object> 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<byte[]> 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<byte[]> 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<Map<String, Object>> checkServiceStatus() {
|
||
|
||
Map<String, Object> 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<Map<String, Object>> textToSpeechPreview(
|
||
@Parameter(description = "要转换的文本") @RequestParam String text) {
|
||
|
||
Map<String, Object> 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<Map<String, Object>> getAudioInfo(
|
||
@Parameter(description = "要转换的文本") @RequestParam String text) {
|
||
|
||
Map<String, Object> 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<Map<String, Object>> 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<String, Object> 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);
|
||
}
|
||
}
|
||
}
|