From da6dab7dd15efe0fe8241d051ec133583e57fb71 Mon Sep 17 00:00:00 2001 From: cst61 Date: Sat, 4 Apr 2026 17:12:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9A=E6=9C=9F=E6=B1=87=E6=80=BB=E8=BD=AC?= =?UTF-8?q?=E5=86=99=E7=9A=84=E6=96=87=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rj/controller/AudioFileController.java | 9 ++ .../controller/AudioManagementController.java | 5 + .../com/rj/mapper/AudioManagementMapper.java | 13 +- .../mapper/AudioManagementSegmentsMapper.java | 19 +++ .../scheduler/AudioStatisticsScheduler.java | 143 ++++++++++++++++++ src/main/resources/application-audio.yml | 3 + 6 files changed, 191 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/rj/controller/AudioFileController.java b/src/main/java/com/rj/controller/AudioFileController.java index 1a3b6aa..8bca749 100644 --- a/src/main/java/com/rj/controller/AudioFileController.java +++ b/src/main/java/com/rj/controller/AudioFileController.java @@ -1281,6 +1281,9 @@ public class AudioFileController { } audioManagement.setDuration(currentDuration.add(segmentDuration)); audioManagement.setUpdateTime(LocalDateTime.now()); + if (deviceManagement != null) { + audioManagement.setScenario(deviceManagement.getScenario()); + } serviceManager.getAudioManagementService().updateById(audioManagement); log.info("成功更新AudioManagement记录,ID: {}, 累计时长: {}", segment.getParentId(), audioManagement.getDuration()); } else { @@ -1844,6 +1847,7 @@ public class AudioFileController { audio.setServiceStatus(AudioManagementConstants.SERVICE_STATUS_SERVICE_FINISH_SYS); audio.setDuration(total); + audio.setScenario(deviceManagement.getScenario()); serviceManager.getAudioManagementService().updateById(audio); log.info("更新audio_management记录状态为服务结束,ID: {}, updateTime: {}, 距离现在: {}小时", audio.getId(), audio.getUpdateTime(), daysBetween); @@ -1876,6 +1880,7 @@ public class AudioFileController { audio.setServiceStatus(AudioManagementConstants.SERVICE_STATUS_SERVICE_FINISH_SYS); audio.setUpdateTime(LocalDateTime.now()); audio.setDuration(total); + audio.setScenario(deviceManagement.getScenario()); serviceManager.getAudioManagementService().updateById(audio); } } @@ -1918,6 +1923,7 @@ public class AudioFileController { newAudio.setDealershipName(deviceManagement.getDealershipName()); newAudio.setDealershipId(deviceManagement.getDealershipId()); newAudio.setServiceStatus(AudioManagementConstants.SERVICE_STATUS_IN_SERVICE); + newAudio.setScenario(deviceManagement.getScenario()); newAudio.setCreateTime(TimeZoneUtils.now()); newAudio.setUpdateTime(TimeZoneUtils.now()); @@ -2387,6 +2393,9 @@ public class AudioFileController { audioManagement.setId(parentId); audioManagement.setRecordingText(concatenatedText.toString()); audioManagement.setUpdateTime(LocalDateTime.now()); + if (deviceManagement != null) { + audioManagement.setScenario(deviceManagement.getScenario()); + } boolean updateAudioManagementResult = serviceManager.getAudioManagementService().updateById(audioManagement); if (updateAudioManagementResult) { diff --git a/src/main/java/com/rj/controller/AudioManagementController.java b/src/main/java/com/rj/controller/AudioManagementController.java index e27bba5..fce5593 100644 --- a/src/main/java/com/rj/controller/AudioManagementController.java +++ b/src/main/java/com/rj/controller/AudioManagementController.java @@ -507,6 +507,8 @@ public class AudioManagementController { customerToUpdate.setCustomerName(audioManagement.getCustomerName()); customerToUpdate.setContact(audioManagement.getCustomerPhone()); customerToUpdate.setDetailedAddress(audioManagement.getRemarks()); + customerToUpdate.setSalesPhone(audioManagement.getSalesPhone()); + customerToUpdate.setSalesName(audioManagement.getSalesName()); customerToUpdate.setUpdateTime(LocalDateTime.now()); customerManagementService.updateById(customerToUpdate); log.info("同步更新客户信息成功,客户ID: {}", customerToUpdate.getId()); @@ -519,6 +521,9 @@ public class AudioManagementController { customerToUpdate.setContact(audioManagement.getCustomerPhone()); customerToUpdate.setDetailedAddress(audioManagement.getRemarks()); customerToUpdate.setUpdateTime(LocalDateTime.now()); + customerToUpdate.setRecordingCount(1); + customerToUpdate.setSalesPhone(audioManagement.getSalesPhone()); + customerToUpdate.setSalesName(audioManagement.getSalesName()); customerManagementService.save(customerToUpdate); } } catch (Exception e) { diff --git a/src/main/java/com/rj/mapper/AudioManagementMapper.java b/src/main/java/com/rj/mapper/AudioManagementMapper.java index a753df5..9d7e3bc 100644 --- a/src/main/java/com/rj/mapper/AudioManagementMapper.java +++ b/src/main/java/com/rj/mapper/AudioManagementMapper.java @@ -1,7 +1,12 @@ package com.rj.mapper; -import com.rj.entity.AudioManagement; +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.rj.entity.AudioManagement; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Update; + +import java.math.BigDecimal; /** *

@@ -13,4 +18,10 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; */ public interface AudioManagementMapper extends BaseMapper { + @InterceptorIgnore(tenantLine = "true") + @Update("UPDATE audio_management SET recording_text = #{recordingText}, duration = #{duration} WHERE id = #{id}") + int updateRecordingTextAndDurationByIdIgnoreTenant( + @Param("recordingText") String recordingText, + @Param("duration") BigDecimal duration, + @Param("id") String id); } diff --git a/src/main/java/com/rj/mapper/AudioManagementSegmentsMapper.java b/src/main/java/com/rj/mapper/AudioManagementSegmentsMapper.java index da849fc..2649ad3 100644 --- a/src/main/java/com/rj/mapper/AudioManagementSegmentsMapper.java +++ b/src/main/java/com/rj/mapper/AudioManagementSegmentsMapper.java @@ -36,4 +36,23 @@ public interface AudioManagementSegmentsMapper extends BaseMapper listRecordingPartsByParentId(@Param("parentId") String parentId); } diff --git a/src/main/java/com/rj/scheduler/AudioStatisticsScheduler.java b/src/main/java/com/rj/scheduler/AudioStatisticsScheduler.java index 9641308..a137d03 100644 --- a/src/main/java/com/rj/scheduler/AudioStatisticsScheduler.java +++ b/src/main/java/com/rj/scheduler/AudioStatisticsScheduler.java @@ -2,22 +2,37 @@ package com.rj.scheduler; import com.rj.config.AppConfig; import com.rj.entity.AudioManagementSegments; +import com.rj.mapper.AudioManagementMapper; +import com.rj.mapper.AudioManagementSegmentsMapper; import com.rj.service.IAudioManagementSegmentsService; import com.rj.service.IAudioManagementStatisticsService; import com.rj.service.MinIOService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.io.FileInputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Comparator; import java.util.List; +import java.util.Locale; +import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * 门店录音统计定时任务 @@ -41,6 +56,131 @@ public class AudioStatisticsScheduler { @Autowired private MinIOService minIOService; + @Autowired + private AudioManagementSegmentsMapper audioManagementSegmentsMapper; + + @Autowired + private AudioManagementMapper audioManagementMapper; + + private static final Set TRANSCRIPT_TEXT_SUFFIXES = Set.of(".txt", ".text"); + + @Value("${app.audio.upload.yihangyi.txt-scan-dir:/home/lizh/java_env/AIDriverEEBackend/audio/yihangyi_txt}") + private String yihangyiTxtScanDir; + + @Value("${app.audio.upload.yihangyi.txt-finish-dir:/home/lizh/java_env/AIDriverEEBackend/audio/yihangyi_txt_finish}") + private String yihangyiTxtFinishDir; + + /** + * 每 2 分钟:扫描目录中的转写文本,按「文件名去扩展名 + .mp3」匹配 {@code audio_file_original_name}, + * 写入 {@code audio_management_segments.recording_text},成功后移至完成目录并汇总回写 {@code audio_management}。 + */ + @Scheduled(fixedRate = 120000) + public void syncYihangyiTranscriptTextFiles() { + if (!appConfig.getScheduler().isStart()) { + return; + } + if (System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains("win")) { + return; + } + Path scanDir = Paths.get(yihangyiTxtScanDir); + if (!Files.isDirectory(scanDir)) { + log.error("yihangyi 转写文本扫描目录不存在或不是目录: {}", scanDir.toAbsolutePath()); + return; + } + List files; + try (Stream stream = Files.list(scanDir)) { + files = stream + .filter(Files::isRegularFile) + .filter(p -> { + String name = p.getFileName().toString(); + int dot = name.lastIndexOf('.'); + String suf = dot < 0 ? "" : name.substring(dot).toLowerCase(Locale.ROOT); + return TRANSCRIPT_TEXT_SUFFIXES.contains(suf); + }) + .sorted(Comparator.comparing(p -> p.getFileName().toString())) + .collect(Collectors.toList()); + } catch (IOException e) { + log.error("列出 yihangyi 转写文本目录失败: {}", scanDir.toAbsolutePath(), e); + return; + } + log.info("yihangyi 转写文本本次扫描到文件数: {}", files.size()); + for (Path path : files) { + try { + syncOneTranscriptFile(path); + } catch (Exception e) { + log.error("处理 yihangyi 转写文本文件失败: {}", path.toAbsolutePath(), e); + } + } + } + + private void syncOneTranscriptFile(Path path) throws IOException { + String stem = fileNameWithoutExtension(path.getFileName().toString()); + String audioFileOriginalName = stem + ".mp3"; + String content = readTranscriptTextFile(path); + log.info("执行 UPDATE audio_management_segments SET recording_text=? WHERE audio_file_original_name=? 参数长度={}, name={}", + content != null ? content.length() : 0, audioFileOriginalName); + int n = audioManagementSegmentsMapper.updateRecordingTextByAudioFileOriginalNameIgnoreTenant( + content, audioFileOriginalName); + if (n == 0) { + log.warn("未找到 audio_file_original_name={} 的分段记录,跳过: {}", audioFileOriginalName, path.getFileName()); + return; + } + log.info("已更新 recording_text,audio_file_original_name={},影响行数={}", audioFileOriginalName, n); + String parentId = audioManagementSegmentsMapper.selectParentIdByAudioFileOriginalNameLimit1(audioFileOriginalName); + Path finishDir = Paths.get(yihangyiTxtFinishDir); + try { + Files.createDirectories(finishDir); + Path dest = finishDir.resolve(path.getFileName()); + Files.move(path, dest, StandardCopyOption.REPLACE_EXISTING); + log.info("更新成功,已移动文件: {} -> {}", path.getFileName(), dest.toAbsolutePath()); + } catch (Exception e) { + log.error("更新已成功但移动文件失败: {}", path.toAbsolutePath(), e); + return; + } + if (StringUtils.hasText(parentId)) { + try { + refreshAudioManagementFromSegments(parentId); + } catch (Exception e) { + log.error("移动成功后汇总回写 audio_management 失败 parent_id={}", parentId, e); + } + } + } + + private static String fileNameWithoutExtension(String fileName) { + int dot = fileName.lastIndexOf('.'); + return dot < 0 ? fileName : fileName.substring(0, dot); + } + + private static String readTranscriptTextFile(Path path) throws IOException { + byte[] bytes = Files.readAllBytes(path); + return StandardCharsets.UTF_8.newDecoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE) + .decode(ByteBuffer.wrap(bytes)) + .toString(); + } + + private void refreshAudioManagementFromSegments(String parentId) { + List rows = audioManagementSegmentsMapper.listRecordingPartsByParentId(parentId); + List parts = new ArrayList<>(); + BigDecimal durationTotal = BigDecimal.ZERO; + for (AudioManagementSegments row : rows) { + String rt = row.getRecordingText(); + if (rt != null && !rt.isBlank()) { + parts.add(rt); + } + if (row.getDuration() != null) { + durationTotal = durationTotal.add(row.getDuration()); + } + } + String recordingTextTotal = String.join("\n", parts); + log.info("汇总回写 audio_management:id={},子段数={}", parentId, rows.size()); + int um = audioManagementMapper.updateRecordingTextAndDurationByIdIgnoreTenant( + recordingTextTotal, durationTotal, parentId); + log.info("已汇总回写 audio_management id={},子段数={},影响行数={},duration_total={}", + parentId, rows.size(), um, durationTotal); + } + /** * 每 5 分钟:将仅有本地路径、尚未写入 MinIO URL 的录音分段补传到 Minio 并回写 audio_file_url。 */ @@ -49,6 +189,9 @@ public class AudioStatisticsScheduler { if (!appConfig.getScheduler().isStart()) { return; } + if (System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains("win")) { + return; + } try { List pending = audioManagementSegmentsService.listSegmentsNeedingMinioUpload(50); if (pending.isEmpty()) { diff --git a/src/main/resources/application-audio.yml b/src/main/resources/application-audio.yml index a127b3e..b878b0c 100644 --- a/src/main/resources/application-audio.yml +++ b/src/main/resources/application-audio.yml @@ -7,6 +7,9 @@ app: # 文件上传保存路径(用于接收文件上传请求时) yihangyi: path: /usr/local/tomcat/webapps/yihangyi + # 外部 ASR 写入的转写文本目录(与 AudioStatisticsScheduler.syncYihangyiTranscriptTextFiles 一致) + txt-scan-dir: /home/lizh/java_env/AIDriverEEBackend/audio/yihangyi_txt + txt-finish-dir: /home/lizh/java_env/AIDriverEEBackend/audio/yihangyi_txt_finish # 音频文件访问URL前缀 access: url: /api/audio/