|
|
|
@ -4,9 +4,11 @@ import com.alibaba.fastjson2.JSONArray; |
|
|
|
import com.alibaba.fastjson2.JSONObject; |
|
|
|
import com.threecloud.dataserviceyy.entity.MidVoiceCallRecord; |
|
|
|
import com.threecloud.dataserviceyy.entity.MidVoiceDeviceLog; |
|
|
|
import com.threecloud.dataserviceyy.entity.MidVoiceResyncLog; |
|
|
|
import com.threecloud.dataserviceyy.entity.OssFileResponse; |
|
|
|
import com.threecloud.dataserviceyy.mapper.MidVoiceCallRecordMapper; |
|
|
|
import com.threecloud.dataserviceyy.mapper.MidVoiceDeviceLogMapper; |
|
|
|
import com.threecloud.dataserviceyy.mapper.MidVoiceResyncLogMapper; |
|
|
|
import com.threecloud.dataserviceyy.mapper.VoiceSyncMapper; |
|
|
|
import com.threecloud.dataserviceyy.util.*; |
|
|
|
import org.slf4j.Logger; |
|
|
|
@ -45,6 +47,8 @@ public class VaaSyncService { |
|
|
|
@Autowired |
|
|
|
private MidVoiceDeviceLogMapper deviceLogMapper; |
|
|
|
@Autowired |
|
|
|
private MidVoiceResyncLogMapper resyncLogMapper; |
|
|
|
@Autowired |
|
|
|
private VaaHttpUtil vaaHttpUtil; |
|
|
|
@Autowired |
|
|
|
private OssFileService ossFileService; |
|
|
|
@ -97,6 +101,149 @@ public class VaaSyncService { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 根据通话记录编号重新同步录音文件 |
|
|
|
* |
|
|
|
* 【说明】只重新下载录音文件并上传OSS,不修改 mid_voice_call_record 业务数据。 |
|
|
|
* 流程:查通话记录 → 查设备 → 登录 → 按时间范围查录音列表 → 匹配记录 → 下载 → 上传OSS → 记录操作日志 |
|
|
|
* |
|
|
|
* @param callRecordId 通话记录编号 |
|
|
|
* @return 成功提示信息 |
|
|
|
* @throws RuntimeException 重新同步失败(已记录操作日志) |
|
|
|
*/ |
|
|
|
public String resyncByCallRecordId(String callRecordId) { |
|
|
|
String deviceNo = null; |
|
|
|
String fileName = null; |
|
|
|
String ossFilePath = null; |
|
|
|
try { |
|
|
|
if (!StringUtils.hasText(callRecordId)) { |
|
|
|
throw new IllegalArgumentException("通话记录编号为空"); |
|
|
|
} |
|
|
|
|
|
|
|
// 1. 查询通话记录
|
|
|
|
MidVoiceCallRecord record = callRecordMapper.selectByCallRecordId(callRecordId); |
|
|
|
if (record == null) { |
|
|
|
throw new IllegalArgumentException("通话记录不存在: " + callRecordId); |
|
|
|
} |
|
|
|
deviceNo = record.getDeviceNo(); |
|
|
|
fileName = record.getRecordingFileName(); |
|
|
|
Date callStartTime = record.getCallStartTime(); |
|
|
|
Date callEndTime = record.getCallEndTime(); |
|
|
|
if (callStartTime == null) { |
|
|
|
throw new IllegalStateException("通话记录缺少开始时间: " + callRecordId); |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 查询设备
|
|
|
|
Map<String, Object> device = voiceSyncMapper.getDeviceByNo(deviceNo); |
|
|
|
if (device == null || device.isEmpty()) { |
|
|
|
throw new IllegalStateException("语音设备不存在或未配置: " + deviceNo); |
|
|
|
} |
|
|
|
String cityCode = getStr(device, "ORGAN_ID"); |
|
|
|
String ip = getStr(device, "IP"); |
|
|
|
Integer port = getInt(device, "PORT", 80); |
|
|
|
String username = getStr(device, "USERNAME"); |
|
|
|
String password = getStr(device, "PASSWORD"); |
|
|
|
if (!StringUtils.hasText(ip)) { |
|
|
|
throw new IllegalStateException("设备IP为空: " + deviceNo); |
|
|
|
} |
|
|
|
if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) { |
|
|
|
throw new IllegalStateException("设备账号密码未配置: " + deviceNo); |
|
|
|
} |
|
|
|
|
|
|
|
String deviceHost = buildHost(ip, port); |
|
|
|
|
|
|
|
// 3. 登录
|
|
|
|
String authToken = loginDevice(deviceHost, username, password); |
|
|
|
|
|
|
|
// 4. 反推录音盒上的录音ID
|
|
|
|
String recordId = extractRecordId(callRecordId, deviceNo); |
|
|
|
|
|
|
|
// 5. 按通话时间前后各扩展1小时查询录音列表
|
|
|
|
long startEpoch = callStartTime.getTime() / 1000 - 3600; |
|
|
|
long endEpoch = (callEndTime != null ? callEndTime.getTime() : callStartTime.getTime()) / 1000 + 3600; |
|
|
|
String recordUrl = String.format("http://%s/service/record/~/time[%d,%d]", |
|
|
|
deviceHost, startEpoch, endEpoch); |
|
|
|
String recordData = httpVisitWithReauth(recordUrl, authToken, deviceHost, username, password); |
|
|
|
JSONArray records = vaaHttpUtil.parseRecordData(recordData); |
|
|
|
|
|
|
|
// 6. 匹配录音记录
|
|
|
|
JSONObject matched = null; |
|
|
|
for (int i = 0; i < records.size(); i++) { |
|
|
|
JSONObject rec = records.getJSONObject(i); |
|
|
|
if (recordId.equals(RecordParser.parseRecordId(rec))) { |
|
|
|
matched = rec; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
if (matched == null) { |
|
|
|
throw new IllegalStateException("录音盒上未找到该录音: " + recordId); |
|
|
|
} |
|
|
|
String filePath = RecordParser.parseFilePath(matched); |
|
|
|
if (filePath == null || filePath.isEmpty()) { |
|
|
|
throw new IllegalStateException("录音文件路径为空: " + recordId); |
|
|
|
} |
|
|
|
|
|
|
|
// 7. 强制重新下载录音文件
|
|
|
|
String localPath = FilePathUtil.buildLocalPath(downloadPath, cityCode, callStartTime, deviceNo, fileName); |
|
|
|
Path localFile = Paths.get(localPath); |
|
|
|
Files.deleteIfExists(localFile); |
|
|
|
String fileUrl = "http://" + deviceHost + filePath; |
|
|
|
vaaHttpUtil.httpDown(fileUrl, localPath, authToken); |
|
|
|
logger.info("【重新同步】录音下载完成: {}", fileName); |
|
|
|
|
|
|
|
// 8. 上传OSS(路径与原记录一致,覆盖同名文件,业务表无需更新)
|
|
|
|
String ossPath = "voice/" + cityCode + "/" + new SimpleDateFormat("yyyy-MM-dd").format(callStartTime) + "/"; |
|
|
|
OssFileResponse ossResponse = ossFileService.uploadFile(localFile.toFile(), fileName, ossPath); |
|
|
|
String previewUrl = ossResponse.getFileUrl(); |
|
|
|
if (previewUrl == null || previewUrl.trim().isEmpty()) { |
|
|
|
throw new IllegalStateException("OSS上传成功但未返回文件地址: " + fileName); |
|
|
|
} |
|
|
|
ossFilePath = previewUrl; |
|
|
|
|
|
|
|
// 9. 记录成功操作日志
|
|
|
|
saveResyncLog(callRecordId, deviceNo, fileName, previewUrl, "1", null); |
|
|
|
logger.info("【重新同步】成功: callRecordId={}, ossUrl={}", callRecordId, previewUrl); |
|
|
|
return "重新同步成功: " + callRecordId + " -> " + previewUrl; |
|
|
|
} catch (Exception e) { |
|
|
|
logger.error("【重新同步】失败: callRecordId={}, 原因={}", callRecordId, e.getMessage(), e); |
|
|
|
saveResyncLog(callRecordId, deviceNo, fileName, ossFilePath, "0", e.getMessage()); |
|
|
|
if (e instanceof RuntimeException) { |
|
|
|
throw (RuntimeException) e; |
|
|
|
} |
|
|
|
throw new RuntimeException("重新同步失败: " + e.getMessage(), e); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 从 call_record_id(格式:设备编号_录音ID)中提取录音盒上的录音ID |
|
|
|
*/ |
|
|
|
private String extractRecordId(String callRecordId, String deviceNo) { |
|
|
|
if (deviceNo != null && callRecordId.startsWith(deviceNo + "_")) { |
|
|
|
return callRecordId.substring(deviceNo.length() + 1); |
|
|
|
} |
|
|
|
return callRecordId; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 保存重新同步操作记录到 mid_voice_resync_log 表 |
|
|
|
*/ |
|
|
|
private void saveResyncLog(String callRecordId, String deviceNo, String fileName, |
|
|
|
String ossFilePath, String resyncStatus, String failReason) { |
|
|
|
try { |
|
|
|
MidVoiceResyncLog log = new MidVoiceResyncLog(); |
|
|
|
log.setCallRecordId(callRecordId); |
|
|
|
log.setDeviceNo(deviceNo); |
|
|
|
log.setRecordingFileName(fileName); |
|
|
|
log.setOssFilePath(ossFilePath); |
|
|
|
log.setResyncStatus(resyncStatus); |
|
|
|
log.setFailReason(failReason); |
|
|
|
log.setCreateTime(new Date()); |
|
|
|
resyncLogMapper.insert(log); |
|
|
|
} catch (Exception e) { |
|
|
|
logger.error("保存重新同步操作日志失败: callRecordId={}, 原因={}", callRecordId, e.getMessage()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 同步单个设备 |
|
|
|
* |
|
|
|
|