浏览代码

CID,批量插入数据代码优化,优化数据类型

yjwang 2 天之前
父节点
当前提交
1195f41f2a

+ 5 - 0
fs-company/src/main/java/com/fs/company/controller/aicall/CcLlmAgentAccountController.java

@@ -282,6 +282,11 @@ public class CcLlmAgentAccountController extends BaseController
     @ResponseBody
     public AjaxResult remove(String ids)
     {
+        //获取公司id
+        Long companyId = getCurrentCompanyId();
+        //删除公司绑定的AI模型
+        companyBindAiModelService.deleteBindAiModelByCompanyIdAndModelIds(companyId,ids);
+
         return toAjax(ccLlmAgentAccountService.deleteCcLlmAgentAccountByIds(ids));
     }
 

+ 7 - 0
fs-service/src/main/java/com/fs/aicall/mapper/CompanyBindAiModelMapper.java

@@ -87,4 +87,11 @@ public interface CompanyBindAiModelMapper
      * @return 结果
      */
     public int batchInsertCompanyBindAiModel(@Param("companyId") Long companyId, @Param("modelIds") List<Long> modelIds);
+
+    /**
+     * 批量删除·1、公司ID、模型ID
+     * @param companyId
+     * @param modelIds 模型ID列表
+     * **/
+    void deleteBindAiModelByCompanyIdAndModelIds(@Param("companyId") Long companyId, @Param("modelIds") String[] modelIds);
 }

+ 9 - 0
fs-service/src/main/java/com/fs/aicall/service/ICompanyBindAiModelService.java

@@ -102,4 +102,13 @@ public interface ICompanyBindAiModelService
      * @return 结果
      */
     public int batchBindCompaniesToModel(Long modelId, List<Long> companyIds);
+
+    /**
+     * 批量删除对应公司模型关系表
+     *
+     * @param companyId 公司ID
+     * @param modelIds 模型ID列表
+     * @return 结果
+     */
+    void deleteBindAiModelByCompanyIdAndModelIds(Long companyId, String modelIds);
 }

+ 9 - 0
fs-service/src/main/java/com/fs/aicall/service/impl/CompanyBindAiModelServiceImpl.java

@@ -3,11 +3,15 @@ package com.fs.aicall.service.impl;
 import com.fs.aicall.domain.CompanyBindAiModel;
 import com.fs.aicall.mapper.CompanyBindAiModelMapper;
 import com.fs.aicall.service.ICompanyBindAiModelService;
+import com.fs.aicall.utils.StringUtils;
+import com.fs.common.core.text.Convert;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 销售公司与AI模型绑定表Service实现类
@@ -167,4 +171,9 @@ public class CompanyBindAiModelServiceImpl implements ICompanyBindAiModelService
         }
         return result;
     }
+
+    @Override
+    public void deleteBindAiModelByCompanyIdAndModelIds(Long companyId, String modelIds) {
+        companyBindAiModelMapper.deleteBindAiModelByCompanyIdAndModelIds(companyId, Convert.toStrArray(modelIds));
+    }
 }

+ 4 - 7
fs-service/src/main/java/com/fs/company/config/AsyncConfig.java → fs-service/src/main/java/com/fs/company/config/AsyncCalleeConfig.java

@@ -10,20 +10,17 @@ import java.util.concurrent.ThreadPoolExecutor;
 
 @Configuration
 @EnableAsync
