Files
smartDriveEE/src/main/java/com/rj/service/impl/TtsRequestLogServiceImpl.java
2026-01-05 21:42:48 +08:00

578 lines
25 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.rj.service.impl;
import com.alibaba.dashscope.audio.asr.transcription.*;
import com.rj.entity.TtsRequestLog;
import com.rj.mapper.TtsRequestLogMapper;
import com.rj.service.ITtsRequestLogService;
import com.rj.service.MinIOService;
import com.rj.utils.MinIOUrlGenerator;
import com.rj.dto.AsrRequest;
import com.rj.dto.AsrResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.mock.web.MockMultipartFile;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.File;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* TTS请求日志服务实现类
*
* @author rj
* @date 2025-01-02
*/
@Slf4j
@Service
public class TtsRequestLogServiceImpl implements ITtsRequestLogService {
@Autowired
private TtsRequestLogMapper ttsRequestLogMapper;
@Autowired
private MinIOUrlGenerator urlGenerator;
@Autowired
private MinIOService minIOService;
@Value("${dashscope.api.key}")
private String apiKey;
@Value("${asr.enabled:true}")
private boolean asrEnabled;
@Value("${asr.default-model:paraformer-v2}")
private String defaultAsrModel;
@Override
public boolean saveTtsRequestLog(TtsRequestLog requestLog) {
try {
int result = ttsRequestLogMapper.insert(requestLog);
log.info("TTS请求日志保存成功: {}", requestLog.getId());
return result > 0;
} catch (Exception e) {
log.error("TTS请求日志保存失败: {}", e.getMessage(), e);
return false;
}
}
@Override
public TtsRequestLog getTtsRequestLogById(String id) {
try {
return ttsRequestLogMapper.selectById(id);
} catch (Exception e) {
log.error("查询TTS请求日志失败: {}", e.getMessage(), e);
return null;
}
}
@Override
public boolean setShortUrlByAudioName(MinIOUrlGenerator.UrlInfo urlInfo ) {
try {
if (urlInfo == null || urlInfo.getFileName() == null || urlInfo.getFileName().trim().isEmpty()) {
log.error("音频名称不能为空");
return false;
}
// 根据音频名称查询日志记录
LambdaQueryWrapper<TtsRequestLog> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TtsRequestLog::getAudioName, urlInfo.getFileName());
TtsRequestLog requestLog = ttsRequestLogMapper.selectOne(queryWrapper);
if (requestLog == null) {
log.error("未找到音频名称为 {} 的日志记录", urlInfo.getFileName());
return false;
}
// 更新日志记录的短链接信息
requestLog.setShortUrl(urlInfo.getUrl());
requestLog.setShortUrlExpireTime(urlInfo.getExpiresAt());
requestLog.setUpdateTime(LocalDateTime.now());
int result = ttsRequestLogMapper.updateById(requestLog);
if (result > 0) {
log.info("成功为音频 {} 设置短链接: {}, 过期时间: {}",
urlInfo.getFileName(), urlInfo.getUrl(), urlInfo.getFormattedExpiresAt());
return true;
} else {
log.error("更新音频 {} 的短链接失败", urlInfo.getFileName());
return false;
}
} catch (Exception e) {
log.error("根据音频名称设置短链接失败: 音频名称={}, 错误={}", urlInfo.getFileName(), e.getMessage(), e);
return false;
}
}
@Override
public boolean setShortUrlByAudioName(String audioName) {
return false;
}
/**
* 音频转文本的公共方法
* 支持本地文件路径或URL
*
* @param audioPathOrUrl 音频文件路径本地路径或URL
* @return 转换后的文本内容失败时返回null
*/
public String transcribeAudioToText(String audioPathOrUrl) {
return transcribeAudioToText(audioPathOrUrl, defaultAsrModel);
}
/**
* 音频转文本的公共方法
* 支持本地文件路径或URL
*
* @param audioPathOrUrl 音频文件路径本地路径或URL
* @param model ASR模型名称如果为null则使用默认模型
* @return 转换后的文本内容失败时返回null
*/
public String transcribeAudioToText(String audioPathOrUrl, String model) {
if (audioPathOrUrl == null || audioPathOrUrl.trim().isEmpty()) {
log.error("音频文件路径或URL不能为空");
return null;
}
if (!isAsrServiceAvailable()) {
log.error("ASR服务不可用请检查配置");
return null;
}
try {
// 判断是URL还是本地文件路径
String audioUrl = audioPathOrUrl;
// 判断是否为URL以http://或https://开头)
if (!audioPathOrUrl.startsWith("http://") && !audioPathOrUrl.startsWith("https://")) {
log.info("检测到本地文件路径: {}", audioPathOrUrl);
// 检查文件是否存在
File file = new File(audioPathOrUrl);
if (!file.exists() || !file.isFile()) {
log.error("本地文件不存在或不是文件: {}", audioPathOrUrl);
return null;
}
// 上传本地文件到MinIO获取URL
try {
String fileName = file.getName();
String contentType = getContentType(fileName);
// 读取文件内容
byte[] fileBytes = Files.readAllBytes(file.toPath());
// 创建MultipartFile对象
MultipartFile multipartFile = new MockMultipartFile(
"file",
fileName,
contentType,
fileBytes
);
// 上传到MinIO
audioUrl = minIOService.uploadFile(multipartFile);
log.info("本地文件已上传到MinIOURL: {}", audioUrl);
} catch (Exception e) {
log.error("上传本地文件到MinIO失败: {}", e.getMessage(), e);
return null;
}
} else {
log.info("使用音频URL: {}", audioUrl);
}
// 使用默认模型或指定模型
String asrModel = (model != null && !model.trim().isEmpty()) ? model : defaultAsrModel;
log.info("开始语音识别 - 音频URL: {}, 模型: {}", audioUrl, asrModel);
// 创建转写请求参数
TranscriptionParam param = TranscriptionParam.builder()
.apiKey(apiKey)
.model(asrModel)
.fileUrls(Arrays.asList(audioUrl))
.build();
// 创建转写对象
Transcription transcription = new Transcription();
// 提交转写请求
TranscriptionResult result = transcription.asyncCall(param);
String taskId = result.getTaskId();
log.info("ASR任务已提交 - TaskId: {}", taskId);
// 等待任务完成
TranscriptionQueryParam queryParam = TranscriptionQueryParam.FromTranscriptionParam(param, taskId);
result = transcription.wait(queryParam);
log.info("语音识别完成 - TaskId: {}", taskId);
if (result.getResults() != null && !result.getResults().isEmpty()) {
// 解析识别结果
StringBuilder fullText = new StringBuilder();
// 遍历所有结果获取transcriptionUrl并下载识别文本
for (TranscriptionTaskResult taskResult : result.getResults()) {
if (taskResult.getTranscriptionUrl() != null) {
try {
// 从transcriptionUrl下载识别结果
String transcriptionResult = downloadTranscriptionResult(taskResult.getTranscriptionUrl());
if (transcriptionResult != null && !transcriptionResult.trim().isEmpty()) {
// 解析transcription结果JSON
String extractedText = parseTranscriptionResult(transcriptionResult);
if (extractedText != null && !extractedText.trim().isEmpty()) {
if (fullText.length() > 0) {
fullText.append(" ");
}
fullText.append(extractedText);
}
}
} catch (Exception e) {
log.warn("下载transcription结果失败: {}", e.getMessage());
}
}
}
String resultText = fullText.toString().trim();
if (resultText.isEmpty()) {
log.warn("ASR识别结果为空 - TaskId: {}", taskId);
return null;
}
log.info("语音识别完成 - 识别文本长度: {}, TaskId: {}", resultText.length(), taskId);
return resultText;
} else {
log.warn("ASR识别结果为空 - TaskId: {}", taskId);
return null;
}
} catch (Exception e) {
log.error("语音识别失败: {}", e.getMessage(), e);
return null;
}
}
/**
* 根据文件名获取Content-Type
*/
private String getContentType(String fileName) {
if (fileName == null) {
return "application/octet-stream";
}
String lowerName = fileName.toLowerCase();
if (lowerName.endsWith(".mp3")) {
return "audio/mpeg";
} else if (lowerName.endsWith(".wav")) {
return "audio/wav";
} else if (lowerName.endsWith(".m4a")) {
return "audio/mp4";
} else if (lowerName.endsWith(".aac")) {
return "audio/aac";
} else if (lowerName.endsWith(".ogg")) {
return "audio/ogg";
} else if (lowerName.endsWith(".flac")) {
return "audio/flac";
} else if (lowerName.endsWith(".wma")) {
return "audio/x-ms-wma";
}
return "application/octet-stream";
}
@Override
public AsrResponse speechToText(AsrRequest request) {
long startTime = System.currentTimeMillis();
try {
if (!isAsrServiceAvailable()) {
return AsrResponse.error("ASR服务不可用请检查配置");
}
if (request.getAudioUrl() == null || request.getAudioUrl().trim().isEmpty()) {
return AsrResponse.error("音频文件URL不能为空");
}
// 设置默认值
if (request.getModel() == null || request.getModel().trim().isEmpty()) {
request.setModel(defaultAsrModel);
}
if (request.getFormat() == null || request.getFormat().trim().isEmpty()) {
request.setFormat("wav");
}
if (request.getSampleRate() == null) {
request.setSampleRate(16000);
}
if (request.getEnablePunctuation() == null) {
request.setEnablePunctuation(true);
}
if (request.getEnableNumberConversion() == null) {
request.setEnableNumberConversion(true);
}
if (request.getEnableSpeakerDiarization() == null) {
request.setEnableSpeakerDiarization(false);
}
if (request.getSpeakerCount() == null) {
request.setSpeakerCount(1);
}
log.info("开始语音识别 - 音频URL: {}, 模型: {}, 格式: {}, 采样率: {}",
request.getAudioUrl(), request.getModel(), request.getFormat(), request.getSampleRate());
// 创建转写请求参数
TranscriptionParam param = TranscriptionParam.builder()
.apiKey(apiKey)
.model(request.getModel())
.fileUrls(Arrays.asList(request.getAudioUrl()))
.build();
// 创建转写对象
Transcription transcription = new Transcription();
// 提交转写请求
TranscriptionResult result = transcription.asyncCall(param);
String taskId = result.getTaskId();
String requestId = result.getRequestId();
log.info("ASR任务已提交 - TaskId: {}, RequestId: {}", taskId, requestId);
// 等待任务完成
TranscriptionQueryParam queryParam = TranscriptionQueryParam.FromTranscriptionParam(param, taskId);
result = transcription.wait(queryParam);
log.info("语音识别完成 - result: {} ", result);
long processingTime = System.currentTimeMillis() - startTime;
if (result.getResults() != null && !result.getResults().isEmpty()) {
// 解析识别结果 - 从results中获取transcriptionUrl并下载识别结果
String fullText = "";
Double duration = 0.0;
List<AsrResponse.AsrResult> asrResults = new ArrayList<>();
try {
// 从usage中获取时长信息
if (result.getUsage() != null) {
// 从usage对象中获取durationusage通常是一个JsonObject
try {
String usageStr = result.getUsage().toString();
if (usageStr.contains("\"duration\"")) {
int durationStart = usageStr.indexOf("\"duration\":") + 11;
int durationEnd = usageStr.indexOf(",", durationStart);
if (durationEnd == -1) {
durationEnd = usageStr.indexOf("}", durationStart);
}
if (durationStart > 10 && durationEnd > durationStart) {
duration = Double.parseDouble(usageStr.substring(durationStart, durationEnd).trim());
}
}
} catch (Exception e) {
log.warn("解析usage中的duration失败: {}", e.getMessage());
}
}
// 遍历所有结果获取transcriptionUrl并下载识别文本
for (TranscriptionTaskResult taskResult : result.getResults()) {
if (taskResult.getTranscriptionUrl() != null) {
try {
// 从transcriptionUrl下载识别结果
String transcriptionResult = downloadTranscriptionResult(taskResult.getTranscriptionUrl());
// log.info("transcriptionResult: {}" , transcriptionResult);
if (transcriptionResult != null && !transcriptionResult.trim().isEmpty()) {
// 解析transcription结果JSON
String extractedText = parseTranscriptionResult(transcriptionResult);
if (extractedText != null && !extractedText.trim().isEmpty()) {
fullText += extractedText;
// 输出识别文本到控制台
System.out.println("=== 语音识别结果 ===");
System.out.println("任务ID: " + taskId);
System.out.println("识别文本: " + extractedText);
System.out.println("==================");
// 创建结果对象
AsrResponse.AsrResult asrResult = new AsrResponse.AsrResult();
asrResult.setText(extractedText);
asrResult.setStartTime(0.0);
asrResult.setEndTime(duration);
asrResults.add(asrResult);
}
}
} catch (Exception e) {
log.warn("下载transcription结果失败: {}", e.getMessage());
}
}
}
} catch (Exception e) {
log.warn("解析ASR结果时出错: {}", e.getMessage());
}
if (fullText.trim().isEmpty()) {
log.warn("ASR识别结果为空 - TaskId: {}", taskId);
return AsrResponse.error("语音识别结果为空");
}
log.info("语音识别完成 - 识别文本长度: {}, 处理时间: {}ms", fullText.length(), processingTime);
// 如果提供了TTS日志ID则保存识别文本到数据库
if (request.getId() != null && !request.getId().trim().isEmpty()) {
saveRecognizedTextToDatabase(request.getId(), fullText);
}
return AsrResponse.success(fullText, asrResults, duration, processingTime, taskId, requestId);
} else {
log.warn("ASR识别结果为空 - TaskId: {}", taskId);
return AsrResponse.error("语音识别结果为空");
}
} catch (Exception e) {
log.error("语音识别失败: {}", e.getMessage(), e);
return AsrResponse.error("语音识别失败: " + e.getMessage());
}
}
@Override
public boolean isAsrServiceAvailable() {
return asrEnabled && apiKey != null && !apiKey.trim().isEmpty();
}
/**
* 下载transcription结果
*/
private String downloadTranscriptionResult(String transcriptionUrl) {
try {
log.info("开始下载transcription结果URL: {}", transcriptionUrl);
// 使用简单的URL连接下载transcription结果避免HttpClient依赖问题
java.net.URL url = new java.net.URL(transcriptionUrl);
java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();
// 设置请求头
connection.setRequestMethod("GET");
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
connection.setRequestProperty("Accept", "application/json");
connection.setConnectTimeout(30000); // 30秒连接超时
connection.setReadTimeout(60000); // 60秒读取超时
int responseCode = connection.getResponseCode();
if (responseCode == 200) {
// 读取响应内容
StringBuilder result = new StringBuilder();
try (java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(connection.getInputStream(), java.nio.charset.StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
}
String responseBody = result.toString().trim();
log.info("成功下载transcription结果长度: {} 字符", responseBody.length());
return responseBody;
} else {
log.warn("下载transcription结果失败状态码: {}", responseCode);
return null;
}
} catch (Exception e) {
log.error("下载transcription结果异常: {}", e.getMessage(), e);
return null;
}
}
/**
* 解析transcription结果JSON提取识别文本
*/
private String parseTranscriptionResult(String transcriptionJson) {
try {
// 使用Jackson解析JSON
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(transcriptionJson);
log.info("开始解析transcription JSON");
// 根据阿里云ASR返回的JSON结构解析
// 阿里云返回的结构: {"transcripts": [{"text": "识别的文本", "sentences": [...]}]}
JsonNode transcriptsNode = rootNode.get("transcripts");
if (transcriptsNode != null && transcriptsNode.isArray() && transcriptsNode.size() > 0) {
JsonNode firstTranscript = transcriptsNode.get(0);
JsonNode textNode = firstTranscript.get("text");
if (textNode != null && !textNode.isNull()) {
String text = textNode.asText();
log.info("成功提取识别文本: {}", text);
return text;
}
}
// 如果没有transcripts字段尝试其他可能的字段结构
JsonNode resultsNode = rootNode.get("results");
if (resultsNode != null && resultsNode.isArray()) {
StringBuilder textBuilder = new StringBuilder();
for (JsonNode resultNode : resultsNode) {
JsonNode textNode = resultNode.get("text");
if (textNode != null && !textNode.isNull()) {
String text = textNode.asText();
textBuilder.append(text).append(" ");
log.info("提取到文本片段: {}", text);
}
}
String fullText = textBuilder.toString().trim();
if (!fullText.isEmpty()) {
log.info("完整识别文本: {}", fullText);
return fullText;
}
}
// 如果没有results字段尝试直接获取text字段
JsonNode textNode = rootNode.get("text");
if (textNode != null && !textNode.isNull()) {
String text = textNode.asText();
log.info("直接获取文本: {}", text);
return text;
}
// 如果都没有记录JSON结构用于调试
log.warn("未找到预期的文本字段JSON结构: {}", rootNode.toString());
return null;
} catch (Exception e) {
log.error("解析transcription结果JSON失败: {}", e.getMessage(), e);
return null;
}
}
/**
* 保存识别文本到数据库
*/
private void saveRecognizedTextToDatabase(String ttsLogId, String recognizedText) {
try {
// 根据ID查询TTS请求日志
TtsRequestLog ttsLog = ttsRequestLogMapper.selectById(ttsLogId);
if (ttsLog != null) {
// 将识别文本保存到inputText字段
ttsLog.setInputText(recognizedText);
ttsLog.setInputLength(recognizedText.length());
ttsLog.setUpdateTime(LocalDateTime.now());
// 保存到数据库
int updateResult = ttsRequestLogMapper.updateById(ttsLog);
if (updateResult > 0) {
log.info("成功保存识别文本到数据库TTS日志ID: {}, 文本长度: {}", ttsLogId, recognizedText.length());
} else {
log.warn("保存识别文本到数据库失败TTS日志ID: {}", ttsLogId);
}
} else {
log.warn("未找到对应的TTS请求日志ID: {}", ttsLogId);
}
} catch (Exception e) {
log.error("保存识别文本到数据库异常: {}", e.getMessage(), e);
}
}
}