|
|
@@ -30,6 +30,7 @@ import com.fs.company.vo.easycall.EasyCallCallPhoneVO;
|
|
|
import com.fs.crm.domain.CrmCustomer;
|
|
|
import com.fs.crm.mapper.CrmCustomerMapper;
|
|
|
import com.fs.crm.param.SmsSendBatchParam;
|
|
|
+import com.fs.crm.service.ICrmCustomerAnalyzeService;
|
|
|
import com.fs.crm.service.impl.CrmCustomerServiceImpl;
|
|
|
import com.fs.enums.ExecutionStatusEnum;
|
|
|
import com.fs.enums.NodeTypeEnum;
|
|
|
@@ -47,12 +48,15 @@ import lombok.Synchronized;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.scheduling.annotation.Async;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.time.temporal.ChronoField;
|
|
|
import java.util.*;
|
|
|
import java.util.function.Function;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
import static com.fs.company.service.impl.call.node.AbstractWorkflowNode.companyVoiceRoboticCallLogCallphoneMapper;
|
|
|
import static com.fs.company.service.impl.call.node.AiCallTaskNode.EASYCALL_WORKFLOW_REDIS_KEY;
|
|
|
+import static java.time.LocalTime.now;
|
|
|
|
|
|
|
|
|
/**
|
|
|
@@ -84,6 +88,7 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
private final CompanyVoiceRoboticWxServiceImpl companyVoiceRoboticWxService;
|
|
|
|
|
|
private final CrmCustomerMapper crmCustomerMapper;
|
|
|
+ private final ICrmCustomerAnalyzeService crmCustomerAnalyzeService;
|
|
|
|
|
|
private final CompanyWxClientServiceImpl companyWxClientServiceImpl;
|
|
|
|
|
|
@@ -125,6 +130,24 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
/** 每次重试等待时长(毫秒) */
|
|
|
private static final long EASYCALL_INTENT_RETRY_INTERVAL_MS = 30000L;
|
|
|
|
|
|
+ /** EasyCall dialogue 对话内容重试队列 Redis key 前缀,value 为已重试次数 */
|
|
|
+ private static final String EASYCALL_DIALOGUE_RETRY_KEY = "easycall:dialogue:retry:";
|
|
|
+ /** dialogue 对话内容等待重试最大次数(每次间隔约30秒,最多等待 5*30=150秒) */
|
|
|
+ private static final int EASYCALL_DIALOGUE_MAX_RETRY = 5;
|
|
|
+ /** dialogue 每次重试等待时长(毫秒) */
|
|
|
+ private static final long EASYCALL_DIALOGUE_RETRY_INTERVAL_MS = 30000L;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断 dialogue 对话内容是否为空(null、空字符串、空数组 "[]" 均视为无对话内容)
|
|
|
+ */
|
|
|
+ private boolean isDialogueEmpty(String dialogue) {
|
|
|
+ if (StringUtils.isBlank(dialogue)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ String trimmed = dialogue.trim();
|
|
|
+ return "[]".equals(trimmed);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 查询机器人外呼任务
|
|
|
*
|
|
|
@@ -822,11 +845,11 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
@Override
|
|
|
@Async("cidWorkFlowExecutor")
|
|
|
public void callerResult4EasyCall(CdrDetailVo result) {
|
|
|
- try {
|
|
|
- Thread.sleep(20000L);
|
|
|
- } catch (InterruptedException e) {
|
|
|
- throw new RuntimeException(e);
|
|
|
- }
|
|
|
+// try {
|
|
|
+// Thread.sleep(3000L);
|
|
|
+// } catch (InterruptedException e) {
|
|
|
+// throw new RuntimeException(e);
|
|
|
+// }
|
|
|
// EASYCALL
|
|
|
log.info("进入easyCall外呼结果回调:{}", JSON.toJSONString(result));
|
|
|
if (result == null || StringUtils.isBlank(result.getUuid())) return;
|
|
|
@@ -843,6 +866,7 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
return;
|
|
|
}
|
|
|
// intent(意向度)由对方异步评估写入,回调时可能尚未赋值,进入延迟重试队列等待
|
|
|
+ /*
|
|
|
if (StringUtils.isBlank(callPhoneRes.getIntent())) {
|
|
|
String retryKey = EASYCALL_INTENT_RETRY_KEY + result.getUuid();
|
|
|
Integer retryCount = redisCache2.getCacheObject(retryKey);
|
|
|
@@ -864,6 +888,76 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
// intent 已有值,直接正常处理
|
|
|
redisCache2.deleteObject(EASYCALL_INTENT_RETRY_KEY + result.getUuid());
|
|
|
doHandleEasyCallResult(callPhoneRes);
|
|
|
+ */
|
|
|
+
|
|
|
+ // 当前:根据对话内容同步调用自家 AI 计算意向度,不依赖第三方 intent
|
|
|
+// redisCache2.deleteObject(EASYCALL_INTENT_RETRY_KEY + result.getUuid());
|
|
|
+
|
|
|
+ // dialogue(对话内容)由对方异步写入,回调时可能尚未赋值,进入延迟重试队列等待
|
|
|
+ if (isDialogueEmpty(callPhoneRes.getDialogue())) {
|
|
|
+ String retryKey = EASYCALL_DIALOGUE_RETRY_KEY + result.getUuid();
|
|
|
+ Integer retryCount = redisCache2.getCacheObject(retryKey);
|
|
|
+ if (retryCount == null) {
|
|
|
+ retryCount = 0;
|
|
|
+ }
|
|
|
+ if (retryCount < EASYCALL_DIALOGUE_MAX_RETRY) {
|
|
|
+ redisCache2.setCacheObject(retryKey, retryCount + 1, 10, java.util.concurrent.TimeUnit.MINUTES);
|
|
|
+ log.info("easyCall外呼回调dialogue对话内容暂未写入,uuid={},第{}次放入延迟重试队列", result.getUuid(), retryCount + 1);
|
|
|
+ doRetryDialogue4EasyCall(result, retryCount + 1);
|
|
|
+ } else {
|
|
|
+ // 超过最大重试次数,以 dialogue 为空兜底继续处理
|
|
|
+ log.warn("easyCall外呼回调dialogue对话内容在{}次重试后仍为空,uuid={},以对话为空兜底处理", EASYCALL_DIALOGUE_MAX_RETRY, result.getUuid());
|
|
|
+ redisCache2.deleteObject(retryKey);
|
|
|
+ doHandleEasyCallResult(callPhoneRes);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // dialogue 已有值,直接正常处理
|
|
|
+ redisCache2.deleteObject(EASYCALL_DIALOGUE_RETRY_KEY + result.getUuid());
|
|
|
+ doHandleEasyCallResult(callPhoneRes);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 延迟重试处理 EasyCall 外呼回调(等待 dialogue 对话内容异步写入完成)
|
|
|
+ * 每次重试前等待 {@link #EASYCALL_DIALOGUE_RETRY_INTERVAL_MS} 毫秒后重新拉取数据
|
|
|
+ */
|
|
|
+ @Async("cidWorkFlowExecutor")
|
|
|
+ public void doRetryDialogue4EasyCall(CdrDetailVo result, int currentRetry) {
|
|
|
+ try {
|
|
|
+ Thread.sleep(EASYCALL_DIALOGUE_RETRY_INTERVAL_MS);
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.warn("easyCall dialogue重试等待被中断, uuid={}", result.getUuid());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ log.info("easyCall dialogue重试第{}次开始, uuid={}", currentRetry, result.getUuid());
|
|
|
+ EasyCallCallPhoneVO callPhoneRes = easyCallMapper.getCallPhoneInfoByUuid(result.getUuid());
|
|
|
+ if (null == callPhoneRes) {
|
|
|
+ log.error("easyCall dialogue重试时仍未查询到外呼结果, uuid={}", result.getUuid());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (isDialogueEmpty(callPhoneRes.getDialogue())) {
|
|
|
+ // dialogue 仍为空,继续判断是否还有剩余重试次数
|
|
|
+ String retryKey = EASYCALL_DIALOGUE_RETRY_KEY + result.getUuid();
|
|
|
+ Integer retryCount = redisCache2.getCacheObject(retryKey);
|
|
|
+ if (retryCount == null) {
|
|
|
+ retryCount = currentRetry;
|
|
|
+ }
|
|
|
+ if (retryCount < EASYCALL_DIALOGUE_MAX_RETRY) {
|
|
|
+ redisCache2.setCacheObject(retryKey, retryCount + 1, 10, java.util.concurrent.TimeUnit.MINUTES);
|
|
|
+ log.info("easyCall dialogue对话内容仍未写入,uuid={},第{}次继续延迟重试", result.getUuid(), retryCount + 1);
|
|
|
+ doRetryDialogue4EasyCall(result, retryCount + 1);
|
|
|
+ } else {
|
|
|
+ log.warn("easyCall dialogue对话内容在{}次重试后仍为空,uuid={},以对话为空兜底处理", EASYCALL_DIALOGUE_MAX_RETRY, result.getUuid());
|
|
|
+ redisCache2.deleteObject(retryKey);
|
|
|
+ doHandleEasyCallResult(callPhoneRes);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // dialogue 已写入完成,正常处理
|
|
|
+ log.info("easyCall dialogue重试第{}次成功获取到对话内容,uuid={}", currentRetry, result.getUuid());
|
|
|
+ redisCache2.deleteObject(EASYCALL_DIALOGUE_RETRY_KEY + result.getUuid());
|
|
|
+ doHandleEasyCallResult(callPhoneRes);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -916,7 +1010,15 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
private void doHandleEasyCallResult(EasyCallCallPhoneVO callPhoneRes) {
|
|
|
//等待数据信息
|
|
|
JSONObject bizJson = JSONObject.parseObject(callPhoneRes.getBizJson());
|
|
|
- String cacheString = (String) redisCache2.getCacheObject(EASYCALL_WORKFLOW_REDIS_KEY + bizJson.getString("callBackUuid"));
|
|
|
+ Object cacheObj = redisCache2.getCacheObject(EASYCALL_WORKFLOW_REDIS_KEY + bizJson.getString("callBackUuid"));
|
|
|
+ String cacheString;
|
|
|
+ if (cacheObj instanceof String) {
|
|
|
+ cacheString = (String) cacheObj;
|
|
|
+ } else if (cacheObj instanceof JSONObject) {
|
|
|
+ cacheString = ((JSONObject) cacheObj).toJSONString();
|
|
|
+ } else {
|
|
|
+ cacheString = cacheObj == null ? null : JSONObject.toJSONString(cacheObj);
|
|
|
+ }
|
|
|
if (StringUtils.isBlank(cacheString)) {
|
|
|
log.error("easyCall外呼回调缓存信息缺失, uuid={}", callPhoneRes.getUuid());
|
|
|
return;
|
|
|
@@ -974,7 +1076,23 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
|
|
|
public void pushDialogContent4EasyCall(JSONObject cacheInfo, EasyCallCallPhoneVO callPhoneRes) {
|
|
|
|
|
|
- String intention = getIntention(callPhoneRes.getIntent());
|
|
|
+ String intention = null;
|
|
|
+ String intentionDegree = null;
|
|
|
+ if (StringUtils.isNotBlank(callPhoneRes.getDialogue())) {
|
|
|
+ log.info("【验证】意向度来源=自家AI, uuid={}, dialogueLength={}", callPhoneRes.getUuid(),
|
|
|
+ StringUtils.isBlank(callPhoneRes.getDialogue()) ? 0 : callPhoneRes.getDialogue().length());
|
|
|
+ try {
|
|
|
+ intentionDegree = crmCustomerAnalyzeService.aiIntentionDegree(
|
|
|
+ callPhoneRes.getDialogue(),
|
|
|
+ now().getLong(ChronoField.MILLI_OF_SECOND)
|
|
|
+ );
|
|
|
+ log.info("【验证】意向度结果={}, uuid={}", intentionDegree, callPhoneRes.getUuid());
|
|
|
+ intention = getIntention(intentionDegree);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("easyCall意向度AI解析失败,uuid={},将使用意向未知兜底", callPhoneRes.getUuid(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 2) 最终兜底:意向未知
|
|
|
if (StringUtils.isEmpty(intention)) {
|
|
|
intention = "0";
|
|
|
}
|
|
|
@@ -1337,8 +1455,10 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
companyWxClient.setDialogId(companyVoiceRoboticWx.getWxDialogId());
|
|
|
if (Integer.valueOf(2).equals(robotic.getIsWeCom())) {
|
|
|
companyWxClient.setCompanyUserId(qwUser.getCompanyUserId());
|
|
|
+ companyWxClient.setCompanyId(qwUser.getCompanyId());
|
|
|
} else if (Integer.valueOf(1).equals(robotic.getIsWeCom())) {
|
|
|
companyWxClient.setCompanyUserId(companyWxAccount.getCompanyUserId());
|
|
|
+ companyWxClient.setCompanyId(companyWxAccount.getCompanyId());
|
|
|
}
|
|
|
CrmCustomer crmCustomer = crmCustomerService.selectCrmCustomerById(companyWxClient.getCustomerId());
|
|
|
companyWxClient.setNickName(crmCustomer.getCustomerName());
|
|
|
@@ -1400,8 +1520,10 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
companyWxClient.setDialogId(wx.getWxDialogId());
|
|
|
if (robotic.getIsWeCom() == 2) {
|
|
|
companyWxClient.setCompanyUserId(qwMap.get(wx.getAccountId()).getCompanyUserId());
|
|
|
+ companyWxClient.setCompanyId(qwMap.get(wx.getAccountId()).getCompanyId());
|
|
|
} else {
|
|
|
companyWxClient.setCompanyUserId(accountMap.get(wx.getAccountId()).getCompanyUserId());
|
|
|
+ companyWxClient.setCompanyId(accountMap.get(wx.getAccountId()).getCompanyId());
|
|
|
}
|
|
|
companyWxClient.setNickName(crmCustomer.getCustomerName());
|
|
|
companyWxClient.setPhone(crmCustomer.getMobile());
|
|
|
@@ -1473,6 +1595,7 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
client.setRoboticWxId(companyVoiceRoboticWx.getId());
|
|
|
client.setCompanyId(companyVoiceRoboticWx.getAccount().getCompanyId());
|
|
|
client.setCompanyUserId(companyVoiceRoboticWx.getAccount().getCompanyUserId());
|
|
|
+ client.setCompanyId(companyVoiceRoboticWx.getAccount().getCompanyId());
|
|
|
CompanyWxAccount account = new CompanyWxAccount();
|
|
|
account.setId(companyVoiceRoboticWx.getAccount().getId());
|
|
|
account.setAllocateNum(companyVoiceRoboticWx.getAccount().getAllocateNum() + 1);
|
|
|
@@ -1547,25 +1670,25 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
.filter(Objects::nonNull)
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
- if (!businessIds.isEmpty()) {
|
|
|
- List<CompanyVoiceRoboticBusiness> businesses = companyVoiceRoboticBusinessMapper.selectList(new LambdaQueryWrapper<CompanyVoiceRoboticBusiness>()
|
|
|
- .in(CompanyVoiceRoboticBusiness::getId, businessIds));
|
|
|
- if (ObjectUtil.isNotEmpty(businesses)) {
|
|
|
- Map<Long, CompanyVoiceRoboticBusiness> businessMap = businesses.stream().collect(Collectors.toMap(CompanyVoiceRoboticBusiness::getId, Function.identity()));
|
|
|
- records.forEach(record -> {
|
|
|
- if (record.getBusinessId() != null && businessMap.containsKey(record.getBusinessId())) {
|
|
|
- CompanyVoiceRoboticBusiness business = businessMap.get(record.getBusinessId());
|
|
|
- CompanyVoiceRoboticCallLogCallphone callLogCallphone = companyVoiceRoboticCallLogCallphoneMapper.selectOne(new LambdaQueryWrapper<CompanyVoiceRoboticCallLogCallphone>()
|
|
|
- .eq(CompanyVoiceRoboticCallLogCallphone::getRoboticId, business.getRoboticId())
|
|
|
- .eq(CompanyVoiceRoboticCallLogCallphone::getCallerId, business.getCalleeId()));
|
|
|
- if (ObjectUtil.isNotEmpty(callLogCallphone)) {
|
|
|
- record.setContentList(callLogCallphone.getContentList());
|
|
|
- record.setIntention(callLogCallphone.getIntention());
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
+// if (!businessIds.isEmpty()) {
|
|
|
+// List<CompanyVoiceRoboticBusiness> businesses = companyVoiceRoboticBusinessMapper.selectList(new LambdaQueryWrapper<CompanyVoiceRoboticBusiness>()
|
|
|
+// .in(CompanyVoiceRoboticBusiness::getId, businessIds));
|
|
|
+// if (ObjectUtil.isNotEmpty(businesses)) {
|
|
|
+// Map<Long, CompanyVoiceRoboticBusiness> businessMap = businesses.stream().collect(Collectors.toMap(CompanyVoiceRoboticBusiness::getId, Function.identity()));
|
|
|
+// records.forEach(record -> {
|
|
|
+// if (record.getBusinessId() != null && businessMap.containsKey(record.getBusinessId())) {
|
|
|
+// CompanyVoiceRoboticBusiness business = businessMap.get(record.getBusinessId());
|
|
|
+// CompanyVoiceRoboticCallLogCallphone callLogCallphone = companyVoiceRoboticCallLogCallphoneMapper.selectOne(new LambdaQueryWrapper<CompanyVoiceRoboticCallLogCallphone>()
|
|
|
+// .eq(CompanyVoiceRoboticCallLogCallphone::getRoboticId, business.getRoboticId())
|
|
|
+// .eq(CompanyVoiceRoboticCallLogCallphone::getCallerId, business.getCalleeId()));
|
|
|
+// if (ObjectUtil.isNotEmpty(callLogCallphone)) {
|
|
|
+// record.setContentList(callLogCallphone.getContentList());
|
|
|
+// record.setIntention(callLogCallphone.getIntention());
|
|
|
+// }
|
|
|
+// }
|
|
|
+// });
|
|
|
+// }
|
|
|
+// }
|
|
|
|
|
|
if (!instanceIds.isEmpty()) {
|
|
|
List<CompanyAiWorkflowExecLog> allLogs = companyAiWorkflowExecLogMapper.selectByInstanceIds(instanceIds);
|