-public class AsyncConfig {
+public class AsyncCalleeConfig {
     @Bean(name = "calleeTaskExecutor")
     public Executor calleeTaskExecutor() {
         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
-        int cpuCores = Runtime.getRuntime().availableProcessors();
-        executor.setCorePoolSize(cpuCores);
-        executor.setMaxPoolSize(20);
-        executor.setQueueCapacity(1000);
+        executor.setCorePoolSize(10);
+        executor.setMaxPoolSize(50);
+        executor.setQueueCapacity(500);
         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
         executor.setKeepAliveSeconds(60);
-        executor.setAllowCoreThreadTimeOut(false);
         executor.setWaitForTasksToCompleteOnShutdown(true);
         executor.setAwaitTerminationSeconds(60);
-
         executor.initialize();
         return executor;
     }

+ 1 - 1
fs-service/src/main/java/com/fs/company/domain/CompanyAiWorkflowExec.java

@@ -69,7 +69,7 @@ public class CompanyAiWorkflowExec {
 
     /** 业务键值 */
     @Excel(name = "业务键值")
-    private String businessKey;
+    private Long businessKey;
 
     /**
      * 开始节点key

+ 1 - 1
fs-service/src/main/java/com/fs/company/param/ExecutionContext.java

@@ -53,7 +53,7 @@ public class ExecutionContext {
     /**
      * 业务关键id
      */
-    private String businessId;
+    private Long businessId;
     /**
      * 设置本地变量
      */

+ 21 - 0
fs-service/src/main/java/com/fs/company/service/IAsyncCalleeProcessorService.java

@@ -0,0 +1,21 @@
+package com.fs.company.service;
+
+import com.fs.company.domain.CompanyVoiceRobotic;
+import com.fs.company.domain.CompanyVoiceRoboticCallees;
+import com.fs.company.domain.CompanyWxClient;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 异步调用服务加入日志
+ * **/
+public interface IAsyncCalleeProcessorService {
+    /**
+     * 生成客户信息日志
+     * @param calleesList 客户列表对象
+     * @param clientMp 添加个微信账号对象
+     * @param robotic 任务
+     * **/
+    void generateCustomerInfo(List<CompanyVoiceRoboticCallees> calleesList, Map<String, CompanyWxClient> clientMp, CompanyVoiceRobotic robotic);
+}

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

@@ -0,0 +1,473 @@
+package com.fs.company.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.*;
+import com.fs.company.mapper.*;
+import com.fs.company.service.IAsyncCalleeProcessorService;
+import com.fs.company.util.RandomNameGeneratorUtil;
+import com.fs.company.vo.CompanyNodeInfoVo;
+import com.fs.enums.ExecutionStatusEnum;
+import com.fs.enums.NodeTypeEnum;
+import com.fs.his.config.CidPhoneConfig;
+import com.fs.system.service.ISysConfigService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
+
+@Service
+public class AsyncCalleeProcessorServiceImpl implements IAsyncCalleeProcessorService {
+
+    private static final Logger log = LoggerFactory.getLogger(AsyncCalleeProcessorServiceImpl.class);
+
+    private static final int BATCH_SIZE = 1500;
+    private static final int CPU_YIELD_INTERVAL = 500;
+    private static final int SLEEP_INTERVAL = 2000;
+    private static final int SLEEP_MILLIS = 10;
+    private static final int PHONE_LENGTH = 11;
+    private static final char DIGIT_ZERO = '0';
+    private static final int RADIX_TEN = 10;
+
+    private final ISysConfigService configService;
+    private final CompanyWorkflowEdgeMapper edgeMapper;
+    private final CompanyConfigMapper companyConfigMapper;
+    private final CompanyWorkflowMapper companyWorkflowMapper;
+    private final CompanyAiWorkflowExecMapper companyAiWorkflowExecMapper;
+    private final CompanyAiWorkflowExecLogMapper companyAiWorkflowExecLogMapper;
+    private final CompanyVoiceRoboticBusinessMapper companyVoiceRoboticBusinessMapper;
+    private final CompanyVoiceRoboticCalleesMapper companyVoiceRoboticCalleesMapper;
+
+    AsyncCalleeProcessorServiceImpl(CompanyConfigMapper companyConfigMapper,
+                                    ISysConfigService configService,
+                                    CompanyWorkflowEdgeMapper edgeMapper,
+                                    CompanyWorkflowMapper companyWorkflowMapper,
+                                    CompanyAiWorkflowExecMapper companyAiWorkflowExecMapper,
+                                    CompanyAiWorkflowExecLogMapper companyAiWorkflowExecLogMapper,
+                                    CompanyVoiceRoboticBusinessMapper companyVoiceRoboticBusinessMapper,
+                                    CompanyVoiceRoboticCalleesMapper companyVoiceRoboticCalleesMapper) {
+        this.companyConfigMapper = companyConfigMapper;
+        this.configService = configService;
+        this.edgeMapper = edgeMapper;
+        this.companyWorkflowMapper = companyWorkflowMapper;
+        this.companyAiWorkflowExecMapper = companyAiWorkflowExecMapper;
+        this.companyAiWorkflowExecLogMapper = companyAiWorkflowExecLogMapper;
+        this.companyVoiceRoboticBusinessMapper = companyVoiceRoboticBusinessMapper;
+        this.companyVoiceRoboticCalleesMapper = companyVoiceRoboticCalleesMapper;
+    }
+
+    @Override
+    @Async("calleeTaskExecutor")
+    public void generateCustomerInfo(List<CompanyVoiceRoboticCallees> calleesList,
+                                     Map<String, CompanyWxClient> clientMp,
+                                     CompanyVoiceRobotic robotic) {
+        if (calleesList == null || calleesList.isEmpty() || robotic == null) {
+            return;
+        }
+
+        Thread currentThread = Thread.currentThread();
+        if (currentThread.isInterrupted()) {
+            log.info("任务被中断,退出处理");
+            return;
+        }
+
+        CidPhoneConfig phoneConfig = loadPhoneConfig(robotic.getCompanyId());
+        if (phoneConfig == null || !Boolean.TRUE.equals(phoneConfig.getEnablePhoneConfig())) {
+            log.warn("电话配置未启用或为空,companyId: {}", robotic.getCompanyId());
+            return;
+        }
+
+        CompanyWorkflow workflow = loadWorkflow(robotic.getCompanyAiWorkflowId());
+        if (workflow == null) {
+            log.warn("工作流不存在,workflowId: {}", robotic.getCompanyAiWorkflowId());
+            return;
+        }
+
+        CompanyNodeInfoVo nodeInfoVo = loadNodeInfo(workflow.getWorkflowId(), workflow.getStartNodeKey());
+        if (nodeInfoVo == null) {
+            log.warn("节点信息不存在,workflowId: {}, startNodeKey: {}", workflow.getWorkflowId(), workflow.getStartNodeKey());
+            return;
+        }
+
+        List<CompanyVoiceRoboticCallees> batchToInsert = new ArrayList<>(BATCH_SIZE);
+        int processedCount = 0;
+
+        for (CompanyVoiceRoboticCallees callees : calleesList) {
+            if (currentThread.isInterrupted()) {
+                log.info("任务被中断,已处理 {} 条", processedCount);
+                break;
+            }
+
+            try {
+                generatePhoneNumberInBatch(phoneConfig, callees, batchToInsert, clientMp, robotic, workflow, nodeInfoVo);
+                processedCount++;
+
+                if (processedCount % CPU_YIELD_INTERVAL == 0) {
+                    Thread.yield();
+                }
+
+                if (processedCount % SLEEP_INTERVAL == 0) {
+                    Thread.sleep(SLEEP_MILLIS);
+                }
+            } catch (InterruptedException e) {
+                log.info("任务被中断,已处理 {} 条", processedCount);
+                Thread.currentThread().interrupt();
+                break;
+            } catch (Exception e) {
+                log.error("处理被叫信息异常,phone: {}", callees.getPhone(), e);
+            }
+        }
+
+        log.info("generateCustomerInfo处理完成,共处理 {} 条", processedCount);
+    }
+
+    private CidPhoneConfig loadPhoneConfig(Long companyId) {
+        try {
+            CompanyConfig companyConfig = companyConfigMapper.selectCompanyConfigByKey(companyId, "cid.config");
+            if (companyConfig != null && StringUtils.isNotEmpty(companyConfig.getConfigValue())) {
+                return JSONObject.parseObject(companyConfig.getConfigValue(), CidPhoneConfig.class);
+            }
+            String json = configService.selectConfigByKey("his.store");
+            if (StringUtils.isNotEmpty(json)) {
+                return JSONObject.parseObject(json, CidPhoneConfig.class);
+            }
+        } catch (Exception e) {
+            log.error("加载电话配置异常,companyId: {}", companyId, e);
+        }
+        return null;
+    }
+
+    private CompanyWorkflow loadWorkflow(Long workflowId) {
+        try {
+            return companyWorkflowMapper.selectCompanyWorkflowById(workflowId);
+        } catch (Exception e) {
+            log.error("加载工作流异常,workflowId: {}", workflowId, e);
+            return null;
+        }
+    }
+
+    private CompanyNodeInfoVo loadNodeInfo(Long workflowId, String startNodeKey) {
+        try {
+            return edgeMapper.slectNodeInfoByWorkflowId(workflowId, startNodeKey);
+        } catch (Exception e) {
+            log.error("加载节点信息异常,workflowId: {}, startNodeKey: {}", workflowId, startNodeKey, e);
+            return null;
+        }
+    }
+
+    private void generatePhoneNumberInBatch(CidPhoneConfig config,
+                                            CompanyVoiceRoboticCallees callees,
+                                            List<CompanyVoiceRoboticCallees> batchToInsert,
+                                            Map<String, CompanyWxClient> clientMp,
+                                            CompanyVoiceRobotic robotic,
+                                            CompanyWorkflow workflow,
+                                            CompanyNodeInfoVo nodeInfoVo) {
+        String basePhone = callees.getPhone();
+        if (basePhone == null || basePhone.length() != PHONE_LENGTH) {
+            return;
+        }
+
+        int start = config.getStartIndex();
+        int end = config.getEndIndex();
+        int totalCount = config.getGenerateCount();
+
+        if (!isValidRange(start, end)) {
+            return;
+        }
+
+        int startIdx = start - 1;
+        int endIdx = end - 1;
+        char[] baseChars = basePhone.toCharArray();
+        ThreadLocalRandom random = ThreadLocalRandom.current();
+        int nameIndex = 0;
+
+        while (nameIndex < totalCount) {
+            if (Thread.currentThread().isInterrupted()) {
+                break;
+            }
+
+            int currentBatchSize = Math.min(BATCH_SIZE, totalCount - nameIndex);
+
+            for (int i = 0; i < currentBatchSize; i++) {
+                CompanyVoiceRoboticCallees roboticCallees = createCalleesWithPhone(
+                        baseChars, startIdx, endIdx, random, callees);
+                batchToInsert.add(roboticCallees);
+            }
+
+            nameIndex += currentBatchSize;
+
+            if (batchToInsert.size() >= BATCH_SIZE || nameIndex >= totalCount) {
+                flushGeneratedCalleesBatch(batchToInsert, clientMp, robotic, workflow, nodeInfoVo);
+            }
+
+            if (nameIndex % SLEEP_INTERVAL == 0) {
+                try {
+                    Thread.sleep(SLEEP_MILLIS);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    break;
+                }
+            }
+        }
+    }
+
+    private boolean isValidRange(int start, int end) {
+        return start >= 1 && start <= PHONE_LENGTH
+                && end >= 1 && end <= PHONE_LENGTH
+                && start <= end;
+    }
+
+    private CompanyVoiceRoboticCallees createCalleesWithPhone(char[] baseChars, int startIdx, int endIdx,
+                                                              ThreadLocalRandom random,
+                                                              CompanyVoiceRoboticCallees template) {
+        char[] newChars = baseChars.clone();
+        for (int j = startIdx; j <= endIdx; j++) {
+            newChars[j] = (char) (DIGIT_ZERO + random.nextInt(RADIX_TEN));
+        }
+
+        CompanyVoiceRoboticCallees roboticCallees = new CompanyVoiceRoboticCallees();
+        roboticCallees.setPhone(new String(newChars));
+        roboticCallees.setRoboticId(template.getRoboticId());
+        roboticCallees.setTaskFlow(template.getTaskFlow());
+        roboticCallees.setRunTaskFlow(template.getRunTaskFlow());
+        roboticCallees.setIsWeCom(template.getIsWeCom());
+        roboticCallees.setUserId(0L);
+        roboticCallees.setUserName(RandomNameGeneratorUtil.generateOne());
+        roboticCallees.setIsGenerate(1);
+
+        return roboticCallees;
+    }
+
+    private void flushGeneratedCalleesBatch(List<CompanyVoiceRoboticCallees> batchToInsert,
+                                            Map<String, CompanyWxClient> clientMap,
+                                            CompanyVoiceRobotic robotic,
+                                            CompanyWorkflow workflow,
+                                            CompanyNodeInfoVo nodeInfoVo) {
+        if (batchToInsert == null || batchToInsert.isEmpty()) {
+            return;
+        }
+
+        try {
+            int rows = companyVoiceRoboticCalleesMapper.batchInsertGenerateInfo(batchToInsert);
+            if (rows > 0) {
+                generateVoiceRoboticBusiness(batchToInsert, clientMap, robotic, workflow, nodeInfoVo);
+            }
+        } catch (Exception e) {
+            log.error("批量插入被叫数据异常,size: {}", batchToInsert.size(), e);
+        } finally {
+            batchToInsert.clear();
+        }
+    }
+
+    private void generateVoiceRoboticBusiness(List<CompanyVoiceRoboticCallees> calleesList,
+                                              Map<String, CompanyWxClient> clientMp,
+                                              CompanyVoiceRobotic robotic,
+                                              CompanyWorkflow workflow,
+                                              CompanyNodeInfoVo nodeInfoVo) {
+        if (calleesList == null || calleesList.isEmpty()) {
+            return;
+        }
+
+        List<CompanyVoiceRoboticBusiness> addList = new ArrayList<>(calleesList.size());
+        Date date = new Date();
+
+        for (CompanyVoiceRoboticCallees callees : calleesList) {
+            CompanyVoiceRoboticBusiness business = createBusiness(callees, clientMp, date);
+            addList.add(business);
+        }
+
+        if (!addList.isEmpty()) {
+            flushGeneratedBusinessBatch(addList, robotic, workflow, nodeInfoVo);
+        }
+    }
+
+    private CompanyVoiceRoboticBusiness createBusiness(CompanyVoiceRoboticCallees callees,
+                                                       Map<String, CompanyWxClient> clientMp,
+                                                       Date date) {
+        CompanyVoiceRoboticBusiness business = new CompanyVoiceRoboticBusiness();
+        business.setRoboticId(callees.getRoboticId());
+        business.setCalleeId(callees.getId());
+
+        String clientKey = callees.getRoboticId() + "-" + callees.getUserId();
+        CompanyWxClient client = clientMp.get(clientKey);
+        business.setWxClientId(client != null ? client.getId() : null);
+
+        business.setAddWxDone(0);
+        business.setCallPhoneDone(0);
+        business.setSendMsgDone(0);
+        business.setCreateTime(date);
+        business.setIsGenerate(1);
+
+        return business;
+    }
+
+    private void flushGeneratedBusinessBatch(List<CompanyVoiceRoboticBusiness> batchToInsert,
+                                             CompanyVoiceRobotic robotic,
+                                             CompanyWorkflow workflow,
+                                             CompanyNodeInfoVo nodeInfoVo) {
+        if (batchToInsert == null || batchToInsert.isEmpty()) {
+            return;
+        }
+
+        try {
+            int rows = companyVoiceRoboticBusinessMapper.insertBatchGenerateInfo(batchToInsert);
+            if (rows > 0) {
+                generateWorkflowExecRecords(batchToInsert, robotic, workflow, nodeInfoVo);
+            }
+        } catch (Exception e) {
+            log.error("批量插入业务数据异常,size: {}", batchToInsert.size(), e);
+        } finally {
+            batchToInsert.clear();
+        }
+    }
+
+    private void generateWorkflowExecRecords(List<CompanyVoiceRoboticBusiness> businessList,
+                                             CompanyVoiceRobotic robotic,
+                                             CompanyWorkflow workflow,
+                                             CompanyNodeInfoVo nodeInfoVo) {
+        if (businessList == null || businessList.isEmpty()) {
+            return;
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        List<CompanyAiWorkflowExec> startExecList = new ArrayList<>(businessList.size());
+        List<CompanyAiWorkflowExec> targetExecList = new ArrayList<>(businessList.size());
+
+        for (CompanyVoiceRoboticBusiness business : businessList) {
+            if (Thread.currentThread().isInterrupted()) {
+                break;
+            }
+
+            CompanyAiWorkflowExec startExec = createStartExec(business, robotic, workflow, now);
+            startExecList.add(startExec);
+
+            CompanyAiWorkflowExec targetExec = createTargetExec(startExec, nodeInfoVo, now);
+            targetExecList.add(targetExec);
+
+            if (targetExecList.size() >= BATCH_SIZE) {
+                persistExecLogs(targetExecList, startExecList);
+            }
+        }
+
+        if (!targetExecList.isEmpty()) {
+            persistExecLogs(targetExecList, startExecList);
+        }
+    }
+
+    private CompanyAiWorkflowExec createStartExec(CompanyVoiceRoboticBusiness business,
+                                                  CompanyVoiceRobotic robotic,
+                                                  CompanyWorkflow workflow,
+                                                  LocalDateTime now) {
+        CompanyAiWorkflowExec exec = new CompanyAiWorkflowExec();
+        exec.setWorkflowInstanceId(generateInstanceId());
+        exec.setWorkflowId(robotic.getCompanyAiWorkflowId());
+        exec.setCurrentNodeKey(workflow.getStartNodeKey());
+        exec.setCurrentNodeType(NodeTypeEnum.START.getValue());
+        exec.setCurrentNodeName(NodeTypeEnum.START.getDescription());
+        exec.setStatus(ExecutionStatusEnum.SUCCESS.getValue());
+        exec.setStartTime(now);
+        exec.setVariables(buildVariables(robotic, business));
+        exec.setBusinessKey(business.getId());
+        exec.setStartNodeKey(workflow.getStartNodeKey());
+        exec.setEndNodeKey(workflow.getEndNodeKey());
+        exec.setCidGroupNo(robotic.getCidGroupNo());
+        exec.setRuntimeRangeStart(robotic.getRuntimeRangeStart());
+        exec.setRuntimeRangeEnd(robotic.getRuntimeRangeEnd());
+        exec.setIsGenerate(1);
+        return exec;
+    }
+
+    private CompanyAiWorkflowExec createTargetExec(CompanyAiWorkflowExec startExec,
+                                                   CompanyNodeInfoVo nodeInfoVo,
+                                                   LocalDateTime now) {
+        CompanyAiWorkflowExec exec = new CompanyAiWorkflowExec();
+        exec.setWorkflowInstanceId(startExec.getWorkflowInstanceId());
+        exec.setWorkflowId(startExec.getWorkflowId());
+        exec.setCurrentNodeKey(nodeInfoVo.getTargetNodeKey());
+        exec.setCurrentNodeName(nodeInfoVo.getNodeName());
+        exec.setCurrentNodeType(NodeTypeEnum.fromCode(nodeInfoVo.getNodeType()).getValue());
+        exec.setStatus(ExecutionStatusEnum.INTERRUPT.getValue());
+        exec.setStartTime(now);
+        exec.setVariables(startExec.getVariables());
+        exec.setBusinessKey(startExec.getBusinessKey());
+        exec.setStartNodeKey(startExec.getStartNodeKey());
+        exec.setEndNodeKey(startExec.getEndNodeKey());
+        exec.setCidGroupNo(startExec.getCidGroupNo());
+        exec.setRuntimeRangeStart(startExec.getRuntimeRangeStart());
+        exec.setRuntimeRangeEnd(startExec.getRuntimeRangeEnd());
+        exec.setIsGenerate(1);
+        return exec;
+    }
+
+    private String buildVariables(CompanyVoiceRobotic robotic, CompanyVoiceRoboticBusiness business) {
+        JSONObject variables = new JSONObject(5);
+        variables.put("roboticId", robotic.getId());
+        variables.put("businessId", business.getId());
+        variables.put("cidGroupNo", robotic.getCidGroupNo());
+        variables.put("runtimeRangeStart", robotic.getRuntimeRangeStart());
+        variables.put("runtimeRangeEnd", robotic.getRuntimeRangeEnd());
+        return variables.toJSONString();
+    }
+
+    private void persistExecLogs(List<CompanyAiWorkflowExec> workflowExecs,
+                                 List<CompanyAiWorkflowExec> startExecList) {
+        if (workflowExecs == null || workflowExecs.isEmpty()) {
+            return;
+        }
+
+        try {
+            int rows = companyAiWorkflowExecMapper.insertBatchInfo(workflowExecs);
+            if (rows > 0) {
+                workflowExecLogBatchInsert(startExecList);
+                workflowExecs.forEach(exec -> exec.setStatus(ExecutionStatusEnum.FAILURE.getValue()));
+                workflowExecLogBatchInsert(workflowExecs);
+            }
+        } catch (Exception e) {
+            log.error("持久化执行日志异常,size: {}", workflowExecs.size(), e);
+        } finally {
+            workflowExecs.clear();
+            startExecList.clear();
+        }
+    }
+
+    private void workflowExecLogBatchInsert(List<CompanyAiWorkflowExec> workflowExecs) {
+        if (workflowExecs == null || workflowExecs.isEmpty()) {
+            return;
+        }
+
+        List<CompanyAiWorkflowExecLog> batch = new ArrayList<>(workflowExecs.size());
+        Date date = new Date();
+
+        for (CompanyAiWorkflowExec w : workflowExecs) {
+            CompanyAiWorkflowExecLog execLog = new CompanyAiWorkflowExecLog();
+            execLog.setWorkflowInstanceId(w.getWorkflowInstanceId());
+            execLog.setNodeKey(w.getCurrentNodeKey());
+            execLog.setNodeName(w.getCurrentNodeName());
+            execLog.setNodeType(w.getCurrentNodeType());
+            execLog.setInputData(w.getVariables());
+            execLog.setStatus(w.getStatus());
+            execLog.setOutputData("null");
+            execLog.setStartTime(date);
+            execLog.setEndTime(date);
+            execLog.setIsGenerate(1);
+            batch.add(execLog);
+        }
+
+        try {
+            companyAiWorkflowExecLogMapper.batchInsert(batch);
+        } catch (Exception e) {
+            log.error("批量插入执行日志异常,size: {}", batch.size(), e);
+        } finally {
+            batch.clear();
+        }
+    }
+
+    private String generateInstanceId() {
+        return "wf_" + System.currentTimeMillis() + "_" +
+                UUID.randomUUID().toString().replace("-", "");
+    }
+}

+ 5 - 286
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java

@@ -23,7 +23,6 @@ import com.fs.company.domain.*;
 import com.fs.company.mapper.*;
 import com.fs.company.param.ExecutionContext;
 import com.fs.company.service.*;
-import com.fs.company.util.RandomNameGeneratorUtil;
 import com.fs.company.vo.*;
 import com.fs.company.vo.easycall.EasyCallCallPhoneVO;
 import com.fs.crm.domain.CrmCustomer;
@@ -33,14 +32,11 @@ import com.fs.crm.service.impl.CrmCustomerServiceImpl;
 import com.fs.enums.ExecutionStatusEnum;
 import com.fs.enums.NodeTypeEnum;
 import com.fs.enums.TaskTypeEnum;
-import com.fs.his.config.CidPhoneConfig;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.impl.QwExternalContactServiceImpl;
-import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.mapper.SysDictDataMapper;
-import com.fs.system.service.ISysConfigService;
 import com.fs.system.service.impl.SysDictTypeServiceImpl;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
@@ -49,11 +45,8 @@ import lombok.Synchronized;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
-
-import java.time.LocalDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
-
 import static com.fs.company.service.impl.call.node.AiCallTaskNode.EASYCALL_WORKFLOW_REDIS_KEY;
 
 
@@ -75,7 +68,6 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
     private final CompanyVoiceRoboticMapper companyVoiceRoboticMapper;
     private final AiCallService aiCallService;
     private final CrmCustomerServiceImpl crmCustomerService;
-    private final CompanyVoiceRoboticCalleesMapper companyVoiceRoboticCalleesMapper;
 
     private final CompanyVoiceRoboticCalleesServiceImpl companyVoiceRoboticCalleesService;
     private final ICompanyWxAccountService companyWxAccountService;
@@ -90,8 +82,6 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
 
     private final CompanyWxClientServiceImpl companyWxClientServiceImpl;
 
-    private final ISysConfigService configService;
-
     private final SysDictDataMapper sysDictDataMapper;
 
     private final SmsServiceImpl smsService;
@@ -111,23 +101,17 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
     private final CompanyWorkflowEngine companyWorkflowEngine;
     private final CompanyAiWorkflowExecMapper companyAiWorkflowExecMapper;
     private final CompanyAiWorkflowExecLogMapper companyAiWorkflowExecLogMapper;
+
+    private final CompanyVoiceRoboticCalleesMapper companyVoiceRoboticCalleesMapper;
     private final RedisCache redisCache2;
     private final CompanyAiWorkflowServerMapper companyAiWorkflowServerMapper;
     private final QwUserMapper qwUserMapper;
     private final EasyCallMapper easyCallMapper;
-
-    private final SysConfigMapper sysConfigMapper;
-    private final CompanyConfigMapper companyConfigMapper;
-
-    private final CompanyWorkflowMapper companyWorkflowMapper;
-
-    private final CompanyWorkflowEdgeMapper edgeMapper;
-
     private final QwExternalContactServiceImpl qwExternalContactService;
 
     private final SysDictTypeServiceImpl sysDictTypeService;
 
-    final int BATCH_SIZE = 1500;
+    private final IAsyncCalleeProcessorService asyncCalleeProcessorService;
 
     /** EasyCall intent 意向度重试队列 Redis key 前缀,value 为已重试次数 */
     private static final String EASYCALL_INTENT_RETRY_KEY = "easycall:intent:retry:";
@@ -1416,42 +1400,12 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
     }
     public void buildTaskBussiness(CompanyVoiceRobotic robotic) {
         List<CompanyVoiceRoboticCallees> calleesList = companyVoiceRoboticCalleesMapper.selectByRoboticId(robotic.getId());
-        //获取电话生成配置
-        CidPhoneConfig phoneConfig = null;
-        //获取销售公司手机配置
-        CompanyConfig companyConfig = companyConfigMapper.selectCompanyConfigByKey(robotic.getCompanyId(), "cid.config");
-        //如果配置为空就获取总后台配置
-        if(companyConfig == null){
-            String json = configService.selectConfigByKey("his.store");
-            if(StringUtils.isNotEmpty(json)){
-                phoneConfig = JSONObject.parseObject(json,CidPhoneConfig.class);
-            }
-        } else {
-            phoneConfig = JSONObject.parseObject(companyConfig.getConfigValue(), CidPhoneConfig.class);
-        }
-
-        //获取工作流主表信息
-        CompanyWorkflow workflow = companyWorkflowMapper.selectCompanyWorkflowById(robotic.getCompanyAiWorkflowId());
-
-        //获取相关节点信息
-        CompanyNodeInfoVo nodeInfoVo = edgeMapper.slectNodeInfoByWorkflowId(workflow.getWorkflowId(), workflow.getStartNodeKey());
-
         List<CompanyWxClient> companyWxClients = companyWxClientMapper.selectListByRoboticId(robotic.getId());
         Map<String, CompanyWxClient> clientMp = companyWxClients.stream().collect(Collectors.toMap(e -> e.getRoboticId() + "-" + e.getCustomerId(), e -> e));
         List<CompanyVoiceRoboticBusiness> addList = new ArrayList<>();
-        List<CompanyVoiceRoboticCallees> batchToInsert = new LinkedList<>();
+        //异步生成列表日志
+        asyncCalleeProcessorService.generateCustomerInfo(calleesList,clientMp,robotic);
         for (CompanyVoiceRoboticCallees callees : calleesList) {
-            //根据配置随机生成电话号
-            if (phoneConfig != null && phoneConfig.getEnablePhoneConfig()) {//配置不为空并且开启了
-                List<CompanyVoiceRoboticCallees> roboticCallees = generatePhoneNumber(phoneConfig, callees);
-                if (!roboticCallees.isEmpty()) {
-                    batchToInsert.addAll(roboticCallees);
-                    if (batchToInsert.size() >= BATCH_SIZE) {
-                        flushGeneratedCalleesBatch(new LinkedList<>(batchToInsert), clientMp, robotic, workflow, nodeInfoVo);
-                        batchToInsert.clear();
-                    }
-                }
-            }
             CompanyVoiceRoboticBusiness companyVoiceRoboticBusiness = new CompanyVoiceRoboticBusiness();
             companyVoiceRoboticBusiness.setRoboticId(robotic.getId());
             companyVoiceRoboticBusiness.setCalleeId(callees.getId());
@@ -1462,13 +1416,6 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             companyVoiceRoboticBusiness.setCreateTime(new Date());
             addList.add(companyVoiceRoboticBusiness);
         }
-
-        //处理剩余数据
-        if (!batchToInsert.isEmpty()) {
-            flushGeneratedCalleesBatch(new LinkedList<>(batchToInsert), clientMp, robotic, workflow, nodeInfoVo);
-            batchToInsert.clear();
-        }
-
         companyVoiceRoboticBusinessMapper.insertBatch(addList);
 
     }
@@ -1678,232 +1625,4 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             return vo;
         }).collect(Collectors.toList());
     }
