|
@@ -6,25 +6,21 @@ import com.fs.aiSipCall.RemoteCommon;
|
|
|
import com.fs.aiSipCall.domain.AiSipCallOutboundCdr;
|
|
import com.fs.aiSipCall.domain.AiSipCallOutboundCdr;
|
|
|
import com.fs.aiSipCall.domain.CcCustInfo;
|
|
import com.fs.aiSipCall.domain.CcCustInfo;
|
|
|
import com.fs.aiSipCall.mapper.AiSipCallOutboundCdrMapper;
|
|
import com.fs.aiSipCall.mapper.AiSipCallOutboundCdrMapper;
|
|
|
-import com.fs.aiSipCall.param.ApiCallRecordByUuidQueryParams;
|
|
|
|
|
-import com.fs.aiSipCall.param.ApiCallRecordQueryParams;
|
|
|
|
|
import com.fs.aiSipCall.service.IAiSipCallOutboundCdrService;
|
|
import com.fs.aiSipCall.service.IAiSipCallOutboundCdrService;
|
|
|
import com.fs.aiSipCall.utils.DateUtils;
|
|
import com.fs.aiSipCall.utils.DateUtils;
|
|
|
-import com.fs.aiSipCall.vo.ApiCallRecordQueryVo;
|
|
|
|
|
|
|
+import com.fs.aiSipCall.utils.SipLogUtil;
|
|
|
import com.fs.common.core.domain.AjaxResult;
|
|
import com.fs.common.core.domain.AjaxResult;
|
|
|
-import com.fs.common.core.page.TableDataInfo;
|
|
|
|
|
-import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
|
|
|
|
|
-import com.fs.company.mapper.CompanyVoiceRoboticCallLogCallphoneMapper;
|
|
|
|
|
-import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
|
|
+import com.fs.company.mapper.CompanyMapper;
|
|
|
|
|
+import com.fs.his.utils.PhoneUtil;
|
|
|
|
|
+import com.fs.his.vo.OptionsVO;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
-import org.springframework.scheduling.annotation.Async;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.util.CollectionUtils;
|
|
|
|
|
|
|
|
-import java.net.URLEncoder;
|
|
|
|
|
|
|
+import java.net.URLDecoder;
|
|
|
import java.util.*;
|
|
import java.util.*;
|
|
|
-import java.util.concurrent.CompletableFuture;
|
|
|
|
|
-import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -33,36 +29,57 @@ import java.util.stream.Collectors;
|
|
|
* @author fs
|
|
* @author fs
|
|
|
* @date 2026-03-19
|
|
* @date 2026-03-19
|
|
|
*/
|
|
*/
|
|
|
-@Slf4j
|
|
|
|
|
@Service
|
|
@Service
|
|
|
public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutboundCdrMapper, AiSipCallOutboundCdr> implements IAiSipCallOutboundCdrService {
|
|
public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutboundCdrMapper, AiSipCallOutboundCdr> implements IAiSipCallOutboundCdrService {
|
|
|
|
|
|
|
|
|
|
+ private static final Logger log = SipLogUtil.createSipLogger(AiSipCallOutboundCdrServiceImpl.class);
|
|
|
|
|
+
|
|
|
@Autowired
|
|
@Autowired
|
|
|
- private CompanyVoiceRoboticCallLogCallphoneMapper companyVoiceRoboticCallLogCallphoneMapper;
|
|
|
|
|
|
|
+ private CompanyMapper companyMapper;
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
public AiSipCallOutboundCdr selectAiSipCallOutboundCdrById(String id) {
|
|
public AiSipCallOutboundCdr selectAiSipCallOutboundCdrById(String id) {
|
|
|
return baseMapper.selectAiSipCallOutboundCdrById(id);
|
|
return baseMapper.selectAiSipCallOutboundCdrById(id);
|
|
|
}
|
|
}
|
|
|
- /**
|
|
|
|
|
- * 查询aiSIP手动外呼通话记录列表
|
|
|
|
|
- *
|
|
|
|
|
- * @param aiSipCallOutboundCdr aiSIP手动外呼通话记录
|
|
|
|
|
- * @return aiSIP手动外呼通话记录
|
|
|
|
|
- */
|
|
|
|
|
|
|
+
|
|
|
@Override
|
|
@Override
|
|
|
- public TableDataInfo remoteList(ApiCallRecordQueryParams aiSipCallOutboundCdr)
|
|
|
|
|
- {
|
|
|
|
|
- String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.CALL_RECORDS_API,JSONObject.toJSONString(aiSipCallOutboundCdr));
|
|
|
|
|
|
|
+ public void callEndSyncByUuid(AiSipCallOutboundCdr request) {
|
|
|
|
|
+ String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.QUERY_OUTBOUNDCDR_LIST_API,JSONObject.toJSONString(request));
|
|
|
if(StringUtils.isNotBlank(result)){
|
|
if(StringUtils.isNotBlank(result)){
|
|
|
JSONObject jsonObject = JSONObject.parseObject(result);
|
|
JSONObject jsonObject = JSONObject.parseObject(result);
|
|
|
- if(jsonObject.getInteger("code") == 0){
|
|
|
|
|
- return JSONObject.parseObject(result, TableDataInfo.class);
|
|
|
|
|
|
|
+ Integer code = jsonObject.getInteger("code");
|
|
|
|
|
+ if(code != null && code == 0){
|
|
|
|
|
+ String rows = jsonObject.getString("rows");
|
|
|
|
|
+ if(StringUtils.isNotBlank(rows)){
|
|
|
|
|
+ List<AiSipCallOutboundCdr> list = JSONObject.parseArray(rows, AiSipCallOutboundCdr.class);
|
|
|
|
|
+ if(list != null && !list.isEmpty()){
|
|
|
|
|
+ AiSipCallOutboundCdr data = list.get(0);
|
|
|
|
|
+ data.setSourceType(request.getSourceType());
|
|
|
|
|
+ data.setCompanyId(request.getCompanyId());
|
|
|
|
|
+ data.setCompanyUserId(request.getCompanyUserId());
|
|
|
|
|
+ data.setCompanyUserName(request.getCompanyUserName());
|
|
|
|
|
+ data.setStatus(request.getStatus());
|
|
|
|
|
+ // 对opnum字段进行URL解码,防止乱码
|
|
|
|
|
+ if(StringUtils.isNotBlank(data.getOpnum())){
|
|
|
|
|
+ try {
|
|
|
|
|
+ data.setOpnum(URLDecoder.decode(data.getOpnum(), "UTF-8"));
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("opnum字段URL解码失败,原值:{}", data.getOpnum(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ this.save(data);
|
|
|
|
|
+ }else{
|
|
|
|
|
+ log.error("获取手动外呼记录接口转化数据失败:原数据:{},原因:{}",rows, jsonObject.getString("msg"));
|
|
|
|
|
+ }
|
|
|
|
|
+ }else{
|
|
|
|
|
+ log.error("获取手动外呼记录接口获取rows失败:原因:{}",jsonObject.getString("msg"));
|
|
|
|
|
+ }
|
|
|
}else{
|
|
}else{
|
|
|
- log.error("获取手动外呼记录接口失败:{}", jsonObject.getString("msg"));
|
|
|
|
|
|
|
+ log.error("同步手动外呼记录接口失败:返回状态码:{},原因:{}",code, jsonObject.getString("msg"));
|
|
|
}
|
|
}
|
|
|
|
|
+ }else{
|
|
|
|
|
+ log.error("同步手动外呼记录接口失败:无返回结果");
|
|
|
}
|
|
}
|
|
|
- return null;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -99,7 +116,15 @@ public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutbou
|
|
|
if (StringUtils.isNotBlank(aiSipCallOutboundCdr.getEndTimeEnd())) {
|
|
if (StringUtils.isNotBlank(aiSipCallOutboundCdr.getEndTimeEnd())) {
|
|
|
aiSipCallOutboundCdr.setEndTimeEndLong(DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", aiSipCallOutboundCdr.getEndTimeEnd()).getTime());
|
|
aiSipCallOutboundCdr.setEndTimeEndLong(DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", aiSipCallOutboundCdr.getEndTimeEnd()).getTime());
|
|
|
}
|
|
}
|
|
|
- return baseMapper.selectAiSipCallOutboundCdrList(aiSipCallOutboundCdr);
|
|
|
|
|
|
|
+ List<AiSipCallOutboundCdr> list = baseMapper.selectAiSipCallOutboundCdrList(aiSipCallOutboundCdr);
|
|
|
|
|
+ if(!CollectionUtils.isEmpty(list)){
|
|
|
|
|
+ // 批量查询公司名称
|
|
|
|
|
+ Map<Long, String> companyNameMap = buildCompanyNameMap(list);
|
|
|
|
|
+
|
|
|
|
|
+ // 处理每条记录
|
|
|
|
|
+ list.forEach(data -> processCallRecord(data, companyNameMap));
|
|
|
|
|
+ }
|
|
|
|
|
+ return list;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -151,7 +176,11 @@ public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutbou
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
- public AjaxResult getCustCommunicationInfo(String phoneNum, Integer callType, String uuid) {
|
|
|
|
|
|
|
+ public AjaxResult getCustCommunicationInfo(String phoneNum, Integer callType, String uuid, String dialMode) {
|
|
|
|
|
+ if(StringUtils.isNotBlank(dialMode) && dialMode.equals("encrypted")){
|
|
|
|
|
+ //密文需要解密
|
|
|
|
|
+ phoneNum = PhoneUtil.decryptPhone(phoneNum);
|
|
|
|
|
+ }
|
|
|
String paramStr = "?phoneNum=" + phoneNum + "&callType=" + callType + "&uuid=" + uuid;
|
|
String paramStr = "?phoneNum=" + phoneNum + "&callType=" + callType + "&uuid=" + uuid;
|
|
|
String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.GET_CUST_COMMUNICATION_INFO_API + paramStr);
|
|
String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.GET_CUST_COMMUNICATION_INFO_API + paramStr);
|
|
|
if (StringUtils.isNotBlank(result)) {
|
|
if (StringUtils.isNotBlank(result)) {
|
|
@@ -159,10 +188,10 @@ public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutbou
|
|
|
if (jsonObject.getInteger("code") == 0) {
|
|
if (jsonObject.getInteger("code") == 0) {
|
|
|
return JSONObject.parseObject(result, AjaxResult.class);
|
|
return JSONObject.parseObject(result, AjaxResult.class);
|
|
|
} else {
|
|
} else {
|
|
|
- log.error("取手动外呼客户沟通信息失败:{}", jsonObject.getString("msg"));
|
|
|
|
|
|
|
+ log.error("获取手动外呼客户沟通信息失败:{}", jsonObject.getString("msg"));
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
- log.error("取手动外呼客户沟通信息失败:{}", "接口返回为空");
|
|
|
|
|
|
|
+ log.error("获取手动外呼客户沟通信息失败:接口返回为空");
|
|
|
}
|
|
}
|
|
|
return AjaxResult.error();
|
|
return AjaxResult.error();
|
|
|
}
|
|
}
|
|
@@ -175,390 +204,84 @@ public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutbou
|
|
|
if (jsonObject.getInteger("code") == 0) {
|
|
if (jsonObject.getInteger("code") == 0) {
|
|
|
return JSONObject.parseObject(result, AjaxResult.class);
|
|
return JSONObject.parseObject(result, AjaxResult.class);
|
|
|
} else {
|
|
} else {
|
|
|
- log.error("取手动外呼客户沟通信息失败:{}", jsonObject.getString("msg"));
|
|
|
|
|
|
|
+ log.error("新增手动外呼客户沟通信息失败:{}", jsonObject.getString("msg"));
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
- log.error("取手动外呼客户沟通信息失败:{}", "接口返回为空");
|
|
|
|
|
|
|
+ log.error("新增手动外呼客户沟通信息失败:接口返回为空");
|
|
|
}
|
|
}
|
|
|
return AjaxResult.error();
|
|
return AjaxResult.error();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private final AtomicBoolean isRunning = new AtomicBoolean(false);
|
|
|
|
|
- @Override
|
|
|
|
|
- @Async
|
|
|
|
|
- public CompletableFuture<String> scheduledGetCallRecord() {
|
|
|
|
|
- if (!isRunning.compareAndSet(false, true)) {
|
|
|
|
|
- log.error("sip手动外呼同步电话 任务正在执行中,请稍后再试");
|
|
|
|
|
- return CompletableFuture.completedFuture("任务正在执行中,请稍后再试");
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- try {
|
|
|
|
|
- log.error("sip手动外呼同步电话 开始执行异步任务");
|
|
|
|
|
- String todayStartStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, new Date()) + " 00:00:00";
|
|
|
|
|
- String now = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, new Date());
|
|
|
|
|
- long startTime = System.currentTimeMillis();
|
|
|
|
|
|
|
|
|
|
- // 构建查询参数
|
|
|
|
|
- List<ApiCallRecordQueryParams> paramsList = buildDayQueryParams(todayStartStr,now);
|
|
|
|
|
|
|
|
|
|
- // 获取远程数据
|
|
|
|
|
- List<AiSipCallOutboundCdr> remoteList = fetchAllRemoteCallRecords(paramsList);
|
|
|
|
|
- if (remoteList.isEmpty()) {
|
|
|
|
|
- log.error("sip手动外呼同步电话 异步任务完成,耗时:{}ms, 结果:当天无最新数据", System.currentTimeMillis() - startTime);
|
|
|
|
|
- return CompletableFuture.completedFuture("当天无最新数据");
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- // 获取本地当天所有外呼记录
|
|
|
|
|
- List<AiSipCallOutboundCdr> localList = baseMapper.selectCurrentDayCallRecords(DateUtils.toStartTime(), System.currentTimeMillis());
|
|
|
|
|
|
|
|
|
|
- // 筛选并处理新增和更新数据
|
|
|
|
|
- String result = processAndSaveData(remoteList, localList);
|
|
|
|
|
- log.error("sip手动外呼同步电话 异步任务完成,耗时:{}ms, 结果:{}", System.currentTimeMillis() - startTime, result);
|
|
|
|
|
- return CompletableFuture.completedFuture(result);
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.error("sip手动外呼同步电话 异步任务执行失败", e);
|
|
|
|
|
- return CompletableFuture.completedFuture("任务执行失败:" + e.getMessage());
|
|
|
|
|
- } finally {
|
|
|
|
|
- isRunning.set(false);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 构建当天查询参数 - 使用当天 00:00:00 到当前时间
|
|
|
|
|
- * @return 查询参数对象列表(支持多时段查询)
|
|
|
|
|
|
|
+ * 构建公司名称映射
|
|
|
*/
|
|
*/
|
|
|
- private List<ApiCallRecordQueryParams> buildDayQueryParams(String todayStartStr,String now) {
|
|
|
|
|
-
|
|
|
|
|
- List<ApiCallRecordQueryParams> paramsList = new ArrayList<>();
|
|
|
|
|
-
|
|
|
|
|
- // 如果时间跨度超过 12 小时,分段查询避免遗漏
|
|
|
|
|
- Date today = DateUtils.parseDate(todayStartStr);
|
|
|
|
|
- long hoursDiff = (System.currentTimeMillis() - today.getTime()) / (1000 * 60 * 60);
|
|
|
|
|
-
|
|
|
|
|
- if (hoursDiff > 12) {
|
|
|
|
|
- // 分两段查询:00:00-12:00 和 12:00-当前时间
|
|
|
|
|
- ApiCallRecordQueryParams params1 = createSingleParam(todayStartStr, todayStartStr.substring(0, 10) + " 12:00:00");
|
|
|
|
|
- ApiCallRecordQueryParams params2 = createSingleParam(todayStartStr.substring(0, 10) + " 12:00:00", now);
|
|
|
|
|
- paramsList.add(params1);
|
|
|
|
|
- paramsList.add(params2);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 单段查询:00:00-当前时间
|
|
|
|
|
- paramsList.add(createSingleParam(todayStartStr, now));
|
|
|
|
|
|
|
+ private Map<Long, String> buildCompanyNameMap(List<AiSipCallOutboundCdr> list) {
|
|
|
|
|
+ List<Long> companyIds = list.stream()
|
|
|
|
|
+ .map(AiSipCallOutboundCdr::getCompanyId)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .distinct()
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ if (companyIds.isEmpty()) {
|
|
|
|
|
+ return Collections.emptyMap();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return paramsList;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 创建单个查询参数对象
|
|
|
|
|
- */
|
|
|
|
|
- private ApiCallRecordQueryParams createSingleParam(String startTime, String endTime) {
|
|
|
|
|
- ApiCallRecordQueryParams params = new ApiCallRecordQueryParams();
|
|
|
|
|
- params.setPageNum(1);
|
|
|
|
|
- params.setPageSize(1000); // 减小单次请求量,提升响应速度
|
|
|
|
|
- params.setCallType("03");
|
|
|
|
|
- params.setCalloutTimeStart(startTime);
|
|
|
|
|
- params.setCalloutTimeEnd(endTime);
|
|
|
|
|
- return params;
|
|
|
|
|
|
|
+ List<OptionsVO> companyOptions = companyMapper.selectByIds(companyIds);
|
|
|
|
|
+ return companyOptions.stream()
|
|
|
|
|
+ .collect(Collectors.toMap(OptionsVO::getDictValue, OptionsVO::getDictLabel, (v1, v2) -> v1));
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 分页轮询获取所有远程通话记录 (带去重和失败重试)
|
|
|
|
|
- * @param paramsList 查询参数列表
|
|
|
|
|
- * @return 通话记录列表 (已去重)
|
|
|
|
|
|
|
+ * 处理单条通话记录
|
|
|
*/
|
|
*/
|
|
|
- private List<AiSipCallOutboundCdr> fetchAllRemoteCallRecords(List<ApiCallRecordQueryParams> paramsList) {
|
|
|
|
|
- List<AiSipCallOutboundCdr> allRecords = new ArrayList<>();
|
|
|
|
|
- for (ApiCallRecordQueryParams params : paramsList) {
|
|
|
|
|
- int currentPage = 1;
|
|
|
|
|
- boolean hasMore = true;
|
|
|
|
|
-
|
|
|
|
|
- while (hasMore) {
|
|
|
|
|
- params.setPageNum(currentPage);
|
|
|
|
|
- List<AiSipCallOutboundCdr> pageData = fetchSinglePageRecords(params);
|
|
|
|
|
-
|
|
|
|
|
- // 失败时直接结束
|
|
|
|
|
- if (pageData == null) {
|
|
|
|
|
- log.error("sip手动外呼同步电话 分页查询第{}页失败,已丢弃该页数据,停止查询", currentPage);
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (pageData.isEmpty()) {
|
|
|
|
|
- log.error("sip手动外呼同步电话 第{}页无数据,查询结束", currentPage);
|
|
|
|
|
- hasMore = false;
|
|
|
|
|
- } else {
|
|
|
|
|
- allRecords.addAll(pageData);
|
|
|
|
|
- log.error("sip手动外呼同步电话 第{}页数据:{},累计总数:{}", currentPage, pageData.size(), allRecords.size());
|
|
|
|
|
-
|
|
|
|
|
- // 如果返回数据少于页大小,说明已是最后一页
|
|
|
|
|
- if (pageData.size() < params.getPageSize()) {
|
|
|
|
|
- hasMore = false;
|
|
|
|
|
- }
|
|
|
|
|
- currentPage++;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 安全限制:最多拉取 50 页
|
|
|
|
|
- if (currentPage > 50) {
|
|
|
|
|
- log.error("sip手动外呼同步电话 已达到最大页数限制 50 页,停止查询。已获取数据量:{}", allRecords.size());
|
|
|
|
|
- hasMore = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ private void processCallRecord(AiSipCallOutboundCdr data, Map<Long, String> companyNameMap) {
|
|
|
|
|
+ // 设置公司名称
|
|
|
|
|
+ if (data.getCompanyId() != null) {
|
|
|
|
|
+ data.setCompanyName(companyNameMap.getOrDefault(data.getCompanyId(), ""));
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- log.error("sip手动外呼同步电话 远程数据获取完成,总计:{} 条", allRecords.size());
|
|
|
|
|
- return allRecords.isEmpty() ? Collections.emptyList() : allRecords;
|
|
|
|
|
|
|
+ // 格式化时间字段
|
|
|
|
|
+ formatTimestampField(data.getStartTime(), data::setStartTimeStr);
|
|
|
|
|
+ formatTimestampField(data.getAnsweredTime(), data::setAnsweredTimeStr);
|
|
|
|
|
+ formatTimestampField(data.getEndTime(), data::setEndTimeStr);
|
|
|
|
|
+ data.setTimeLenValidStr(DateUtils.formatTimeLength(data.getTimeLenValid()/1000));
|
|
|
|
|
+ // 格式化通话时长
|
|
|
|
|
+ data.setTimeLenSec(DateUtils.formatTimeLength(data.getTimeLen() / 1000));
|
|
|
|
|
+ // 处理录音文件路径
|
|
|
|
|
+ formatWavfilePath(data);
|
|
|
|
|
+ // 处理电话号码加密显示
|
|
|
|
|
+ encryptPhoneNumber(data);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 获取单页数据
|
|
|
|
|
- * @param params 查询参数(需预先设置页码)
|
|
|
|
|
- * @return 单页通话记录列表
|
|
|
|
|
|
|
+ * 格式化时间戳字段
|
|
|
*/
|
|
*/
|
|
|
- private List<AiSipCallOutboundCdr> fetchSinglePageRecords(ApiCallRecordQueryParams params) {
|
|
|
|
|
- try {
|
|
|
|
|
- String result = RemoteCommon.sendPost(
|
|
|
|
|
- RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.CALL_RECORDS_API,
|
|
|
|
|
- JSONObject.toJSONString(params)
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- if (StringUtils.isBlank(result)) {
|
|
|
|
|
- log.error("sip手动外呼同步电话 查询第{}页失败:接口返回为空", params.getPageNum());
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- JSONObject jsonObject = JSONObject.parseObject(result);
|
|
|
|
|
- Integer code = jsonObject.getInteger("code");
|
|
|
|
|
-
|
|
|
|
|
- if (code == null || code != 0) {
|
|
|
|
|
- String errorMsg = jsonObject.getString("msg") != null
|
|
|
|
|
- ? jsonObject.getString("msg") : "未知错误";
|
|
|
|
|
- log.error("sip手动外呼同步电话 查询第{}页失败:{}", params.getPageNum(), errorMsg);
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Long total = jsonObject.getLong("total");
|
|
|
|
|
- if (total == null || total <= 0) {
|
|
|
|
|
- return Collections.emptyList();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- String rows = jsonObject.getString("rows");
|
|
|
|
|
- if (StringUtils.isBlank(rows)) {
|
|
|
|
|
- return Collections.emptyList();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return JSONObject.parseArray(rows, AiSipCallOutboundCdr.class);
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.error("sip手动外呼同步电话 查询第{}页异常", params.getPageNum(), e);
|
|
|
|
|
- return null;
|
|
|
|
|
|
|
+ private void formatTimestampField(Long timestamp, java.util.function.Consumer<String> setter) {
|
|
|
|
|
+ if (timestamp != null && timestamp != 0) {
|
|
|
|
|
+ setter.accept(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(timestamp)));
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 处理并保存数据 (新增和更新)
|
|
|
|
|
- * @param remoteList 远程数据列表
|
|
|
|
|
- * @param localList 本地数据列表
|
|
|
|
|
- * @return 处理结果信息
|
|
|
|
|
|
|
+ * 格式化录音文件路径
|
|
|
*/
|
|
*/
|
|
|
- private String processAndSaveData(List<AiSipCallOutboundCdr> remoteList, List<AiSipCallOutboundCdr> localList) {
|
|
|
|
|
- if (remoteList == null || remoteList.isEmpty()) {
|
|
|
|
|
- return "远程数据列表为空,无需处理";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (localList == null || localList.isEmpty()) {
|
|
|
|
|
- log.error("sip手动外呼同步电话 本地数据列表为空,所有远程数据都将作为新增处理");
|
|
|
|
|
- return processDataWithStats(remoteList, "新增");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 构建本地数据的唯一标识映射
|
|
|
|
|
- Set<String> localIdSet = localList.stream()
|
|
|
|
|
- .map(AiSipCallOutboundCdr::getId)
|
|
|
|
|
- .collect(Collectors.toSet());
|
|
|
|
|
-
|
|
|
|
|
- // 分类数据:新增和更新
|
|
|
|
|
- List<AiSipCallOutboundCdr> insertList = new ArrayList<>();
|
|
|
|
|
- List<AiSipCallOutboundCdr> updateList = new ArrayList<>();
|
|
|
|
|
-
|
|
|
|
|
- for (AiSipCallOutboundCdr remote : remoteList) {
|
|
|
|
|
- if (StringUtils.isBlank(remote.getId())) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- if (localIdSet.contains(remote.getId())) {
|
|
|
|
|
- updateList.add(remote);
|
|
|
|
|
- } else {
|
|
|
|
|
- insertList.add(remote);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- log.error("sip手动外呼同步电话 数据筛选完成 - 预计新增:{},预计更新:{}", insertList.size(), updateList.size());
|
|
|
|
|
-
|
|
|
|
|
- StringBuilder resultMsg = new StringBuilder();
|
|
|
|
|
- if (!insertList.isEmpty()) {
|
|
|
|
|
- resultMsg.append(processDataWithStats(insertList, "新增"));
|
|
|
|
|
|
|
+ private void formatWavfilePath(AiSipCallOutboundCdr data) {
|
|
|
|
|
+ if (StringUtils.isNotBlank(data.getRecordFilename())) {
|
|
|
|
|
+ String filename = data.getRecordFilename().startsWith("/") ? data.getRecordFilename().substring(1) : data.getRecordFilename();
|
|
|
|
|
+ data.setWavfile("/recordings/files?filename=" + filename);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- if (!updateList.isEmpty()) {
|
|
|
|
|
- if (resultMsg.length() > 0) {
|
|
|
|
|
- resultMsg.append(",");
|
|
|
|
|
- }
|
|
|
|
|
- resultMsg.append(processDataWithStats(updateList, "更新"));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return resultMsg.toString();
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 处理批量数据并统计成功失败信息
|
|
|
|
|
- * @param dataList 数据列表
|
|
|
|
|
- * @param operationType 操作类型:"新增" 或 "更新"
|
|
|
|
|
- * @return 处理结果信息
|
|
|
|
|
|
|
+ * 加密电话号码
|
|
|
*/
|
|
*/
|
|
|
- private String processDataWithStats(List<AiSipCallOutboundCdr> dataList,String operationType) {
|
|
|
|
|
- log.error("sip手动外呼同步电话 开始处理{}Ai 外呼记录数据,数量:{}", operationType, dataList.size());
|
|
|
|
|
- if (dataList.isEmpty()) {
|
|
|
|
|
- return "无有效数据,无需处理";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- int batchSize = 500;
|
|
|
|
|
- int totalSize = dataList.size();
|
|
|
|
|
- int batchCount = (totalSize + batchSize - 1) / batchSize;
|
|
|
|
|
- int successCount = 0;
|
|
|
|
|
- int failCount = 0;
|
|
|
|
|
- List<int[]> failedBatchRanges = new ArrayList<>();
|
|
|
|
|
-
|
|
|
|
|
- for (int i = 0; i < batchCount; i++) {
|
|
|
|
|
- int fromIndex = i * batchSize;
|
|
|
|
|
- int toIndex = Math.min(fromIndex + batchSize, totalSize);
|
|
|
|
|
- List<AiSipCallOutboundCdr> batchList = dataList.subList(fromIndex, toIndex);
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- if ("新增".equals(operationType)) {
|
|
|
|
|
- this.saveBatch(batchList);
|
|
|
|
|
- } else if ("更新".equals(operationType)) {
|
|
|
|
|
- this.updateBatchById(batchList);
|
|
|
|
|
- }
|
|
|
|
|
- successCount += batchList.size();
|
|
|
|
|
- log.error("sip手动外呼同步电话 第{}/{}批{}成功,本批数量:{}", i + 1, batchCount, operationType, batchList.size());
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- failCount += batchList.size();
|
|
|
|
|
- int[] range = {fromIndex, toIndex - 1};
|
|
|
|
|
- failedBatchRanges.add(range);
|
|
|
|
|
- log.error("sip手动外呼同步电话 第{}批数据{}失败,本批数量:{},起始位:{},结束位:{}",
|
|
|
|
|
- i + 1, operationType, batchList.size(), fromIndex, toIndex - 1, e);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- StringBuilder result = new StringBuilder();
|
|
|
|
|
- result.append(operationType).append("数据处理完成,总计:").append(totalSize).append("条");
|
|
|
|
|
- result.append(",成功:").append(successCount).append("条");
|
|
|
|
|
-
|
|
|
|
|
- if (failCount > 0) {
|
|
|
|
|
- result.append(",失败:").append(failCount).append("条");
|
|
|
|
|
- result.append(",失败数据位置:");
|
|
|
|
|
- for (int i = 0; i < failedBatchRanges.size(); i++) {
|
|
|
|
|
- int[] range = failedBatchRanges.get(i);
|
|
|
|
|
- if (i > 0) result.append(";");
|
|
|
|
|
- result.append("[").append(range[0]).append("-").append(range[1]).append("]");
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return result.toString();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public int syncByUuid(ApiCallRecordByUuidQueryParams req) {
|
|
|
|
|
- if (req == null || StringUtils.isBlank(req.getUuid())) {
|
|
|
|
|
-// throw new ServiceException("uuid不能为空");
|
|
|
|
|
|
|
+ private void encryptPhoneNumber(AiSipCallOutboundCdr data) {
|
|
|
|
|
+ if (StringUtils.isNotBlank(data.getCallee())) {
|
|
|
|
|
+ data.setCallee(PhoneUtil.encryptPhone(data.getCallee()));
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- String callType = StringUtils.isBlank(req.getCallType()) ? "03" : req.getCallType();
|
|
|
|
|
-
|
|
|
|
|
- // 1. 先查本地是否已存在,防重复
|
|
|
|
|
-// CompanyVoiceRoboticCallLogCallphone exist = companyVoiceRoboticCallLogCallphoneMapper.selectByCallbackUuid(req.getUuid());
|
|
|
|
|
-// if (exist != null) {
|
|
|
|
|
-// log.info("通话记录已存在,无需重复同步,uuid={}", req.getUuid());
|
|
|
|
|
-// return 1;
|
|
|
|
|
-// }
|
|
|
|
|
-
|
|
|
|
|
- // 2. 调远程接口按 uuid 查询
|
|
|
|
|
- ApiCallRecordQueryVo remoteRecord = getRemoteRecordByUuid(req.getUuid(), callType);
|
|
|
|
|
- if (remoteRecord == null) {
|
|
|
|
|
- log.warn("远程未查到通话记录,uuid={}, callType={}", req.getUuid(), callType);
|
|
|
|
|
- return 0;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 3. 转成本地实体
|
|
|
|
|
- CompanyVoiceRoboticCallLogCallphone entity = buildLocalEntity(remoteRecord, callType,2);
|
|
|
|
|
-
|
|
|
|
|
- // 4. 插入本地表
|
|
|
|
|
- return companyVoiceRoboticCallLogCallphoneMapper.insertCompanyVoiceRoboticCallLogCallphone(entity);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private ApiCallRecordQueryVo getRemoteRecordByUuid(String uuid, String callType) {
|
|
|
|
|
- try {
|
|
|
|
|
- String url = RemoteCommon.REMOTE_ADDERSS_PREFIX
|
|
|
|
|
- + RemoteCommon.QUERY_OUTBOUNDCDR_BYUUID_API
|
|
|
|
|
- + "?uuid=" + URLEncoder.encode(uuid, "UTF-8")
|
|
|
|
|
- + "&callType=" + URLEncoder.encode(callType, "UTF-8");
|
|
|
|
|
-
|
|
|
|
|
- String result = RemoteCommon.sendGet(url);
|
|
|
|
|
-
|
|
|
|
|
- if (StringUtils.isBlank(result)) {
|
|
|
|
|
- log.error("远程查询通话记录失败,返回为空,uuid={}", uuid);
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- JSONObject jsonObject = JSONObject.parseObject(result);
|
|
|
|
|
- Integer code = jsonObject.getInteger("code");
|
|
|
|
|
- if (code == null || code != 0) {
|
|
|
|
|
- String msg = jsonObject.getString("msg");
|
|
|
|
|
- log.error("远程查询通话记录失败,uuid={}, msg={}", uuid, msg);
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Object dataObj = jsonObject.get("data");
|
|
|
|
|
- if (dataObj == null) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return JSONObject.parseObject(JSONObject.toJSONString(dataObj), ApiCallRecordQueryVo.class);
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.error("远程查询通话记录异常,uuid={}", uuid, e);
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- private CompanyVoiceRoboticCallLogCallphone buildLocalEntity(ApiCallRecordQueryVo remoteRecord, String callType,Integer status) {
|
|
|
|
|
- CompanyVoiceRoboticCallLogCallphone entity = new CompanyVoiceRoboticCallLogCallphone();
|
|
|
|
|
-
|
|
|
|
|
- entity.setCallbackUuid(null);
|
|
|
|
|
- entity.setRoboticId(12345L);
|
|
|
|
|
- entity.setCallerId(null);
|
|
|
|
|
- entity.setRunTime(null);
|
|
|
|
|
- entity.setRunParam(null);
|
|
|
|
|
- entity.setResult(null);
|
|
|
|
|
- entity.setStatus(status);
|
|
|
|
|
- entity.setRecordPath(remoteRecord.getWavFileUrl());
|
|
|
|
|
- entity.setCallerNum(remoteRecord.getTelephone());
|
|
|
|
|
- entity.setCalleeNum(remoteRecord.getCallerNumber());
|
|
|
|
|
- entity.setUuid(remoteRecord.getUuid());
|
|
|
|
|
-// entity.setCallCreateTime(Long.valueOf(remoteRecord.getManualAnsweredTime()));
|
|
|
|
|
-// entity.setCallAnswerTime(Long.valueOf(remoteRecord.getAnsweredTime()));
|
|
|
|
|
- entity.setIntention(null);
|
|
|
|
|
- entity.setCompanyId(null);
|
|
|
|
|
- entity.setCompanyUserId(null);
|
|
|
|
|
- entity.setCallTime(Long.valueOf(remoteRecord.getTimeLen()));
|
|
|
|
|
- entity.setCost(null);
|
|
|
|
|
- entity.setCallType(Integer.valueOf(callType));
|
|
|
|
|
- if (remoteRecord.getDialogue() != null) {
|
|
|
|
|
- entity.setContentList(JSONObject.toJSONString(remoteRecord.getDialogue()));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- entity.setCreateTime(new Date());
|
|
|
|
|
- entity.setUpdateTime(new Date());
|
|
|
|
|
-
|
|
|
|
|
- return entity;
|
|
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
}
|
|
}
|