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

649 lines
28 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.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 com.rj.tenant.TenantContextHolder;
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.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
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;
/**
* <p>
* 录音分段管理表 前端控制器
* </p>
*
* 对应表audio_management_segments
*/
@RestController
@RequestMapping("/api/audioManagementSegments")
@Tag(name = "录音分段管理", description = "录音分段管理相关接口")
@Slf4j
public class AudioManagementSegmentsController {
@Autowired
private IAudioManagementSegmentsService audioManagementSegmentsService;
/**
* 新增录音分段
*/
@PostMapping("/add")
@Operation(summary = "新增录音分段", description = "添加新的录音分段信息")
public ResponseEntity<Map<String, Object>> addSegment(
@Parameter(description = "录音分段信息", required = true)
@RequestBody AudioManagementSegments segment) {
Map<String, Object> result = new HashMap<>();
try {
if (segment.getCreateTime() == null) {
segment.setCreateTime(LocalDateTime.now());
}
boolean success = audioManagementSegmentsService.save(segment);
if (success) {
result.put("success", true);
result.put("message", "录音分段添加成功");
result.put("data", segment);
return ResponseEntity.ok(result);
} else {
result.put("success", false);
result.put("message", "录音分段添加失败");
return ResponseEntity.badRequest().body(result);
}
} catch (Exception e) {
log.error("录音分段添加异常:{}", e.getMessage(), e);
result.put("success", false);
result.put("message", "录音分段添加异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
/**
* 根据ID查询录音分段
*/
@GetMapping("/get/{id}")
@Operation(summary = "根据ID查询录音分段", description = "根据分段ID获取录音分段详细信息")
public ResponseEntity<Map<String, Object>> getSegmentById(
@Parameter(description = "分段ID", required = true)
@PathVariable String id) {
Map<String, Object> result = new HashMap<>();
try {
AudioManagementSegments segment = audioManagementSegmentsService.getById(id);
if (segment != null) {
result.put("success", true);
result.put("message", "查询成功");
result.put("data", segment);
return ResponseEntity.ok(result);
} else {
result.put("success", false);
result.put("message", "录音分段不存在");
return ResponseEntity.notFound().build();
}
} catch (Exception e) {
log.error("录音分段查询异常:{}", e.getMessage(), e);
result.put("success", false);
result.put("message", "查询异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
/**
* 根据ID播放录音分段音频文件
*/
@PostMapping("/playSegmentById/{id}")
@Operation(summary = "播放分段音频文件", description = "播放音频文件")
public ResponseEntity<Resource> playSegmentById(
@Parameter(description = "分段ID", required = true)
@PathVariable String id) {
try {
AudioManagementSegments segment = audioManagementSegmentsService.getById(id);
if (segment == null) {
log.warn("录音分段不存在ID: {}", id);
return ResponseEntity.notFound().build();
}
String audioFilePath = segment.getAudioFilePath();
if (audioFilePath == null || audioFilePath.trim().isEmpty()) {
log.warn("录音分段文件路径为空ID: {}", id);
return ResponseEntity.badRequest().build();
}
File file = new File(audioFilePath);
if (!file.exists() || !file.isFile()) {
log.warn("音频文件不存在或不是文件,路径: {}", audioFilePath);
return ResponseEntity.notFound().build();
}
// 检查文件是否为音频文件
if (!isAudioFile(audioFilePath)) {
log.warn("文件不是音频文件,路径: {}", audioFilePath);
return ResponseEntity.badRequest().build();
}
Resource resource = new FileSystemResource(file);
// 设置响应头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(getContentType(audioFilePath)));
headers.setContentLength(file.length());
headers.set("Accept-Ranges", "bytes");
return ResponseEntity.ok()
.headers(headers)
.body(resource);
} catch (Exception e) {
log.error("播放录音分段音频文件异常ID: {}, error: {}", id, e.getMessage(), e);
return ResponseEntity.internalServerError().build();
}
}
@Autowired
private ITtsRequestLogService ttsRequestLogService;
@Autowired
private MinIOService minIOService;
/**
* 转文本请求
*/
@PostMapping("/transcribe/{id}")
@Operation(summary = "转文本", description = "分段音频文件转文本")
public ResponseEntity<Map<String, Object>> transcribeSegmentById(
@Parameter(description = "分段ID", required = true)
@PathVariable String id) {
Map<String, Object> 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<AudioManagementSegments> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(AudioManagementSegments::getId, id);
boolean success = audioManagementSegmentsService.updateNoTenant(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: 启用异步线程处理转文本
// 获取租户ID优先从segment对象获取如果没有则从当前线程的租户上下文获取
final String tenantId = segment.getTenantId() != null
? segment.getTenantId()
: TenantContextHolder.getTenantId();
final String finalPresignedUrl = presignedUrl;
final String segmentId = id;
final String audioFileName = segment.getAudioFileOriginalName();
CompletableFuture.runAsync(() -> {
try {
// 在异步线程中设置租户上下文
if (tenantId != null && !tenantId.trim().isEmpty()) {
TenantContextHolder.setTenantId(tenantId);
log.info("异步线程中设置租户ID: {}, segmentId: {}", tenantId, segmentId);
} else {
log.warn("租户ID为空segmentId: {}", segmentId);
}
log.info("开始异步转文本处理ID: {}, tenantId: {}", segmentId, tenantId);
AsrRequest asrRequest = new AsrRequest();
asrRequest.setAudioUrl(finalPresignedUrl);
asrRequest.setAudioName(audioFileName);
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<AudioManagementSegments> textUpdateWrapper = new LambdaUpdateWrapper<>();
textUpdateWrapper.eq(AudioManagementSegments::getId, segmentId);
// 使用正常的update方法因为已经设置了租户上下文
boolean updateSuccess = audioManagementSegmentsService.update(textUpdateEntity, textUpdateWrapper);
if (updateSuccess) {
log.info("转文本结果保存成功ID: {}, tenantId: {}, 文本长度: {}", segmentId, tenantId, asrResponse.getText().length());
} else {
log.error("转文本结果保存失败ID: {}, tenantId: {}", segmentId, tenantId);
}
} else {
String errorMsg = asrResponse != null ? asrResponse.getMessage() : "ASR响应为空";
log.error("转文本失败ID: {}, tenantId: {}, error: {}", segmentId, tenantId, errorMsg);
}
} catch (Exception e) {
log.error("异步转文本处理异常ID: {}, tenantId: {}, error: {}", segmentId, tenantId, e.getMessage(), e);
} finally {
// 清理租户上下文,防止线程复用导致租户串用
TenantContextHolder.clear();
log.debug("清理异步线程租户上下文segmentId: {}", segmentId);
}
});
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;
}
}
/**
* 判断文件是否为音频文件
*/
private boolean isAudioFile(String fileName) {
if (fileName == null || fileName.isEmpty()) {
return false;
}
String extension = getFileExtension(fileName).toLowerCase();
String[] audioExtensions = {"mp3", "wav", "m4a", "aac", "ogg", "flac", "wma"};
for (String ext : audioExtensions) {
if (ext.equals(extension)) {
return true;
}
}
return false;
}
/**
* 获取文件扩展名
*/
private String getFileExtension(String fileName) {
if (fileName == null || fileName.isEmpty()) {
return "";
}
int lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) {
return fileName.substring(lastDotIndex + 1);
}
return "";
}
/**
* 根据文件名获取Content-Type
*/
private String getContentType(String fileName) {
String extension = getFileExtension(fileName).toLowerCase();
switch (extension) {
case "mp3":
return "audio/mpeg";
case "wav":
return "audio/wav";
case "m4a":
return "audio/mp4";
case "aac":
return "audio/aac";
case "ogg":
return "audio/ogg";
case "flac":
return "audio/flac";
case "wma":
return "audio/x-ms-wma";
default:
return "application/octet-stream";
}
}
/**
* 分页查询录音分段列表
*/
@GetMapping("/list")
@Operation(summary = "分页查询录音分段列表", description = "分页查询录音分段信息列表")
public ResponseEntity<Map<String, Object>> getSegmentList(
@Parameter(description = "页码", example = "1")
@RequestParam(defaultValue = "1") Integer current,
@Parameter(description = "每页大小", example = "10")
@RequestParam(defaultValue = "10") Integer size,
@Parameter(description = "父录音ID")
@RequestParam(required = false) String parentId,
@Parameter(description = "租户ID")
@RequestParam(required = false) String tenantId,
@Parameter(description = "设备编号(模糊查询)")
@RequestParam(required = false) String deviceNo,
@Parameter(description = "销售姓名(模糊查询)")
@RequestParam(required = false) String salesName,
@Parameter(description = "销售电话(模糊查询)")
@RequestParam(required = false) String salesPhone,
@Parameter(description = "意向级别")
@RequestParam(required = false) String intentionLevel,
@Parameter(description = "上传状态")
@RequestParam(required = false) String uploadStatus,
@Parameter(description = "同步状态")
@RequestParam(required = false) String syncStatus) {
Map<String, Object> result = new HashMap<>();
try {
Page<AudioManagementSegments> page = new Page<>(current, size);
LambdaQueryWrapper<AudioManagementSegments> queryWrapper = new LambdaQueryWrapper<>();
if (parentId != null && !parentId.trim().isEmpty()) {
queryWrapper.eq(AudioManagementSegments::getParentId, parentId);
}
if (tenantId != null && !tenantId.trim().isEmpty()) {
queryWrapper.eq(AudioManagementSegments::getTenantId, tenantId);
}
if (deviceNo != null && !deviceNo.trim().isEmpty()) {
queryWrapper.like(AudioManagementSegments::getDeviceNo, deviceNo);
}
if (salesName != null && !salesName.trim().isEmpty()) {
queryWrapper.like(AudioManagementSegments::getSalesName, salesName);
}
if (salesPhone != null && !salesPhone.trim().isEmpty()) {
queryWrapper.like(AudioManagementSegments::getSalesPhone, salesPhone);
}
if (intentionLevel != null && !intentionLevel.trim().isEmpty()) {
queryWrapper.eq(AudioManagementSegments::getIntentionLevel, intentionLevel);
}
if (uploadStatus != null && !uploadStatus.trim().isEmpty()) {
queryWrapper.eq(AudioManagementSegments::getUploadStatus, uploadStatus);
}
if (syncStatus != null && !syncStatus.trim().isEmpty()) {
queryWrapper.eq(AudioManagementSegments::getSyncStatus, syncStatus);
}
// 按创建时间倒序排列
queryWrapper.orderByDesc(AudioManagementSegments::getCreateTime);
Page<AudioManagementSegments> segmentPage = audioManagementSegmentsService.page(page, queryWrapper);
result.put("success", true);
result.put("message", "查询成功");
result.put("data", segmentPage.getRecords());
result.put("total", segmentPage.getTotal());
result.put("current", segmentPage.getCurrent());
result.put("size", segmentPage.getSize());
result.put("pages", segmentPage.getPages());
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("录音分段分页查询异常:{}", e.getMessage(), e);
result.put("success", false);
result.put("message", "查询异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
/**
* 根据父ID查询录音分段列表不分页
*/
@GetMapping("/listByParentId")
@Operation(summary = "根据父ID查询录音分段列表不分页",
description = "根据父录音ID查询所有分段列表不分页")
public ResponseEntity<Map<String, Object>> listByParentId(
@Parameter(description = "父录音ID", required = true)
@RequestParam String parentId) {
Map<String, Object> result = new HashMap<>();
try {
LambdaQueryWrapper<AudioManagementSegments> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AudioManagementSegments::getParentId, parentId);
// 通常按开始时间或段索引排序
queryWrapper.orderByAsc(AudioManagementSegments::getStartTime);
List<AudioManagementSegments> list = audioManagementSegmentsService.list(queryWrapper);
result.put("success", true);
result.put("message", "查询成功");
result.put("data", list);
result.put("count", list.size());
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("根据父ID查询录音分段列表异常{}", e.getMessage(), e);
result.put("success", false);
result.put("message", "查询异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
/**
* 更新录音分段信息
*/
@PutMapping("/update")
@Operation(summary = "更新录音分段信息", description = "更新录音分段详细信息")
public ResponseEntity<Map<String, Object>> updateSegment(
@Parameter(description = "录音分段信息", required = true)
@RequestBody AudioManagementSegments segment) {
Map<String, Object> result = new HashMap<>();
try {
if (segment.getId() == null || segment.getId().trim().isEmpty()) {
result.put("success", false);
result.put("message", "分段ID不能为空");
return ResponseEntity.badRequest().body(result);
}
boolean success = audioManagementSegmentsService.updateById(segment);
if (success) {
result.put("success", true);
result.put("message", "录音分段信息更新成功");
result.put("data", segment);
return ResponseEntity.ok(result);
} else {
result.put("success", false);
result.put("message", "录音分段信息更新失败");
return ResponseEntity.badRequest().body(result);
}
} catch (Exception e) {
log.error("录音分段更新异常:{}", e.getMessage(), e);
result.put("success", false);
result.put("message", "更新异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
/**
* 根据ID删除录音分段
*/
@DeleteMapping("/delete/{id}")
@Operation(summary = "删除录音分段", description = "根据分段ID删除录音分段信息")
public ResponseEntity<Map<String, Object>> deleteSegment(
@Parameter(description = "分段ID", required = true)
@PathVariable String id) {
Map<String, Object> result = new HashMap<>();
try {
boolean success = audioManagementSegmentsService.removeById(id);
if (success) {
result.put("success", true);
result.put("message", "录音分段删除成功");
return ResponseEntity.ok(result);
} else {
result.put("success", false);
result.put("message", "录音分段删除失败");
return ResponseEntity.badRequest().body(result);
}
} catch (Exception e) {
log.error("录音分段删除异常:{}", e.getMessage(), e);
result.put("success", false);
result.put("message", "删除异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
/**
* 批量删除录音分段
*/
@DeleteMapping("/batchDelete")
@Operation(summary = "批量删除录音分段", description = "根据分段ID列表批量删除录音分段信息")
public ResponseEntity<Map<String, Object>> batchDeleteSegments(
@Parameter(description = "分段ID列表", required = true)
@RequestBody List<String> ids) {
Map<String, Object> result = new HashMap<>();
try {
if (ids == null || ids.isEmpty()) {
result.put("success", false);
result.put("message", "分段ID列表不能为空");
return ResponseEntity.badRequest().body(result);
}
boolean success = audioManagementSegmentsService.removeByIds(ids);
if (success) {
result.put("success", true);
result.put("message", "批量删除成功,共删除 " + ids.size() + " 条记录");
return ResponseEntity.ok(result);
} else {
result.put("success", false);
result.put("message", "批量删除失败");
return ResponseEntity.badRequest().body(result);
}
} catch (Exception e) {
log.error("批量删除录音分段异常:{}", e.getMessage(), e);
result.put("success", false);
result.put("message", "批量删除异常:" + e.getMessage());
return ResponseEntity.internalServerError().body(result);
}
}
}