Browse Source

CID手机号数据生成,分流CID执行数据

yjwang 1 ngày trước cách đây
mục cha
commit
78df8c2898
18 tập tin đã thay đổi với 569 bổ sung14 xóa
  1. 5 3
      fs-company/src/main/java/com/fs/company/controller/crm/CrmCustomerController.java
  2. 30 0
      fs-service/src/main/java/com/fs/company/config/AsyncConfig.java
  3. 2 1
      fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticBusiness.java
  4. 3 0
      fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallees.java
  5. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyAiWorkflowExecLogMapper.java
  6. 7 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyAiWorkflowExecMapper.java
  7. 6 1
      fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticBusinessMapper.java
  8. 7 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticCalleesMapper.java
  9. 7 2
      fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowEdgeMapper.java
  10. 274 7
      fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java
  11. 81 0
      fs-service/src/main/java/com/fs/company/util/RandomNameGeneratorUtil.java
  12. 12 0
      fs-service/src/main/java/com/fs/company/vo/CompanyNodeInfoVo.java
  13. 34 0
      fs-service/src/main/java/com/fs/his/config/CidPhoneConfig.java
  14. 29 0
      fs-service/src/main/resources/mapper/company/CompanyAiWorkflowExecLogMapper.xml
  15. 29 0
      fs-service/src/main/resources/mapper/company/CompanyAiWorkflowExecMapper.xml
  16. 19 0
      fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticBusinessMapper.xml
  17. 11 0
      fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticCalleesMapper.xml
  18. 11 0
      fs-service/src/main/resources/mapper/company/CompanyWorkflowEdgeMapper.xml

+ 5 - 3
fs-company/src/main/java/com/fs/company/controller/crm/CrmCustomerController.java

@@ -242,10 +242,12 @@ public class CrmCustomerController extends BaseController
     ){
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         CrmCustomer customer=crmCustomerService.selectCrmCustomerById(customerId);
-        customer.setMobile(customer.getMobile().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
         Boolean isReceive=false;
-        if(customer.getIsReceive()!=null&&customer.getIsReceive()==1&&customer.getReceiveUserId()!=null&&loginUser.getUser().getUserId().equals(customer.getReceiveUserId())){
-            isReceive=true;
+        if (customer != null){
+            customer.setMobile(customer.getMobile().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+            if(customer.getIsReceive()!=null&&customer.getIsReceive()==1&&customer.getReceiveUserId()!=null&&loginUser.getUser().getUserId().equals(customer.getReceiveUserId())){
+                isReceive=true;
+            }
         }
         return R.ok().put("customer",customer).put("isReceive",isReceive);
 

+ 30 - 0
fs-service/src/main/java/com/fs/company/config/AsyncConfig.java

@@ -0,0 +1,30 @@
+package com.fs.company.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+@Configuration
+@EnableAsync
+public class AsyncConfig {
+    @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.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        executor.setKeepAliveSeconds(60);
+        executor.setAllowCoreThreadTimeOut(false);
+        executor.setWaitForTasksToCompleteOnShutdown(true);
+        executor.setAwaitTerminationSeconds(60);
+
+        executor.initialize();
+        return executor;
+    }
+}

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

@@ -45,5 +45,6 @@ public class CompanyVoiceRoboticBusiness extends BaseEntity{
     @Excel(name = "发送短信动作完成,每次加1 初始0")
     private Integer sendMsgDone;
 
-
+    //是否生成数据(0否,1是)
+    private Integer isGenerate;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallees.java

@@ -64,4 +64,7 @@ public class CompanyVoiceRoboticCallees{
     private String idToString;
 
     private Integer isWeCom;
+
+    //是否生成数据(0否,1是)
+    private Integer isGenerate;
 }

+ 2 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyAiWorkflowExecLogMapper.java

@@ -66,4 +66,6 @@ public interface CompanyAiWorkflowExecLogMapper extends BaseMapper<CompanyAiWork
      * @return 执行日志列表
      */
     List<CompanyAiWorkflowExecLog> selectByWorkflowInstanceId(@Param("workflowInstanceId") String workflowInstanceId);
+
+    void batchInsert(@Param("list") List<CompanyAiWorkflowExecLog> logList);
 }

+ 7 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyAiWorkflowExecMapper.java

@@ -97,4 +97,11 @@ public interface CompanyAiWorkflowExecMapper extends BaseMapper<CompanyAiWorkflo
     List<CompanyAiWorkflowExec> selectExecListWithTimeAvailableByStatusAndGroupNo(@Param("status") Integer status, @Param("groupNo") Integer groupNo);
 
     CompanyAiWorkflowExec selectExecWithTimeAvailableByInstanceId(@Param("workflowInstanceId") String workflowInstanceId);
+
+    /**
+     * 批量新增数据
+     * @param list
+     * @return int
+     * **/
+    int insertBatchInfo(@Param("list") List<CompanyAiWorkflowExec> list);
 }

+ 6 - 1
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticBusinessMapper.java

@@ -87,5 +87,10 @@ public interface CompanyVoiceRoboticBusinessMapper extends BaseMapper<CompanyVoi
 
     Integer selectUnfinishedTaskCountByRoboticId(@Param("roboticId") Long roboticId, @Param("endNodeKey") String endNodeKey);
 
-
+    /**
+     * 批量插入生成业务数据
+     * @param businessList 业务数据列表
+     * @return 影响的行数
+     */
+    int insertBatchGenerateInfo(@Param("list") List<CompanyVoiceRoboticBusiness> businessList);
 }

+ 7 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticCalleesMapper.java

@@ -86,4 +86,11 @@ public interface CompanyVoiceRoboticCalleesMapper extends BaseMapper<CompanyVoic
 
     List<CompanyVoiceRoboticCallees> selectExcludeList(@Param("list")List<CompanyWxClient> list,@Param("isWeCom") Integer isWeCom);
     List<Long> getNotFinishAddWxRobotic(@Param("roboticIds") Set<Long> roboticIds);
+
+    /**
+     * 批量插入生成数据
+     * @param list 插入数据
+     * @return int
+     * **/
+    int batchInsertGenerateInfo(@Param("list") List<CompanyVoiceRoboticCallees> list);
 }

