Bläddra i källkod

新个微改动

lmx 18 timmar sedan
förälder
incheckning
5c76a6e1f4

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

@@ -4,6 +4,7 @@ import java.util.List;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.company.domain.CompanyAiWorkflowExec;
 import com.fs.company.vo.WorkflowExecRecordVo;
+import com.fs.wxcid.domain.WxContact;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
@@ -111,4 +112,6 @@ public interface CompanyAiWorkflowExecMapper extends BaseMapper<CompanyAiWorkflo
             @Param("customerPhone") String customerPhone,
             @Param(("onlyCallNode")) Boolean onlyCallNode
     );
+
+    WxContact selectWxContectByWorkflowInstanceId(@Param("workflowInstanceId") String workflowInstanceId);
 }

+ 17 - 0
fs-service/src/main/java/com/fs/company/param/AddWxActionParam.java

@@ -0,0 +1,17 @@
+package com.fs.company.param;
+
+import com.fs.wxcid.vo.wxvo.AddWxVo;
+import lombok.Data;
+
+/**
+ * @author MixLiu
+ * @date 2026/4/22 14:56
+ * @description
+ */
+
+@Data
+public class AddWxActionParam extends AddWxVo {
+
+    private String wxId;
+
+}

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

@@ -73,6 +73,8 @@ public interface ICompanyWxClientService extends IService<CompanyWxClient> {
 
     List<CompanyWxClient4WorkFlowVO> getAddWxList4Workflow(List<Long> accountIdList,Integer cidGroupId);
 
+    List<CompanyWxClient4WorkFlowVO> getAddWxList4WorkflowNew(List<Long> accountIdList, Integer cidGroupId);
+
     List<CompanyWxClient> getQwAddWxList(List<Long> accountIdList,Integer isWeCom);
 
     List<CompanyWxClient4WorkFlowVO> getQwAddWxList4Workflow(List<Long> accountIdList,Integer cidGroupNo);

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

@@ -1455,8 +1455,10 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
         companyWxClient.setDialogId(companyVoiceRoboticWx.getWxDialogId());
         if (Integer.valueOf(2).equals(robotic.getIsWeCom())) {
             companyWxClient.setCompanyUserId(qwUser.getCompanyUserId());
+            companyWxClient.setCompanyId(qwUser.getCompanyId());
         } else if (Integer.valueOf(1).equals(robotic.getIsWeCom())) {
             companyWxClient.setCompanyUserId(companyWxAccount.getCompanyUserId());
+            companyWxClient.setCompanyId(companyWxAccount.getCompanyId());
         }
         CrmCustomer crmCustomer = crmCustomerService.selectCrmCustomerById(companyWxClient.getCustomerId());
         companyWxClient.setNickName(crmCustomer.getCustomerName());
@@ -1518,8 +1520,10 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             companyWxClient.setDialogId(wx.getWxDialogId());
             if (robotic.getIsWeCom() == 2) {
                 companyWxClient.setCompanyUserId(qwMap.get(wx.getAccountId()).getCompanyUserId());
+                companyWxClient.setCompanyId(qwMap.get(wx.getAccountId()).getCompanyId());
             } else {
                 companyWxClient.setCompanyUserId(accountMap.get(wx.getAccountId()).getCompanyUserId());
+                companyWxClient.setCompanyId(accountMap.get(wx.getAccountId()).getCompanyId());
             }
             companyWxClient.setNickName(crmCustomer.getCustomerName());
             companyWxClient.setPhone(crmCustomer.getMobile());
@@ -1591,6 +1595,7 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             client.setRoboticWxId(companyVoiceRoboticWx.getId());
             client.setCompanyId(companyVoiceRoboticWx.getAccount().getCompanyId());
             client.setCompanyUserId(companyVoiceRoboticWx.getAccount().getCompanyUserId());
+            client.setCompanyId(companyVoiceRoboticWx.getAccount().getCompanyId());
             CompanyWxAccount account = new CompanyWxAccount();
             account.setId(companyVoiceRoboticWx.getAccount().getId());
             account.setAllocateNum(companyVoiceRoboticWx.getAccount().getAllocateNum() + 1);

+ 5 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyWxClientServiceImpl.java

@@ -242,6 +242,11 @@ public class CompanyWxClientServiceImpl extends ServiceImpl<CompanyWxClientMappe
         return baseMapper.getAddWxList4Workflow(accountIdList, ExecutionStatusEnum.PAUSED.getValue(), NodeTypeEnum.AI_ADD_WX_TASK.getValue(),cidGroupId);
     }
 
