Просмотр исходного кода

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

caoliqin 20 часов назад
Родитель
Сommit
48854a9b1c

+ 2 - 0
fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticController.java

@@ -9,6 +9,7 @@ import com.fs.aicall.domain.result.EditDialogResult;
 import com.fs.aicall.domain.result.GetairobotResult;
 import com.fs.aicall.domain.result.GetairobotResult;
 import com.fs.aicall.domain.result.QueryCallTaskInfoResult;
 import com.fs.aicall.domain.result.QueryCallTaskInfoResult;
 import com.fs.aicall.service.AiCallService;
 import com.fs.aicall.service.AiCallService;
+import com.fs.common.annotation.CallbackIpCheck;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.AjaxResult;
@@ -323,6 +324,7 @@ public class CompanyVoiceRoboticController extends BaseController
     }
     }
 
 
     @PostMapping("/callerResult4EasyCall")
     @PostMapping("/callerResult4EasyCall")
+    @CallbackIpCheck
     public String callerResult4EasyCall(@RequestBody String cdrStr) {
     public String callerResult4EasyCall(@RequestBody String cdrStr) {
         log.info("callerResult4EasyCall:回调结果:{}",cdrStr);
         log.info("callerResult4EasyCall:回调结果:{}",cdrStr);
         CdrDetailVo cdrDetailVo = JSONObject.parseObject(cdrStr, CdrDetailVo.class);
         CdrDetailVo cdrDetailVo = JSONObject.parseObject(cdrStr, CdrDetailVo.class);

+ 1 - 1
fs-service/src/main/java/com/fs/company/service/impl/AsyncCalleeProcessorServiceImpl.java

@@ -127,7 +127,7 @@ public class AsyncCalleeProcessorServiceImpl implements IAsyncCalleeProcessorSer
 
 
     private CidPhoneConfig loadPhoneConfig(Long companyId) {
     private CidPhoneConfig loadPhoneConfig(Long companyId) {
         try {
         try {
-            CompanyConfig companyConfig = companyConfigMapper.selectCompanyConfigByKey(companyId, "cid.config");
+            CompanyConfig companyConfig = companyConfigMapper.selectCompanyConfigByKey(companyId, "cId.config");
             if (companyConfig != null && StringUtils.isNotEmpty(companyConfig.getConfigValue())) {
             if (companyConfig != null && StringUtils.isNotEmpty(companyConfig.getConfigValue())) {
                 return JSONObject.parseObject(companyConfig.getConfigValue(), CidPhoneConfig.class);
                 return JSONObject.parseObject(companyConfig.getConfigValue(), CidPhoneConfig.class);
             }
             }

+ 23 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyInboundCallManageServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.company.service.impl;
 package com.fs.company.service.impl;
 
 
+import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.text.Convert;
 import com.fs.common.core.text.Convert;
 import com.fs.company.domain.CompanyInboundBind;
 import com.fs.company.domain.CompanyInboundBind;
 import com.fs.company.domain.EasyCallInboundCdrVO;
 import com.fs.company.domain.EasyCallInboundCdrVO;
@@ -7,6 +8,9 @@ import com.fs.company.mapper.CompanyInboundBindMapper;
 import com.fs.company.mapper.EasyCallInboundLlmMapper;
 import com.fs.company.mapper.EasyCallInboundLlmMapper;
 import com.fs.company.service.ICompanyInboundCallManageService;
 import com.fs.company.service.ICompanyInboundCallManageService;
 import com.fs.company.vo.easycall.EasyCallInboundLlmVO;
 import com.fs.company.vo.easycall.EasyCallInboundLlmVO;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.service.ISysConfigService;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -22,6 +26,7 @@ import java.util.stream.Collectors;
  * @author fs
  * @author fs
  */
  */
 @Service
 @Service
+@Slf4j
 public class CompanyInboundCallManageServiceImpl implements ICompanyInboundCallManageService {
 public class CompanyInboundCallManageServiceImpl implements ICompanyInboundCallManageService {
 
 
     @Autowired
     @Autowired
@@ -29,6 +34,8 @@ public class CompanyInboundCallManageServiceImpl implements ICompanyInboundCallM
 
 
     @Autowired
     @Autowired
     CompanyInboundBindMapper companyInboundBindMapper;
     CompanyInboundBindMapper companyInboundBindMapper;
+    @Autowired
+    private ISysConfigService sysConfigService;
 
 
     /**
     /**
      * 查询呼入大模型配置
      * 查询呼入大模型配置
@@ -75,6 +82,22 @@ public class CompanyInboundCallManageServiceImpl implements ICompanyInboundCallM
         if(b){
         if(b){
           throw new RuntimeException("被叫号码已存在,不能重复插入");
           throw new RuntimeException("被叫号码已存在,不能重复插入");
         }
         }
+        try {
+            //获得总后台配置
+            SysConfig cidConf = sysConfigService.selectConfigByConfigKey("cId.config");
+            if (null != cidConf) {
+                String configValue = cidConf.getConfigValue();
+                if (com.fs.common.utils.StringUtils.isNotBlank(configValue)) {
+                    JSONObject jsonObject = JSONObject.parseObject(configValue);
+                    if (null != jsonObject && jsonObject.containsKey("inboundCallbackUrl")) {
+                        vo.setCallBackUrl(jsonObject.getString("inboundCallbackUrl"));
+                    }
+                }
+            }
+        } catch (Exception ex) {
+            log.error("获取总后台配置异常", ex);
+        }
+        
         int i = inboundLlmMapper.insertInboundLlm(vo);
         int i = inboundLlmMapper.insertInboundLlm(vo);
         if(i >0 && vo.getId()!= null) {
         if(i >0 && vo.getId()!= null) {
             CompanyInboundBind bind = new CompanyInboundBind();
             CompanyInboundBind bind = new CompanyInboundBind();

+ 2 - 2
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticCallLogCallphoneServiceImpl.java

@@ -190,7 +190,7 @@ public class CompanyVoiceRoboticCallLogCallphoneServiceImpl extends ServiceImpl<
 //                updateCallees.setRunTaskFlow(runTaskFlow);
 //                updateCallees.setRunTaskFlow(runTaskFlow);
 //                companyVoiceRoboticCalleesMapper.updateById(updateCallees);
 //                companyVoiceRoboticCalleesMapper.updateById(updateCallees);
 //            }
 //            }
-            String json = configService.selectConfigByKey("cid.config");
+            String json = configService.selectConfigByKey("cId.config");
             if (StringUtils.isBlank(json)) {
             if (StringUtils.isBlank(json)) {
                 log.error("未配置cid.config");
                 log.error("未配置cid.config");
             }
             }
@@ -270,7 +270,7 @@ public class CompanyVoiceRoboticCallLogCallphoneServiceImpl extends ServiceImpl<
     @Async("callLogExcutor")
     @Async("callLogExcutor")
     public void asyncHandleCalleeCallBackResult4EasyCall(EasyCallCallPhoneVO result, CompanyVoiceRoboticCallees callees) {
     public void asyncHandleCalleeCallBackResult4EasyCall(EasyCallCallPhoneVO result, CompanyVoiceRoboticCallees callees) {
         try {
         try {
-            String json = configService.selectConfigByKey("cid.config");
+            String json = configService.selectConfigByKey("cId.config");
             if (StringUtils.isBlank(json)) {
             if (StringUtils.isBlank(json)) {
                 log.error("未配置cid.config");
                 log.error("未配置cid.config");
             }
             }

+ 6 - 1
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java

@@ -1796,7 +1796,12 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             return new ArrayList<>();
             return new ArrayList<>();
         }
         }
         List<CompanyAiWorkflowExecLog> callLogs = logs.stream().filter(a -> "外呼".equals(a.getNodeName())).collect(Collectors.toList());
         List<CompanyAiWorkflowExecLog> callLogs = logs.stream().filter(a -> "外呼".equals(a.getNodeName())).collect(Collectors.toList());
-        HashMap<Long,String> callContentMap = selectCallContentByCallLogs(callLogs);
+        HashMap<Long,String> callContentMap;
+        if (null != callLogs && !callLogs.isEmpty()) {
+            callContentMap = selectCallContentByCallLogs(callLogs);
+        } else {
+            callContentMap = new HashMap<>();
+        }
 
 
         return logs.stream().map(log -> {
         return logs.stream().map(log -> {
             WorkflowExecRecordVo.NodeExecLogVo vo = new WorkflowExecRecordVo.NodeExecLogVo();
             WorkflowExecRecordVo.NodeExecLogVo vo = new WorkflowExecRecordVo.NodeExecLogVo();

+ 105 - 6
fs-service/src/main/java/com/fs/company/service/impl/GeneralCustomerEntryServiceImpl.java

@@ -9,6 +9,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.entity.SysDictData;
 import com.fs.common.core.domain.entity.SysDictData;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.common.utils.spring.SpringUtils;
@@ -85,6 +86,27 @@ public class GeneralCustomerEntryServiceImpl implements IGeneralCustomerEntrySer
     CrmCustomerPropertyServiceImpl crmCustomerPropertyService;
     CrmCustomerPropertyServiceImpl crmCustomerPropertyService;
     @Autowired
     @Autowired
     EasyCallMapper easyCallMapper;
     EasyCallMapper easyCallMapper;
+    @Autowired
+    private RedisCache redisCache2;
+
+    /** 呼入回调 chatContent(对话内容)重试队列 Redis key 前缀,value 为已重试次数 */
+    private static final String INBOUND_CHAT_CONTENT_RETRY_KEY = "inbound:chat:retry:";
+    /** chatContent 对话内容等待重试最大次数(每次间隔约30秒,最多等待 5*30=150秒) */
+    private static final int INBOUND_CHAT_CONTENT_MAX_RETRY = 5;
+    /** chatContent 每次重试等待时长(毫秒) */
+    private static final long INBOUND_CHAT_CONTENT_RETRY_INTERVAL_MS = 30000L;
+
+    /**
+     * 判断 chatContent 对话内容是否为空(null、空字符串、空数组 "[]" 均视为无对话内容)
+     */
+    private boolean isChatContentEmpty(String chatContent) {
+        if (StringUtils.isBlank(chatContent)) {
+            return true;
+        }
+        String trimmed = chatContent.trim();
+        return "[]".equals(trimmed);
+    }
+
     /**
     /**
      * 录入客户
      * 录入客户
      *
      *
@@ -123,7 +145,7 @@ public class GeneralCustomerEntryServiceImpl implements IGeneralCustomerEntrySer
     }
     }
 
 
     private static final String TRADE_TYPE = "trade_type";
     private static final String TRADE_TYPE = "trade_type";
-    @Value("${crm.customer.ai.key:mygpt-oPG2ifhnq0ODGioOBMUvMfOZGrtCykqw3oMeYLchdUDK5He6iNiactrhFWA0sID}")
+    @Value("${crm.customer.ai.Key:mygpt-iTUua2CHVd4WGrBbQQGl1HHjyyBAD1KuXARsxHj5eHpLYv5CfnOh8iwVU}")
     private String appKey;
     private String appKey;
     private List<CrmCustomerAiTagVo> getAiTags(String chatRecord) throws JsonProcessingException {
     private List<CrmCustomerAiTagVo> getAiTags(String chatRecord) throws JsonProcessingException {
         Map<String, Object> requestParam = new HashMap<>();
         Map<String, Object> requestParam = new HashMap<>();
@@ -298,11 +320,13 @@ public class GeneralCustomerEntryServiceImpl implements IGeneralCustomerEntrySer
                     property.setCreateTime(new Date());
                     property.setCreateTime(new Date());
                     return property;
                     return property;
                 }).collect(Collectors.toList());
                 }).collect(Collectors.toList());
-                crmCustomerPropertyService.remove(new LambdaQueryWrapper<CrmCustomerProperty>()
-                        .eq(CrmCustomerProperty::getCustomerId, data.getCustomerId())
-                        .in(CrmCustomerProperty::getPropertyId, ids)
-                );
-                crmCustomerPropertyService.saveBatch(propertyList);
+                if(null != ids && !ids.isEmpty()){
+                    crmCustomerPropertyService.remove(new LambdaQueryWrapper<CrmCustomerProperty>()
+                            .eq(CrmCustomerProperty::getCustomerId, data.getCustomerId())
+                            .in(CrmCustomerProperty::getPropertyId, ids)
+                    );
+                    crmCustomerPropertyService.saveBatch(propertyList);
+                }
             } catch (JsonProcessingException e) {
             } catch (JsonProcessingException e) {
                 throw new RuntimeException(e);
                 throw new RuntimeException(e);
             }
             }
@@ -415,21 +439,96 @@ public class GeneralCustomerEntryServiceImpl implements IGeneralCustomerEntrySer
      * @param param
      * @param param
      */
      */
     @Override
     @Override
+    @Async("cidWorkFlowExecutor")
     public void inboundCallback(InboundCallbackParam param){
     public void inboundCallback(InboundCallbackParam param){
         try {
         try {
             Thread.sleep(5000L);
             Thread.sleep(5000L);
         } catch (InterruptedException e) {
         } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
             throw new RuntimeException(e);
             throw new RuntimeException(e);
         }
         }
         if (param == null || StringUtils.isBlank(param.getUuid())){
         if (param == null || StringUtils.isBlank(param.getUuid())){
             return;
             return;
         }
         }
         InboundCallInfo info  = easyCallMapper.selectInboundCallbackInfoByUuid(param.getUuid());
         InboundCallInfo info  = easyCallMapper.selectInboundCallbackInfoByUuid(param.getUuid());
+        // chatContent(对话内容)由对方异步写入,回调时可能尚未赋值,进入延迟重试队列等待
+        if (info == null || isChatContentEmpty(info.getChatContent())) {
+            String retryKey = INBOUND_CHAT_CONTENT_RETRY_KEY + param.getUuid();
+            Integer retryCount = redisCache2.getCacheObject(retryKey);
+            if (retryCount == null) {
+                retryCount = 0;
+            }
+            if (retryCount < INBOUND_CHAT_CONTENT_MAX_RETRY) {
+                redisCache2.setCacheObject(retryKey, retryCount + 1, 10, java.util.concurrent.TimeUnit.MINUTES);
+                log.info("呼入回调chatContent对话内容暂未写入,uuid={},第{}次放入延迟重试队列", param.getUuid(), retryCount + 1);
+                doRetryInboundCallback(param, retryCount + 1);
+            } else {
+                // 超过最大重试次数,以 chatContent 为空兜底继续处理
+                log.warn("呼入回调chatContent对话内容在{}次重试后仍为空,uuid={},以对话为空兜底处理", INBOUND_CHAT_CONTENT_MAX_RETRY, param.getUuid());
+                redisCache2.deleteObject(retryKey);
+                doHandleInboundCallback(param, info);
+            }
+            return;
+        }
+        // chatContent 已有值,直接正常处理
+        redisCache2.deleteObject(INBOUND_CHAT_CONTENT_RETRY_KEY + param.getUuid());
+        doHandleInboundCallback(param, info);
+    }
+
+    /**
+     * 延迟重试处理呼入回调(等待 chatContent 对话内容异步写入完成)
+     * 每次重试前等待 {@link #INBOUND_CHAT_CONTENT_RETRY_INTERVAL_MS} 毫秒后重新拉取数据
+     */
+    @Async("cidWorkFlowExecutor")
+    public void doRetryInboundCallback(InboundCallbackParam param, int currentRetry) {
+        try {
+            Thread.sleep(INBOUND_CHAT_CONTENT_RETRY_INTERVAL_MS);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.error("呼入回调chatContent重试等待被中断, uuid={}", param.getUuid());
+            return;
+        }
+        log.info("呼入回调chatContent重试第{}次开始, uuid={}", currentRetry, param.getUuid());
+        InboundCallInfo info = easyCallMapper.selectInboundCallbackInfoByUuid(param.getUuid());
+        if (info == null || isChatContentEmpty(info.getChatContent())) {
+            // chatContent 仍为空,继续判断是否还有剩余重试次数
+            String retryKey = INBOUND_CHAT_CONTENT_RETRY_KEY + param.getUuid();
+            Integer retryCount = redisCache2.getCacheObject(retryKey);
+            if (retryCount == null) {
+                retryCount = currentRetry;
+            }
+            if (retryCount < INBOUND_CHAT_CONTENT_MAX_RETRY) {
+                redisCache2.setCacheObject(retryKey, retryCount + 1, 10, java.util.concurrent.TimeUnit.MINUTES);
+                log.info("呼入回调chatContent对话内容仍未写入,uuid={},第{}次继续延迟重试", param.getUuid(), retryCount + 1);
+                doRetryInboundCallback(param, retryCount + 1);
+            } else {
+                log.error("呼入回调chatContent对话内容在{}次重试后仍为空,uuid={},以对话为空兜底处理", INBOUND_CHAT_CONTENT_MAX_RETRY, param.getUuid());
+                redisCache2.deleteObject(retryKey);
+                doHandleInboundCallback(param, info);
+            }
+            return;
+        }
+        // chatContent 已写入完成,正常处理
+        log.info("呼入回调chatContent重试第{}次成功获取到对话内容,uuid={}", currentRetry, param.getUuid());
+        redisCache2.deleteObject(INBOUND_CHAT_CONTENT_RETRY_KEY + param.getUuid());
+        doHandleInboundCallback(param, info);
+    }
+
+    /**
+     * 执行呼入回调核心业务处理(组装客户入参并录入)
+     * 供 {@link #inboundCallback} 和重试逻辑统一调用
+     */
+    private void doHandleInboundCallback(InboundCallbackParam param, InboundCallInfo info) {
+        if (info == null) {
+            log.error("呼入回调信息未查询到结果, uuid={}", param.getUuid());
+            return;
+        }
         EntryCustomerParam entry = new EntryCustomerParam();
         EntryCustomerParam entry = new EntryCustomerParam();
         entry.setTraceId(param.getUuid());
         entry.setTraceId(param.getUuid());
         entry.setCompanyId(info.getFsCompanyId());
         entry.setCompanyId(info.getFsCompanyId());
         entry.setSceneType(info.getFsSceneType());
         entry.setSceneType(info.getFsSceneType());
         entry.setMobile(info.getCaller());
         entry.setMobile(info.getCaller());
+        entry.setDialogue(info.getChatContent());
         entryCustomer(entry);
         entryCustomer(entry);
     }
     }
 
 

+ 9 - 1
fs-service/src/main/java/com/fs/company/service/impl/call/node/AiCallTaskNode.java

@@ -271,6 +271,14 @@ public class AiCallTaskNode extends AbstractWorkflowNode {
             if(null != cidConf && StringUtils.isNotBlank(cidConf.getCallbackUrl())){
             if(null != cidConf && StringUtils.isNotBlank(cidConf.getCallbackUrl())){
                 callBackUrl = cidConf.getCallbackUrl();
                 callBackUrl = cidConf.getCallbackUrl();
             }
             }
+            //读取总后台配置
+            if(StringUtils.isBlank(callBackUrl)){
+                String s = configService.selectConfigByKey("cId.config");
+                JSONObject obj = JSONObject.parseObject(s);
+                if(null != obj && obj.containsKey("callbackUrl") && StringUtils.isNotBlank(obj.getString("callbackUrl"))){
+                    callBackUrl = obj.getString("callbackUrl");
+                }
+            }
         } catch (Exception ex){
         } catch (Exception ex){
             log.error("获取公司Cid配置失败:{}", ex);
             log.error("获取公司Cid配置失败:{}", ex);
         }
         }
@@ -375,7 +383,7 @@ public class AiCallTaskNode extends AbstractWorkflowNode {
     }
     }
 
 
     private boolean checkPhoneCallLimit(Long businessId){
     private boolean checkPhoneCallLimit(Long businessId){
-        String json = configService.selectConfigByKey("cid.config");
+        String json = configService.selectConfigByKey("cId.config");
         if(StringUtils.isNotEmpty(json)){//数据存在
         if(StringUtils.isNotEmpty(json)){//数据存在
             //转换数据
             //转换数据
             CidPhoneConfig config =JSONObject.parseObject(json,CidPhoneConfig.class);
             CidPhoneConfig config =JSONObject.parseObject(json,CidPhoneConfig.class);

+ 0 - 1
fs-service/src/main/resources/mapper/company/EasyCallMapper.xml

@@ -14,7 +14,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectInboundCallbackInfoByUuid" resultType="com.fs.company.vo.InboundCallInfo">
     <select id="selectInboundCallbackInfoByUuid" resultType="com.fs.company.vo.InboundCallInfo">
         select
         select
             t1.uuid,
             t1.uuid,
-            t2.call_back_url,
             t2.fs_company_id,
             t2.fs_company_id,
             t2.fs_scene_type,
             t2.fs_scene_type,
             t1.chat_content,
             t1.chat_content,