|
|
@ -129,7 +129,7 @@ public class FtpSyncService { |
|
|
FTPClient ftp = null; |
|
|
FTPClient ftp = null; |
|
|
try { |
|
|
try { |
|
|
ftp = FtpUtil.connect(city.getFtpHost(), city.getFtpPort(), |
|
|
ftp = FtpUtil.connect(city.getFtpHost(), city.getFtpPort(), |
|
|
city.getFtpUsername(), city.getFtpPassword()); |
|
|
city.getFtpUsername(), city.getFtpPassword(), city.isPassiveMode()); |
|
|
logger.info("【FTP地市】FTP连接成功"); |
|
|
logger.info("【FTP地市】FTP连接成功"); |
|
|
|
|
|
|
|
|
FTPFile[] files = FtpUtil.listFiles(ftp, city.getFtpSourceDir()); |
|
|
FTPFile[] files = FtpUtil.listFiles(ftp, city.getFtpSourceDir()); |
|
|
@ -140,6 +140,11 @@ public class FtpSyncService { |
|
|
Set<String> processedFiles = loadProcessedFileNames(city.getCityCode()); |
|
|
Set<String> processedFiles = loadProcessedFileNames(city.getCityCode()); |
|
|
logger.info("【FTP地市】已处理文件记录: {} 个", processedFiles.size()); |
|
|
logger.info("【FTP地市】已处理文件记录: {} 个", processedFiles.size()); |
|
|
|
|
|
|
|
|
|
|
|
// 按文件名升序排序,确保按时间先后顺序依次处理
|
|
|
|
|
|
if (files != null && files.length > 0) { |
|
|
|
|
|
Arrays.sort(files, Comparator.comparing(FTPFile::getName)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
int fileSuccess = 0; |
|
|
int fileSuccess = 0; |
|
|
int fileFail = 0; |
|
|
int fileFail = 0; |
|
|
int fileSkipped = 0; |
|
|
int fileSkipped = 0; |
|
|
@ -149,10 +154,9 @@ public class FtpSyncService { |
|
|
continue; |
|
|
continue; |
|
|
} |
|
|
} |
|
|
String fileName = file.getName(); |
|
|
String fileName = file.getName(); |
|
|
// 历史目录中只处理当天已经封口的时间段文件:
|
|
|
// 检查是否为有效的数据TXT文件(支持跨天、一天内多个切片文件;排除空文件或未封口占位文件)
|
|
|
// 例如 20260722123-20260722163.txt;空文件或无横杠的占位文件暂不处理。
|
|
|
if (!isValidDataTxtFile(file)) { |
|
|
if (!isTodayCompletedIntervalTxtFile(file)) { |
|
|
logger.debug("【FTP文件】非有效数据文件,跳过: {} ({} 字节)", |
|
|
logger.debug("【FTP文件】非当天有效时间段文件,跳过: {} ({} 字节)", |
|
|
|
|
|
fileName, file.getSize()); |
|
|
fileName, file.getSize()); |
|
|
continue; |
|
|
continue; |
|
|
} |
|
|
} |
|
|
@ -173,7 +177,7 @@ public class FtpSyncService { |
|
|
try { |
|
|
try { |
|
|
FtpUtil.disconnect(ftp); // 关闭老连接
|
|
|
FtpUtil.disconnect(ftp); // 关闭老连接
|
|
|
ftp = FtpUtil.connect(city.getFtpHost(), city.getFtpPort(), |
|
|
ftp = FtpUtil.connect(city.getFtpHost(), city.getFtpPort(), |
|
|
city.getFtpUsername(), city.getFtpPassword()); |
|
|
city.getFtpUsername(), city.getFtpPassword(), city.isPassiveMode()); |
|
|
logger.info("【FTP地市】重新连接 FTP 成功,正在重试处理刚才失败的文件: {}", fileName); |
|
|
logger.info("【FTP地市】重新连接 FTP 成功,正在重试处理刚才失败的文件: {}", fileName); |
|
|
|
|
|
|
|
|
// 重新尝试处理文件
|
|
|
// 重新尝试处理文件
|
|
|
@ -341,15 +345,21 @@ public class FtpSyncService { |
|
|
String remoteRecordPath = recordDir + "/" + dateStr + "/" + recordFileName; |
|
|
String remoteRecordPath = recordDir + "/" + dateStr + "/" + recordFileName; |
|
|
logger.info("【FTP行】准备下载录音: {}", remoteRecordPath); |
|
|
logger.info("【FTP行】准备下载录音: {}", remoteRecordPath); |
|
|
byte[] recordBytes = FtpUtil.downloadFile(ftp, remoteRecordPath); |
|
|
byte[] recordBytes = FtpUtil.downloadFile(ftp, remoteRecordPath); |
|
|
logger.info("【FTP行】下载录音: {} ({} 字节)", recordFileName, recordBytes.length); |
|
|
String ossUrl = null; |
|
|
|
|
|
int fileSize = 0; |
|
|
|
|
|
if (recordBytes != null && recordBytes.length > 0) { |
|
|
|
|
|
fileSize = recordBytes.length; |
|
|
|
|
|
logger.info("【FTP行】下载录音: {} ({} 字节)", recordFileName, fileSize); |
|
|
// 上传到OSS(获取可访问的URL)
|
|
|
// 上传到OSS(获取可访问的URL)
|
|
|
String ossUrl = fileUploadUtil.uploadWav(ossPath, recordFileName, recordBytes); |
|
|
ossUrl = fileUploadUtil.uploadWav(ossPath, recordFileName, recordBytes); |
|
|
logger.info("【FTP行】上传OSS: {}", ossUrl); |
|
|
logger.info("【FTP行】上传OSS: {}", ossUrl); |
|
|
|
|
|
} else { |
|
|
|
|
|
logger.warn("【FTP行】录音文件不存在或为空,跳过OSS上传: {}", remoteRecordPath); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 构建实体对象并入库
|
|
|
// 构建实体对象并入库
|
|
|
MidVoiceCallRecord rec = buildCallRecord(city, callRecordId, callStartTime, callEndTime, |
|
|
MidVoiceCallRecord rec = buildCallRecord(city, callRecordId, callStartTime, callEndTime, |
|
|
thsc, isOutgoing, zjhm, bjhm, recordFileName, recordBytes.length, ossUrl); |
|
|
thsc, isOutgoing, zjhm, bjhm, recordFileName, fileSize, ossUrl); |
|
|
callRecordMapper.insert(rec); |
|
|
callRecordMapper.insert(rec); |
|
|
existingIds.add(callRecordId); // 防止同批内重复(本次循环内)
|
|
|
existingIds.add(callRecordId); // 防止同批内重复(本次循环内)
|
|
|
logger.info("【FTP行】保存成功: id={}, callRecordId={}", rec.getId(), callRecordId); |
|
|
logger.info("【FTP行】保存成功: id={}, callRecordId={}", rec.getId(), callRecordId); |
|
|
@ -478,19 +488,32 @@ public class FtpSyncService { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 判断是否为当天已经生成完成的时间段TXT文件。 |
|
|
* 判断是否为有效且已封口的TXT数据文件(支持跨天、多天、一天多个时间切片文件) |
|
|
* 文件名规则:以当天yyyyMMdd开头,日期后同时包含起止时间横杠,且文件非空。 |
|
|
* 兼容格式: |
|
|
|
|
|
* 1. 芜湖等带横杠时间段格式:如 202608170700-202608170730.txt(要求两端时间戳完整,排除未写完的占位文件) |
|
|
|
|
|
* 2. 淮南/蚌埠等纯时间戳格式:如 202608170740.txt(yyyyMMddHHmm.txt 或 yyyyMMddHHmmss.txt) |
|
|
*/ |
|
|
*/ |
|
|
private boolean isTodayCompletedIntervalTxtFile(FTPFile file) { |
|
|
private boolean isValidDataTxtFile(FTPFile file) { |
|
|
if (!FtpUtil.isTxtFile(file) || file.getSize() <= 0) { |
|
|
if (!FtpUtil.isTxtFile(file) || file.getSize() <= 0) { |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
String fileName = file.getName(); |
|
|
String fileName = file.getName(); |
|
|
String today = DateUtil.formatDate(new Date(), "yyyyMMdd"); |
|
|
// 去掉后缀 .txt
|
|
|
int separatorIndex = fileName.indexOf('-', today.length()); |
|
|
String nameWithoutExt = fileName.substring(0, fileName.length() - ".txt".length()); |
|
|
return fileName.startsWith(today) |
|
|
|
|
|
&& separatorIndex > today.length() |
|
|
// 基础规则:文件名开头至少是 8 位合法数字日期(yyyyMMdd,如 20260817)
|
|
|
&& separatorIndex < fileName.length() - ".txt".length() - 1; |
|
|
if (nameWithoutExt.length() < 8 || !nameWithoutExt.substring(0, 8).matches("\\d{8}")) { |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (nameWithoutExt.contains("-")) { |
|
|
|
|
|
// 模式1:芜湖等时间段文件(必须包含横杠且两端时间戳完整)
|
|
|
|
|
|
int separatorIndex = nameWithoutExt.indexOf('-'); |
|
|
|
|
|
return separatorIndex >= 8 && separatorIndex < nameWithoutExt.length() - 1; |
|
|
|
|
|
} else { |
|
|
|
|
|
// 模式2:淮南/蚌埠等纯时间戳文件(纯数字,如 202608170740)
|
|
|
|
|
|
return nameWithoutExt.matches("\\d{12,14}"); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
|