-
-    /**
-     * 根据配置生成手机号
-     *
-     * @param config  配置对象(包含是否启用、生成数量、起始位置、结束位置)
-     * @param callees 任务外呼电话对象
-     * @return 生成的手机号列表
-     */
-    public static List<CompanyVoiceRoboticCallees> generatePhoneNumber(CidPhoneConfig config, CompanyVoiceRoboticCallees callees) {
-        String basePhone = callees.getPhone();
-        if (basePhone == null || basePhone.length() != 11) {
-            return null;
-        }
-
-        int start = config.getStartIndex();
-        int end = config.getEndIndex();
-        int count = config.getGenerateCount();
-
-        // 校验索引范围
-        if (start < 1 || start > 11 || end < 1 || end > 11 || start > end) {
-            return null;
-        }
-
-        int startIdx = start;
-        int endIdx = end - 1;
-
-        char[] baseChars = basePhone.toCharArray();
-        //预加载随机
-        List<String> nameList = RandomNameGeneratorUtil.generateBatch(count);
-        Random random = new Random();
-        List<CompanyVoiceRoboticCallees> result = new ArrayList<>(count);
-        for (int i = 0; i < count; i++) {
-            CompanyVoiceRoboticCallees roboticCallees = new CompanyVoiceRoboticCallees();
-            // 克隆基础数组
-            char[] newChars = baseChars.clone();
-            for (int j = startIdx; j <= endIdx; j++) {
-                // 生成0-9的随机数字字符
-                newChars[j] = (char) ('0' + random.nextInt(10));
-            }
-            String phone = String.valueOf(newChars);
-            roboticCallees.setPhone(phone);//电话
-            roboticCallees.setRoboticId(callees.getRoboticId());//任务ID
-            roboticCallees.setTaskFlow(callees.getTaskFlow());//任务流程
-            roboticCallees.setRunTaskFlow(callees.getRunTaskFlow());//已执行流程
-            roboticCallees.setIsWeCom(callees.getIsWeCom());
-            roboticCallees.setUserId(0L);//用户ID
-            roboticCallees.setUserName(nameList.get(i));//姓名
-            roboticCallees.setIsGenerate(1);
-            result.add(roboticCallees);
-        }
-        return result;
-    }
-
-    /**
-     * 生成外呼任务表
-     *
-     **/
-    public void generateVoiceRoboticBusiness(List<CompanyVoiceRoboticCallees> calleesList,
-                                             Map<String, CompanyWxClient> clientMp
-            , CompanyVoiceRobotic robotic
-            , CompanyWorkflow workflow
-            , CompanyNodeInfoVo nodeInfoVo) {
-        if (!calleesList.isEmpty()) {
-            List<CompanyVoiceRoboticBusiness> addList = new LinkedList<>();
-            Date date = new Date();
-            for (CompanyVoiceRoboticCallees callees : calleesList) {
-                CompanyVoiceRoboticBusiness companyVoiceRoboticBusiness = new CompanyVoiceRoboticBusiness();
-                companyVoiceRoboticBusiness.setRoboticId(callees.getRoboticId());
-                companyVoiceRoboticBusiness.setCalleeId(callees.getId());
-                companyVoiceRoboticBusiness.setWxClientId(clientMp.getOrDefault(callees.getRoboticId() + "-" + callees.getUserId(), new CompanyWxClient()).getId());
-                companyVoiceRoboticBusiness.setAddWxDone(0);
-                companyVoiceRoboticBusiness.setCallPhoneDone(0);
-                companyVoiceRoboticBusiness.setSendMsgDone(0);
-                companyVoiceRoboticBusiness.setCreateTime(date);
-                companyVoiceRoboticBusiness.setIsGenerate(1);
-                addList.add(companyVoiceRoboticBusiness);
-            }
-
-            //批量插入ai外呼业务对象
-            if (!addList.isEmpty()) {
-                flushGeneratedBusinessBatch(addList, robotic, workflow, nodeInfoVo);
-            }
-        }
-    }
-
-    /**
-     * 刷新并批量插入生成的被叫数据,并生成对应的业务对象
-     *
-     * @param batchToInsert 待插入的被叫列表
-     * @param clientMap     微信客户映射,用于关联客户ID
-     */
-    @Async("calleeTaskExecutor")
-    public void flushGeneratedCalleesBatch(List<CompanyVoiceRoboticCallees> batchToInsert,
-                                           Map<String, CompanyWxClient> clientMap,
-                                           CompanyVoiceRobotic robotic,
-                                           CompanyWorkflow workflow,
-                                           CompanyNodeInfoVo nodeInfoVo) {
-        if (batchToInsert.isEmpty()) {
-            return;
-        }
-        int rows = companyVoiceRoboticCalleesMapper.batchInsertGenerateInfo(batchToInsert);
-        if (rows > 0) {
-            generateVoiceRoboticBusiness(batchToInsert, clientMap, robotic, workflow, nodeInfoVo);
-        }
-        batchToInsert.clear();
-    }
-
-    /**
-     * 批量插入业务对象,并创建工作流执行记录
-     */
-    private void flushGeneratedBusinessBatch(List<CompanyVoiceRoboticBusiness> batchToInsert,CompanyVoiceRobotic robotic,CompanyWorkflow workflow,CompanyNodeInfoVo nodeInfoVo){
-        int rows = companyVoiceRoboticBusinessMapper.insertBatchGenerateInfo(batchToInsert);
-        if(rows > 0){
-            LocalDateTime now = LocalDateTime.now();
-            List<CompanyAiWorkflowExec> startExecList = new LinkedList<>();//第一节点
-            List<CompanyAiWorkflowExec> targetExecList = new LinkedList<>();//第二节点
-            //插入执行流程代码
-            for (CompanyVoiceRoboticBusiness business : batchToInsert){
-                // 第一个节点(开始节点)
-                CompanyAiWorkflowExec startExec = new CompanyAiWorkflowExec();
-                startExec.setWorkflowInstanceId(generateInstanceId());
-                startExec.setWorkflowId(robotic.getCompanyAiWorkflowId());
-                startExec.setCurrentNodeKey(workflow.getStartNodeKey());
-                startExec.setCurrentNodeType(NodeTypeEnum.START.getValue());
-                startExec.setCurrentNodeName(NodeTypeEnum.START.getDescription());
-                startExec.setStatus(ExecutionStatusEnum.SUCCESS.getValue()); // 开始节点执行成功
-                startExec.setStartTime(now);
-
-                JSONObject variables = new JSONObject();
-                variables.put("roboticId", robotic.getId());
-                variables.put("businessId", business.getId());
-                variables.put("cidGroupNo", robotic.getCidGroupNo());
-                variables.put("runtimeRangeStart", robotic.getRuntimeRangeStart());
-                variables.put("runtimeRangeEnd", robotic.getRuntimeRangeEnd());
-                startExec.setVariables(variables.toJSONString());
-                startExec.setBusinessKey(business.getId().toString());
-                startExec.setStartNodeKey(workflow.getStartNodeKey());
-                startExec.setEndNodeKey(workflow.getEndNodeKey());
-                startExec.setCidGroupNo(robotic.getCidGroupNo());
-                startExec.setRuntimeRangeStart(robotic.getRuntimeRangeStart());
-                startExec.setRuntimeRangeEnd(robotic.getRuntimeRangeEnd());
-                startExec.setIsGenerate(1);
-                startExecList.add(startExec);
-
-                CompanyAiWorkflowExec targetExec = new CompanyAiWorkflowExec();
-                // 复制公共字段
-                targetExec.setWorkflowInstanceId(startExec.getWorkflowInstanceId());
-                targetExec.setWorkflowId(startExec.getWorkflowId());
-                targetExec.setCurrentNodeKey(nodeInfoVo.getTargetNodeKey());
-                targetExec.setCurrentNodeName(nodeInfoVo.getNodeName());
-                targetExec.setCurrentNodeType(NodeTypeEnum.fromCode(nodeInfoVo.getNodeType()).getValue());
-                targetExec.setStatus(ExecutionStatusEnum.INTERRUPT.getValue());
-                targetExec.setStartTime(now);
-                targetExec.setVariables(variables.toJSONString());
-                targetExec.setBusinessKey(startExec.getBusinessKey());
-                targetExec.setStartNodeKey(startExec.getStartNodeKey());
-                targetExec.setEndNodeKey(startExec.getEndNodeKey());
-                targetExec.setCidGroupNo(startExec.getCidGroupNo());
-                targetExec.setRuntimeRangeStart(startExec.getRuntimeRangeStart());
-                targetExec.setRuntimeRangeEnd(startExec.getRuntimeRangeEnd());
-                targetExec.setIsGenerate(1);
-                targetExecList.add(targetExec);
-
-                if(targetExecList.size() >= BATCH_SIZE){
-                    generateVoiceRoboticCurrentExecLogs(targetExecList, startExecList);
-                }
-            }
-
-            if(!targetExecList.isEmpty()){
-                generateVoiceRoboticCurrentExecLogs(targetExecList, startExecList);
-            }
-        }
-        batchToInsert.clear();
-    }
-
-    public void generateVoiceRoboticCurrentExecLogs(List<CompanyAiWorkflowExec> workflowExecs, List<CompanyAiWorkflowExec> startExecList){
-        if (workflowExecs.isEmpty()) {
-            return;
-        }
-
-        int rows = companyAiWorkflowExecMapper.insertBatchInfo(workflowExecs);
-        if(rows > 0){
-            workflowExecLogBatchInsert(startExecList);//第一节点
-            workflowExecs.stream().forEach(a->a.setStatus(ExecutionStatusEnum.FAILURE.getValue()));
-            workflowExecLogBatchInsert(workflowExecs);//第二节点
-        }
-        workflowExecs.clear();
-        startExecList.clear();
-    }
-
-    /**
-     * AI外呼流程执行记录对象批量插入
-     * **/
-    private void workflowExecLogBatchInsert(List<CompanyAiWorkflowExec> workflowExecs){
-        //插入日志记录表
-        Date date =new Date();
-        List<CompanyAiWorkflowExecLog> batch = new LinkedList<>();
-        workflowExecs.forEach(w->{
-            CompanyAiWorkflowExecLog execLog = new CompanyAiWorkflowExecLog();
-            execLog.setWorkflowInstanceId(w.getWorkflowInstanceId());
-            execLog.setNodeKey(w.getCurrentNodeKey());
-            execLog.setNodeName(w.getCurrentNodeName());
-            execLog.setNodeType(w.getCurrentNodeType());
-            execLog.setInputData(w.getVariables());
-            execLog.setStatus(w.getStatus());
-            execLog.setOutputData("null");
-            execLog.setStartTime(date);
-            execLog.setEndTime(date);
-            execLog.setIsGenerate(1);
-            batch.add(execLog);
-        });
-        if(!batch.isEmpty()){
-            companyAiWorkflowExecLogMapper.batchInsert(batch);
-            batch.clear();
-        }
-        workflowExecs.clear();
-    }
-
-
-    /**
-     * 生成工作流实例ID
-     */
-    private String generateInstanceId() {
-        return "wf_" + System.currentTimeMillis() + "_" +
-                UUID.randomUUID().toString().replace("-", "");
-    }
-
-
 }

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

@@ -368,7 +368,7 @@ public class CompanyWorkflowEngineImpl implements CompanyWorkflowEngine {
         context.setVariables(inputVariables != null ? inputVariables : new HashMap<>());
         context.setStartTime(LocalDateTime.now());
         context.setCurrentTime(LocalDateTime.now());
-        context.setBusinessId(inputVariables.containsKey("businessId") ? inputVariables.get("businessId").toString() : null);
+        context.setBusinessId(inputVariables.containsKey("businessId") ? Long.parseLong(inputVariables.get("businessId").toString()) : null);
 
         return context;
     }

+ 7 - 0
fs-service/src/main/resources/mapper/aicall/CompanyBindAiModelMapper.xml

@@ -77,4 +77,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             (#{companyId}, #{modelId}, now(), now())
         </foreach>
     </insert>
+
+    <delete id="deleteBindAiModelByCompanyIdAndModelIds">
+        DELETE  FROM company_bind_ai_model WHERE company_id = #{companyId} AND model_id IN
+        <foreach item="modelId" collection="modelIds" open="(" separator="," close=")">
+            #{modelId}
+        </foreach>
+    </delete>
 </mapper>