+ 7 - 2
fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowEdgeMapper.java

@@ -2,10 +2,9 @@ package com.fs.company.mapper;
 
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.fs.company.domain.CompanyVoiceRobotic;
 import com.fs.company.domain.CompanyWorkflowEdge;
+import com.fs.company.vo.CompanyNodeInfoVo;
 import org.apache.ibatis.annotations.Param;
-import org.mapstruct.Mapper;
 
 import java.util.List;
 
@@ -32,4 +31,10 @@ public interface CompanyWorkflowEdgeMapper  extends BaseMapper<CompanyWorkflowEd
     Integer deleteCompanyWorkflowEdgeByWorkflowId(Long workflowId);
 
     List<CompanyWorkflowEdge> selectListByWorkflowIdAndNodeKey(@Param("workflowId")Long workflowId, @Param("nodeKey")String nodeKey);
+
+    /**
+     *获取节点信息
+     *
+     * */
+    CompanyNodeInfoVo slectNodeInfoByWorkflowId(@Param("workflowId")Long workflowId, @Param("nodeKey")String nodeKey);
 }

+ 274 - 7
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java

@@ -22,28 +22,31 @@ 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;
 import com.fs.crm.mapper.CrmCustomerMapper;
 import com.fs.crm.param.SmsSendBatchParam;
 import com.fs.crm.service.impl.CrmCustomerServiceImpl;
+import com.fs.enums.ExecutionStatusEnum;
+import com.fs.enums.NodeTypeEnum;
+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 lombok.RequiredArgsConstructor;
 import lombok.Synchronized;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDateTime;
 import java.util.*;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
 import static com.fs.company.service.impl.call.node.AiCallTaskNode.EASYCALL_WORKFLOW_REDIS_KEY;
@@ -107,8 +110,18 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
     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;
 
