定期汇总转写的文本
This commit is contained in:
@@ -1281,6 +1281,9 @@ public class AudioFileController {
|
|||||||
}
|
}
|
||||||
audioManagement.setDuration(currentDuration.add(segmentDuration));
|
audioManagement.setDuration(currentDuration.add(segmentDuration));
|
||||||
audioManagement.setUpdateTime(LocalDateTime.now());
|
audioManagement.setUpdateTime(LocalDateTime.now());
|
||||||
|
if (deviceManagement != null) {
|
||||||
|
audioManagement.setScenario(deviceManagement.getScenario());
|
||||||
|
}
|
||||||
serviceManager.getAudioManagementService().updateById(audioManagement);
|
serviceManager.getAudioManagementService().updateById(audioManagement);
|
||||||
log.info("成功更新AudioManagement记录,ID: {}, 累计时长: {}", segment.getParentId(), audioManagement.getDuration());
|
log.info("成功更新AudioManagement记录,ID: {}, 累计时长: {}", segment.getParentId(), audioManagement.getDuration());
|
||||||
} else {
|
} else {
|
||||||
@@ -1844,6 +1847,7 @@ public class AudioFileController {
|
|||||||
|
|
||||||
audio.setServiceStatus(AudioManagementConstants.SERVICE_STATUS_SERVICE_FINISH_SYS);
|
audio.setServiceStatus(AudioManagementConstants.SERVICE_STATUS_SERVICE_FINISH_SYS);
|
||||||
audio.setDuration(total);
|
audio.setDuration(total);
|
||||||
|
audio.setScenario(deviceManagement.getScenario());
|
||||||
serviceManager.getAudioManagementService().updateById(audio);
|
serviceManager.getAudioManagementService().updateById(audio);
|
||||||
log.info("更新audio_management记录状态为服务结束,ID: {}, updateTime: {}, 距离现在: {}小时",
|
log.info("更新audio_management记录状态为服务结束,ID: {}, updateTime: {}, 距离现在: {}小时",
|
||||||
audio.getId(), audio.getUpdateTime(), daysBetween);
|
audio.getId(), audio.getUpdateTime(), daysBetween);
|
||||||
@@ -1876,6 +1880,7 @@ public class AudioFileController {
|
|||||||
audio.setServiceStatus(AudioManagementConstants.SERVICE_STATUS_SERVICE_FINISH_SYS);
|
audio.setServiceStatus(AudioManagementConstants.SERVICE_STATUS_SERVICE_FINISH_SYS);
|
||||||
audio.setUpdateTime(LocalDateTime.now());
|
audio.setUpdateTime(LocalDateTime.now());
|
||||||
audio.setDuration(total);
|
audio.setDuration(total);
|
||||||
|
audio.setScenario(deviceManagement.getScenario());
|
||||||
serviceManager.getAudioManagementService().updateById(audio);
|
serviceManager.getAudioManagementService().updateById(audio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1918,6 +1923,7 @@ public class AudioFileController {
|
|||||||
newAudio.setDealershipName(deviceManagement.getDealershipName());
|
newAudio.setDealershipName(deviceManagement.getDealershipName());
|
||||||
newAudio.setDealershipId(deviceManagement.getDealershipId());
|
newAudio.setDealershipId(deviceManagement.getDealershipId());
|
||||||
newAudio.setServiceStatus(AudioManagementConstants.SERVICE_STATUS_IN_SERVICE);
|
newAudio.setServiceStatus(AudioManagementConstants.SERVICE_STATUS_IN_SERVICE);
|
||||||
|
newAudio.setScenario(deviceManagement.getScenario());
|
||||||
newAudio.setCreateTime(TimeZoneUtils.now());
|
newAudio.setCreateTime(TimeZoneUtils.now());
|
||||||
newAudio.setUpdateTime(TimeZoneUtils.now());
|
newAudio.setUpdateTime(TimeZoneUtils.now());
|
||||||
|
|
||||||
@@ -2387,6 +2393,9 @@ public class AudioFileController {
|
|||||||
audioManagement.setId(parentId);
|
audioManagement.setId(parentId);
|
||||||
audioManagement.setRecordingText(concatenatedText.toString());
|
audioManagement.setRecordingText(concatenatedText.toString());
|
||||||
audioManagement.setUpdateTime(LocalDateTime.now());
|
audioManagement.setUpdateTime(LocalDateTime.now());
|
||||||
|
if (deviceManagement != null) {
|
||||||
|
audioManagement.setScenario(deviceManagement.getScenario());
|
||||||
|
}
|
||||||
|
|
||||||
boolean updateAudioManagementResult = serviceManager.getAudioManagementService().updateById(audioManagement);
|
boolean updateAudioManagementResult = serviceManager.getAudioManagementService().updateById(audioManagement);
|
||||||
if (updateAudioManagementResult) {
|
if (updateAudioManagementResult) {
|
||||||
|
|||||||
@@ -507,6 +507,8 @@ public class AudioManagementController {
|
|||||||
customerToUpdate.setCustomerName(audioManagement.getCustomerName());
|
customerToUpdate.setCustomerName(audioManagement.getCustomerName());
|
||||||
customerToUpdate.setContact(audioManagement.getCustomerPhone());
|
customerToUpdate.setContact(audioManagement.getCustomerPhone());
|
||||||
customerToUpdate.setDetailedAddress(audioManagement.getRemarks());
|
customerToUpdate.setDetailedAddress(audioManagement.getRemarks());
|
||||||
|
customerToUpdate.setSalesPhone(audioManagement.getSalesPhone());
|
||||||
|
customerToUpdate.setSalesName(audioManagement.getSalesName());
|
||||||
customerToUpdate.setUpdateTime(LocalDateTime.now());
|
customerToUpdate.setUpdateTime(LocalDateTime.now());
|
||||||
customerManagementService.updateById(customerToUpdate);
|
customerManagementService.updateById(customerToUpdate);
|
||||||
log.info("同步更新客户信息成功,客户ID: {}", customerToUpdate.getId());
|
log.info("同步更新客户信息成功,客户ID: {}", customerToUpdate.getId());
|
||||||
@@ -519,6 +521,9 @@ public class AudioManagementController {
|
|||||||
customerToUpdate.setContact(audioManagement.getCustomerPhone());
|
customerToUpdate.setContact(audioManagement.getCustomerPhone());
|
||||||
customerToUpdate.setDetailedAddress(audioManagement.getRemarks());
|
customerToUpdate.setDetailedAddress(audioManagement.getRemarks());
|
||||||
customerToUpdate.setUpdateTime(LocalDateTime.now());
|
customerToUpdate.setUpdateTime(LocalDateTime.now());
|
||||||
|
customerToUpdate.setRecordingCount(1);
|
||||||
|
customerToUpdate.setSalesPhone(audioManagement.getSalesPhone());
|
||||||
|
customerToUpdate.setSalesName(audioManagement.getSalesName());
|
||||||
customerManagementService.save(customerToUpdate);
|
customerManagementService.save(customerToUpdate);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
package com.rj.mapper;
|
package com.rj.mapper;
|
||||||
|
|
||||||
import com.rj.entity.AudioManagement;
|
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -13,4 +18,10 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|||||||
*/
|
*/
|
||||||
public interface AudioManagementMapper extends BaseMapper<AudioManagement> {
|
public interface AudioManagementMapper extends BaseMapper<AudioManagement> {
|
||||||
|
|
||||||
|
@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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,4 +36,23 @@ public interface AudioManagementSegmentsMapper extends BaseMapper<AudioManagemen
|
|||||||
@InterceptorIgnore(tenantLine = "true")
|
@InterceptorIgnore(tenantLine = "true")
|
||||||
@Update("UPDATE audio_management_segments SET audio_file_url = #{audioFileUrl} WHERE id = #{id}")
|
@Update("UPDATE audio_management_segments SET audio_file_url = #{audioFileUrl} WHERE id = #{id}")
|
||||||
int updateAudioFileUrlByIdIgnoreTenant(@Param("id") String id, @Param("audioFileUrl") String audioFileUrl);
|
int updateAudioFileUrlByIdIgnoreTenant(@Param("id") String id, @Param("audioFileUrl") String audioFileUrl);
|
||||||
|
|
||||||
|
/** 依原始音频文件名更新分段转写文本(调度任务等无租户上下文场景)。 */
|
||||||
|
@InterceptorIgnore(tenantLine = "true")
|
||||||
|
@Update("UPDATE audio_management_segments SET recording_text = #{recordingText} "
|
||||||
|
+ "WHERE audio_file_original_name = #{audioFileOriginalName}")
|
||||||
|
int updateRecordingTextByAudioFileOriginalNameIgnoreTenant(
|
||||||
|
@Param("recordingText") String recordingText,
|
||||||
|
@Param("audioFileOriginalName") String audioFileOriginalName);
|
||||||
|
|
||||||
|
@InterceptorIgnore(tenantLine = "true")
|
||||||
|
@Select("SELECT parent_id FROM audio_management_segments WHERE audio_file_original_name = #{audioFileOriginalName} "
|
||||||
|
+ "LIMIT 1")
|
||||||
|
String selectParentIdByAudioFileOriginalNameLimit1(@Param("audioFileOriginalName") String audioFileOriginalName);
|
||||||
|
|
||||||
|
/** 汇总父录音下各段文本与时长(仅查必要列)。 */
|
||||||
|
@InterceptorIgnore(tenantLine = "true")
|
||||||
|
@Select("SELECT id, recording_text, duration FROM audio_management_segments WHERE parent_id = #{parentId} "
|
||||||
|
+ "ORDER BY id ASC")
|
||||||
|
List<AudioManagementSegments> listRecordingPartsByParentId(@Param("parentId") String parentId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,37 @@ package com.rj.scheduler;
|
|||||||
|
|
||||||
import com.rj.config.AppConfig;
|
import com.rj.config.AppConfig;
|
||||||
import com.rj.entity.AudioManagementSegments;
|
import com.rj.entity.AudioManagementSegments;
|
||||||
|
import com.rj.mapper.AudioManagementMapper;
|
||||||
|
import com.rj.mapper.AudioManagementSegmentsMapper;
|
||||||
import com.rj.service.IAudioManagementSegmentsService;
|
import com.rj.service.IAudioManagementSegmentsService;
|
||||||
import com.rj.service.IAudioManagementStatisticsService;
|
import com.rj.service.IAudioManagementStatisticsService;
|
||||||
import com.rj.service.MinIOService;
|
import com.rj.service.MinIOService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
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.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 门店录音统计定时任务
|
* 门店录音统计定时任务
|
||||||
@@ -41,6 +56,131 @@ public class AudioStatisticsScheduler {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private MinIOService minIOService;
|
private MinIOService minIOService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AudioManagementSegmentsMapper audioManagementSegmentsMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AudioManagementMapper audioManagementMapper;
|
||||||
|
|
||||||
|
private static final Set<String> 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<Path> files;
|
||||||
|
try (Stream<Path> 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<AudioManagementSegments> rows = audioManagementSegmentsMapper.listRecordingPartsByParentId(parentId);
|
||||||
|
List<String> 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。
|
* 每 5 分钟:将仅有本地路径、尚未写入 MinIO URL 的录音分段补传到 Minio 并回写 audio_file_url。
|
||||||
*/
|
*/
|
||||||
@@ -49,6 +189,9 @@ public class AudioStatisticsScheduler {
|
|||||||
if (!appConfig.getScheduler().isStart()) {
|
if (!appConfig.getScheduler().isStart()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains("win")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
List<AudioManagementSegments> pending = audioManagementSegmentsService.listSegmentsNeedingMinioUpload(50);
|
List<AudioManagementSegments> pending = audioManagementSegmentsService.listSegmentsNeedingMinioUpload(50);
|
||||||
if (pending.isEmpty()) {
|
if (pending.isEmpty()) {
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ app:
|
|||||||
# 文件上传保存路径(用于接收文件上传请求时)
|
# 文件上传保存路径(用于接收文件上传请求时)
|
||||||
yihangyi:
|
yihangyi:
|
||||||
path: /usr/local/tomcat/webapps/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前缀
|
# 音频文件访问URL前缀
|
||||||
access:
|
access:
|
||||||
url: /api/audio/
|
url: /api/audio/
|
||||||
|
|||||||
Reference in New Issue
Block a user