diff --git a/pom.xml b/pom.xml
index a34edfd..5e75a17 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,7 @@
com.cst
- Langchain4j-rj
+ AIDriverEEBackend
1.251130.7-SNAPSHOT
Langchain4j-rj
Langchain4j-rj20250803
diff --git a/src/main/java/com/rj/controller/AudioFileController.java b/src/main/java/com/rj/controller/AudioFileController.java
index b006c71..3a0951e 100644
--- a/src/main/java/com/rj/controller/AudioFileController.java
+++ b/src/main/java/com/rj/controller/AudioFileController.java
@@ -34,6 +34,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
+import java.math.BigDecimal;
import java.util.*;
/**
@@ -80,6 +81,9 @@ public class AudioFileController {
@Autowired
private DeviceManagementMapper deviceManagementMapper;
+
+ @Autowired
+ private com.rj.service.IAudioManagementSegmentsService audioManagementSegmentsService;
public String deviceKey = "deviceManagementKey";
/**
@@ -972,6 +976,14 @@ public class AudioFileController {
audioUploadLog.getId(), audioUploadLog.getDeviceNo(), audioUploadLog.getFileName(),
audioUploadLog.getAudioManagementId());
+ // 保存到 audio_management_segments 表
+ try {
+ saveAudioManagementSegment(requestBody, processedData, localFilePath, file, audioUploadLog);
+ } catch (Exception e) {
+ log.error("保存音频分段记录到audio_management_segments表失败", e);
+ // 即使保存失败,也不影响音频上传日志的保存,只记录日志
+ }
+
} catch (Exception e) {
log.error("保存音频上传日志到数据库失败", e);
throw new RuntimeException("保存音频上传日志失败: " + e.getMessage(), e);
@@ -1112,6 +1124,384 @@ public class AudioFileController {
audioUploadLog.setCreateTime(now);
audioUploadLog.setUpdateTime(now);
}
+
+ /**
+ * 保存音频分段记录到 audio_management_segments 表
+ * 如果已存在相同的分段记录,则跳过保存(避免重复)
+ *
+ * @param requestBody 请求体数据
+ * @param processedData 处理后的数据
+ * @param localFilePath 本地文件路径
+ * @param file 上传的文件(可能为null)
+ * @param audioUploadLog 音频上传日志对象(已保存)
+ */
+ private void saveAudioManagementSegment(Map requestBody,
+ Map processedData,
+ String localFilePath,
+ MultipartFile file,
+ YhyAudioUploadLog audioUploadLog) {
+ try {
+ // 1. 检查是否已经保存过(使用requestBody中的标记)
+ String segmentSavedKey = "audioManagementSegmentSaved";
+ if (requestBody.containsKey(segmentSavedKey) && Boolean.TRUE.equals(requestBody.get(segmentSavedKey))) {
+ log.info("音频分段记录已保存过,跳过重复保存");
+ return;
+ }
+
+ // 2. 创建音频分段对象
+ AudioManagementSegments segment = new AudioManagementSegments();
+ segment.setId(UUID.randomUUID().toString());
+
+ // 3. 设置父录音ID(从requestBody中获取audioManagementId)
+ Object audioManagementIdObj = requestBody.get("audioManagementId");
+ if (audioManagementIdObj != null) {
+ segment.setParentId(audioManagementIdObj.toString());
+ }
+
+ // 4. 从DeviceManagement中获取设备信息
+ DeviceManagement deviceManagement = (DeviceManagement) requestBody.get(deviceKey);
+ if (deviceManagement != null) {
+ segment.setTenantId(deviceManagement.getTenantId());
+ segment.setSalesPhone(deviceManagement.getSalesPhone());
+ segment.setSalesName(deviceManagement.getSalesName());
+ segment.setDealershipId(deviceManagement.getDealershipId());
+ segment.setDealershipName(deviceManagement.getDealershipName());
+ }
+
+ // 5. 从requestBody和processedData中提取字段
+ populateSegmentFromRequestData(segment, requestBody, processedData);
+
+ // 6. 设置文件相关字段
+ setSegmentFileFields(segment, localFilePath, file, audioUploadLog);
+
+ // 7. 解析时间字段并计算录音时长
+ parseAndSetSegmentTimeFields(segment, processedData);
+
+ // 8. 检查数据库中是否已存在相同的分段记录
+ if (isSegmentExists(segment)) {
+ log.info("音频分段记录已存在,跳过保存。父录音ID: {}, chunkIndex: {}, deviceNo: {}, startTime: {}",
+ segment.getParentId(), segment.getChunkIndex(), segment.getDeviceNo(), segment.getStartTime());
+ // 标记为已保存,避免后续重复检查
+ requestBody.put(segmentSavedKey, true);
+ return;
+ }
+
+ // 9. 从AudioManagement中获取录音名称(如果存在父录音)
+ if (segment.getParentId() != null) {
+ populateSegmentFromAudioManagement(segment, segment.getParentId());
+ }
+
+ // 10. 设置通用字段
+ LocalDateTime now = TimeZoneUtils.now();
+ segment.setCreateTime(now);
+ segment.setUploadTime(now);
+ segment.setRecordingTime(now);
+
+ // 11. 设置默认状态
+ if (segment.getUploadStatus() == null) {
+ segment.setUploadStatus("已上传");
+ }
+ if (segment.getSyncStatus() == null) {
+ segment.setSyncStatus("未同步");
+ }
+ if (segment.getIsMerged() == null) {
+ segment.setIsMerged(false);
+ }
+
+ // 12. 保存到数据库
+ audioManagementSegmentsService.save(segment);
+ log.info("音频分段记录保存成功,ID: {}, 父录音ID: {}, 设备号: {}, 文件名: {}",
+ segment.getId(), segment.getParentId(), segment.getDeviceNo(), segment.getAudioFileOriginalName());
+
+ // 13. 标记为已保存,避免重复保存
+ requestBody.put(segmentSavedKey, true);
+
+ } catch (Exception e) {
+ log.error("保存音频分段记录到audio_management_segments表失败", e);
+ throw new RuntimeException("保存音频分段记录失败: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 检查音频分段记录是否已存在
+ * 判断标准:parent_id + chunk_index + deviceNo + start_time(如果都存在)
+ *
+ * @param segment 音频分段对象
+ * @return true表示已存在,false表示不存在
+ */
+ private boolean isSegmentExists(AudioManagementSegments segment) {
+ try {
+ // 如果缺少关键字段,无法判断,返回false(允许保存)
+ if (segment.getParentId() == null || segment.getParentId().isEmpty()) {
+ return false;
+ }
+
+ // 构建查询条件
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper.eq(AudioManagementSegments::getParentId, segment.getParentId());
+
+ // chunkIndex 如果存在,则必须匹配
+ if (segment.getChunkIndex() != null && !segment.getChunkIndex().isEmpty()) {
+ queryWrapper.eq(AudioManagementSegments::getChunkIndex, segment.getChunkIndex());
+ }
+
+ // deviceNo 如果存在,则必须匹配
+ if (segment.getDeviceNo() != null && !segment.getDeviceNo().isEmpty()) {
+ queryWrapper.eq(AudioManagementSegments::getDeviceNo, segment.getDeviceNo());
+ }
+
+ // startTime 如果存在,则必须匹配(精确到秒)
+ if (segment.getStartTime() != null) {
+ // 使用时间范围查询(前后1秒内),避免时间精度问题
+ LocalDateTime startTime = segment.getStartTime();
+ queryWrapper.between(AudioManagementSegments::getStartTime,
+ startTime.minusSeconds(1), startTime.plusSeconds(1));
+ }
+
+ // 如果文件路径存在,也作为判断条件之一
+ if (segment.getAudioFilePath() != null && !segment.getAudioFilePath().isEmpty()) {
+ queryWrapper.eq(AudioManagementSegments::getAudioFilePath, segment.getAudioFilePath());
+ }
+
+ // 查询是否存在
+ long count = audioManagementSegmentsService.count(queryWrapper);
+ return count > 0;
+
+ } catch (Exception e) {
+ log.warn("检查音频分段记录是否存在时发生异常,将允许保存: {}", e.getMessage());
+ // 如果检查失败,允许保存(避免因为检查逻辑问题导致数据丢失)
+ return false;
+ }
+ }
+
+ /**
+ * 从请求数据中填充音频分段字段
+ *
+ * @param segment 音频分段对象
+ * @param requestBody 请求体数据
+ * @param processedData 处理后的数据
+ */
+ private void populateSegmentFromRequestData(AudioManagementSegments segment,
+ Map requestBody,
+ Map processedData) {
+ // 从requestBody中提取设备号
+ String deviceNo = extractStringField(requestBody, "deviceNo");
+ if (deviceNo != null) {
+ segment.setDeviceNo(deviceNo);
+ }
+
+ // 从processedData中提取字段
+ if (processedData != null) {
+ segment.setChunkIndex(extractStringField(processedData, "chunkIndex"));
+ }
+ }
+
+ /**
+ * 设置音频分段的文件相关字段
+ *
+ * @param segment 音频分段对象
+ * @param localFilePath 本地文件路径
+ * @param file 上传的文件(可能为null)
+ * @param audioUploadLog 音频上传日志对象
+ */
+ private void setSegmentFileFields(AudioManagementSegments segment,
+ String localFilePath,
+ MultipartFile file,
+ YhyAudioUploadLog audioUploadLog) {
+ // 设置文件路径
+ if (localFilePath != null && !localFilePath.isEmpty()) {
+ segment.setAudioFilePath(localFilePath);
+ } else if (audioUploadLog.getSavePath() != null && audioUploadLog.getFileName() != null) {
+ segment.setAudioFilePath(audioUploadLog.getSavePath() + File.separator + audioUploadLog.getFileName());
+ }
+
+ // 设置文件原始名称
+ if (file != null && file.getOriginalFilename() != null) {
+ segment.setAudioFileOriginalName(file.getOriginalFilename());
+ } else if (audioUploadLog.getFileName() != null) {
+ segment.setAudioFileOriginalName(audioUploadLog.getFileName());
+ }
+
+ // 设置文件扩展名
+ String fileName = segment.getAudioFileOriginalName();
+ if (fileName != null) {
+ String extension = getFileExtension(fileName);
+ if (!extension.isEmpty()) {
+ segment.setAudioFileExtension(extension);
+ }
+ }
+
+ // 设置文件大小
+ if (file != null && file.getSize() > 0) {
+ segment.setAudioFileSize(file.getSize());
+ }
+ }
+
+ /**
+ * 解析时间字段并计算录音时长
+ *
+ * @param segment 音频分段对象
+ * @param processedData 处理后的数据
+ */
+ private void parseAndSetSegmentTimeFields(AudioManagementSegments segment,
+ Map processedData) {
+ if (processedData == null) {
+ return;
+ }
+
+ // 解析开始时间
+ LocalDateTime startTime = parseDateTimeField(processedData, "startTime");
+ if (startTime != null) {
+ segment.setStartTime(startTime);
+ }
+
+ // 解析结束时间
+ LocalDateTime endTime = parseDateTimeField(processedData, "endTime");
+ if (endTime != null) {
+ segment.setEndTime(endTime);
+ }
+
+ // 计算录音时长(分钟)
+ if (startTime != null && endTime != null && endTime.isAfter(startTime)) {
+ long seconds = java.time.Duration.between(startTime, endTime).getSeconds();
+ BigDecimal durationMinutes = BigDecimal.valueOf(seconds).divide(BigDecimal.valueOf(60), 2,
+ java.math.RoundingMode.HALF_UP);
+ segment.setDuration(durationMinutes);
+ }
+ }
+
+ /**
+ * 解析时间字段
+ *
+ * @param dataMap 数据Map
+ * @param fieldName 字段名
+ * @return LocalDateTime对象,如果解析失败则返回null
+ */
+ private LocalDateTime parseDateTimeField(Map dataMap, String fieldName) {
+ if (dataMap == null || fieldName == null) {
+ return null;
+ }
+
+ Object timeObj = dataMap.get(fieldName);
+ if (timeObj == null) {
+ return null;
+ }
+
+ // 如果已经是LocalDateTime类型,直接返回
+ if (timeObj instanceof LocalDateTime) {
+ return (LocalDateTime) timeObj;
+ }
+
+ // 如果是String类型,尝试解析
+ if (timeObj instanceof String) {
+ String timeStr = (String) timeObj;
+ if (timeStr.trim().isEmpty()) {
+ return null;
+ }
+
+ try {
+ // 使用TimeZoneUtils或类似的方法解析时间字符串
+ // 这里简化处理,使用LocalDateTimeDeserializer的逻辑
+ return parseTimeString(timeStr);
+ } catch (Exception e) {
+ log.warn("解析时间字段失败: {} = {}", fieldName, timeStr, e);
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * 解析时间字符串
+ * 支持多种格式:ISO格式、标准格式等
+ *
+ * @param timeStr 时间字符串
+ * @return LocalDateTime对象
+ */
+ private LocalDateTime parseTimeString(String timeStr) {
+ if (timeStr == null || timeStr.trim().isEmpty()) {
+ return null;
+ }
+
+ try {
+ // 检测是否包含时区信息
+ boolean hasTimezone = timeStr.endsWith("Z") ||
+ timeStr.matches(".*[+-]\\d{2}:\\d{2}(?:\\d{2})?$");
+
+ if (hasTimezone) {
+ // 处理带时区的时间
+ java.time.ZonedDateTime zonedDateTime;
+ if (timeStr.endsWith("Z")) {
+ zonedDateTime = java.time.ZonedDateTime.parse(timeStr.replace("Z", "+00:00"))
+ .withZoneSameInstant(TimeZoneUtils.getZoneId());
+ } else {
+ zonedDateTime = java.time.ZonedDateTime.parse(timeStr)
+ .withZoneSameInstant(TimeZoneUtils.getZoneId());
+ }
+ return zonedDateTime.toLocalDateTime();
+ } else {
+ // 处理不带时区的时间
+ String cleanTimeStr = timeStr;
+ if (cleanTimeStr.contains("T")) {
+ cleanTimeStr = cleanTimeStr.replace("T", " ");
+ }
+ if (cleanTimeStr.contains(".")) {
+ cleanTimeStr = cleanTimeStr.substring(0, cleanTimeStr.indexOf("."));
+ }
+ if (cleanTimeStr.length() > 19) {
+ cleanTimeStr = cleanTimeStr.substring(0, 19);
+ }
+ return LocalDateTime.parse(cleanTimeStr,
+ java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+ }
+ } catch (Exception e) {
+ log.warn("解析时间字符串失败: {}", timeStr, e);
+ return null;
+ }
+ }
+
+ /**
+ * 从AudioManagement中获取录音名称等信息
+ *
+ * @param segment 音频分段对象
+ * @param parentId 父录音ID
+ */
+ private void populateSegmentFromAudioManagement(AudioManagementSegments segment, String parentId) {
+ try {
+ AudioManagement audioManagement = serviceManager.getAudioManagementService().getById(parentId);
+ if (audioManagement != null) {
+ // 如果录音名称为空,使用父录音的名称
+ if (segment.getRecordingName() == null || segment.getRecordingName().isEmpty()) {
+ segment.setRecordingName(audioManagement.getRecordingName());
+ }
+
+ // 如果销售信息为空,使用父录音的销售信息
+ if (segment.getSalesPhone() == null || segment.getSalesPhone().isEmpty()) {
+ segment.setSalesPhone(audioManagement.getSalesPhone());
+ }
+ if (segment.getSalesName() == null || segment.getSalesName().isEmpty()) {
+ segment.setSalesName(audioManagement.getSalesName());
+ }
+
+ // 如果经销商信息为空,使用父录音的经销商信息
+ if (segment.getDealershipId() == null || segment.getDealershipId().isEmpty()) {
+ segment.setDealershipId(audioManagement.getDealershipId());
+ }
+ if (segment.getDealershipName() == null || segment.getDealershipName().isEmpty()) {
+ segment.setDealershipName(audioManagement.getDealershipName());
+ }
+
+ // 如果租户ID为空,使用父录音的租户ID
+ if (segment.getTenantId() == null || segment.getTenantId().isEmpty()) {
+ segment.setTenantId(audioManagement.getTenantId());
+ }
+ }
+ } catch (Exception e) {
+ log.warn("从AudioManagement获取信息失败,parentId: {}", parentId, e);
+ // 不影响主流程,只记录日志
+ }
+ }
/**
* 提取请求头信息
@@ -1467,48 +1857,153 @@ public class AudioFileController {
/**
* 处理Audio数据类型(录音推送)
* 适配新的数据结构:dataType="Audio",数据在data字段中
+ * 同时兼容旧的数据结构(数据直接在requestBody中)
+ *
+ * @param requestBody 请求体数据
+ * @param file 上传的音频文件
+ * @return 处理结果,包含success、message、localFilePath等
*/
private Map handleAudioDataType(Map requestBody, MultipartFile file) {
- Map result = new HashMap<>();
+ log.info("开始处理Audio数据类型(录音推送)");
try {
- log.info("处理Audio数据类型(录音推送)");
- // 提取data字段
- Object dataObj = requestBody.get("data");
- Map dataMap = null;
- if (dataObj != null) {
- dataMap = objectMapper.convertValue(dataObj, new TypeReference