Quellcode durchsuchen

侧边栏 优先按照 销售公司的设置/cid 新增的一个外呼任务 在外呼平台上 建了很多很多的bug迁移修复

三七 vor 4 Tagen
Ursprung
Commit
564b38ee49

+ 14 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyController.java

@@ -305,4 +305,18 @@ public class CompanyController extends BaseController
         List<OptionsVO> list = companyService.selectAllCompanyList(deptId);
         return getDataTable(list);
     }
+
+    //获取侧边栏图片地址
+    @GetMapping("/getSidebarCompanyImage/{companyId}")
+    public R getSidebarCompanyImage(@PathVariable Long companyId)
+    {
+        return companyService.getSidebarCompanyImage(companyId);
+    }
+
+    //获取侧边栏图片地址
+    @PostMapping("/saveSidebarCompanyImage")
+    public R saveSidebarCompanyImage(@RequestBody SidebarCompanyImageParam param)
+    {
+        return companyService.saveSidebarCompanyImage(param);
+    }
 }

+ 9 - 0
fs-service/src/main/java/com/fs/company/param/SidebarCompanyImageParam.java

@@ -0,0 +1,9 @@
+package com.fs.company.param;
+
+import lombok.Data;
+
+@Data
+public class SidebarCompanyImageParam {
+    private Integer companyId;
+    private String imageUrl;
+}

+ 4 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyService.java

@@ -10,6 +10,7 @@ import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.param.CompanyLiveShowParam;
 import com.fs.company.param.CompanyParam;
+import com.fs.company.param.SidebarCompanyImageParam;
 import com.fs.company.vo.CompanyCrmVO;
 import com.fs.company.vo.CompanyNameVO;
 import com.fs.company.vo.CompanyVO;
@@ -203,5 +204,8 @@ public interface ICompanyService
 
     R checkMchTransferStatusByBatchID(String batchId, Long companyId);
 
+    R getSidebarCompanyImage(Long companyId);
+    R saveSidebarCompanyImage(SidebarCompanyImageParam param);
+
 
 }

+ 18 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -20,6 +20,7 @@ import com.fs.company.domain.*;
 import com.fs.company.mapper.*;
 import com.fs.company.param.CompanyLiveShowParam;
 import com.fs.company.param.CompanyParam;
+import com.fs.company.param.SidebarCompanyImageParam;
 import com.fs.company.service.*;
 import com.fs.company.util.CompanyTuiMoneyCalc;
 import com.fs.company.vo.*;
@@ -2013,4 +2014,21 @@ public class CompanyServiceImpl implements ICompanyService
         }));
     }
 
+
+    @Override
+    public R getSidebarCompanyImage(Long companyId) {
+        String url = redisCache.getCacheObject("SidebarCompany:" + companyId).toString() != null
+                ? redisCache.getCacheObject("SidebarCompany:" + companyId)
+                : "";
+        return R.ok().put("imageUrl", url);
+    }
+
+    @Override
+    public R saveSidebarCompanyImage(SidebarCompanyImageParam param) {
+
+        redisCache.setCacheObject("SidebarCompany:"+param.getCompanyId(),param.getImageUrl());
+
+        return R.ok();
+    }
+
 }

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

@@ -1319,6 +1319,8 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
         final Long workflowId = robotic.getCompanyAiWorkflowId();
         final Long roboticId = robotic.getId();
 