+    @Override
+    public List<CompanyWxClient4WorkFlowVO> getAddWxList4WorkflowNew(List<Long> accountIdList, Integer cidGroupId){
+        return baseMapper.getAddWxList4Workflow(accountIdList, ExecutionStatusEnum.PAUSED.getValue(), NodeTypeEnum.AI_ADD_WX_TASK_NEW.getValue(), cidGroupId);
+    }
+
     @Override
     public List<CompanyWxClient> getQwAddWxList(List<Long> accountIdList, Integer isWeCom) {
         return baseMapper.getQwAddWxList(accountIdList,isWeCom);

+ 27 - 12
fs-service/src/main/java/com/fs/company/service/impl/CompanyWxServiceImpl.java

@@ -14,6 +14,7 @@ import com.fs.company.domain.*;
 import com.fs.company.mapper.*;
 import com.fs.company.service.*;
 import com.fs.company.service.impl.call.node.AiAddWxTaskNode;
+import com.fs.company.service.impl.call.node.AiAddWxTaskNewNode;
 import com.fs.enums.ExecutionStatusEnum;
 import com.fs.enums.NodeTypeEnum;
 import com.fs.wxcid.domain.CidIpadServerUser;
@@ -586,15 +587,30 @@ public class CompanyWxServiceImpl extends ServiceImpl<CompanyWxAccountMapper, Co
      */
     public void triggerWorkflowOnAddWxSuccess(Long wxClientId) {
         try {
-            // 查找等待中的加微工作流实例
+            // 先查老类型的等待中工作流实例
             CompanyAiWorkflowExec waitingExec = companyAiWorkflowExecMapper.selectWaitingAddWxWorkflowByWxClientId(
                     wxClientId,
                     ExecutionStatusEnum.WAITING.getValue(),
                     NodeTypeEnum.AI_ADD_WX_TASK.getValue());
+            boolean isNewNodeType = false;
+            // 老类型未找到,再查新类型
+            if (waitingExec == null) {
+                waitingExec = companyAiWorkflowExecMapper.selectWaitingAddWxWorkflowByWxClientId(
+                        wxClientId,
+                        ExecutionStatusEnum.WAITING.getValue(),
+                        NodeTypeEnum.AI_ADD_WX_TASK_NEW.getValue());
+                isNewNodeType = true;
+            }
+
+            if (waitingExec == null) {
+                log.info("未找到等待中的加微工作流实例 - wxClientId: {}", wxClientId);
+                return;
+            }
+
             //查询工作流加微执行日志是否未更新状态
             CompanyAiWorkflowExecLog queryP = new CompanyAiWorkflowExecLog();
             queryP.setWorkflowInstanceId(waitingExec.getWorkflowInstanceId());
-            queryP.setNodeType(NodeTypeEnum.AI_ADD_WX_TASK.getValue());
+            queryP.setNodeType(isNewNodeType ? NodeTypeEnum.AI_ADD_WX_TASK_NEW.getValue() : NodeTypeEnum.AI_ADD_WX_TASK.getValue());
             queryP.setStatus(ExecutionStatusEnum.WAITING.getValue());
             List<CompanyAiWorkflowExecLog> companyAiWorkflowExecLogs = companyAiWorkflowExecLogMapper.selectCompanyAiWorkflowExecLogList(queryP);
             companyAiWorkflowExecLogs.forEach(log -> {
@@ -602,10 +618,6 @@ public class CompanyWxServiceImpl extends ServiceImpl<CompanyWxAccountMapper, Co
                 companyAiWorkflowExecLogMapper.updateById(log);
                 }
             );
-            if (waitingExec == null) {
-                log.info("未找到等待中的加微工作流实例 - wxClientId: {}", wxClientId);
-                return;
-            }
 
             String workflowInstanceId = waitingExec.getWorkflowInstanceId();
             String currentNodeKey = waitingExec.getCurrentNodeKey();
@@ -613,21 +625,24 @@ public class CompanyWxServiceImpl extends ServiceImpl<CompanyWxAccountMapper, Co
             log.info("加微成功回调,尝试触发工作流继续执行 - workflowInstanceId: {}, nodeKey: {}, wxClientId: {}",
                     workflowInstanceId, currentNodeKey, wxClientId);
 
-            // 互斥检查:如果已经被执行过(超时路径或其他回调),则不再执行
-            if (!AiAddWxTaskNode.tryMarkAsExecuted(workflowInstanceId, wxClientId)) {
+            // 互斥检查:根据节点类型使用对应的互斥方法
+            boolean canExecute;
+            if (isNewNodeType) {
+                canExecute = AiAddWxTaskNewNode.tryMarkAsExecuted(workflowInstanceId, wxClientId);
+            } else {
+                canExecute = AiAddWxTaskNode.tryMarkAsExecuted(workflowInstanceId, wxClientId);
+            }
+            if (!canExecute) {
                 log.info("工作流已被其他路径执行,跳过 - workflowInstanceId: {}, wxClientId: {}",
                         workflowInstanceId, wxClientId);
                 return;
             }
 
-            // 清除超时检测Key(回调成功了,不需要超时检测了)
-//            AiAddWxTaskNode.clearTimeoutKey(workflowInstanceId, wxClientId);
-
             // 触发工作流继续执行
             Map<String, Object> inputData = new HashMap<>();
             inputData.put("addWxSuccess", true);
             inputData.put("wxClientId", wxClientId);
-            inputData.put("triggerType", "callback"); // 回调触发
+            inputData.put("triggerType", "callback");
 
             companyWorkflowEngine.resumeFromBlockingNode(workflowInstanceId, currentNodeKey, inputData);
 

+ 2 - 0
fs-service/src/main/java/com/fs/company/service/impl/call/node/AbstractWorkflowNode.java

@@ -169,6 +169,8 @@ public abstract class AbstractWorkflowNode implements IWorkflowNode {
                     updateLogStatusIfExist(context,ExecutionStatusEnum.WAITING_DO_CALL,ExecutionStatusEnum.SUCCESS);
                 } else if(getType().equals(NodeTypeEnum.AI_ADD_WX_TASK)){
                     updateLogStatusIfExist(context,ExecutionStatusEnum.WAITING,ExecutionStatusEnum.SUCCESS);
+                } else if(getType().equals(NodeTypeEnum.AI_ADD_WX_TASK_NEW)){
+                    updateLogStatusIfExist(context,ExecutionStatusEnum.WAITING,ExecutionStatusEnum.SUCCESS);
                 } else if (getType().equals(NodeTypeEnum.OUTBOUND_TASK)) {
                     updateLogStatusIfExist(context,ExecutionStatusEnum.WAITING_DO_CALL,ExecutionStatusEnum.SUCCESS);
                 }

+ 309 - 0
fs-service/src/main/java/com/fs/company/service/impl/call/node/AiAddWxTaskNewNode.java

@@ -0,0 +1,309 @@
+package com.fs.company.service.impl.call.node;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.constant.Constants;
+import com.fs.common.core.redis.RedisCacheT;
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.http.HttpUtils;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.company.domain.*;
+import com.fs.company.mapper.*;
+import com.fs.company.param.AddWxActionParam;
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.util.ObjectPlaceholderResolver;
+import com.fs.company.vo.AiAddWxConfigVO;
+import com.fs.company.vo.AiCallWorkflowConditionVo;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.crm.domain.CrmCustomer;
+import com.fs.crm.service.impl.CrmCustomerServiceImpl;
+import com.fs.enums.ExecutionStatusEnum;
+import com.fs.enums.NodeTypeEnum;
+import com.fs.system.service.ISysConfigService;
+import com.fs.wxcid.domain.CidIpadServer;
+import com.fs.wxcid.domain.WxContact;
+import com.fs.wxcid.mapper.CidIpadServerMapper;
+import com.fs.wxcid.mapper.WxContactMapper;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author MixLiu
+ * @date 2026/04/22 13:15
+ * @description AI添加微信任务节点(新)
+ */
+@Slf4j
+public class AiAddWxTaskNewNode extends AbstractWorkflowNode {
+
+    private static final CompanyWxClientMapper companyWxClientMapper = SpringUtils.getBean(CompanyWxClientMapper.class);
+    @SuppressWarnings("unchecked")
+    private static final RedisCacheT<String> redisCache = SpringUtils.getBean(RedisCacheT.class);
+    private static final WxContactMapper wxContactMapper = SpringUtils.getBean(WxContactMapper.class);
+    private static final CompanyWxAccountMapper companyWxAccountMapper = SpringUtils.getBean(CompanyWxAccountMapper.class);
+    public static final String DELAY_ADD_WX_NEW_KEY = "addWxTaskNew:delay:%s:%s:%s:";
+    private static final CompanyWxDialogMapper companyWxDialogMapper = SpringUtils.getBean(CompanyWxDialogMapper.class);
+    private static final CrmCustomerServiceImpl crmCustomerService = SpringUtils.getBean(CrmCustomerServiceImpl.class);
+    private static final ObjectPlaceholderResolver objectPlaceholderResolver = SpringUtils.getBean(ObjectPlaceholderResolver.class);
+    private static final ISysConfigService sysConfigService = SpringUtils.getBean(ISysConfigService.class);
+    private static final CidIpadServerMapper cidIpadServerMapper = SpringUtils.getBean(CidIpadServerMapper.class);
+    private static final String ADDWX_POST_URL = "/app/common/addWxAction";
+    /**
+     * 默认加微超时时间(分钟)
+     */
+    private static final int DEFAULT_ADD_WX_TIMEOUT_MINUTES = 30;
+
+    public AiAddWxTaskNewNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.AI_ADD_WX_TASK_NEW;
+    }
+
+    @Override
+    public Boolean isAsync() {
+        return true;
+    }
+
+    /**
+     * 执行加微节点逻辑(准备数据并发起加微请求)
+     *
+     * @param context 执行上下文
+     * @return 执行结果
+     */
+    @Override
+    protected ExecutionResult doExecute(ExecutionContext context) {
+        if (!isAsync()) {
+            return ExecutionResult.failure().nextNodeKey(null).build();
+        }
+        try {
+            // 设置加微话术
+            CompanyWorkflowNode node = context.getVariable("currentNode", CompanyWorkflowNode.class) == null
+                    ? getNodeByKey(nodeKey)
+                    : context.getVariable("currentNode", CompanyWorkflowNode.class);
+            String nodeConfig = node.getNodeConfig();
+            AiAddWxConfigVO addWxConfig = nodeConfig == null ? null : JSONObject.parseObject(nodeConfig, AiAddWxConfigVO.class);
+
+            if (addWxConfig == null || addWxConfig.getDialogId() == null) {
+                throw new CustomException("加微节点未配置加微话术,执行失败");
+            }
+
+            WxContact wxQuery = companyAiWorkflowExecMapper.selectWxContectByWorkflowInstanceId(context.getWorkflowInstanceId());
+            wxQuery.setNickName(wxQuery.getRemark());
+            wxQuery.setFriends(0);
+            wxContactMapper.insert(wxQuery);
+
+            CompanyVoiceRoboticBusiness roboticBusiness = getRoboticBusiness(context.getWorkflowInstanceId());
+            CompanyWxClient update = new CompanyWxClient();
+            update.setDialogId(addWxConfig.getDialogId());
+            update.setId(roboticBusiness.getWxClientId());
+            companyWxClientMapper.updateCompanyWxClient(update);
+            super.asyncWorkflowForBlockingNode(context.getWorkflowInstanceId(), context.getCurrentNodeKey(), context, ExecutionStatusEnum.PAUSED);
+            pendingAddWx(wxQuery.getAccountId(),wxQuery.getRemark(),wxQuery.getPhone(),addWxConfig.getDialogId(),wxQuery.getCrmUserId());
+            doneAddwx(context.getWorkflowInstanceId());
+            return ExecutionResult.paused()
+                    .outputData(context.getVariables())
+                    .nextNodeKey("").build();
+
+        } catch (Exception e) {
+            log.error("准备加微任务数据异常 流程:{}:节点:{}执行失败,", context.getWorkflowInstanceId(), nodeKey, e);
+            super.updateWorkflowStatus(context.getWorkflowInstanceId(), ExecutionStatusEnum.INTERRUPT);
+            return ExecutionResult.failure().errorMessage("准备加微任务数据异常: " + e.getMessage()).build();
+        }
+    }
+
+    /**
+     * 收到加微回调后,继续判定和执行下一步动作
+     *
+     * @param context 执行上下文
+     * @return 执行结果
+     */
+    @Override
+    protected ExecutionResult doContinue(ExecutionContext context) {
+        CompanyAiWorkflowExec exec = companyAiWorkflowExecMapper.selectByWorkflowInstanceId(context.getWorkflowInstanceId());
+        if (exec == null) {
+            log.warn("doContinue: 工作流执行实例不存在 - workflowInstanceId: {}", context.getWorkflowInstanceId());
+            return null;
+        }
+        List<CompanyWorkflowEdge> edges = companyWorkflowEdgeMapper.selectListByWorkflowIdAndNodeKey(exec.getWorkflowId(), nodeKey);
+        if (edges == null || edges.isEmpty()) {
+            log.warn("doContinue: 未找到出边 - workflowInstanceId: {}, nodeKey: {}", context.getWorkflowInstanceId(), nodeKey);
+            return null;
+        }
+
+        // 获取业务数据
+        CompanyVoiceRoboticBusiness business = super.getRoboticBusiness(context.getWorkflowInstanceId());
+        // 获取加微记录
+        CompanyWxClient wxClient = companyWxClientMapper.selectById(business.getWxClientId());
+
+        log.info("收到加微回调 - workflowInstanceId: {}, wxClientId: {}, isAdd: {}",
+                context.getWorkflowInstanceId(), business.getWxClientId(), wxClient != null ? wxClient.getIsAdd() : null);
+
+        // 判断加微是否成功 (isAdd: 0否 1是 2待添加 3作废)
+        boolean addSuccess = wxClient != null && Integer.valueOf(1).equals(wxClient.getIsAdd());
+        // 回调加微成功,走成功分支出边
+        if (addSuccess) {
+            List<CompanyWorkflowEdge> cList = edges.stream().filter(a -> {
+                if (StringUtils.isBlank(a.getConditionExpr())) {
+                    return false;
+                }
+                List<AiCallWorkflowConditionVo> list = JSONObject.parseArray(a.getConditionExpr(), AiCallWorkflowConditionVo.class);
+                return list != null && !list.isEmpty() && list.get(0).isAdd();
+            }).collect(Collectors.toList());
+            if (null != cList && !cList.isEmpty() && nodeKey.equals(exec.getCurrentNodeKey())) {
+                super.runNextNode(context, cList.get(0));
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 完成加微动作,设置为 WAITING 并处理出边条件(延时/直通)
+     *
+     * @param workflowInstanceId 工作流实例ID
+     */
+    public void doneAddwx(String workflowInstanceId) {
+        ExecutionContext context = createExecutionContext(workflowInstanceId, nodeKey);
+        context.setVariable("lastNodeKey", nodeKey);
+        CompanyAiWorkflowExec exec = companyAiWorkflowExecMapper.selectByWorkflowInstanceId(context.getWorkflowInstanceId());
+        if (exec == null) {
+            log.error("doneAddwx: 工作流执行实例不存在 - workflowInstanceId: {}", workflowInstanceId);
+            return;
+        }
+        if (!exec.getCurrentNodeKey().equals(nodeKey)) {
+            log.error("当前节点已流转 ,目标:{},实际:{}", nodeKey, exec.getCurrentNodeKey());
+            return;
+        }
+        // 更新加微日志执行状态
+        super.updateLogStatusIfExist(context, ExecutionStatusEnum.PAUSED, ExecutionStatusEnum.WAITING);
+        super.asyncWorkflowForBlockingNode(context.getWorkflowInstanceId(), nodeKey, context, ExecutionStatusEnum.WAITING);
+        List<CompanyWorkflowEdge> edges = companyWorkflowEdgeMapper.selectListByWorkflowIdAndNodeKey(exec.getWorkflowId(), nodeKey);
+        if (edges == null) {
+            return;
+        }
+        edges.forEach(edge -> {
+            String conditionExpr = edge.getConditionExpr();
+            List<AiCallWorkflowConditionVo> conditions = StringUtils.isBlank(conditionExpr) ? null : JSONObject.parseArray(conditionExpr, AiCallWorkflowConditionVo.class);
+            if (null == conditions || conditions.isEmpty()) {
+                super.runNextNode(context, edge);
+            } else {
+                AiCallWorkflowConditionVo condition = conditions.get(0);
+                // 节点包含延时条件
+                if (null != condition.getAddTime() && !condition.isAdd()) {
+                    long l = System.currentTimeMillis() + condition.getAddTime() * 60 * 1000;
+                    String redisKey = getDelayAddWxKeyPrefix(exec.getCidGroupNo(), l) + workflowInstanceId;
+                    ExecutionContext nextContext = context.clone();
+                    nextContext.setCurrentNodeKey(edge.getTargetNodeKey());
+                    super.redisCache.setCacheObject(redisKey, nextContext);
+                }
+            }
+        });
+    }
+
+    /**
+     * 检查并标记已执行(互斥控制)
+     * 返回 true 表示当前是第一个执行的,可以继续;
+     * 返回 false 表示已被其他路径执行过,不再执行。
+     *
+     * @param workflowInstanceId 工作流实例ID
+     * @param wxClientId         加微客户ID
+     * @return 是否可以执行
+     */
+    public static boolean tryMarkAsExecuted(String workflowInstanceId, Long wxClientId) {
+        String executedKey = Constants.WORKFLOW_ADD_WX_EXECUTED + "new:" + workflowInstanceId + ":" + wxClientId;
+        String existingValue = redisCache.getCacheObject(executedKey);
+        if (existingValue != null) {
+            return false;
+        }
+        redisCache.setCacheObject(executedKey, "1");
+        redisCache.expire(executedKey, 3600);
+        return true;
+    }
+
+    /**
+     * 生成延时加微 Redis Key 前缀
+     *
+     * @param cidGroupNo CID分组号
+     * @param time       目标时间戳(毫秒),为 null 时取当前时间
+     * @return Redis Key 前缀
+     */
+    public static String getDelayAddWxKeyPrefix(Integer cidGroupNo, Long time) {
+        Date nowDay = new Date();
+        if (null != time) {
+            nowDay = new Date(time);
+        }
+        return String.format(DELAY_ADD_WX_NEW_KEY, cidGroupNo, nowDay.getHours(), nowDay.getMinutes());
+    }
+
+    /**
+     * 从节点配置获取超时时间(分钟)
+     */
+    private int getTimeoutFromProperties() {
+        if (properties != null && properties.containsKey("timeout")) {
+            Object timeout = properties.get("timeout");
+            if (timeout instanceof Number) {
+                return ((Number) timeout).intValue();
+            }
+            if (timeout instanceof String) {
+                try {
+                    return Integer.parseInt((String) timeout);
+                } catch (NumberFormatException e) {
+                    log.warn("解析超时时间失败: {}, 使用默认值: {}", timeout, DEFAULT_ADD_WX_TIMEOUT_MINUTES);
+                }
+            }
+        }
+        return DEFAULT_ADD_WX_TIMEOUT_MINUTES;
+    }
+
+    /**
+     * 发起加微请求
+     * @param accountId 微信账号ID
+     * @param remark 备注
+     * @param phone 手机号
+     * @param dialogId 话术ID
+     * @param crmUserId CRM用户ID
+     */
+    private void pendingAddWx(Long accountId, String remark, String phone, Long dialogId, Long crmUserId) {
+        // 1. 获取基础数据
+        CompanyWxAccount companyWxAccount = companyWxAccountMapper.selectCompanyWxAccountById(accountId);
+        if (companyWxAccount == null) {
+            throw new CustomException("未找到对应的微信账号配置, accountId: " + accountId);
+        }
+
+        CompanyWxDialog dialog = companyWxDialogMapper.selectCompanyWxDialogById(dialogId);
+        if (dialog == null) {
+            throw new CustomException("未找到对应的对话模板, dialogId: " + dialogId);
+        }
+
+        CrmCustomer crmCustomer = crmCustomerService.selectCrmCustomerById(crmUserId);
+
+        // 2. 解析话术模板
+        String newTxt = objectPlaceholderResolver.resolvePlaceholders(crmCustomer, dialog.getTemplateDetails());
+
+        // 3. 构建请求参数
+        AddWxActionParam param = new AddWxActionParam();
+        param.setWxId(companyWxAccount.getWxNo());
+        param.setRemark(remark);
+        param.setPhone(phone);
+        param.setApplyMsg(newTxt);
+        CidIpadServer cidIpadServer = cidIpadServerMapper.selectCidIpadServerById(companyWxAccount.getServerId());
+        if (null == cidIpadServer || StringUtils.isBlank(cidIpadServer.getUrl())) {
+            throw new CustomException("加微接口地址未配置");
+        }
+        // 4. 从系统配置获取加微接口地址
+        String addWxUrl = cidIpadServer.getUrl() + ADDWX_POST_URL;
+
+        // 5. 发送 HTTP 请求
+        try {
+            String result = HttpUtils.sendPost(addWxUrl, JSONObject.toJSONString(param));
+            log.info("pendingAddWx: 加微任务提交成功, phone: {}, result: {}", phone, result);
+        } catch (Exception e) {
+            throw new CustomException("发起加微请求异常, phone: " + phone + ", error: " + e.getMessage());
+        }
+    }
+}

+ 3 - 0
fs-service/src/main/java/com/fs/company/service/impl/call/node/WorkflowNodeFactory.java

@@ -39,6 +39,9 @@ public class WorkflowNodeFactory implements IWorkflowNodeFactory {
             case AI_ADD_WX_TASK:
                 node = new AiAddWxTaskNode(nodeKey, nodeName, properties);
                 break;
+            case AI_ADD_WX_TASK_NEW:
+                node = new AiAddWxTaskNewNode(nodeKey, nodeName, properties);
+                break;
             case AI_QW_ADD_WX_TASK:
                 node = new AiQwAddWxTaskNode(nodeKey, nodeName, properties);
                 break;

+ 5 - 1
fs-service/src/main/java/com/fs/enums/NodeTypeEnum.java

@@ -48,7 +48,11 @@ public enum NodeTypeEnum {
     /**
      * AI企微添加个微
      */
-    AI_QW_ADD_WX_TASK("AI_QW_ADD_WX_TASK", "AI企微添加个微",9);
+    AI_QW_ADD_WX_TASK("AI_QW_ADD_WX_TASK", "AI企微添加个微",9),
+    /**
+     * AI添加微信(新)
+     */
+    AI_ADD_WX_TASK_NEW("AI_ADD_WX_TASK_NEW", "AI添加微信(新)", 10);
 
     private final String code;
     private final String description;

+ 7 - 0
fs-service/src/main/java/com/fs/wxcid/domain/WxContact.java

@@ -1,5 +1,6 @@
 package com.fs.wxcid.domain;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntityTow;
@@ -72,5 +73,11 @@ public class WxContact extends BaseEntityTow {
     // 是否是好友1是0否
     private Integer friends;
 
+    /**
+     * crm 用户ID
+     */
+    @TableField(exist = false)
+    private Long crmUserId;
+
 
 }

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

@@ -259,4 +259,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             )
         </foreach>
     </insert>
+
+    <select id="selectWxContectByWorkflowInstanceId" resultType="com.fs.wxcid.domain.WxContact">
+        SELECT
+            t3.user_id as crmUserId,
+            t3.user_name as remark,
+            t3.phone as phone,
+            t2.wx_client_id as accountId,
+            t4.account_id as accountId,
+            t4.company_id as companyId,
+            t4.company_user_id as companyUserId
+        FROM
+            company_ai_workflow_exec t1
+                left join company_voice_robotic_business t2 on t1.business_key = t2.id
+                left join company_voice_robotic_callees t3 on t3.id  =  t2.callee_id
+                left join company_wx_client t4 on t4.id = t2.wx_client_id
+        WHERE
+            t1.workflow_instance_id = #{workflowInstanceId}
+    </select>
 </mapper>

+ 9 - 0
fs-wx-api/src/main/java/com/fs/app/controller/CommonController.java

@@ -3,6 +3,7 @@ package com.fs.app.controller;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.app.enums.CmdType;
+import com.fs.company.param.AddWxActionParam;
 import com.fs.app.websocket.bean.ResultMsgVo;
 import com.fs.company.domain.CompanyWxAccount;
 import com.fs.company.mapper.CompanyWxAccountMapper;
@@ -62,4 +63,12 @@ public class CommonController {
         return R.ok();
     }
 
+    @PostMapping("/addWxAction")
+    public R addWxAction(@RequestBody AddWxActionParam param){
+        String wxId = param.getWxId();
+        Session session = WebSocketServer.sessionPools.get(wxId);
+        webSocketServer.sendMessage(session, ResultMsgVo.<AddWxVo>builder().cmd(CmdType.ADD_WX).data(new AddWxVo(param.getRemark(), param.getPhone(), param.getApplyMsg())).build());
+        return R.ok();
+    }
+
 }

+ 20 - 2
fs-wx-task/src/main/java/com/fs/app/service/WxTaskService.java

@@ -18,6 +18,7 @@ import com.fs.company.param.ExecutionContext;
 import com.fs.company.service.*;
 import com.fs.company.service.impl.*;
 import com.fs.company.service.impl.call.node.AiAddWxTaskNode;
+import com.fs.company.service.impl.call.node.AiAddWxTaskNewNode;
 import com.fs.company.service.impl.call.node.AiQwAddWxTaskNode;
 import com.fs.company.service.impl.call.node.WorkflowNodeFactory;
 import com.fs.company.vo.CompanyWxClient4WorkFlowVO;
@@ -283,6 +284,12 @@ public class WxTaskService {
         WxConfig config = JSONUtil.toBean(json, WxConfig.class);
         // 需要添加微信的列表
         List<CompanyWxClient4WorkFlowVO> list = companyWxClientService.getAddWxList4Workflow(accountIdList,cidGroupNo);
+//        // 同时获取新加微节点类型的记录
+//        List<CompanyWxClient4WorkFlowVO> listNew = companyWxClientService.getAddWxList4WorkflowNew(accountIdList,cidGroupNo);
+//        if (listNew != null && !listNew.isEmpty()) {
+//            list = new ArrayList<>(list);
+//            list.addAll(listNew);
+//        }
         log.info("需要添加微信的数量:{}", list.size());
         if (list.isEmpty()) return;
         List<CompanyWxClient> addList = new ArrayList<>();
@@ -364,6 +371,12 @@ public class WxTaskService {
                         addWxNode.doneAddwx(vo.getWorkflowInstanceId());
                     }, cidExcutor);
                 }
+//                if (node instanceof AiAddWxTaskNewNode) {
+//                    CompletableFuture.runAsync(() -> {
+//                        AiAddWxTaskNewNode addWxNewNode = (AiAddWxTaskNewNode) node;
+//                        addWxNewNode.doneAddwx(vo.getWorkflowInstanceId());
+//                    }, cidExcutor);
+//                }
             }
             if (!addAccountList.isEmpty()) {
                 companyWxAccountService.updateBatchById(addAccountList);
@@ -859,8 +872,13 @@ public class WxTaskService {
      */
     public void cidWorkflowAddWxRun() {
         log.info("===========工作流延时任务开始扫描===========");
-        String delayAddWxKeyPrefix = AiAddWxTaskNode.getDelayAddWxKeyPrefix(cidGroupNo,null) + "*";
-        Set<String> keys = redisKeyScanner.scanMatchKey(delayAddWxKeyPrefix);
+//        String delayAddWxKeyPrefix = AiAddWxTaskNode.getDelayAddWxKeyPrefix(cidGroupNo,null) + "*";
+//        Set<String> keys = redisKeyScanner.scanMatchKey(delayAddWxKeyPrefix);
+        // 扫描新加微节点的延时Key
+        String delayAddWxNewKeyPrefix = AiAddWxTaskNewNode.getDelayAddWxKeyPrefix(cidGroupNo,null) + "*";
+        Set<String> keys = redisKeyScanner.scanMatchKey(delayAddWxNewKeyPrefix);
+        keys = new HashSet<>(keys);
+//        keys.addAll(keysNew);
         log.info("共扫描到 {} 个待处理键", keys.size());
         keys.parallelStream().forEach(key -> {
             try {