+    final int BATCH_SIZE = 1500;
+
     /**
      * 查询机器人外呼任务
      *
@@ -1047,7 +1060,7 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
 
         // 查询业务列表
         List<CompanyVoiceRoboticBusiness> roboticBusinesseList = companyVoiceRoboticBusinessMapper
-                .selectList(new QueryWrapper<CompanyVoiceRoboticBusiness>().eq("robotic_id", id));
+                .selectList(new QueryWrapper<CompanyVoiceRoboticBusiness>().eq("robotic_id", id).eq("is_generate",0));
 
         if (roboticBusinesseList.isEmpty()) {
             log.warn("任务没有业务数据: {}", id);
@@ -1175,12 +1188,43 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
         return resArr;
     }
 
-    public void buildTaskBussiness(CompanyVoiceRobotic robotic){
-        List<CompanyVoiceRoboticCallees> calleesList = companyVoiceRoboticCalleesMapper.selectByRoboticId(robotic.getId());
+    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){
+            SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("cid.config");
+            if(sysConfig != null){
+                phoneConfig = JSONObject.parseObject(sysConfig.getConfigValue(),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<>();
         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());
@@ -1191,7 +1235,15 @@ 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);
+
     }
 
     private void taskJoin2Workflow(){
@@ -1315,4 +1367,219 @@ 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> workflowExecs = 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);
+                workflowExecs.add(startExec);
+                CompanyAiWorkflowExec targetExec = new CompanyAiWorkflowExec();
+
+                // 复制公共字段
+                targetExec.setWorkflowInstanceId(generateInstanceId());
+                targetExec.setWorkflowId(startExec.getWorkflowId());
+                targetExec.setCurrentNodeKey(nodeInfoVo.getTargetNodeKey());
+                targetExec.setCurrentNodeName(nodeInfoVo.getNodeName());
+                targetExec.setCurrentNodeType(NodeTypeEnum.fromCode(nodeInfoVo.getNodeType()).getValue());
+                targetExec.setStatus(ExecutionStatusEnum.FAILURE.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);
+                workflowExecs.add(targetExec);
+
+                if(workflowExecs.size() >= BATCH_SIZE){
+                    generateVoiceRoboticCurrentExecLogs(new LinkedList<>(workflowExecs));
+                    workflowExecs.clear();
+                }
+            }
+
+            if(!workflowExecs.isEmpty()){
+                generateVoiceRoboticCurrentExecLogs(new LinkedList<>(workflowExecs));
+                workflowExecs.clear();
+            }
+        }
+        batchToInsert.clear();
+    }
+
+    public void generateVoiceRoboticCurrentExecLogs(List<CompanyAiWorkflowExec> workflowExecs){
+        if (workflowExecs.isEmpty()) {
+            return;
+        }
+
+        int rows = companyAiWorkflowExecMapper.insertBatchInfo(workflowExecs);
+        if(rows > 0){
+
+            //插入日志记录表
+            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("-", "");
+    }
+
+
 }

+ 81 - 0
fs-service/src/main/java/com/fs/company/util/RandomNameGeneratorUtil.java

@@ -0,0 +1,81 @@
+package com.fs.company.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+//随机生成
+public class RandomNameGeneratorUtil {
+    // 常见姓氏(单姓 + 少量复姓)
+    private static final String[] SURNAMES = {
+            "赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈",
+            "褚", "卫", "蒋", "沈", "韩", "杨", "朱", "秦", "尤", "许",
+            "何", "吕", "施", "张", "孔", "曹", "严", "华", "金", "魏",
+            "陶", "姜", "戚", "谢", "邹", "喻", "柏", "水", "窦", "章",
+            "云", "苏", "潘", "葛", "奚", "范", "彭", "郎", "鲁", "韦",
+            "昌", "马", "苗", "凤", "花", "方", "俞", "任", "袁", "柳",
+            "鲍", "史", "唐", "费", "廉", "岑", "薛", "雷", "贺", "倪",
+            "汤", "殷", "罗", "毕", "郝", "邬", "安", "常", "乐", "于",
+            "时", "傅", "皮", "卞", "齐", "康", "伍", "余", "元", "卜",
+            "顾", "孟", "平", "黄", "和", "穆", "萧", "尹", "姚", "邵",
+            "湛", "汪", "祁", "毛", "禹", "狄", "米", "贝", "明", "臧",
+            "欧阳", "慕容", "上官", "司马", "夏侯", "诸葛", "东方", "皇甫", "尉迟", "公孙"
+    };
+
+    // 常用名字用字
+    private static final String[] GIVEN_NAMES = {
+            "伟", "强", "军", "勇", "杰", "涛", "斌", "鹏", "宇", "浩",
+            "鑫", "磊", "帅", "超", "俊", "帆", "波", "辉", "刚", "健",
+            "明", "亮", "峰", "松", "林", "森", "荣", "华", "富", "贵",
+            "芳", "娜", "敏", "静", "秀", "娟", "英", "华", "慧", "巧",
+            "美", "颖", "玲", "燕", "红", "丽", "艳", "倩", "婷", "娇",
+            "淑", "贞", "珠", "琴", "雪", "云", "霞", "露", "雯", "姗",
+            "欣", "怡", "晨", "曦", "阳", "光", "天", "然", "乐", "悦",
+            "思", "念", "文", "武", "双", "全", "子", "涵", "泽", "洋",
+            "博", "睿", "智", "远", "达", "通", "道", "德", "仁", "义"
+    };
+
+    // 名字长度范围
+    private static final int MIN_NAME_LENGTH = 2;
+    private static final int MAX_NAME_LENGTH = 3;
+
+    /**
+     * 生成随机姓名
+     * @return 随机姓名
+     */
+    public static String generateOne() {
+        ThreadLocalRandom rand = ThreadLocalRandom.current();
+        // 随机选择姓氏
+        String surname = SURNAMES[rand.nextInt(SURNAMES.length)];
+
+        // 随机决定名字长度
+        int nameLength = rand.nextInt(MIN_NAME_LENGTH, MAX_NAME_LENGTH + 1);
+
+        StringBuilder sb = new StringBuilder(surname);
+        for (int i = 1; i < nameLength; i++) {
+            sb.append(GIVEN_NAMES[rand.nextInt(GIVEN_NAMES.length)]);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 批量生成随机姓名
+     * @param count 生成数量
+     * @return 姓名列表
+     */
+    public static List<String> generateBatch(int count) {
+        List<String> result = new ArrayList<>(count);
+        for (int i = 0; i < count; i++) {
+            result.add(generateOne());
+        }
+        return result;
+    }
+
+    public static void main(String[] args) {
+        long start = System.currentTimeMillis();
+        List<String> names = generateBatch(100_000);
+        long end = System.currentTimeMillis();
+        System.out.println("生成10万个姓名耗时:" + (end - start) + " ms");
+        names.stream().limit(10).forEach(System.out::println);
+    }
+}

+ 12 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyNodeInfoVo.java

@@ -0,0 +1,12 @@
+package com.fs.company.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class CompanyNodeInfoVo implements Serializable {
+    private String targetNodeKey;
+    private String nodeName;
+    private String nodeType;
+}

+ 34 - 0
fs-service/src/main/java/com/fs/his/config/CidPhoneConfig.java

@@ -0,0 +1,34 @@
+package com.fs.his.config;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * cId手机号生成配置类
+ * **/
+@Data
+public class CidPhoneConfig implements Serializable {
+    /**
+     * 是否开启手机号配置
+     * true: 开启,显示生成条数输入框
+     * false: 关闭,隐藏生成条数输入框
+     */
+    private Boolean enablePhoneConfig;
+
+    /**
+     * 生成条数
+     * 当 enablePhoneConfig 为 true 时有效,表示需要生成的手机号数量
+     */
+    private Integer generateCount;
+
+    /**
+     * 开始位置(从第几位开始生成)
+     */
+    private Integer startIndex;
+
+    /**
+     * 结束位置(到第几位结束)
+     */
+    private Integer endIndex;
+}

+ 29 - 0
fs-service/src/main/resources/mapper/company/CompanyAiWorkflowExecLogMapper.xml

@@ -122,4 +122,33 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         WHERE workflow_instance_id = #{workflowInstanceId}
         ORDER BY start_time ASC
     </select>
+
+    <insert id="batchInsert" parameterType="java.util.List">
+        INSERT INTO company_ai_workflow_exec_log (
+        workflow_instance_id,
+        node_key,
+        node_name,
+        node_type,
+        input_data,
+        status,
+        output_data,
+        start_time,
+        end_time,
+        is_generate
+        ) VALUES
+        <foreach collection="list" item="item" separator=",">
+            (
+            #{item.workflowInstanceId},
+            #{item.nodeKey},
+            #{item.nodeName},
+            #{item.nodeType},
+            #{item.inputData, jdbcType=OTHER},
+            #{item.status},
+            #{item.outputData},
+            #{item.startTime},
+            #{item.endTime},
+            #{item.isGenerate}
+            )
+        </foreach>
+    </insert>
 </mapper>

+ 29 - 0
fs-service/src/main/resources/mapper/company/CompanyAiWorkflowExecMapper.xml

@@ -193,4 +193,33 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           and t1.cid_group_no = #{groupNo}
           and NOW() BETWEEN t1.runtime_range_start and t1.runtime_range_end
     </select>
+
+    <insert id="insertBatchInfo" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO company_ai_workflow_exec (
+        workflow_instance_id, workflow_id, current_node_key,
+        current_node_type, current_node_name, status,
+        start_time, variables, business_key,
+        start_node_key, end_node_key, cid_group_no,
+        runtime_range_start, runtime_range_end,is_generate
+        ) VALUES
+        <foreach collection="list" item="item" separator=",">
+            (
+            #{item.workflowInstanceId},
+            #{item.workflowId},
+            #{item.currentNodeKey},
+            #{item.currentNodeType},
+            #{item.currentNodeName},
+            #{item.status},
+            #{item.startTime},
+            #{item.variables},
+            #{item.businessKey},
+            #{item.startNodeKey},
+            #{item.endNodeKey},
+            #{item.cidGroupNo},
+            #{item.runtimeRangeStart},
+            #{item.runtimeRangeEnd},
+             #{item.isGenerate}
+            )
+        </foreach>
+    </insert>
 </mapper>

+ 19 - 0
fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticBusinessMapper.xml

@@ -149,4 +149,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 inner join company_ai_workflow_exec t2 on t1.id = t2.business_key
         where t1.robotic_id = #{roboticId} and t2.current_node_key != #{endNodeKey} and t2.status != 8
     </select>
+
+    <insert id="insertBatchGenerateInfo" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO company_voice_robotic_business
+        (robotic_id, callee_id, wx_client_id, add_wx_done, call_phone_done, send_msg_done, create_time, update_time, is_generate)
+        VALUES
+        <foreach collection="list" item="item" index="index" separator=",">
+            (
+            #{item.roboticId},
+            #{item.calleeId},
+            #{item.wxClientId},
+            #{item.addWxDone},
+            #{item.callPhoneDone},
+            #{item.sendMsgDone},
+            #{item.createTime},
+            #{item.updateTime},
+            #{item.isGenerate}
+            )
+        </foreach>
+    </insert>
 </mapper>

+ 11 - 0
fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticCalleesMapper.xml

@@ -201,4 +201,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </if>
 
     </select>
+
+    <insert id="batchInsertGenerateInfo" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO company_voice_robotic_callees
+        (phone, robotic_id, task_flow, run_task_flow, is_we_com, user_id, user_name, is_generate)
+        VALUES
+        <foreach item="item" collection="list" separator=",">
+            (#{item.phone}, #{item.roboticId}, #{item.taskFlow},
+            #{item.runTaskFlow}, #{item.isWeCom}, #{item.userId},
+            #{item.userName}, #{item.isGenerate})
+        </foreach>
+    </insert>
 </mapper>

+ 11 - 0
fs-service/src/main/resources/mapper/company/CompanyWorkflowEdgeMapper.xml

@@ -72,4 +72,15 @@
     <select id="selectListByWorkflowIdAndNodeKey" resultType="CompanyWorkflowEdge">
         select * from company_ai_workflow_edge where workflow_id = #{workflowId} and source_node_key = #{nodeKey}
     </select>
+
+    <select id="slectNodeInfoByWorkflowId" resultType="com.fs.company.vo.CompanyNodeInfoVo">
+        SELECT
+            e.target_node_key,
+            n.node_name,
+            n.node_type
+        FROM
+            company_ai_workflow_edge e
+                INNER JOIN company_ai_workflow_node n ON e.target_node_key = n.node_key
+        WHERE e.workflow_id = #{workflowId} and e.source_node_key = #{nodeKey} LIMIT 1
+    </select>
 </mapper>