+        companyWorkflowEngine.createSipTask(roboticId, workflowId);
+
         // 先初始化所有工作流实例
         List<ExecutionResult> initResults = new ArrayList<>();
         for (CompanyVoiceRoboticBusiness business : roboticBusinesseList) {

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

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.*;
@@ -29,6 +30,7 @@ import org.springframework.transaction.annotation.Transactional;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author MixLiu
@@ -78,6 +80,9 @@ public class CompanyWorkflowEngineImpl implements CompanyWorkflowEngine {
     @Autowired
     private CloudHostProper cloudHostProper;
 
+    @Autowired
+    private RedisCache redisCache;
+
     /**
      * 初始化工作流
      * 创建工作流实例并保存初始状态
@@ -102,8 +107,6 @@ public class CompanyWorkflowEngineImpl implements CompanyWorkflowEngine {
                     definition.getStartNodeKey(), context, definition);
 
             log.info("工作流初始化成功: {} -> {}", workflowInstanceId, workflowDefinitionId);
-            //为任务创建sip任务并存入表数据
-            createSipTask(Long.parseLong(inputVariables.get("roboticId").toString()),workflowDefinitionId);
             return ExecutionResult.success()
                     .nextNodeKey(definition.getStartNodeKey())
                     .workflowInstanceId(workflowInstanceId).build();
@@ -570,37 +573,59 @@ public class CompanyWorkflowEngineImpl implements CompanyWorkflowEngine {
      * @param workFlowId
      */
     public Long createSipTask(Long roboticId, Long workFlowId) {
-        try {
-            List<String> nodeTypes = Arrays.asList(NodeTypeEnum.AI_CALL_TASK.getCode());
-            CompanyVoiceRobotic robotic = companyVoiceRoboticMapper.selectCompanyVoiceRoboticById(roboticId);
-            List<CompanyWorkflowNode> companyWorkflowNodes = companyWorkflowNodeMapper.selectNodesByWorkflowIdAndTypes(workFlowId, nodeTypes);
-            //为所有外呼节点创建任务的对应sip外呼任务
-            for (CompanyWorkflowNode callNode : companyWorkflowNodes) {
+
+        List<String> nodeTypes = Arrays.asList(NodeTypeEnum.AI_CALL_TASK.getCode());
+        CompanyVoiceRobotic robotic = companyVoiceRoboticMapper.selectCompanyVoiceRoboticById(roboticId);
+        List<CompanyWorkflowNode> companyWorkflowNodes = companyWorkflowNodeMapper.selectNodesByWorkflowIdAndTypes(workFlowId, nodeTypes);
+        for (CompanyWorkflowNode callNode : companyWorkflowNodes) {
+            String lockKey = "sipTask:lock:" + roboticId + ":" + callNode.getNodeKey();
+            boolean locked = redisCache.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
+            if (!locked) {
+                log.info("createSipTask: 其他线程正在创建SIP任务,等待后重试 - roboticId: {}, nodeKey: {}", roboticId, callNode.getNodeKey());
+                for (int i = 0; i < 20; i++) {
+                    try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
+                    CompanySiptaskInfo existing = companySiptaskInfoMapper.selectSipTaskInfoByTaskIdAndNodeKey(roboticId, callNode.getNodeKey());
+                    if (existing != null && existing.getBatchId() != null) {
+                        return existing.getBatchId();
+                    }
+                }
+                log.warn("createSipTask: 等待超时,尝试直接创建 - roboticId: {}, nodeKey: {}", roboticId, callNode.getNodeKey());
+            }
+            try {
+                CompanySiptaskInfo existingSipTask = companySiptaskInfoMapper.selectSipTaskInfoByTaskIdAndNodeKey(roboticId, callNode.getNodeKey());
+                if (existingSipTask != null && existingSipTask.getBatchId() != null) {
+                    log.info("createSipTask: SIP任务已存在,跳过创建 - roboticId: {}, nodeKey: {}, batchId: {}", roboticId, callNode.getNodeKey(), existingSipTask.getBatchId());
+                    return existingSipTask.getBatchId();
+                }
                 String nodeConfig = callNode.getNodeConfig();
                 AiCallConfigVO callConfigVo = JSONObject.parseObject(nodeConfig, AiCallConfigVO.class);
                 EasyCallCreateTaskParam createParam = new EasyCallCreateTaskParam();
-                // 任务名称:使用任务名称_工作流id_节点key
                 createParam.setBatchName(cloudHostProper.getCompanyName()+"-"+robotic.getName() + "_" + workFlowId + "_" + callNode.getNodeKey());
                 if (null != callConfigVo.getMaxConcurrency()) {
                     createParam.setThreadNum(Long.valueOf(callConfigVo.getMaxConcurrency()));
                 } else {
                     createParam.setThreadNum(3L);
                 }
-                // AI 外呼模式
                 createParam.setTaskType(1);
-                // 外呼线路(网关)
                 createParam.setGatewayId(callConfigVo.getGatewayId());
-                // 大模型底座
                 createParam.setLlmAccountId(callConfigVo.getLlmAccountId());
-                // 音色编号
                 createParam.setVoiceCode(callConfigVo.getVoiceCode());
-                // 音色来源(如未配置默认留空,由 EasyCallCenter365 使用默认值)
                 createParam.setVoiceSource(callConfigVo.getVoiceSource());
-                // 技能组(转人工客服分组,可选)
                 createParam.setGroupId(callConfigVo.getBusiGroupId());
-                // 模型参数
                 createParam.setTtsModels(callConfigVo.getTtsModels());
 
+                if (callConfigVo.getExtensionList() != null && !callConfigVo.getExtensionList().isEmpty()) {
+                    createParam.setAiTransferType("extension");
+                    StringBuilder aiTransferExtNumber = new StringBuilder();
+                    callConfigVo.getExtensionList().forEach(extension -> {
+                        aiTransferExtNumber.append(extension.getExtensionNum()).append(" ");
+                    });
+                    if (null != aiTransferExtNumber && aiTransferExtNumber.length() > 0) {
+                        aiTransferExtNumber.deleteCharAt(aiTransferExtNumber.length() - 1);
+                    }
+                    createParam.setAiTransferExtNumber(aiTransferExtNumber.toString());
+                }
+
                 EasyCallTaskVO task = easyCallService.createTask(createParam, null);
                 if (task == null || task.getBatchId() == null) {
                     log.error("createSipTask: 创建 EasyCall 任务失败 - workflowInstanceId: {}", workFlowId);
@@ -614,11 +639,64 @@ public class CompanyWorkflowEngineImpl implements CompanyWorkflowEngine {
                 sipTaskInfo.setTaskJson(JSONObject.toJSONString(task));
                 companySiptaskInfoMapper.insertCompanySiptaskInfo(sipTaskInfo);
                 return task.getBatchId();
+            } catch (Exception ex) {
+                log.error("创建SIP任务失败:{}", ex);
+                return null;
+            } finally {
+                redisCache.deleteObject(lockKey);
             }
-        } catch (Exception ex) {
-            log.error("创建SIP任务失败:{}", ex);
         }
         return null;
+
+//        try {
+//            List<String> nodeTypes = Arrays.asList(NodeTypeEnum.AI_CALL_TASK.getCode());
+//            CompanyVoiceRobotic robotic = companyVoiceRoboticMapper.selectCompanyVoiceRoboticById(roboticId);
+//            List<CompanyWorkflowNode> companyWorkflowNodes = companyWorkflowNodeMapper.selectNodesByWorkflowIdAndTypes(workFlowId, nodeTypes);
+//            //为所有外呼节点创建任务的对应sip外呼任务
+//            for (CompanyWorkflowNode callNode : companyWorkflowNodes) {
+//                String nodeConfig = callNode.getNodeConfig();
+//                AiCallConfigVO callConfigVo = JSONObject.parseObject(nodeConfig, AiCallConfigVO.class);
+//                EasyCallCreateTaskParam createParam = new EasyCallCreateTaskParam();
+//                // 任务名称:使用任务名称_工作流id_节点key
+//                createParam.setBatchName(cloudHostProper.getCompanyName()+"-"+robotic.getName() + "_" + workFlowId + "_" + callNode.getNodeKey());
+//                if (null != callConfigVo.getMaxConcurrency()) {
+//                    createParam.setThreadNum(Long.valueOf(callConfigVo.getMaxConcurrency()));
+//                } else {
+//                    createParam.setThreadNum(3L);
+//                }
+//                // AI 外呼模式
+//                createParam.setTaskType(1);
+//                // 外呼线路(网关)
+//                createParam.setGatewayId(callConfigVo.getGatewayId());
+//                // 大模型底座
+//                createParam.setLlmAccountId(callConfigVo.getLlmAccountId());
+//                // 音色编号
+//                createParam.setVoiceCode(callConfigVo.getVoiceCode());
+//                // 音色来源(如未配置默认留空,由 EasyCallCenter365 使用默认值)
+//                createParam.setVoiceSource(callConfigVo.getVoiceSource());
+//                // 技能组(转人工客服分组,可选)
+//                createParam.setGroupId(callConfigVo.getBusiGroupId());
+//                // 模型参数
+//                createParam.setTtsModels(callConfigVo.getTtsModels());
+//
+//                EasyCallTaskVO task = easyCallService.createTask(createParam, null);
+//                if (task == null || task.getBatchId() == null) {
+//                    log.error("createSipTask: 创建 EasyCall 任务失败 - workflowInstanceId: {}", workFlowId);
+//                    throw new RuntimeException("EasyCallCenter365 创建任务失败");
+//                }
+//                CompanySiptaskInfo sipTaskInfo = new CompanySiptaskInfo();
+//                sipTaskInfo.setTaskId(roboticId);
+//                sipTaskInfo.setWorkflowId(workFlowId);
+//                sipTaskInfo.setNodeKey(callNode.getNodeKey());
+//                sipTaskInfo.setBatchId(task.getBatchId());
+//                sipTaskInfo.setTaskJson(JSONObject.toJSONString(task));
+//                companySiptaskInfoMapper.insertCompanySiptaskInfo(sipTaskInfo);
+//                return task.getBatchId();
+//            }
+//        } catch (Exception ex) {
+//            log.error("创建SIP任务失败:{}", ex);
+//        }
+//        return null;
     }
 
     /**

+ 8 - 0
fs-service/src/main/java/com/fs/company/vo/AiCallConfigVO.java

@@ -1,7 +1,10 @@
 package com.fs.company.vo;
 
+import com.fs.company.vo.easycall.ExtensionVO;
 import lombok.Data;
 
+import java.util.List;
+
 /**
  * @author MixLiu
  * @date 2026/2/3 10:13
@@ -69,4 +72,9 @@ public class AiCallConfigVO {
 
     /** 最大并发数 */
     private Integer maxConcurrency;
+
+    /**
+     * 分机选择列表
+     */
+    private List<ExtensionVO> extensionList;
 }

+ 6 - 0
fs-service/src/main/java/com/fs/company/vo/easycall/EasyCallCreateTaskParam.java

@@ -35,4 +35,10 @@ public class EasyCallCreateTaskParam {
     private Double avgCallEndProcessTimeLen;
 
     private String ttsModels;
+
+    /** aiTransferType */
+    private String aiTransferType;
+
+    /** aiTransferExtNumber */
+    private String aiTransferExtNumber;
 }

+ 34 - 0
fs-service/src/main/java/com/fs/company/vo/easycall/ExtensionVO.java

@@ -0,0 +1,34 @@
+package com.fs.company.vo.easycall;
+
+import lombok.Data;
+
+/**
+ * @author MixLiu
+ * @date 2026/5/20 15:16
+ * @description
+ */
+@Data
+public class ExtensionVO {
+    /**
+     * 分机号码
+     */
+    private String extensionNum;
+
+    /**
+     * 公司id
+     */
+    private Long companyId;
+    /**
+     * 销售id
+     */
+    private Long companyUserId;
+    /**
+     * 销售名称
+     */
+    private String companyUserName;
+    /**
+     * 分机id EasyCall 的分机ID
+     */
+    private Integer extId;
+
+}

+ 28 - 3
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -3305,6 +3305,14 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             return R.error().put("msg", "企业不存在,请联系管理员");
         }
 
+        String  miniprogramPicUrl="";
+        String sidebarUrl = redisCache.getCacheObject("SidebarCompany:" + qwUser.getCompanyId()).toString();
+        if (!StringUtil.strIsNullOrEmpty(sidebarUrl)){
+            miniprogramPicUrl=sidebarUrl;
+        }else {
+            miniprogramPicUrl=config.getSidebarImageUrl();
+        }
+
         //看课记录
         addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(), param.getFsUserId(), qwUser, param.getExternalUserId(),2);
 
@@ -3314,7 +3322,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         JSONObject news = new JSONObject(true);
         news.put("miniprogramAppid", qwCompany.getMiniAppId());
         news.put("miniprogramTitle", param.getTitle());
-        news.put("miniprogramPicUrl", config.getSidebarImageUrl());
+        news.put("miniprogramPicUrl", miniprogramPicUrl);
         news.put("miniprogramPage", linkByMiniApp);
 
         return R.ok().put("data", news);
@@ -3343,6 +3351,15 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             return R.error().put("msg", "课程默认配置为空,请联系管理员");
         }
 
+
+        String  miniprogramPicUrl="";
+        String sidebarUrl = redisCache.getCacheObject("SidebarCompany:" + qwUser.getCompanyId()).toString();
+        if (!StringUtil.strIsNullOrEmpty(sidebarUrl)){
+            miniprogramPicUrl=sidebarUrl;
+        }else {
+            miniprogramPicUrl=config.getSidebarImageUrl();
+        }
+
         //域名
         String domainName = companyUserMapper.selectDomainByUserId(qwUser.getCompanyUserId());
         if (StringUtils.isEmpty(domainName)) {
@@ -3358,7 +3375,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         JSONObject news = new JSONObject(true); // true 表示保持字段顺序
         news.put("linkTitle", param.getTitle());
         news.put("linkDescribe", param.getTitle());
-        news.put("linkImageUrl", config.getSidebarImageUrl());
+        news.put("linkImageUrl", miniprogramPicUrl);
         news.put("linkUrl", linkByCartLink);
 
         return R.ok().put("data", news);
@@ -3600,13 +3617,21 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             return R.error().put("msg", "企业不存在,请联系管理员");
         }
 
+        String  miniprogramPicUrl="";
+        String sidebarUrl = redisCache.getCacheObject("SidebarCompany:" + qwUser.getCompanyId()).toString();
+        if (!StringUtil.strIsNullOrEmpty(sidebarUrl)){
+            miniprogramPicUrl=sidebarUrl;
+        }else {
+            miniprogramPicUrl=config.getSidebarImageUrl();
+        }
+
         //生成小程序链接
         String linkByMiniApp = createLinkByMiniApp(new Date(), param.getCourseId(), param.getVideoId(), qwUser, param.getExternalUserId(), 2, null, 1);
 
         JSONObject news = new JSONObject(true);
         news.put("miniprogramAppid", qwCompany.getMiniAppId());
         news.put("miniprogramTitle", param.getTitle());
-        news.put("miniprogramPicUrl", config.getSidebarImageUrl());
+        news.put("miniprogramPicUrl", miniprogramPicUrl);
         news.put("miniprogramPage", linkByMiniApp);
 
         return R.ok().put("data", news);