diff --git a/src/main/java/com/rj/config/RestTemplateConfig.java b/src/main/java/com/rj/config/RestTemplateConfig.java index 69c4932..fc67390 100644 --- a/src/main/java/com/rj/config/RestTemplateConfig.java +++ b/src/main/java/com/rj/config/RestTemplateConfig.java @@ -1,6 +1,5 @@ package com.rj.config; -import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @@ -18,9 +17,4 @@ public class RestTemplateConfig { public RestTemplate restTemplate() { return new RestTemplate(); } - - @Bean - public ObjectMapper objectMapper() { - return new ObjectMapper(); - } } \ No newline at end of file diff --git a/src/main/java/com/rj/controller/AudioManagementSegmentsController.java b/src/main/java/com/rj/controller/AudioManagementSegmentsController.java index da82975..f303815 100644 --- a/src/main/java/com/rj/controller/AudioManagementSegmentsController.java +++ b/src/main/java/com/rj/controller/AudioManagementSegmentsController.java @@ -1,9 +1,13 @@ package com.rj.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.rj.dto.AsrRequest; +import com.rj.dto.AsrResponse; import com.rj.entity.AudioManagementSegments; import com.rj.service.IAudioManagementSegmentsService; +import com.rj.service.ITtsRequestLogService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -17,10 +21,15 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.io.File; +import java.nio.file.Files; import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; +import com.rj.service.MinIOService; /** *

@@ -149,6 +158,201 @@ public class AudioManagementSegmentsController { return ResponseEntity.internalServerError().build(); } } + @Autowired + private ITtsRequestLogService ttsRequestLogService; + + @Autowired + private MinIOService minIOService; + + /** + * 转文本请求 + */ + @PostMapping("/transcribe/{id}") + @Operation(summary = "转文本", description = "分段音频文件转文本") + public ResponseEntity> transcribeSegmentById( + @Parameter(description = "分段ID", required = true) + @PathVariable String id) { + Map result = new HashMap<>(); + try { + // 先查询记录是否存在 + AudioManagementSegments segment = audioManagementSegmentsService.getById(id); + if (segment == null) { + result.put("success", false); + result.put("message", "录音分段不存在,ID: " + id); + return ResponseEntity.notFound().build(); + } + + // 检查本地文件路径是否存在 + String audioFilePath = segment.getAudioFilePath(); + if (audioFilePath == null || audioFilePath.trim().isEmpty()) { + result.put("success", false); + result.put("message", "音频文件路径为空"); + return ResponseEntity.badRequest().body(result); + } + + File audioFile = new File(audioFilePath); + if (!audioFile.exists() || !audioFile.isFile()) { + result.put("success", false); + result.put("message", "音频文件不存在: " + audioFilePath); + return ResponseEntity.badRequest().body(result); + } + + // TODO 1: 上传文件到MinIO + String minioUrl = null; + try { + // 读取本地文件 + byte[] fileBytes = Files.readAllBytes(audioFile.toPath()); + String fileName = segment.getAudioFileOriginalName() != null + ? segment.getAudioFileOriginalName() + : audioFile.getName(); + + // 获取文件扩展名,确定contentType + String contentType = getContentType(fileName); + + // 创建MultipartFile对象 + MultipartFile multipartFile = new MockMultipartFile( + "file", + fileName, + contentType, + fileBytes + ); + + // 上传到MinIO + minioUrl = minIOService.uploadFile(multipartFile); + log.info("文件上传到MinIO成功,ID: {}, minioUrl: {}", id, minioUrl); + } catch (Exception e) { + log.error("上传文件到MinIO失败,ID: {}, error: {}", id, e.getMessage(), e); + result.put("success", false); + result.put("message", "上传文件到MinIO失败: " + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + + // 根据id修改audio_management_segments 表字段: needTranscribe为 1, transcribeRequestTime为当前时间 + AudioManagementSegments updateEntity = new AudioManagementSegments(); + updateEntity.setNeedTranscribe(true); + updateEntity.setTranscribeRequestTime(LocalDateTime.now()); + updateEntity.setAudioFileUrl(minioUrl); + + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(AudioManagementSegments::getId, id); + + boolean success = audioManagementSegmentsService.update(updateEntity, updateWrapper); + + if (!success) { + result.put("success", false); + result.put("message", "更新转文本状态失败"); + return ResponseEntity.badRequest().body(result); + } + + // 从minioUrl中提取objectName,生成presignedUrl + String presignedUrl = null; + try { + String objectName = extractObjectNameFromUrl(minioUrl); + if (objectName != null) { + // 生成7天有效期的预签名URL + presignedUrl = minIOService.getPresignedUrl(objectName, 7 * 24 * 60 * 60); + log.info("生成预签名URL成功,ID: {}, presignedUrl: {}", id, presignedUrl); + } else { + // 如果无法提取objectName,直接使用minioUrl + presignedUrl = minioUrl; + log.warn("无法从minioUrl提取objectName,使用原始URL: {}", minioUrl); + } + } catch (Exception e) { + log.error("生成预签名URL失败,使用原始URL: {}", e.getMessage(), e); + presignedUrl = minioUrl; + } + + // TODO 2: 启用异步线程处理转文本 + final String finalPresignedUrl = presignedUrl; + final String segmentId = id; + CompletableFuture.runAsync(() -> { + try { + log.info("开始异步转文本处理,ID: {}", segmentId); + + AsrRequest asrRequest = new AsrRequest(); + asrRequest.setAudioUrl(finalPresignedUrl); + asrRequest.setAudioName(segment.getAudioFileOriginalName()); + asrRequest.setModel("paraformer-v2"); // 使用默认模型 + asrRequest.setFormat("wav"); // 默认格式 + asrRequest.setSampleRate(16000); // 默认采样率 + asrRequest.setEnablePunctuation(true); // 启用标点符号 + asrRequest.setEnableNumberConversion(true); // 启用数字转换 + asrRequest.setEnableSpeakerDiarization(false); // 不启用说话人分离 + + // 调用ASR服务进行语音识别 + AsrResponse asrResponse = ttsRequestLogService.speechToText(asrRequest); + + if (asrResponse != null && asrResponse.isSuccess() && asrResponse.getText() != null) { + // 更新转文本结果到数据库 + AudioManagementSegments textUpdateEntity = new AudioManagementSegments(); + textUpdateEntity.setRecordingText(asrResponse.getText()); + textUpdateEntity.setTranscribeEndTime(LocalDateTime.now()); + + LambdaUpdateWrapper textUpdateWrapper = new LambdaUpdateWrapper<>(); + textUpdateWrapper.eq(AudioManagementSegments::getId, segmentId); + + boolean updateSuccess = audioManagementSegmentsService.update(textUpdateEntity, textUpdateWrapper); + if (updateSuccess) { + log.info("转文本结果保存成功,ID: {}, 文本长度: {}", segmentId, asrResponse.getText().length()); + } else { + log.error("转文本结果保存失败,ID: {}", segmentId); + } + } else { + String errorMsg = asrResponse != null ? asrResponse.getMessage() : "ASR响应为空"; + log.error("转文本失败,ID: {}, error: {}", segmentId, errorMsg); + } + } catch (Exception e) { + log.error("异步转文本处理异常,ID: {}, error: {}", segmentId, e.getMessage(), e); + } + }); + + result.put("success", true); + result.put("message", "转文本请求已提交,正在后台处理"); + result.put("data", updateEntity); + return ResponseEntity.ok(result); + + } catch (Exception e) { + log.error("转文本请求异常,ID: {}, error: {}", id, e.getMessage(), e); + result.put("success", false); + result.put("message", "转文本请求异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 从MinIO URL中提取对象名 + * @param url MinIO文件URL + * @return 对象名 + */ + private String extractObjectNameFromUrl(String url) { + if (url == null || url.trim().isEmpty()) { + return null; + } + + try { + // 去掉查询参数 + String urlWithoutParams = url; + if (url.contains("?")) { + urlWithoutParams = url.substring(0, url.indexOf("?")); + } + + // 按"/"分割URL,获取最后一个部分作为对象名 + String[] parts = urlWithoutParams.split("/"); + if (parts.length > 0) { + // 获取最后一个非空部分作为对象名 + for (int i = parts.length - 1; i >= 0; i--) { + if (parts[i] != null && !parts[i].trim().isEmpty()) { + return parts[i]; + } + } + } + + return null; + } catch (Exception e) { + log.error("从URL提取对象名失败: {}", url, e); + return null; + } + } /** * 判断文件是否为音频文件 diff --git a/src/main/java/com/rj/entity/AudioManagementSegments.java b/src/main/java/com/rj/entity/AudioManagementSegments.java index 410caee..1e5ba89 100644 --- a/src/main/java/com/rj/entity/AudioManagementSegments.java +++ b/src/main/java/com/rj/entity/AudioManagementSegments.java @@ -149,5 +149,17 @@ public class AudioManagementSegments implements Serializable { @TableField("summary") private String summary; + @Schema(description = "需要转文本(是或否,默认:否)") + @TableField("need_transcribe") + private Boolean needTranscribe; + + @Schema(description = "需要转文本请求的时间") + @TableField("transcribe_request_time") + private LocalDateTime transcribeRequestTime; + + @Schema(description = "需要转文本结束的时间") + @TableField("transcribe_end_time") + private LocalDateTime transcribeEndTime; + } diff --git a/src/main/resources/mapper/AudioManagementSegmentsMapper.xml b/src/main/resources/mapper/AudioManagementSegmentsMapper.xml index 47336d6..5c68118 100644 --- a/src/main/resources/mapper/AudioManagementSegmentsMapper.xml +++ b/src/main/resources/mapper/AudioManagementSegmentsMapper.xml @@ -34,6 +34,9 @@ + + + @@ -67,7 +70,10 @@ audio_file_original_name, audio_file_extension, recording_text, - summary + summary, + need_transcribe, + transcribe_request_time, + transcribe_end_time