lmx 1 ay önce
ebeveyn
işleme
6e40254286
50 değiştirilmiş dosya ile 3577 ekleme ve 0 silme
  1. 160 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyWorkflowController.java
  2. 73 0
      fs-service/src/main/java/com/fs/company/domain/CompanyAiWorkflowExec.java
  3. 82 0
      fs-service/src/main/java/com/fs/company/domain/CompanyAiWorkflowExecLog.java
  4. 59 0
      fs-service/src/main/java/com/fs/company/domain/CompanyWorkflow.java
  5. 62 0
      fs-service/src/main/java/com/fs/company/domain/CompanyWorkflowEdge.java
  6. 70 0
      fs-service/src/main/java/com/fs/company/domain/CompanyWorkflowNode.java
  7. 49 0
      fs-service/src/main/java/com/fs/company/domain/CompanyWorkflowNodeType.java
  8. 61 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyAiWorkflowExecLogMapper.java
  9. 61 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyAiWorkflowExecMapper.java
  10. 29 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowEdgeMapper.java
  11. 55 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowMapper.java
  12. 38 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowNodeMapper.java
  13. 23 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowNodeTypeMapper.java
  14. 40 0
      fs-service/src/main/java/com/fs/company/param/CompanyWorkflowSaveParam.java
  15. 15 0
      fs-service/src/main/java/com/fs/company/param/CompanyWorkflowUpdateBindWCParam.java
  16. 91 0
      fs-service/src/main/java/com/fs/company/param/ExecutionContext.java
  17. 49 0
      fs-service/src/main/java/com/fs/company/service/CompanyWorkflowEngine.java
  18. 91 0
      fs-service/src/main/java/com/fs/company/service/ICompanyWorkflowService.java
  19. 24 0
      fs-service/src/main/java/com/fs/company/service/IWorkflowNode.java
  20. 15 0
      fs-service/src/main/java/com/fs/company/service/IWorkflowNodeFactory.java
  21. 94 0
      fs-service/src/main/java/com/fs/company/service/impl/AbstractWorkflowNode.java
  22. 29 0
      fs-service/src/main/java/com/fs/company/service/impl/ActionNode.java
  23. 35 0
      fs-service/src/main/java/com/fs/company/service/impl/AiAddWxTaskNode.java
  24. 29 0
      fs-service/src/main/java/com/fs/company/service/impl/AiAnalysisNode.java
  25. 35 0
      fs-service/src/main/java/com/fs/company/service/impl/AiCallTaskNode.java
  26. 29 0
      fs-service/src/main/java/com/fs/company/service/impl/AiDialogNode.java
  27. 35 0
      fs-service/src/main/java/com/fs/company/service/impl/AiSendMsgTaskNode.java
  28. 411 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyWorkflowEngineImpl.java
  29. 362 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyWorkflowServiceImpl.java
  30. 29 0
      fs-service/src/main/java/com/fs/company/service/impl/ConditionNode.java
  31. 35 0
      fs-service/src/main/java/com/fs/company/service/impl/DelayNode.java
  32. 29 0
      fs-service/src/main/java/com/fs/company/service/impl/EndNode.java
  33. 29 0
      fs-service/src/main/java/com/fs/company/service/impl/HttpRequestNode.java
  34. 29 0
      fs-service/src/main/java/com/fs/company/service/impl/MessageNotificationNode.java
  35. 29 0
      fs-service/src/main/java/com/fs/company/service/impl/OutBoundTaskNode.java
  36. 29 0
      fs-service/src/main/java/com/fs/company/service/impl/StartNode.java
  37. 29 0
      fs-service/src/main/java/com/fs/company/service/impl/VariableSetNode.java
  38. 81 0
      fs-service/src/main/java/com/fs/company/service/impl/WorkflowNodeFactory.java
  39. 106 0
      fs-service/src/main/java/com/fs/company/vo/CompanyWorkflowExportVO.java
  40. 33 0
      fs-service/src/main/java/com/fs/company/vo/CompanyWorkflowNodeVoiceVo.java
  41. 25 0
      fs-service/src/main/java/com/fs/company/vo/CompanyWorkflowVO.java
  42. 46 0
      fs-service/src/main/java/com/fs/company/vo/ExecutionResult.java
  43. 103 0
      fs-service/src/main/java/com/fs/enums/ExecutionStatusEnum.java
  44. 119 0
      fs-service/src/main/java/com/fs/enums/NodeTypeEnum.java
  45. 118 0
      fs-service/src/main/resources/mapper/company/CompanyAiWorkflowExecLogMapper.xml
  46. 108 0
      fs-service/src/main/resources/mapper/company/CompanyAiWorkflowExecMapper.xml
  47. 71 0
      fs-service/src/main/resources/mapper/company/CompanyWorkflowEdgeMapper.xml
  48. 194 0
      fs-service/src/main/resources/mapper/company/CompanyWorkflowMapper.xml
  49. 89 0
      fs-service/src/main/resources/mapper/company/CompanyWorkflowNodeMapper.xml
  50. 40 0
      fs-service/src/main/resources/mapper/company/CompanyWorkflowNodeTypeMapper.xml

+ 160 - 0
fs-company/src/main/java/com/fs/company/controller/company/CompanyWorkflowController.java

@@ -0,0 +1,160 @@
+package com.fs.company.controller.company;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.CompanyWorkflow;
+import com.fs.company.domain.CompanyWorkflowNodeType;
+import com.fs.company.param.CompanyWorkflowSaveParam;
+import com.fs.company.param.CompanyWorkflowUpdateBindWCParam;
+import com.fs.company.service.ICompanyWorkflowService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * AI工作流Controller
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+@RestController
+@RequestMapping("/company/companyWorkflow")
+public class CompanyWorkflowController extends BaseController {
+
+    @Autowired
+    private ICompanyWorkflowService companyWorkflowService;
+
+    /**
+     * 查询AI工作流列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyWorkflow fsAiWorkflow) {
+        startPage();
+        List<CompanyWorkflow> list = companyWorkflowService.selectCompanyWorkflowList(fsAiWorkflow);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出AI工作流列表
+     */
+    @Log(title = "AI工作流", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyWorkflow fsAiWorkflow) {
+        List<CompanyWorkflow> list = companyWorkflowService.selectCompanyWorkflowList(fsAiWorkflow);
+        ExcelUtil<CompanyWorkflow> util = new ExcelUtil<CompanyWorkflow>(CompanyWorkflow.class);
+        return util.exportExcel(list, "AI工作流数据");
+    }
+
+    /**
+     * 获取AI工作流详细信息(包含节点和连线)
+     */
+    @GetMapping(value = "/{workflowId}")
+    public AjaxResult getInfo(@PathVariable("workflowId") Long workflowId) {
+        return AjaxResult.success(companyWorkflowService.selectCompanyWorkflowById(workflowId));
+    }
+
+    /**
+     * 保存AI工作流(新增或更新)
+     */
+    @Log(title = "AI工作流", businessType = BusinessType.INSERT)
+    @PostMapping("/save")
+    public AjaxResult save(@RequestBody CompanyWorkflowSaveParam param) {
+        Long workflowId = companyWorkflowService.saveCompanyWorkflow(param);
+        return AjaxResult.success(workflowId);
+    }
+
+    /**
+     * 修改AI工作流状态
+     */
+    @Log(title = "AI工作流", businessType = BusinessType.UPDATE)
+    @PutMapping("/status/{workflowId}/{status}")
+    public AjaxResult updateStatus(@PathVariable("workflowId") Long workflowId,
+                                   @PathVariable("status") Integer status) {
+        return toAjax(companyWorkflowService.updateCompanyWorkflowStatus(workflowId, status));
+    }
+
+    /**
+     * 删除AI工作流
+     */
+    @Log(title = "AI工作流", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{workflowIds}")
+    public AjaxResult remove(@PathVariable Long[] workflowIds) {
+        return toAjax(companyWorkflowService.deleteCompanyWorkflowByIds(workflowIds));
+    }
+
+    /**
+     * 复制AI工作流
+     */
+    @Log(title = "AI工作流", businessType = BusinessType.INSERT)
+    @PostMapping("/copy/{workflowId}")
+    public AjaxResult copy(@PathVariable("workflowId") Long workflowId) {
+        Long newWorkflowId = companyWorkflowService.copyCompanyWorkflow(workflowId);
+        if (newWorkflowId != null) {
+            return AjaxResult.success(newWorkflowId);
+        }
+        return AjaxResult.error("复制失败,工作流不存在");
+    }
+
+    /**
+     * 获取所有启用的节点类型
+     */
+    @GetMapping("/nodeTypes")
+    public AjaxResult getNodeTypes() {
+        List<CompanyWorkflowNodeType> list = companyWorkflowService.selectAllEnabledNodeTypes();
+        return AjaxResult.success(list);
+    }
+    /**
+     * 导出工作流流程图JSON
+     * 包含节点信息、连接顺序、节点类型
+     */
+//    @Log(title = "AI工作流", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportJson/{workflowId}")
+    public AjaxResult exportJson(@PathVariable("workflowId") Long workflowId) {
+        return AjaxResult.success(companyWorkflowService.exportWorkflowJson(workflowId));
+    }
+    /**
+     * 分页销售
+     */
+    @GetMapping("/listCompanyUser")
+    public AjaxResult listCompanyUser() {
+        return AjaxResult.success(companyWorkflowService.listCompanyUser());
+    }
+
+    /**
+     * 查销售
+     */
+    @GetMapping("/getCompanyUserById/{companyUserId}")
+    public AjaxResult getCompanyUserById(@PathVariable("companyUserId") Long companyUserId) {
+        return AjaxResult.success(companyWorkflowService.getCompanyUserById(companyUserId));
+    }
+
+//    /**
+//     * 查销售是否已经被绑定
+//     */
+//    @GetMapping("/checkCompanyUserBeUsed/{companyUserId}")
+//    public AjaxResult checkCompanyUserBeUsed(@PathVariable("companyUserId") Long companyUserId) {
+//        return AjaxResult.success(companyWorkflowService.checkCompanyUserBeUsed(companyUserId));
+//    }
+
+    /**
+     * 查工作流已绑定的销售
+     */
+    @GetMapping("/getBindCompanyUserByWorkflowId/{workflowId}")
+    public AjaxResult getBindCompanyUserByWorkflowId(@PathVariable("workflowId") Long workflowId) {
+        return AjaxResult.success(companyWorkflowService.getBindCompanyUserByWorkflowId(workflowId));
+    }
+
+    /**
+     * 修改工作流绑定的销售
+     */
+    @PostMapping("/updateWorkflowBindCompanyUser")
+    public AjaxResult updateWorkflowBindCompanyUser(@RequestBody CompanyWorkflowUpdateBindWCParam param) {
+        return companyWorkflowService.updateWorkflowBindCompanyUser(param);
+    }
+
+}

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

@@ -0,0 +1,73 @@
+package com.fs.company.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * AI外呼工作流执行对象 company_ai_workflow_exec
+ *
+ * @author fs
+ * @date 2026-01-28
+ */
+@Data
+public class CompanyAiWorkflowExec {
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 工作流执行实例id */
+    @Excel(name = "工作流执行实例id")
+    private String workflowInstanceId;
+
+    /** 工作流id */
+    @Excel(name = "工作流id")
+    private Long workflowId;
+
+    /** 工作流版本 */
+    @Excel(name = "工作流版本")
+    private Integer workflowVersion;
+
+    /** 当前节点id */
+//    @Excel(name = "当前节点id")
+//    private Long currentNodeId;
+
+    @Excel(name = "当前节点key")
+    private String  currentNodeKey;
+
+    /** 当前节点名称 */
+    @Excel(name = "当前节点名称")
+    private String currentNodeName;
+
+    /** 当前节点类型 */
+    @Excel(name = "当前节点类型")
+    private Integer currentNodeType;
+
+    /** 状态 ExecutionStatus */
+    @Excel(name = "状态 ExecutionStatus")
+    private Integer status;
+
+    /** 参数 */
+    @Excel(name = "参数")
+    private String variables;
+
+    /** 开始时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "开始时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date startTime;
+
+    /** 最后更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "最后更新时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date lastUpdateTime;
+
+    /** 业务键值 */
+    @Excel(name = "业务键值")
+    private String businessKey;
+
+
+}

+ 82 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyAiWorkflowExecLog.java

@@ -0,0 +1,82 @@
+package com.fs.company.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * AI外呼流程执行记录对象 company_ai_workflow_exec_log
+ *
+ * @author fs
+ * @date 2026-01-28
+ */
+@Data
+public class CompanyAiWorkflowExecLog {
+
+    /** 记录主键id */
+    private Long id;
+
+    /** 工作流执行实例id */
+    @Excel(name = "工作流执行实例id")
+    private String workflowInstanceId;
+
+    /** 节点id */
+    @Excel(name = "节点id")
+    private Long nodeId;
+    /**
+     * 节点key
+     */
+    private String nodeKey;
+    /** 节点名称 */
+    @Excel(name = "节点名称")
+    private String nodeName;
+
+    /** 节点类型 */
+    @Excel(name = "节点类型")
+    private Integer nodeType;
+
+    /** 输入数据 */
+    @Excel(name = "输入数据")
+    private String inputData;
+
+    /** 输出数据 */
+    @Excel(name = "输出数据")
+    private String outputData;
+
+    /** 状态 ExecutionStatus */
+    @Excel(name = "状态 ExecutionStatus")
+    private Integer status;
+
+    /** 开始时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "开始时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date startTime;
+
+    /** 结束时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "结束时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date endTime;
+
+    /** 执行时长(毫秒) */
+    @Excel(name = "执行时长(毫秒)")
+    private Long duration;
+
+    /** 错误信息 */
+    @Excel(name = "错误信息")
+    private String errorMessage;
+
+    /** 重试次数 */
+    @Excel(name = "重试次数")
+    private Integer retryCount;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date createdTime;
+
+
+}

+ 59 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyWorkflow.java

@@ -0,0 +1,59 @@
+package com.fs.company.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+/**
+ * AI工作流对象 fs_ai_workflow
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+@Data
+public class CompanyWorkflow extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /** 工作流ID */
+    private Long workflowId;
+
+    /** 工作流名称 */
+    @Excel(name = "工作流名称")
+    private String workflowName;
+
+    /** 工作流描述 */
+    @Excel(name = "工作流描述")
+    private String workflowDesc;
+
+    /** 工作流类型 1对话流程 2任务流程 3审批流程 */
+    @Excel(name = "工作流类型", readConverterExp = "1=对话流程,2=任务流程,3=审批流程")
+    private Integer workflowType;
+
+    /** 状态 0禁用 1启用 */
+    @Excel(name = "状态", readConverterExp = "0=禁用,1=启用")
+    private Integer status;
+
+    /** 版本号 */
+    private Integer version;
+
+    /** 画布数据JSON */
+    private String canvasData;
+
+    /** 删除标志 0正常 1删除 */
+    private Integer delFlag;
+
+    /** 企业用户ID */
+    private Long companyUserId;
+
+    private String startNodeKey;
+
+    private String endNodeKey;
+
+    public Boolean isStartNode(String nodeKey) {
+        return nodeKey.equals(startNodeKey);
+    }
+
+    public Boolean isEndNode(String nodeKey) {
+        return nodeKey.equals(endNodeKey);
+    }
+}

+ 62 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyWorkflowEdge.java

@@ -0,0 +1,62 @@
+package com.fs.company.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * AI工作流连线对象 fs_ai_workflow_edge
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+@Data
+public class CompanyWorkflowEdge implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 连线ID */
+    private Long edgeId;
+
+    /** 工作流ID */
+    private Long workflowId;
+
+    /** 连线唯一标识 */
+    private String edgeKey;
+
+    /** 连线标签 */
+    private String edgeLabel;
+
+    /** 源节点Key */
+    private String sourceNodeKey;
+
+    /** 目标节点Key */
+    private String targetNodeKey;
+
+    /** 源锚点 */
+    private String sourceAnchor;
+
+    /** 目标锚点 */
+    private String targetAnchor;
+
+    /** 连线类型 */
+    private String edgeType;
+
+    /** 连线颜色 */
+    private String edgeColor;
+
+    /** 条件表达式 */
+    private String conditionExpr;
+
+    /** 排序 */
+    private Integer sortOrder;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+}

+ 70 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyWorkflowNode.java

@@ -0,0 +1,70 @@
+package com.fs.company.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * AI工作流节点对象 fs_ai_workflow_node
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+@Data
+public class CompanyWorkflowNode implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 节点ID */
+    private Long nodeId;
+
+    /** 工作流ID */
+    private Long workflowId;
+
+    /** 节点唯一标识 */
+    private String nodeKey;
+
+    /** 节点名称 */
+    private String nodeName;
+
+    /** 节点类型 start/end/condition/action/ai/delay/http */
+    private Integer nodeType;
+
+    /** 节点图标 */
+    private String nodeIcon;
+
+    /** 节点颜色 */
+    private String nodeColor;
+
+    /** X坐标 */
+    private Integer posX;
+
+    /** Y坐标 */
+    private Integer posY;
+
+    /** 节点宽度 */
+    private Integer width;
+
+    /** 节点高度 */
+    private Integer height;
+
+    /** 节点配置JSON */
+    private String nodeConfig;
+
+    /** 排序 */
+    private Integer sortOrder;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /**
+     * 语音URL
+     */
+    private String voiceUrl;
+}

+ 49 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyWorkflowNodeType.java

@@ -0,0 +1,49 @@
+package com.fs.company.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 工作流节点类型配置对象 fs_ai_workflow_node_type
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+@Data
+public class CompanyWorkflowNodeType implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 类型ID */
+    private Long typeId;
+
+    /** 类型编码 */
+    private String typeCode;
+
+    /** 类型名称 */
+    private String typeName;
+
+    /** 类型图标 */
+    private String typeIcon;
+
+    /** 类型颜色 */
+    private String typeColor;
+
+    /** 类型分类 basic/logic/ai/integration */
+    private String typeCategory;
+
+    /** 默认配置JSON */
+    private String defaultConfig;
+
+    /** 排序 */
+    private Integer sortOrder;
+
+    /** 状态 0禁用 1启用 */
+    private Integer status;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+}

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

@@ -0,0 +1,61 @@
+package com.fs.company.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.CompanyAiWorkflowExecLog;
+
+/**
+ * AI外呼流程执行记录Mapper接口
+ * 
+ * @author fs
+ * @date 2026-01-28
+ */
+public interface CompanyAiWorkflowExecLogMapper extends BaseMapper<CompanyAiWorkflowExecLog>{
+    /**
+     * 查询AI外呼流程执行记录
+     * 
+     * @param id AI外呼流程执行记录主键
+     * @return AI外呼流程执行记录
+     */
+    CompanyAiWorkflowExecLog selectCompanyAiWorkflowExecLogById(Long id);
+
+    /**
+     * 查询AI外呼流程执行记录列表
+     * 
+     * @param companyAiWorkflowExecLog AI外呼流程执行记录
+     * @return AI外呼流程执行记录集合
+     */
+    List<CompanyAiWorkflowExecLog> selectCompanyAiWorkflowExecLogList(CompanyAiWorkflowExecLog companyAiWorkflowExecLog);
+
+    /**
+     * 新增AI外呼流程执行记录
+     * 
+     * @param companyAiWorkflowExecLog AI外呼流程执行记录
+     * @return 结果
+     */
+    int insertCompanyAiWorkflowExecLog(CompanyAiWorkflowExecLog companyAiWorkflowExecLog);
+
+    /**
+     * 修改AI外呼流程执行记录
+     * 
+     * @param companyAiWorkflowExecLog AI外呼流程执行记录
+     * @return 结果
+     */
+    int updateCompanyAiWorkflowExecLog(CompanyAiWorkflowExecLog companyAiWorkflowExecLog);
+
+    /**
+     * 删除AI外呼流程执行记录
+     * 
+     * @param id AI外呼流程执行记录主键
+     * @return 结果
+     */
+    int deleteCompanyAiWorkflowExecLogById(Long id);
+
+    /**
+     * 批量删除AI外呼流程执行记录
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteCompanyAiWorkflowExecLogByIds(Long[] ids);
+}

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

@@ -0,0 +1,61 @@
+package com.fs.company.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.CompanyAiWorkflowExec;
+
+/**
+ * AI外呼工作流执行Mapper接口
+ * 
+ * @author fs
+ * @date 2026-01-28
+ */
+public interface CompanyAiWorkflowExecMapper extends BaseMapper<CompanyAiWorkflowExec>{
+    /**
+     * 查询AI外呼工作流执行
+     * 
+     * @param id AI外呼工作流执行主键
+     * @return AI外呼工作流执行
+     */
+    CompanyAiWorkflowExec selectCompanyAiWorkflowExecById(Long id);
+
+    /**
+     * 查询AI外呼工作流执行列表
+     * 
+     * @param companyAiWorkflowExec AI外呼工作流执行
+     * @return AI外呼工作流执行集合
+     */
+    List<CompanyAiWorkflowExec> selectCompanyAiWorkflowExecList(CompanyAiWorkflowExec companyAiWorkflowExec);
+
+    /**
+     * 新增AI外呼工作流执行
+     * 
+     * @param companyAiWorkflowExec AI外呼工作流执行
+     * @return 结果
+     */
+    int insertCompanyAiWorkflowExec(CompanyAiWorkflowExec companyAiWorkflowExec);
+
+    /**
+     * 修改AI外呼工作流执行
+     * 
+     * @param companyAiWorkflowExec AI外呼工作流执行
+     * @return 结果
+     */
+    int updateCompanyAiWorkflowExec(CompanyAiWorkflowExec companyAiWorkflowExec);
+
+    /**
+     * 删除AI外呼工作流执行
+     * 
+     * @param id AI外呼工作流执行主键
+     * @return 结果
+     */
+    int deleteCompanyAiWorkflowExecById(Long id);
+
+    /**
+     * 批量删除AI外呼工作流执行
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteCompanyAiWorkflowExecByIds(Long[] ids);
+}

+ 29 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowEdgeMapper.java

@@ -0,0 +1,29 @@
+package com.fs.company.mapper;
+
+
+import com.fs.company.domain.CompanyWorkflowEdge;
+
+import java.util.List;
+
+/**
+ * AI工作流连线Mapper接口
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+public interface CompanyWorkflowEdgeMapper {
+
+    CompanyWorkflowEdge selectCompanyWorkflowEdgeById(Long edgeId);
+
+    List<CompanyWorkflowEdge> selectCompanyWorkflowEdgeByWorkflowId(Long workflowId);
+
+    int insertCompanyWorkflowEdge(CompanyWorkflowEdge edge);
+
+    int batchInsertCompanyWorkflowEdge(List<CompanyWorkflowEdge> edges);
+
+    int updateCompanyWorkflowEdge(CompanyWorkflowEdge edge);
+
+    int deleteCompanyWorkflowEdgeById(Long edgeId);
+
+    int deleteCompanyWorkflowEdgeByWorkflowId(Long workflowId);
+}

+ 55 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowMapper.java

@@ -0,0 +1,55 @@
+package com.fs.company.mapper;
+
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.domain.CompanyWorkflow;
+import com.fs.company.domain.CompanyWorkflowNode;
+import com.fs.company.vo.CompanyWorkflowNodeVoiceVo;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * AI工作流Mapper接口
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+public interface CompanyWorkflowMapper {
+
+    CompanyWorkflow selectCompanyWorkflowById(Long workflowId);
+
+    List<CompanyWorkflow> selectCompanyWorkflowList(CompanyWorkflow fsAiWorkflow);
+
+    /**
+     * 根据企业用户ID查询工作流列表
+     */
+    List<CompanyWorkflow> selectCompanyWorkflowByCompanyUserId(Long companyUserId);
+
+    int insertCompanyWorkflow(CompanyWorkflow fsAiWorkflow);
+
+    int updateCompanyWorkflow(CompanyWorkflow fsAiWorkflow);
+
+    int deleteCompanyWorkflowById(Long workflowId);
+
+    int deleteCompanyWorkflowByIds(Long[] workflowIds);
+
+    int selectCompanyWorkflowCountByCompanyUserId(Long companyUserId);
+
+    int updateWorkflowBindCompanyUser(@Param("workflowId") Long workflowId, @Param("companyUserId") Long companyUserId);
+
+    List<CompanyUser> getBindCompanyUserByWorkflowId(Long workflowId);
+
+    int insertCompanyWorkflowCompanyUser(@Param("workflowId")Long workflowId,@Param("companyUserIds") List<Long> companyUserIds);
+
+    int insertAiWorkflowCompanyUserVoice(@Param("workflowId") Long workflowId, @Param("companyUserIds") List<Long> companyUserIds, @Param("fsAiWorkflowNodes") List<CompanyWorkflowNode> fsAiWorkflowNodes);
+
+    List<String> selectCompanyWorkflowNodeByWorkflowId(@Param("workflowId") Long workflowId);
+
+    List<Long> selectWorkflowCompanyUserByWfId(@Param("workflowId") Long workflowId);
+
+    void deleteAiWorkflowCompanyUserVoice(@Param("workflowId") Long workflowId,@Param("nodeKeyDel") List<String> nodeKeyDel);
+
+    void addAiWorkflowCompanyUserVoice(@Param("workflowId") Long workflowId, @Param("companyUserIds") List<Long> companyUserIds, @Param("nodeKeyAdd") List<String> nodeKeyAdd);
+
+    List<CompanyWorkflowNodeVoiceVo> getMyWorkflowNodes(@Param("companyUserId") Long companyUserId);
+}

+ 38 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowNodeMapper.java

@@ -0,0 +1,38 @@
+package com.fs.company.mapper;
+
+import com.fs.company.domain.CompanyWorkflowNode;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * AI工作流节点Mapper接口
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+public interface CompanyWorkflowNodeMapper {
+
+    CompanyWorkflowNode selectCompanyWorkflowNodeById(Long nodeId);
+
+    List<CompanyWorkflowNode> selectCompanyWorkflowNodeByWorkflowId(Long workflowId);
+
+    /**
+     * 根据工作流ID和节点类型列表查询节点
+     */
+    List<CompanyWorkflowNode> selectNodesByWorkflowIdAndTypes(@Param("workflowId") Long workflowId,
+                                                           @Param("nodeTypes") List<String> nodeTypes);
+
+    int insertCompanyWorkflowNode(CompanyWorkflowNode node);
+
+    int batchInsertCompanyWorkflowNode(List<CompanyWorkflowNode> nodes);
+
+    int updateCompanyWorkflowNode(CompanyWorkflowNode node);
+
+    int deleteCompanyWorkflowNodeById(Long nodeId);
+
+    int deleteCompanyWorkflowNodeByWorkflowId(Long workflowId);
+
+    CompanyWorkflowNode selectNodeByWorkflowIdAndNodeKey(@Param("workflowId") Long workflowId,
+                                                       @Param("nodeKey") String nodeKey);
+}

+ 23 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowNodeTypeMapper.java

@@ -0,0 +1,23 @@
+package com.fs.company.mapper;
+
+
+import com.fs.company.domain.CompanyWorkflowNodeType;
+
+import java.util.List;
+
+/**
+ * 工作流节点类型Mapper接口
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+public interface CompanyWorkflowNodeTypeMapper {
+
+    CompanyWorkflowNodeType selectCompanyWorkflowNodeTypeById(Long typeId);
+
+    CompanyWorkflowNodeType selectCompanyWorkflowNodeTypeByCode(String typeCode);
+
+    List<CompanyWorkflowNodeType> selectCompanyWorkflowNodeTypeList(CompanyWorkflowNodeType nodeType);
+
+    List<CompanyWorkflowNodeType> selectAllEnabledNodeTypes();
+}

+ 40 - 0
fs-service/src/main/java/com/fs/company/param/CompanyWorkflowSaveParam.java

@@ -0,0 +1,40 @@
+package com.fs.company.param;
+
+import com.fs.company.domain.CompanyWorkflowEdge;
+import com.fs.company.domain.CompanyWorkflowNode;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * AI工作流保存参数
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+@Data
+public class CompanyWorkflowSaveParam implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 工作流ID(新增时为空) */
+    private Long workflowId;
+
+    /** 工作流名称 */
+    private String workflowName;
+
+    /** 工作流描述 */
+    private String workflowDesc;
+
+    /** 工作流类型 */
+    private Integer workflowType;
+
+    /** 画布数据JSON */
+    private String canvasData;
+
+    /** 节点列表 */
+    private List<CompanyWorkflowNode> nodes;
+
+    /** 连线列表 */
+    private List<CompanyWorkflowEdge> edges;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/company/param/CompanyWorkflowUpdateBindWCParam.java

@@ -0,0 +1,15 @@
+package com.fs.company.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * AI工作流-工作流与销售id绑定关系
+ */
+@Data
+public class CompanyWorkflowUpdateBindWCParam {
+    private static final long serialVersionUID = 1L;
+    private Long workflowId;
+    private List<Long> companyUserIds;
+}

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

@@ -0,0 +1,91 @@
+package com.fs.company.param;
+
+import lombok.Data;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:39
+ * @description 执行节点入参
+ */
+@Data
+public class ExecutionContext {
+
+    /**
+     * 工作流实例ID
+     */
+    private String workflowInstanceId;
+    /**
+     * 当前节点ID
+     */
+    private Long currentNodeId;
+
+    /**
+     * 当前节点Key
+     */
+    private String currentNodeKey;
+
+    /**
+     * 流程变量存储
+     */
+    private Map<String, Object> variables = new HashMap<>();
+
+    /**
+     * 全局变量存储
+     */
+    private Map<String, Object> globalVariables = new HashMap<>();
+
+    /**
+     * 执行开始时间
+     */
+    private Date startTime;
+
+    /**
+     * 当前执行时间戳
+     */
+    private Date currentTime;
+
+    /**
+     * 业务关键id
+     */
+    private String businessId;
+    /**
+     * 设置本地变量
+     */
+    public void setVariable(String key, Object value) {
+        this.variables.put(key, value);
+    }
+
+
+    /**
+     * 获取本地变量
+     */
+    public <T> T getVariable(String key, Class<T> type) {
+        Object value = this.variables.get(key);
+        if (value == null) {
+            return null;
+        }
+        return type.isInstance(value) ? type.cast(value) : null;
+    }
+
+    /**
+     * 设置全局变量
+     */
+    public void setGlobalVariable(String key, Object value) {
+        this.globalVariables.put(key, value);
+    }
+
+    /**
+     * 获取全局变量
+     */
+    public <T> T getGlobalVariable(String key, Class<T> type) {
+        Object value = this.globalVariables.get(key);
+        if (value == null) {
+            return null;
+        }
+        return type.isInstance(value) ? type.cast(value) : null;
+    }
+}

+ 49 - 0
fs-service/src/main/java/com/fs/company/service/CompanyWorkflowEngine.java

@@ -0,0 +1,49 @@
+package com.fs.company.service;
+
+import com.fs.company.domain.CompanyAiWorkflowExecLog;
+import com.fs.company.vo.ExecutionResult;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 14:21
+ * @description 外呼ai工作流引擎
+ */
+public interface CompanyWorkflowEngine {
+    /**
+     * 初始化工作流
+     * @param workflowDefinitionId 工作流定义ID
+     * @param inputVariables 输入变量
+     * @return 初始化结果
+     */
+    ExecutionResult initialize(Long workflowDefinitionId, Map<String, Object> inputVariables);
+
+    /**
+     * 执行指定节点
+     * @param workflowInstanceId 工作流实例ID
+     * @param nodeId 节点ID
+     * @return 执行结果
+     */
+    ExecutionResult executeNode(String workflowInstanceId, String nodeId);
+
+    /**
+     * 暂停工作流
+     * @param workflowInstanceId 工作流实例ID
+     * @return 暂停结果
+     */
+    ExecutionResult pauseWorkflow(String workflowInstanceId);
+
+    /**
+     * 结束工作流
+     * @param workflowInstanceId 工作流实例ID
+     * @return 结束结果
+     */
+    ExecutionResult completeWorkflow(String workflowInstanceId);
+
+    /**
+     * 记录执行日志
+     * @param logEntry 日志记录
+     */
+    void logExecution(CompanyAiWorkflowExecLog logEntry);
+}

+ 91 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyWorkflowService.java

@@ -0,0 +1,91 @@
+package com.fs.company.service;
+
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.domain.CompanyWorkflow;
+import com.fs.company.domain.CompanyWorkflowNode;
+import com.fs.company.domain.CompanyWorkflowNodeType;
+import com.fs.company.param.CompanyWorkflowSaveParam;
+import com.fs.company.param.CompanyWorkflowUpdateBindWCParam;
+import com.fs.company.vo.CompanyWorkflowExportVO;
+import com.fs.company.vo.CompanyWorkflowNodeVoiceVo;
+import com.fs.company.vo.CompanyWorkflowVO;
+
+import java.util.List;
+
+/**
+ * AI工作流Service接口
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+public interface ICompanyWorkflowService {
+
+    /**
+     * 查询工作流
+     */
+    CompanyWorkflowVO selectCompanyWorkflowById(Long workflowId);
+
+    /**
+     * 查询工作流列表
+     */
+    List<CompanyWorkflow> selectCompanyWorkflowList(CompanyWorkflow fsAiWorkflow);
+
+    /**
+     * 保存工作流(新增或更新)
+     */
+    Long saveCompanyWorkflow(CompanyWorkflowSaveParam param);
+
+    /**
+     * 修改工作流状态
+     */
+    int updateCompanyWorkflowStatus(Long workflowId, Integer status);
+
+    /**
+     * 删除工作流
+     */
+    int deleteCompanyWorkflowByIds(Long[] workflowIds);
+
+    /**
+     * 复制工作流
+     */
+    Long copyCompanyWorkflow(Long workflowId);
+
+    /**
+     * 获取所有启用的节点类型
+     */
+    List<CompanyWorkflowNodeType> selectAllEnabledNodeTypes();
+    /**
+     * 导出工作流JSON(包含节点、连接顺序、节点类型)
+     */
+    CompanyWorkflowExportVO exportWorkflowJson(Long workflowId);
+
+    List<CompanyUser> listCompanyUser();
+
+    /**
+     * 根据企业用户ID查询工作流的指定类型节点
+     * @param companyUserId 企业用户ID
+     * @param nodeTypes 节点类型列表(如: start, end, ai_chat)
+     * @return 节点列表
+     */
+    List<CompanyWorkflowNode> selectWorkflowNodesByCompanyUserId(Long companyUserId, List<String> nodeTypes);
+
+    /**
+     * 更新节点的voiceUrl
+     * @param nodeId 节点ID
+     * @param voiceUrl 语音URL
+     * @param companyUserId 企业用户ID(用于校验归属)
+     * @return 1成功 0失败 -1节点不存在 -2无权限
+     */
+    int updateNodeVoiceUrl(Long nodeId, String voiceUrl, Long companyUserId);
+
+    CompanyUser getCompanyUserById(Long id);
+
+    Boolean checkCompanyUserBeUsed(Long companyUserId);
+
+    AjaxResult updateWorkflowBindCompanyUser(CompanyWorkflowUpdateBindWCParam param);
+
+    AjaxResult getBindCompanyUserByWorkflowId(Long workflowId);
+
+    List<CompanyWorkflowNodeVoiceVo> getMyWorkflowNodes(Long companyUserId);
+}

+ 24 - 0
fs-service/src/main/java/com/fs/company/service/IWorkflowNode.java

@@ -0,0 +1,24 @@
+package com.fs.company.service;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:36
+ * @description 流程节点接口
+ */
+public interface IWorkflowNode {
+
+    // 执行节点动作
+    ExecutionResult execute(ExecutionContext context);
+
+    // 获取节点类型
+    NodeTypeEnum getType();
+
+    // 验证节点配置
+    boolean validate();
+
+    Boolean isAsync();
+}

+ 15 - 0
fs-service/src/main/java/com/fs/company/service/IWorkflowNodeFactory.java

@@ -0,0 +1,15 @@
+package com.fs.company.service;
+
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 11:06
+ * @description 工作流节点工厂
+ */
+public interface IWorkflowNodeFactory {
+    IWorkflowNode createNode(String nodeId, NodeTypeEnum type, String nodeName,
+                             Map<String, Object> properties);
+}

+ 94 - 0
fs-service/src/main/java/com/fs/company/service/impl/AbstractWorkflowNode.java

@@ -0,0 +1,94 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.service.IWorkflowNode;
+import com.fs.company.vo.ExecutionResult;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:46
+ * @description 工作节点基类
+ */
+@Slf4j
+public abstract class AbstractWorkflowNode implements IWorkflowNode {
+    protected String nodeKey;
+    protected String nodeName;
+    protected Map<String, Object> properties; // 节点配置属性
+
+    public AbstractWorkflowNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        this.nodeKey = nodeKey;
+        this.nodeName = nodeName;
+        this.properties = properties != null ? properties : new HashMap<>();
+    }
+
+    @Override
+    public ExecutionResult execute(ExecutionContext context) {
+        // 记录执行开始时间
+        long startTime = System.currentTimeMillis();
+
+        try {
+            // 执行前的通用处理
+            preExecute(context);
+
+            // 执行具体的业务逻辑
+            ExecutionResult result = doExecute(context);
+
+            // 记录执行时间
+            long executionTime = System.currentTimeMillis() - startTime;
+            context.setVariable("execution_time_" + nodeKey, executionTime);
+
+            return result;
+        } catch (Exception e) {
+            return handleExecutionError(e, context);
+        } finally {
+            postExecute(context);
+        }
+    }
+
+    /**
+     * 子类必须实现的具体执行逻辑
+     */
+    protected abstract ExecutionResult doExecute(ExecutionContext context);
+
+    /**
+     * 执行前的预处理
+     */
+    protected void preExecute(ExecutionContext context) {
+        context.setVariable("node_start_time_" + nodeKey, System.currentTimeMillis());
+        log.info("Starting execution of node: {} ({})", nodeKey, nodeName);
+    }
+
+    /**
+     * 执行后的后处理
+     */
+    protected void postExecute(ExecutionContext context) {
+        long endTime = System.currentTimeMillis();
+        context.setVariable("node_end_time_" + nodeKey, endTime);
+        log.info("Completed execution of node: {} ({})", nodeKey, nodeName);
+    }
+
+    /**
+     * 错误处理
+     */
+    protected ExecutionResult handleExecutionError(Exception e, ExecutionContext context) {
+        log.error("Error executing node: {} ({})", nodeKey, nodeName, e);
+        return ExecutionResult.failure()
+                .withErrorMessage(e.getMessage())
+                .withOutputData(Collections.singletonMap("error", e.getMessage()));
+    }
+
+    @Override
+    public boolean validate() {
+        return nodeKey != null && !nodeKey.isEmpty();
+    }
+
+    public Boolean isAsync() {
+    	return false;
+    }
+
+}

+ 29 - 0
fs-service/src/main/java/com/fs/company/service/impl/ActionNode.java

@@ -0,0 +1,29 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:59
+ * @description
+ */
+public class ActionNode extends AbstractWorkflowNode{
+
+    public ActionNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    public ExecutionResult doExecute(ExecutionContext context) {
+        return null;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.ACTION;
+    }
+}

+ 35 - 0
fs-service/src/main/java/com/fs/company/service/impl/AiAddWxTaskNode.java

@@ -0,0 +1,35 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 13:39
+ * @description AI外呼电话任务节点
+ */
+public class AiAddWxTaskNode extends AbstractWorkflowNode{
+
+    public AiAddWxTaskNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    protected ExecutionResult doExecute(ExecutionContext context) {
+
+        return null;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.AI_ADD_WX_TASK;
+    }
+
+    @Override
+    public Boolean isAsync() {
+        return true;
+    }
+}

+ 29 - 0
fs-service/src/main/java/com/fs/company/service/impl/AiAnalysisNode.java

@@ -0,0 +1,29 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:59
+ * @description
+ */
+public class AiAnalysisNode extends AbstractWorkflowNode{
+
+    public AiAnalysisNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    public ExecutionResult doExecute(ExecutionContext context) {
+        return null;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.AI_ANALYSIS;
+    }
+}

+ 35 - 0
fs-service/src/main/java/com/fs/company/service/impl/AiCallTaskNode.java

@@ -0,0 +1,35 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 13:39
+ * @description AI外呼电话任务节点
+ */
+public class AiCallTaskNode extends AbstractWorkflowNode{
+
+    public AiCallTaskNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    protected ExecutionResult doExecute(ExecutionContext context) {
+
+        return null;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.AI_CALL_TASK;
+    }
+
+    @Override
+    public Boolean isAsync() {
+        return true;
+    }
+}

+ 29 - 0
fs-service/src/main/java/com/fs/company/service/impl/AiDialogNode.java

@@ -0,0 +1,29 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:59
+ * @description
+ */
+public class AiDialogNode extends AbstractWorkflowNode{
+
+    public AiDialogNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    public ExecutionResult doExecute(ExecutionContext context) {
+        return null;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.AI_DIALOG;
+    }
+}

+ 35 - 0
fs-service/src/main/java/com/fs/company/service/impl/AiSendMsgTaskNode.java

@@ -0,0 +1,35 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 13:39
+ * @description AI外呼电话任务节点
+ */
+public class AiSendMsgTaskNode extends AbstractWorkflowNode{
+
+    public AiSendMsgTaskNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    protected ExecutionResult doExecute(ExecutionContext context) {
+
+        return null;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.AI_SEND_MSG_TASK;
+    }
+
+    @Override
+    public Boolean isAsync() {
+        return true;
+    }
+}

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

@@ -0,0 +1,411 @@
+package com.fs.company.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.CompanyAiWorkflowExec;
+import com.fs.company.domain.CompanyAiWorkflowExecLog;
+import com.fs.company.domain.CompanyWorkflow;
+import com.fs.company.domain.CompanyWorkflowNode;
+import com.fs.company.mapper.CompanyAiWorkflowExecLogMapper;
+import com.fs.company.mapper.CompanyAiWorkflowExecMapper;
+import com.fs.company.mapper.CompanyWorkflowMapper;
+import com.fs.company.mapper.CompanyWorkflowNodeMapper;
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.service.CompanyWorkflowEngine;
+import com.fs.company.service.IWorkflowNode;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.ExecutionStatusEnum;
+import com.fs.enums.NodeTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 14:22
+ * @description
+ */
+@Service
+@Slf4j
+public class CompanyWorkflowEngineImpl implements CompanyWorkflowEngine {
+    @Autowired
+    private WorkflowNodeFactory nodeFactory;
+
+    @Autowired
+    private CompanyAiWorkflowExecMapper currentExecutionMapper;
+
+    @Autowired
+    private CompanyAiWorkflowExecLogMapper executionLogMapper;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    @Autowired
+    private CompanyWorkflowMapper companyWorkflowMapper;
+
+    @Autowired
+    private CompanyWorkflowNodeMapper companyWorkflowNodeMapper;
+
+
+    /**
+     * 初始化工作流
+     * 创建工作流实例并保存初始状态
+     */
+    @Override
+    @Transactional
+    public ExecutionResult initialize(Long workflowDefinitionId, Map<String, Object> inputVariables) {
+        try {
+            // 生成工作流实例ID
+            String workflowInstanceId = generateInstanceId();
+
+            // 加载工作流定义
+            CompanyWorkflow definition = loadCompanyWorkflow(workflowDefinitionId);
+
+            // 创建执行上下文
+            ExecutionContext context = createContext(workflowInstanceId, inputVariables);
+
+            // 保存当前执行记录
+            saveInitialExecution(workflowInstanceId, workflowDefinitionId,
+                    definition.getStartNodeKey(), context);
+
+            log.info("工作流初始化成功: {} -> {}", workflowInstanceId, workflowDefinitionId);
+
+            return ExecutionResult.success()
+                    .withNextNodeKey(definition.getStartNodeKey())
+                    .withOutputData(Collections.singletonMap("workflowInstanceId", workflowInstanceId));
+
+        } catch (Exception e) {
+            log.error("工作流初始化失败: {}", workflowDefinitionId, e);
+            return ExecutionResult.failure()
+                    .withErrorMessage("Initialization failed: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 执行指定节点
+     * 根据节点ID执行对应的节点逻辑
+     */
+    @Override
+    @Transactional
+    public ExecutionResult executeNode(String workflowInstanceId, String nodeKey) {
+        try {
+            // 加载当前执行记录
+            CompanyAiWorkflowExec currentExec =
+                    currentExecutionMapper.selectById(workflowInstanceId);
+
+            if (currentExec == null) {
+                return ExecutionResult.failure()
+                        .withErrorMessage("工作流实例不存在: " + workflowInstanceId);
+            }
+
+            // 反序列化执行上下文
+            ExecutionContext context = deserializeContext(currentExec);
+
+            // 加载工作流定义
+            CompanyWorkflow definition = loadCompanyWorkflow(
+                    currentExec.getWorkflowId());
+
+            // 创建节点实例并执行
+            IWorkflowNode node = createNode(definition, nodeKey);
+            if (node == null) {
+                return ExecutionResult.failure()
+                        .withErrorMessage("节点不存在: " + nodeKey);
+            }
+
+            // 更新当前节点到执行上下文
+            context.setCurrentNodeKey(nodeKey);
+
+            if (node.isAsync()) {
+                // 执行节点逻辑 异步执行等待其他方式唤醒继续步骤
+                node.execute(context);
+                return null;
+            } else {
+                // 执行节点
+                ExecutionResult result = node.execute(context);
+
+                // 记录执行日志
+                logExecution(createLogEntry(workflowInstanceId, nodeKey, node.getType(), result));
+
+                // 更新当前执行记录
+                updateCurrentExecution(workflowInstanceId, nodeKey, context);
+
+                // 如果执行成功,返回下一个节点ID
+                if (result.isSuccess()) {
+                    // 检查是否是结束节点
+                    if (StringUtils.isBlank(result.getNextNodeKey())) {
+                        // 结束工作流
+                        completeWorkflow(workflowInstanceId);
+                    } else {
+                        // 更新当前节点为下一个节点
+                        updateCurrentNode(workflowInstanceId, result.getNextNodeKey());
+                    }
+                } else {
+                    // 执行失败时更新状态
+                    updateWorkflowStatus(workflowInstanceId, ExecutionStatusEnum.FAILURE);
+                }
+
+                return result;
+            }
+
+
+        } catch (Exception e) {
+            log.error("节点执行失败: {} -> {}", workflowInstanceId, nodeKey, e);
+            updateWorkflowStatus(workflowInstanceId, ExecutionStatusEnum.FAILURE);
+            return ExecutionResult.failure()
+                    .withErrorMessage("Node execution failed: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 暂停工作流
+     * 将工作流状态设置为暂停
+     */
+    @Override
+    @Transactional
+    public ExecutionResult pauseWorkflow(String workflowInstanceId) {
+        try {
+            CompanyAiWorkflowExec update = new CompanyAiWorkflowExec();
+            update.setWorkflowInstanceId(workflowInstanceId);
+            update.setStatus(ExecutionStatusEnum.PAUSED.getValue());
+            update.setLastUpdateTime(new Date());
+
+            int rows = currentExecutionMapper.updateById(update);
+
+            if (rows > 0) {
+                log.info("工作流暂停成功: {}", workflowInstanceId);
+                return ExecutionResult.success();
+            } else {
+                return ExecutionResult.failure()
+                        .withErrorMessage("工作流实例不存在: " + workflowInstanceId);
+            }
+
+        } catch (Exception e) {
+            log.error("暂停工作流失败: {}", workflowInstanceId, e);
+            return ExecutionResult.failure()
+                    .withErrorMessage("Pause failed: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 结束工作流
+     * 将工作流状态设置为完成
+     */
+    @Override
+    @Transactional
+    public ExecutionResult completeWorkflow(String workflowInstanceId) {
+        try {
+            CompanyAiWorkflowExec update = new CompanyAiWorkflowExec();
+            update.setWorkflowInstanceId(workflowInstanceId);
+            update.setStatus(ExecutionStatusEnum.SUCCESS.getValue());
+            update.setLastUpdateTime(new Date());
+
+            int rows = currentExecutionMapper.updateById(update);
+
+            if (rows > 0) {
+                log.info("工作流结束成功: {}", workflowInstanceId);
+                return ExecutionResult.success();
+            } else {
+                return ExecutionResult.failure()
+                        .withErrorMessage("工作流实例不存在: " + workflowInstanceId);
+            }
+
+        } catch (Exception e) {
+            log.error("结束工作流失败: {}", workflowInstanceId, e);
+            return ExecutionResult.failure()
+                    .withErrorMessage("Complete failed: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 记录执行日志
+     * 保存节点执行的相关信息
+     */
+    @Override
+    public void logExecution(CompanyAiWorkflowExecLog logEntry) {
+        try {
+            CompanyAiWorkflowExecLog logRecord = new CompanyAiWorkflowExecLog();
+            logRecord.setWorkflowInstanceId(logEntry.getWorkflowInstanceId());
+            logRecord.setNodeId(logEntry.getNodeId());
+            logRecord.setNodeType(logEntry.getNodeType());
+            logRecord.setStatus(logEntry.getStatus());
+            logRecord.setInputData(objectMapper.writeValueAsString(logEntry.getInputData()));
+            logRecord.setOutputData(objectMapper.writeValueAsString(logEntry.getOutputData()));
+            logRecord.setStartTime(logEntry.getStartTime());
+            logRecord.setEndTime(logEntry.getEndTime());
+            logRecord.setErrorMessage(logEntry.getErrorMessage());
+            logRecord.setDuration(logEntry.getDuration());
+
+            executionLogMapper.insert(logRecord);
+
+        } catch (Exception e) {
+            log.error("记录执行日志失败", e);
+        }
+    }
+
+    /**
+     * 创建日志记录对象
+     */
+    private CompanyAiWorkflowExecLog createLogEntry(String workflowInstanceId, String nodeKey,
+                                                    NodeTypeEnum nodeType, ExecutionResult result) {
+
+        try {
+            CompanyAiWorkflowExecLog logEntry = new CompanyAiWorkflowExecLog();
+            logEntry.setWorkflowInstanceId(workflowInstanceId);
+            logEntry.setNodeKey(nodeKey);
+            logEntry.setNodeType(nodeType.getValue());
+            logEntry.setStatus(result.getStatus().getValue());
+
+            logEntry.setOutputData(objectMapper.writeValueAsString(result.getOutputData()));
+            logEntry.setErrorMessage(result.getErrorMessage());
+            logEntry.setStartTime(new Date());
+            logEntry.setEndTime(new Date());
+            logEntry.setDuration(0L); // 可以根据实际情况计算持续时间
+
+            return logEntry;
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    /**
+     * 保存初始执行记录
+     */
+    private void saveInitialExecution(String workflowInstanceId, Long workflowDefinitionId,
+                                      String startNodeKey, ExecutionContext context) {
+
+        try {
+            CompanyAiWorkflowExec currentExec = new CompanyAiWorkflowExec();
+            currentExec.setWorkflowInstanceId(workflowInstanceId);
+            currentExec.setWorkflowId(workflowDefinitionId);
+//        currentExec.setCurrentNodeId(startNodeId);
+            currentExec.setCurrentNodeKey(startNodeKey);
+            currentExec.setCurrentNodeType(NodeTypeEnum.START.getValue());
+            currentExec.setStatus(ExecutionStatusEnum.RUNNING.getValue());
+            currentExec.setStartTime(new Date());
+            currentExec.setVariables(objectMapper.writeValueAsString(context.getVariables()));
+            // 如果有业务键
+            currentExec.setBusinessKey(context.getBusinessId());
+            currentExecutionMapper.insert(currentExec);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    /**
+     * 更新当前执行记录
+     */
+    private void updateCurrentExecution(String workflowInstanceId, String currentNodeKey,
+                                        ExecutionContext context) {
+        try {
+            CompanyAiWorkflowExec update = new CompanyAiWorkflowExec();
+            update.setWorkflowInstanceId(workflowInstanceId);
+            update.setCurrentNodeKey(currentNodeKey);
+
+            update.setVariables(objectMapper.writeValueAsString(context.getVariables()));
+            update.setLastUpdateTime(new Date());
+
+            currentExecutionMapper.updateById(update);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    /**
+     * 更新当前节点
+     */
+    private void updateCurrentNode(String workflowInstanceId, String nextNodeKey) {
+        CompanyAiWorkflowExec update = new CompanyAiWorkflowExec();
+        update.setWorkflowInstanceId(workflowInstanceId);
+        update.setCurrentNodeKey(nextNodeKey);
+        update.setLastUpdateTime(new Date());
+        currentExecutionMapper.updateCompanyAiWorkflowExec(update);
+    }
+
+    /**
+     * 更新工作流状态
+     */
+    private void updateWorkflowStatus(String workflowInstanceId, ExecutionStatusEnum status) {
+        CompanyAiWorkflowExec update = new CompanyAiWorkflowExec();
+        update.setWorkflowInstanceId(workflowInstanceId);
+        update.setStatus(status.getValue());
+        update.setLastUpdateTime(new Date());
+
+        currentExecutionMapper.updateById(update);
+    }
+
+    /**
+     * 创建节点实例
+     */
+    private IWorkflowNode createNode(CompanyWorkflow definition, String nodeKey) {
+        CompanyWorkflowNode nodeDef = companyWorkflowNodeMapper.selectNodeByWorkflowIdAndNodeKey(definition.getWorkflowId(),nodeKey);
+        if (nodeDef == null) {
+            return null;
+        }
+        return nodeFactory.createNode(
+                nodeDef.getNodeKey(),
+                NodeTypeEnum.fromValue(nodeDef.getNodeType()),
+                nodeDef.getNodeName(),
+                JSON.parseObject(nodeDef.getNodeConfig(), Map.class));
+    }
+
+    /**
+     * 生成工作流实例ID
+     */
+    private String generateInstanceId() {
+        return "wf_" + System.currentTimeMillis() + "_" +
+                UUID.randomUUID().toString().replace("-", "");
+    }
+
+    /**
+     * 创建执行上下文
+     */
+    private ExecutionContext createContext(String workflowInstanceId,
+                                           Map<String, Object> inputVariables) {
+        ExecutionContext context = new ExecutionContext();
+        context.setWorkflowInstanceId(workflowInstanceId);
+        context.setVariables(inputVariables != null ? inputVariables : new HashMap<>());
+        context.setStartTime(new Date());
+        context.setCurrentTime(new Date());
+
+        return context;
+    }
+
+    /**
+     * 反序列化执行上下文
+     */
+    private ExecutionContext deserializeContext(CompanyAiWorkflowExec record) {
+        ExecutionContext context = new ExecutionContext();
+        context.setWorkflowInstanceId(record.getWorkflowInstanceId());
+        context.setCurrentNodeKey(record.getCurrentNodeKey());
+        context.setStartTime(record.getStartTime());
+        context.setCurrentTime(new Date());
+
+        try {
+            if (StringUtils.isNotBlank(record.getVariables())) {
+                Map<String, Object> variables = objectMapper.readValue(
+                        record.getVariables(), Map.class);
+                context.setVariables(variables);
+            }
+        } catch (Exception e) {
+            log.error("反序列化上下文变量失败", e);
+        }
+
+        return context;
+    }
+
+    /**
+     * 加载工作流定义
+     */
+    private CompanyWorkflow loadCompanyWorkflow(Long id) {
+        // 实现根据ID加载工作流定义的逻辑
+        return companyWorkflowMapper.selectCompanyWorkflowById(id);
+    }
+}

+ 362 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyWorkflowServiceImpl.java

@@ -0,0 +1,362 @@
+package com.fs.company.service.impl;
+
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.company.domain.*;
+import com.fs.company.mapper.*;
+import com.fs.company.param.CompanyWorkflowSaveParam;
+import com.fs.company.param.CompanyWorkflowUpdateBindWCParam;
+import com.fs.company.service.ICompanyWorkflowService;
+import com.fs.company.vo.CompanyWorkflowExportVO;
+import com.fs.company.vo.CompanyWorkflowNodeVoiceVo;
+import com.fs.company.vo.CompanyWorkflowVO;
+import com.fs.enums.NodeTypeEnum;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * AI工作流Service业务层处理
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+@Service
+public class CompanyWorkflowServiceImpl implements ICompanyWorkflowService {
+
+    @Autowired
+    private CompanyWorkflowMapper companyWorkflowMapper;
+    @Autowired
+    private CompanyWorkflowNodeMapper companyWorkflowNodeMapper;
+    @Autowired
+    private CompanyWorkflowEdgeMapper companyWorkflowEdgeMapper;
+    @Autowired
+    private CompanyWorkflowNodeTypeMapper companyWorkflowNodeTypeMapper;
+
+    private static List<String> aiCallNodeTypes = Arrays.asList("param", "ai_chat","end");
+    @Override
+    public CompanyWorkflowVO selectCompanyWorkflowById(Long workflowId) {
+        CompanyWorkflow workflow = companyWorkflowMapper.selectCompanyWorkflowById(workflowId);
+        if (workflow == null) {
+            return null;
+        }
+        CompanyWorkflowVO vo = new CompanyWorkflowVO();
+        BeanUtils.copyProperties(workflow, vo);
+        vo.setNodes(companyWorkflowNodeMapper.selectCompanyWorkflowNodeByWorkflowId(workflowId));
+        vo.setEdges(companyWorkflowEdgeMapper.selectCompanyWorkflowEdgeByWorkflowId(workflowId));
+        return vo;
+    }
+
+    @Override
+    public List<CompanyWorkflow> selectCompanyWorkflowList(CompanyWorkflow fsAiWorkflow) {
+        return companyWorkflowMapper.selectCompanyWorkflowList(fsAiWorkflow);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long saveCompanyWorkflow(CompanyWorkflowSaveParam param) {
+        Date now = DateUtils.getNowDate();
+        Long workflowId = param.getWorkflowId();
+
+        if (workflowId == null) {
+            // 新增
+            CompanyWorkflow workflow = new CompanyWorkflow();
+            workflow.setWorkflowName(param.getWorkflowName());
+            workflow.setWorkflowDesc(param.getWorkflowDesc());
+            workflow.setWorkflowType(param.getWorkflowType());
+            workflow.setCanvasData(param.getCanvasData());
+            workflow.setStatus(1);
+            workflow.setVersion(1);
+            workflow.setCreateTime(now);
+            companyWorkflowMapper.insertCompanyWorkflow(workflow);
+            workflowId = workflow.getWorkflowId();
+        } else {
+            // 更新
+            CompanyWorkflow workflow = new CompanyWorkflow();
+            workflow.setWorkflowId(workflowId);
+            workflow.setWorkflowName(param.getWorkflowName());
+            workflow.setWorkflowDesc(param.getWorkflowDesc());
+            workflow.setWorkflowType(param.getWorkflowType());
+            workflow.setCanvasData(param.getCanvasData());
+            workflow.setUpdateTime(now);
+            companyWorkflowMapper.updateCompanyWorkflow(workflow);
+            // 删除旧的节点和连线
+            companyWorkflowNodeMapper.deleteCompanyWorkflowNodeByWorkflowId(workflowId);
+            companyWorkflowEdgeMapper.deleteCompanyWorkflowEdgeByWorkflowId(workflowId);
+        }
+
+        // 保存节点
+        List<CompanyWorkflowNode> nodes = param.getNodes();
+        if (nodes != null && !nodes.isEmpty()) {
+            for (CompanyWorkflowNode node : nodes) {
+                node.setWorkflowId(workflowId);
+                node.setCreateTime(now);
+            }
+            companyWorkflowNodeMapper.batchInsertCompanyWorkflowNode(nodes);
+        }
+
+        // 保存连线
+        List<CompanyWorkflowEdge> edges = param.getEdges();
+        if (edges != null && !edges.isEmpty()) {
+            for (CompanyWorkflowEdge edge : edges) {
+                edge.setWorkflowId(workflowId);
+                edge.setCreateTime(now);
+            }
+            companyWorkflowEdgeMapper.batchInsertCompanyWorkflowEdge(edges);
+        }
+
+        //更新工作流后,需要删除或新增录音节点对应的id
+
+        List<String> nodeKeyOlds = companyWorkflowMapper.selectCompanyWorkflowNodeByWorkflowId(workflowId);
+        List<Long> companyUserIds = companyWorkflowMapper.selectWorkflowCompanyUserByWfId(workflowId);
+
+        // 提取新节点的nodeKey
+        List<String> newNodeKeys = nodes.stream()
+                .filter(node -> aiCallNodeTypes.contains(node.getNodeType()))
+                .map(CompanyWorkflowNode::getNodeKey)
+                .collect(Collectors.toList());
+
+        // 找出新增的nodeKey(新节点中有,旧节点中没有)
+        List<String> nodeKeyAdd = newNodeKeys.stream()
+                .filter(key -> !nodeKeyOlds.contains(key))
+                .collect(Collectors.toList());
+
+        // 找出需要删除的nodeKey(旧节点中有,新节点中没有)
+        List<String> nodeKeyDel = nodeKeyOlds.stream()
+                .filter(key -> !newNodeKeys.contains(key))
+                .collect(Collectors.toList());
+        if (!nodeKeyDel.isEmpty())companyWorkflowMapper.deleteAiWorkflowCompanyUserVoice(workflowId, nodeKeyDel);
+        if (!nodeKeyAdd.isEmpty())companyWorkflowMapper.addAiWorkflowCompanyUserVoice(workflowId,companyUserIds, nodeKeyAdd);
+        return workflowId;
+    }
+
+    @Override
+    public int updateCompanyWorkflowStatus(Long workflowId, Integer status) {
+        CompanyWorkflow workflow = new CompanyWorkflow();
+        workflow.setWorkflowId(workflowId);
+        workflow.setStatus(status);
+        workflow.setUpdateTime(DateUtils.getNowDate());
+        return companyWorkflowMapper.updateCompanyWorkflow(workflow);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int deleteCompanyWorkflowByIds(Long[] workflowIds) {
+        return companyWorkflowMapper.deleteCompanyWorkflowByIds(workflowIds);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long copyCompanyWorkflow(Long workflowId) {
+        CompanyWorkflowVO source = selectCompanyWorkflowById(workflowId);
+        if (source == null) {
+            return null;
+        }
+        Date now = DateUtils.getNowDate();
+
+        // 复制工作流
+        CompanyWorkflow workflow = new CompanyWorkflow();
+        workflow.setWorkflowName(source.getWorkflowName() + "_副本");
+        workflow.setWorkflowDesc(source.getWorkflowDesc());
+        workflow.setWorkflowType(source.getWorkflowType());
+        workflow.setCanvasData(source.getCanvasData());
+        workflow.setStatus(0);
+        workflow.setVersion(1);
+        workflow.setCreateTime(now);
+        companyWorkflowMapper.insertCompanyWorkflow(workflow);
+        Long newWorkflowId = workflow.getWorkflowId();
+
+        // 复制节点
+        List<CompanyWorkflowNode> nodes = source.getNodes();
+        if (nodes != null && !nodes.isEmpty()) {
+            for (CompanyWorkflowNode node : nodes) {
+                node.setNodeId(null);
+                node.setWorkflowId(newWorkflowId);
+                node.setCreateTime(now);
+            }
+            companyWorkflowNodeMapper.batchInsertCompanyWorkflowNode(nodes);
+        }
+
+        // 复制连线
+        List<CompanyWorkflowEdge> edges = source.getEdges();
+        if (edges != null && !edges.isEmpty()) {
+            for (CompanyWorkflowEdge edge : edges) {
+                edge.setEdgeId(null);
+                edge.setWorkflowId(newWorkflowId);
+                edge.setCreateTime(now);
+            }
+            companyWorkflowEdgeMapper.batchInsertCompanyWorkflowEdge(edges);
+        }
+
+        return newWorkflowId;
+    }
+
+    @Override
+    public List<CompanyWorkflowNodeType> selectAllEnabledNodeTypes() {
+        return companyWorkflowNodeTypeMapper.selectAllEnabledNodeTypes();
+    }
+
+    @Override
+    public CompanyWorkflowExportVO exportWorkflowJson(Long workflowId) {
+        CompanyWorkflowVO workflowVO = selectCompanyWorkflowById(workflowId);
+        if (workflowVO == null) {
+            return null;
+        }
+
+        // 获取节点类型映射
+//        List<CompanyWorkflowNodeType> nodeTypes = companyWorkflowNodeTypeMapper.selectAllEnabledNodeTypes();
+//        Map<String, String> typeNameMap = nodeTypes.stream()
+//                .collect(Collectors.toMap(CompanyWorkflowNodeType::getTypeCode, CompanyWorkflowNodeType::getTypeName));
+
+        // 构建导出VO
+        CompanyWorkflowExportVO exportVO = new CompanyWorkflowExportVO();
+        exportVO.setWorkflowId(workflowVO.getWorkflowId());
+        exportVO.setWorkflowName(workflowVO.getWorkflowName());
+        exportVO.setWorkflowDesc(workflowVO.getWorkflowDesc());
+        exportVO.setWorkflowType(workflowVO.getWorkflowType());
+        exportVO.setWorkflowTypeName(getWorkflowTypeName(workflowVO.getWorkflowType()));
+        exportVO.setVersion(workflowVO.getVersion());
+
+        // 构建节点信息
+        List<CompanyWorkflowNode> nodes = workflowVO.getNodes();
+        Map<String, String> nodeNameMap = new HashMap<>();
+        List<CompanyWorkflowExportVO.NodeInfo> nodeInfoList = new ArrayList<>();
+        if (nodes != null) {
+            for (CompanyWorkflowNode node : nodes) {
+                nodeNameMap.put(node.getNodeKey(), node.getNodeName());
+                CompanyWorkflowExportVO.NodeInfo nodeInfo = new CompanyWorkflowExportVO.NodeInfo();
+                nodeInfo.setNodeKey(node.getNodeKey());
+                nodeInfo.setNodeName(node.getNodeName());
+                nodeInfo.setNodeType(node.getNodeType());
+                nodeInfo.setNodeTypeName(NodeTypeEnum.fromValue(node.getNodeType()).getDescription());
+                nodeInfo.setNodeConfig(node.getNodeConfig());
+                nodeInfo.setPosX(node.getPosX());
+                nodeInfo.setPosY(node.getPosY());
+                nodeInfo.setSortOrder(node.getSortOrder());
+                nodeInfoList.add(nodeInfo);
+            }
+        }
+        exportVO.setNodes(nodeInfoList);
+
+        // 构建连接信息(按sortOrder排序)
+        List<CompanyWorkflowEdge> edges = workflowVO.getEdges();
+        List<CompanyWorkflowExportVO.EdgeInfo> edgeInfoList = new ArrayList<>();
+        if (edges != null) {
+            edges.sort(Comparator.comparing(e -> e.getSortOrder() != null ? e.getSortOrder() : 0));
+            for (CompanyWorkflowEdge edge : edges) {
+                CompanyWorkflowExportVO.EdgeInfo edgeInfo = new CompanyWorkflowExportVO.EdgeInfo();
+                edgeInfo.setEdgeKey(edge.getEdgeKey());
+                edgeInfo.setEdgeLabel(edge.getEdgeLabel());
+                edgeInfo.setSourceNodeKey(edge.getSourceNodeKey());
+                edgeInfo.setSourceNodeName(nodeNameMap.get(edge.getSourceNodeKey()));
+                edgeInfo.setTargetNodeKey(edge.getTargetNodeKey());
+                edgeInfo.setTargetNodeName(nodeNameMap.get(edge.getTargetNodeKey()));
+                edgeInfo.setConditionExpr(edge.getConditionExpr());
+                edgeInfo.setSortOrder(edge.getSortOrder());
+                edgeInfoList.add(edgeInfo);
+            }
+        }
+        exportVO.setEdges(edgeInfoList);
+
+        return exportVO;
+    }
+
+    @Override
+    public List<CompanyUser> listCompanyUser() {
+        return SpringUtils.getBean(CompanyUserMapper.class).selectAllCompanyUserList();
+    }
+
+    private String getWorkflowTypeName(Integer workflowType) {
+        if (workflowType == null) return "";
+        switch (workflowType) {
+            case 1: return "对话流程";
+            case 2: return "任务流程";
+            case 3: return "审批流程";
+            default: return "";
+        }
+    }
+
+    @Override
+    public List<CompanyWorkflowNode> selectWorkflowNodesByCompanyUserId(Long companyUserId, List<String> nodeTypes) {
+        // 根据企业用户ID查询工作流
+        List<CompanyWorkflow> workflows = companyWorkflowMapper.selectCompanyWorkflowByCompanyUserId(companyUserId);
+        if (workflows == null || workflows.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 收集所有工作流的指定类型节点
+        List<CompanyWorkflowNode> result = new ArrayList<>();
+        for (CompanyWorkflow workflow : workflows) {
+            List<CompanyWorkflowNode> nodes = companyWorkflowNodeMapper.selectNodesByWorkflowIdAndTypes(
+                    workflow.getWorkflowId(), nodeTypes);
+            if (nodes != null) {
+                result.addAll(nodes);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public int updateNodeVoiceUrl(Long nodeId, String voiceUrl, Long companyUserId) {
+        // 1. 查询节点是否存在
+        CompanyWorkflowNode node = companyWorkflowNodeMapper.selectCompanyWorkflowNodeById(nodeId);
+        if (node == null) {
+            return -1; // 节点不存在
+        }
+
+        // 2. 查询节点所属的工作流
+        CompanyWorkflow workflow = companyWorkflowMapper.selectCompanyWorkflowById(node.getWorkflowId());
+        if (workflow == null) {
+            return -1; // 工作流不存在
+        }
+
+        // 3. 校验工作流是否属于该企业用户
+        if (workflow.getCompanyUserId() == null || !workflow.getCompanyUserId().equals(companyUserId)) {
+            return -2; // 无权限
+        }
+
+        // 4. 更新voiceUrl
+        CompanyWorkflowNode updateNode = new CompanyWorkflowNode();
+        updateNode.setNodeId(nodeId);
+        updateNode.setVoiceUrl(voiceUrl);
+        return companyWorkflowNodeMapper.updateCompanyWorkflowNode(updateNode);
+    }
+
+    @Override
+    public CompanyUser getCompanyUserById(Long id) {
+        return SpringUtils.getBean(CompanyUserMapper.class).selectCompanyUserById( id);
+    }
+
+    @Override
+    public Boolean checkCompanyUserBeUsed(Long companyUserId) {
+        return companyWorkflowMapper.selectCompanyWorkflowCountByCompanyUserId(companyUserId) > 0;
+    }
+
+    @Override
+    public AjaxResult updateWorkflowBindCompanyUser(CompanyWorkflowUpdateBindWCParam param) {
+        if (param == null ||param.getWorkflowId() == null || param.getCompanyUserIds() == null || param.getCompanyUserIds().size() == 0) return AjaxResult.error("传参异常");
+        CompanyWorkflow fsAiWorkflowResult = companyWorkflowMapper.selectCompanyWorkflowById(Long.valueOf(param.getWorkflowId().toString()));
+        if (fsAiWorkflowResult == null)return AjaxResult.error("未查询到对应工作流,请刷新后重试");
+        int i = companyWorkflowMapper.insertCompanyWorkflowCompanyUser(param.getWorkflowId(),param.getCompanyUserIds());
+        List<CompanyWorkflowNode> fsAiWorkflowNodes = companyWorkflowNodeMapper.selectNodesByWorkflowIdAndTypes(param.getWorkflowId(), aiCallNodeTypes);
+        companyWorkflowMapper.insertAiWorkflowCompanyUserVoice(param.getWorkflowId(), param.getCompanyUserIds(), fsAiWorkflowNodes);
+        return AjaxResult.success("绑定成功");
+    }
+
+    @Override
+    public AjaxResult getBindCompanyUserByWorkflowId(Long workflowId) {
+        CompanyWorkflow fsAiWorkflow = companyWorkflowMapper.selectCompanyWorkflowById(workflowId);
+        if (fsAiWorkflow == null) return AjaxResult.error("工作流不存在");
+        return AjaxResult.success(companyWorkflowMapper.getBindCompanyUserByWorkflowId(workflowId));
+    }
+
+    @Override
+    public List<CompanyWorkflowNodeVoiceVo> getMyWorkflowNodes(Long companyUserId) {
+        return companyWorkflowMapper.getMyWorkflowNodes(companyUserId);
+    }
+}

+ 29 - 0
fs-service/src/main/java/com/fs/company/service/impl/ConditionNode.java

@@ -0,0 +1,29 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:59
+ * @description 开始节点
+ */
+public class ConditionNode extends AbstractWorkflowNode{
+
+    public ConditionNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    public ExecutionResult doExecute(ExecutionContext context) {
+        return null;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.CONDITION;
+    }
+}

+ 35 - 0
fs-service/src/main/java/com/fs/company/service/impl/DelayNode.java

@@ -0,0 +1,35 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 13:39
+ * @description 延时节点
+ */
+public class DelayNode extends AbstractWorkflowNode{
+
+    public DelayNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    protected ExecutionResult doExecute(ExecutionContext context) {
+
+        return null;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.DELAY;
+    }
+
+    @Override
+    public Boolean isAsync() {
+        return true;
+    }
+}

+ 29 - 0
fs-service/src/main/java/com/fs/company/service/impl/EndNode.java

@@ -0,0 +1,29 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:59
+ * @description 开始节点
+ */
+public class EndNode extends AbstractWorkflowNode{
+
+    public EndNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    public ExecutionResult doExecute(ExecutionContext context) {
+        return null;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.END;
+    }
+}

+ 29 - 0
fs-service/src/main/java/com/fs/company/service/impl/HttpRequestNode.java

@@ -0,0 +1,29 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:59
+ * @description
+ */
+public class HttpRequestNode extends AbstractWorkflowNode{
+
+    public HttpRequestNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    public ExecutionResult doExecute(ExecutionContext context) {
+        return null;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.HTTP_REQUEST;
+    }
+}

+ 29 - 0
fs-service/src/main/java/com/fs/company/service/impl/MessageNotificationNode.java

@@ -0,0 +1,29 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:59
+ * @description
+ */
+public class MessageNotificationNode extends AbstractWorkflowNode{
+
+    public MessageNotificationNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    public ExecutionResult doExecute(ExecutionContext context) {
+        return null;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.MESSAGE_NOTIFICATION;
+    }
+}

+ 29 - 0
fs-service/src/main/java/com/fs/company/service/impl/OutBoundTaskNode.java

@@ -0,0 +1,29 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:59
+ * @description
+ */
+public class OutBoundTaskNode extends AbstractWorkflowNode{
+
+    public OutBoundTaskNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    public ExecutionResult doExecute(ExecutionContext context) {
+        return null;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.OUTBOUND_TASK;
+    }
+}

+ 29 - 0
fs-service/src/main/java/com/fs/company/service/impl/StartNode.java

@@ -0,0 +1,29 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:59
+ * @description 开始节点
+ */
+public class StartNode extends AbstractWorkflowNode{
+
+    public StartNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    public ExecutionResult doExecute(ExecutionContext context) {
+        return null;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.START;
+    }
+}

+ 29 - 0
fs-service/src/main/java/com/fs/company/service/impl/VariableSetNode.java

@@ -0,0 +1,29 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.NodeTypeEnum;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:59
+ * @description
+ */
+public class VariableSetNode extends AbstractWorkflowNode{
+
+    public VariableSetNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    @Override
+    public ExecutionResult doExecute(ExecutionContext context) {
+        return null;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.VARIABLE_SET;
+    }
+}

+ 81 - 0
fs-service/src/main/java/com/fs/company/service/impl/WorkflowNodeFactory.java

@@ -0,0 +1,81 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.service.IWorkflowNode;
+import com.fs.company.service.IWorkflowNodeFactory;
+import com.fs.enums.NodeTypeEnum;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 11:07
+ * @description 工厂实现
+ */
+@Component
+public class WorkflowNodeFactory implements IWorkflowNodeFactory {
+
+
+    @Override
+    public IWorkflowNode createNode(String nodeKey, NodeTypeEnum type, String nodeName,
+                                    Map<String, Object> properties) {
+        IWorkflowNode node = null;
+        switch (type) {
+            case START:
+                node = new StartNode(nodeKey, nodeName, properties);
+                break;
+            case CONDITION:
+                node = new ConditionNode(nodeKey, nodeName, properties);
+                break;
+            case ACTION:
+                node = new ActionNode(nodeKey, nodeName, properties);
+                break;
+            case END:
+                node = new EndNode(nodeKey, nodeName, properties);
+                break;
+            case AI_DIALOG:
+                node = new AiDialogNode(nodeKey, nodeName, properties);
+                break;
+            case AI_ANALYSIS:
+                node = new AiAnalysisNode(nodeKey, nodeName, properties);
+                break;
+            case AI_CALL_TASK:
+                node = new AiCallTaskNode(nodeKey, nodeName, properties);
+                break;
+            case AI_SEND_MSG_TASK:
+                node = new AiSendMsgTaskNode(nodeKey, nodeName, properties);
+                break;
+            case AI_ADD_WX_TASK:
+                node = new AiAddWxTaskNode(nodeKey, nodeName, properties);
+                break;
+            case HTTP_REQUEST:
+                node = new HttpRequestNode(nodeKey, nodeName, properties);
+                break;
+            case MESSAGE_NOTIFICATION:
+                node = new MessageNotificationNode(nodeKey, nodeName, properties);
+                break;
+            case OUTBOUND_TASK:
+                node = new OutBoundTaskNode(nodeKey, nodeName, properties);
+                break;
+            case VARIABLE_SET:
+                node = new VariableSetNode(nodeKey, nodeName, properties);
+                break;
+            case DELAY:
+                node = new DelayNode(nodeKey, nodeName, properties);
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported node type: " + type);
+        }
+        injectDependencies(node);
+        return node;
+    }
+
+    private void injectDependencies(Object node) {
+//        if (node instanceof HasUserMapper) {
+//            ((HasUserMapper) node).setUserMapper(userMapper);
+//        }
+//        if (node instanceof HasSomeService) {
+//            ((HasSomeService) node).setSomeService(someService);
+//        }
+    }
+}

+ 106 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyWorkflowExportVO.java

@@ -0,0 +1,106 @@
+package com.fs.company.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * AI工作流导出JSON VO
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+@Data
+public class CompanyWorkflowExportVO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 工作流ID */
+    private Long workflowId;
+
+    /** 工作流名称 */
+    private String workflowName;
+
+    /** 工作流描述 */
+    private String workflowDesc;
+
+    /** 工作流类型 1对话流程 2任务流程 3审批流程 */
+    private Integer workflowType;
+
+    /** 工作流类型名称 */
+    private String workflowTypeName;
+
+    /** 版本号 */
+    private Integer version;
+
+    /** 节点列表 */
+    private List<NodeInfo> nodes;
+
+    /** 连接列表(按顺序) */
+    private List<EdgeInfo> edges;
+
+    /** 节点信息 */
+    @Data
+    public static class NodeInfo implements Serializable {
+        private static final long serialVersionUID = 1L;
+
+        /** 节点唯一标识 */
+        private String nodeKey;
+
+        /** 节点名称 */
+        private String nodeName;
+
+        /** 节点类型编码 */
+        private Integer nodeType;
+
+        /** 节点类型名称 */
+        private String nodeTypeName;
+
+        /** 节点配置JSON */
+        private String nodeConfig;
+
+        /** X坐标 */
+        private Integer posX;
+
+        /** Y坐标 */
+        private Integer posY;
+
+        /** 排序 */
+        private Integer sortOrder;
+
+        /**
+         * 语音URL
+         */
+        private String voiceUrl;
+    }
+
+    /** 连接信息 */
+    @Data
+    public static class EdgeInfo implements Serializable {
+        private static final long serialVersionUID = 1L;
+
+        /** 连线唯一标识 */
+        private String edgeKey;
+
+        /** 连线标签 */
+        private String edgeLabel;
+
+        /** 源节点Key */
+        private String sourceNodeKey;
+
+        /** 源节点名称 */
+        private String sourceNodeName;
+
+        /** 目标节点Key */
+        private String targetNodeKey;
+
+        /** 目标节点名称 */
+        private String targetNodeName;
+
+        /** 条件表达式 */
+        private String conditionExpr;
+
+        /** 排序 */
+        private Integer sortOrder;
+    }
+}

+ 33 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyWorkflowNodeVoiceVo.java

@@ -0,0 +1,33 @@
+package com.fs.company.vo;
+
+import lombok.Data;
+
+/**
+ *
+ */
+@Data
+public class CompanyWorkflowNodeVoiceVo {
+    /** 节点ID */
+    private Long nodeId;
+    /** 销售用户ID */
+    private Long companyUserId;
+
+    /** 工作流ID */
+    private Long workflowId;
+    /** 工作流名称 */
+    private String workflowName;
+
+    /** 节点唯一标识 */
+    private String nodeKey;
+
+    /** 节点名称 */
+    private String nodeName;
+
+    /** 节点类型 start/end/condition/action/ai/delay/http */
+    private String nodeType;
+
+    /**
+     * 语音URL
+     */
+    private String voiceUrl;
+}

+ 25 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyWorkflowVO.java

@@ -0,0 +1,25 @@
+package com.fs.company.vo;
+
+import com.fs.company.domain.CompanyWorkflow;
+import com.fs.company.domain.CompanyWorkflowEdge;
+import com.fs.company.domain.CompanyWorkflowNode;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * AI工作流完整数据VO
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+@Data
+public class CompanyWorkflowVO extends CompanyWorkflow {
+    private static final long serialVersionUID = 1L;
+
+    /** 节点列表 */
+    private List<CompanyWorkflowNode> nodes;
+
+    /** 连线列表 */
+    private List<CompanyWorkflowEdge> edges;
+}

+ 46 - 0
fs-service/src/main/java/com/fs/company/vo/ExecutionResult.java

@@ -0,0 +1,46 @@
+package com.fs.company.vo;
+
+import com.fs.enums.ExecutionStatusEnum;
+import lombok.Data;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:31
+ * @description 流程执行结果类
+ */
+@Data
+public class ExecutionResult {
+    private boolean success;
+    private String nextNodeKey;
+    private String errorMessage;
+    private Object outputData;
+    private ExecutionStatusEnum status;
+
+    public static ExecutionResult success() {
+        ExecutionResult result = new ExecutionResult();
+        result.success = true;
+        result.status = ExecutionStatusEnum.SUCCESS;
+        return result;
+    }
+    public static ExecutionResult failure() {
+        ExecutionResult result = new ExecutionResult();
+        result.success = false;
+        result.status = ExecutionStatusEnum.FAILURE;
+        return result;
+    }
+
+    public ExecutionResult withNextNodeKey(String nextNodeKey) {
+        this.nextNodeKey = nextNodeKey;
+        return this;
+    }
+
+    public ExecutionResult withOutputData(Object data) {
+        this.outputData = data;
+        return this;
+    }
+    public ExecutionResult withErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+        return this;
+    }
+
+}

+ 103 - 0
fs-service/src/main/java/com/fs/enums/ExecutionStatusEnum.java

@@ -0,0 +1,103 @@
+package com.fs.enums;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:35
+ * @description
+ */
+public enum ExecutionStatusEnum {
+    /**
+     * 成功
+     */
+    SUCCESS("SUCCESS", "执行成功", 1),
+
+    /**
+     * 失败
+     */
+    FAILURE("FAILURE", "执行失败", 2),
+
+    /**
+     * 进行中
+     */
+    RUNNING("RUNNING", "执行中", 3),
+
+    /**
+     * 暂停
+     */
+    PAUSED("PAUSED", "已暂停", 4),
+
+    /**
+     * 等待
+     */
+    WAITING("WAITING", "等待中", 5),
+
+    /**
+     * 取消
+     */
+    CANCELLED("CANCELLED", "已取消", 6),
+
+    /**
+     * 超时
+     */
+    TIMEOUT("TIMEOUT", "执行超时", 7);
+
+    private final String code;
+    private final String description;
+    private final int value;
+
+    ExecutionStatusEnum(String code, String description, int value) {
+        this.code = code;
+        this.description = description;
+        this.value = value;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    /**
+     * 根据状态码获取枚举值
+     */
+    public static ExecutionStatusEnum fromCode(String code) {
+        for (ExecutionStatusEnum status : values()) {
+            if (status.code.equals(code)) {
+                return status;
+            }
+        }
+        throw new IllegalArgumentException("Unknown ExecutionStatus code: " + code);
+    }
+
+    /**
+     * 根据int值获取枚举值
+     */
+    public static ExecutionStatusEnum fromValue(int value) {
+        for (ExecutionStatusEnum status : values()) {
+            if (status.value == value) {
+                return status;
+            }
+        }
+        throw new IllegalArgumentException("Unknown ExecutionStatus value: " + value);
+    }
+
+    /**
+     * 是否成功
+     */
+    public boolean isSuccess() {
+        return this == SUCCESS;
+    }
+
+    /**
+     * 是否失败
+     */
+    public boolean isFailure() {
+        return this == FAILURE || this == TIMEOUT || this == CANCELLED;
+    }
+}

+ 119 - 0
fs-service/src/main/java/com/fs/enums/NodeTypeEnum.java

@@ -0,0 +1,119 @@
+package com.fs.enums;
+
+/**
+ * @author MixLiu
+ * @date 2026/1/28 10:43
+ * @description 流程节点类型枚举
+ */
+public enum NodeTypeEnum {
+    /**
+     * 开始节点
+     */
+    START("START", "开始节点",1),
+
+    /**
+     * 结束节点
+     */
+    END("END", "结束节点",2),
+
+    /**
+     * 条件判断节点
+     */
+    CONDITION("CONDITION", "条件判断节点",3),
+
+    /**
+     * 动作节点
+     */
+    ACTION("ACTION", "动作节点",4),
+
+    /**
+     * AI对话节点
+     */
+    AI_DIALOG("AI_DIALOG", "AI对话节点",5),
+
+    /**
+     * AI分析节点
+     */
+    AI_ANALYSIS("AI_ANALYSIS", "AI分析节点",6),
+
+    /**
+     * 延时节点
+     */
+    DELAY("DELAY", "延时节点",7),
+
+    /**
+     * HTTP请求节点
+     */
+    HTTP_REQUEST("HTTP_REQUEST", "HTTP请求节点",8),
+
+    /**
+     * 消息通知节点
+     */
+    MESSAGE_NOTIFICATION("MESSAGE_NOTIFICATION", "消息通知节点",9),
+
+    /**
+     * 变量设置节点
+     */
+    VARIABLE_SET("VARIABLE_SET", "变量设置节点",10),
+
+    /**
+     * 外呼任务节点
+     */
+    OUTBOUND_TASK("OUTBOUND_TASK", "外呼任务节点",11),
+
+    /**
+     * AI外呼电话
+     */
+    AI_CALL_TASK("AI_CALL_TASK", "AI外呼电话",12),
+
+    /**
+     * AI发送短信
+     */
+    AI_SEND_MSG_TASK("AI_SEND_MSG_TASK", "AI发送短信",13),
+
+    /**
+     * AI添加微信
+     */
+    AI_ADD_WX_TASK("AI_ADD_WX_TASK", "AI添加微信",14);
+
+    private final String code;
+    private final String description;
+    private final Integer value;
+
+    NodeTypeEnum(String code, String description, Integer value) {
+        this.code = code;
+        this.description = description;
+        this.value = value;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+    public Integer getValue() {
+        return value;
+    }
+
+    /**
+     * 根据编码获取节点类型
+     */
+    public static NodeTypeEnum fromCode(String code) {
+        for (NodeTypeEnum type : values()) {
+            if (type.code.equals(code)) {
+                return type;
+            }
+        }
+        throw new IllegalArgumentException("Unknown NodeType: " + code);
+    }
+    public static NodeTypeEnum fromValue(Integer v) {
+        for (NodeTypeEnum type : values()) {
+            if (type.value.equals(v)) {
+                return type;
+            }
+        }
+        throw new IllegalArgumentException("Unknown NodeVal: " + v);
+    }
+}

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

@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.CompanyAiWorkflowExecLogMapper">
+    
+    <resultMap type="CompanyAiWorkflowExecLog" id="CompanyAiWorkflowExecLogResult">
+        <result property="id"    column="id"    />
+        <result property="workflowInstanceId"    column="workflow_instance_id"    />
+        <result property="nodeId"    column="node_id"    />
+        <result property="nodeName"    column="node_name"    />
+        <result property="nodeType"    column="node_type"    />
+        <result property="inputData"    column="input_data"    />
+        <result property="outputData"    column="output_data"    />
+        <result property="status"    column="status"    />
+        <result property="startTime"    column="start_time"    />
+        <result property="endTime"    column="end_time"    />
+        <result property="duration"    column="duration"    />
+        <result property="errorMessage"    column="error_message"    />
+        <result property="retryCount"    column="retry_count"    />
+        <result property="createdTime"    column="created_time"    />
+    </resultMap>
+
+    <sql id="selectCompanyAiWorkflowExecLogVo">
+        select id, workflow_instance_id, node_id, node_name, node_type, input_data, output_data, status, start_time, end_time, duration, error_message, retry_count, created_time from company_ai_workflow_exec_log
+    </sql>
+
+    <select id="selectCompanyAiWorkflowExecLogList" parameterType="CompanyAiWorkflowExecLog" resultMap="CompanyAiWorkflowExecLogResult">
+        <include refid="selectCompanyAiWorkflowExecLogVo"/>
+        <where>  
+            <if test="workflowInstanceId != null  and workflowInstanceId != ''"> and workflow_instance_id = #{workflowInstanceId}</if>
+            <if test="nodeId != null "> and node_id = #{nodeId}</if>
+            <if test="nodeName != null  and nodeName != ''"> and node_name like concat('%', #{nodeName}, '%')</if>
+            <if test="nodeType != null "> and node_type = #{nodeType}</if>
+            <if test="inputData != null  and inputData != ''"> and input_data = #{inputData}</if>
+            <if test="outputData != null  and outputData != ''"> and output_data = #{outputData}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="startTime != null "> and start_time = #{startTime}</if>
+            <if test="endTime != null "> and end_time = #{endTime}</if>
+            <if test="duration != null "> and duration = #{duration}</if>
+            <if test="errorMessage != null  and errorMessage != ''"> and error_message = #{errorMessage}</if>
+            <if test="retryCount != null "> and retry_count = #{retryCount}</if>
+            <if test="createdTime != null "> and created_time = #{createdTime}</if>
+        </where>
+    </select>
+    
+    <select id="selectCompanyAiWorkflowExecLogById" parameterType="Long" resultMap="CompanyAiWorkflowExecLogResult">
+        <include refid="selectCompanyAiWorkflowExecLogVo"/>
+        where id = #{id}
+    </select>
+        
+    <insert id="insertCompanyAiWorkflowExecLog" parameterType="CompanyAiWorkflowExecLog">
+        insert into company_ai_workflow_exec_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="workflowInstanceId != null">workflow_instance_id,</if>
+            <if test="nodeId != null">node_id,</if>
+            <if test="nodeName != null">node_name,</if>
+            <if test="nodeType != null">node_type,</if>
+            <if test="inputData != null">input_data,</if>
+            <if test="outputData != null">output_data,</if>
+            <if test="status != null">status,</if>
+            <if test="startTime != null">start_time,</if>
+            <if test="endTime != null">end_time,</if>
+            <if test="duration != null">duration,</if>
+            <if test="errorMessage != null">error_message,</if>
+            <if test="retryCount != null">retry_count,</if>
+            <if test="createdTime != null">created_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="workflowInstanceId != null">#{workflowInstanceId},</if>
+            <if test="nodeId != null">#{nodeId},</if>
+            <if test="nodeName != null">#{nodeName},</if>
+            <if test="nodeType != null">#{nodeType},</if>
+            <if test="inputData != null">#{inputData},</if>
+            <if test="outputData != null">#{outputData},</if>
+            <if test="status != null">#{status},</if>
+            <if test="startTime != null">#{startTime},</if>
+            <if test="endTime != null">#{endTime},</if>
+            <if test="duration != null">#{duration},</if>
+            <if test="errorMessage != null">#{errorMessage},</if>
+            <if test="retryCount != null">#{retryCount},</if>
+            <if test="createdTime != null">#{createdTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateCompanyAiWorkflowExecLog" parameterType="CompanyAiWorkflowExecLog">
+        update company_ai_workflow_exec_log
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="workflowInstanceId != null">workflow_instance_id = #{workflowInstanceId},</if>
+            <if test="nodeId != null">node_id = #{nodeId},</if>
+            <if test="nodeName != null">node_name = #{nodeName},</if>
+            <if test="nodeType != null">node_type = #{nodeType},</if>
+            <if test="inputData != null">input_data = #{inputData},</if>
+            <if test="outputData != null">output_data = #{outputData},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="startTime != null">start_time = #{startTime},</if>
+            <if test="endTime != null">end_time = #{endTime},</if>
+            <if test="duration != null">duration = #{duration},</if>
+            <if test="errorMessage != null">error_message = #{errorMessage},</if>
+            <if test="retryCount != null">retry_count = #{retryCount},</if>
+            <if test="createdTime != null">created_time = #{createdTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteCompanyAiWorkflowExecLogById" parameterType="Long">
+        delete from company_ai_workflow_exec_log where id = #{id}
+    </delete>
+
+    <delete id="deleteCompanyAiWorkflowExecLogByIds" parameterType="String">
+        delete from company_ai_workflow_exec_log where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

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

@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.CompanyAiWorkflowExecMapper">
+    
+    <resultMap type="CompanyAiWorkflowExec" id="CompanyAiWorkflowExecResult">
+        <result property="id"    column="id"    />
+        <result property="workflowInstanceId"    column="workflow_instance_id"    />
+        <result property="workflowId"    column="workflow_id"    />
+        <result property="workflowVersion"    column="workflow_version"    />
+        <result property="currentNodeId"    column="current_node_id"    />
+        <result property="currentNodeName"    column="current_node_name"    />
+        <result property="currentNodeType"    column="current_node_type"    />
+        <result property="status"    column="status"    />
+        <result property="variables"    column="variables"    />
+        <result property="startTime"    column="start_time"    />
+        <result property="lastUpdateTime"    column="last_update_time"    />
+        <result property="businessKey"    column="business_key"    />
+    </resultMap>
+
+    <sql id="selectCompanyAiWorkflowExecVo">
+        select id, workflow_instance_id, workflow_id, workflow_version, current_node_id, current_node_name, current_node_type, status, variables, start_time, last_update_time, business_key from company_ai_workflow_exec
+    </sql>
+
+    <select id="selectCompanyAiWorkflowExecList" parameterType="CompanyAiWorkflowExec" resultMap="CompanyAiWorkflowExecResult">
+        <include refid="selectCompanyAiWorkflowExecVo"/>
+        <where>  
+            <if test="workflowInstanceId != null  and workflowInstanceId != ''"> and workflow_instance_id = #{workflowInstanceId}</if>
+            <if test="workflowId != null "> and workflow_id = #{workflowId}</if>
+            <if test="workflowVersion != null "> and workflow_version = #{workflowVersion}</if>
+            <if test="currentNodeId != null "> and current_node_id = #{currentNodeId}</if>
+            <if test="currentNodeName != null  and currentNodeName != ''"> and current_node_name like concat('%', #{currentNodeName}, '%')</if>
+            <if test="currentNodeType != null "> and current_node_type = #{currentNodeType}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="variables != null  and variables != ''"> and variables = #{variables}</if>
+            <if test="startTime != null "> and start_time = #{startTime}</if>
+            <if test="lastUpdateTime != null "> and last_update_time = #{lastUpdateTime}</if>
+            <if test="businessKey != null  and businessKey != ''"> and business_key = #{businessKey}</if>
+        </where>
+    </select>
+    
+    <select id="selectCompanyAiWorkflowExecById" parameterType="Long" resultMap="CompanyAiWorkflowExecResult">
+        <include refid="selectCompanyAiWorkflowExecVo"/>
+        where id = #{id}
+    </select>
+        
+    <insert id="insertCompanyAiWorkflowExec" parameterType="CompanyAiWorkflowExec">
+        insert into company_ai_workflow_exec
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="workflowInstanceId != null">workflow_instance_id,</if>
+            <if test="workflowId != null">workflow_id,</if>
+            <if test="workflowVersion != null">workflow_version,</if>
+            <if test="currentNodeId != null">current_node_id,</if>
+            <if test="currentNodeName != null">current_node_name,</if>
+            <if test="currentNodeType != null">current_node_type,</if>
+            <if test="status != null">status,</if>
+            <if test="variables != null">variables,</if>
+            <if test="startTime != null">start_time,</if>
+            <if test="lastUpdateTime != null">last_update_time,</if>
+            <if test="businessKey != null">business_key,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="workflowInstanceId != null">#{workflowInstanceId},</if>
+            <if test="workflowId != null">#{workflowId},</if>
+            <if test="workflowVersion != null">#{workflowVersion},</if>
+            <if test="currentNodeId != null">#{currentNodeId},</if>
+            <if test="currentNodeName != null">#{currentNodeName},</if>
+            <if test="currentNodeType != null">#{currentNodeType},</if>
+            <if test="status != null">#{status},</if>
+            <if test="variables != null">#{variables},</if>
+            <if test="startTime != null">#{startTime},</if>
+            <if test="lastUpdateTime != null">#{lastUpdateTime},</if>
+            <if test="businessKey != null">#{businessKey},</if>
+         </trim>
+    </insert>
+
+    <update id="updateCompanyAiWorkflowExec" parameterType="CompanyAiWorkflowExec">
+        update company_ai_workflow_exec
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="workflowInstanceId != null">workflow_instance_id = #{workflowInstanceId},</if>
+            <if test="workflowId != null">workflow_id = #{workflowId},</if>
+            <if test="workflowVersion != null">workflow_version = #{workflowVersion},</if>
+            <if test="currentNodeId != null">current_node_id = #{currentNodeId},</if>
+            <if test="currentNodeName != null">current_node_name = #{currentNodeName},</if>
+            <if test="currentNodeType != null">current_node_type = #{currentNodeType},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="variables != null">variables = #{variables},</if>
+            <if test="startTime != null">start_time = #{startTime},</if>
+            <if test="lastUpdateTime != null">last_update_time = #{lastUpdateTime},</if>
+            <if test="businessKey != null">business_key = #{businessKey},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteCompanyAiWorkflowExecById" parameterType="Long">
+        delete from company_ai_workflow_exec where id = #{id}
+    </delete>
+
+    <delete id="deleteCompanyAiWorkflowExecByIds" parameterType="String">
+        delete from company_ai_workflow_exec where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

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

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.CompanyWorkflowEdgeMapper">
+
+    <resultMap type="CompanyWorkflowEdge" id="CompanyWorkflowEdgeResult">
+        <result property="edgeId" column="edge_id"/>
+        <result property="workflowId" column="workflow_id"/>
+        <result property="edgeKey" column="edge_key"/>
+        <result property="edgeLabel" column="edge_label"/>
+        <result property="sourceNodeKey" column="source_node_key"/>
+        <result property="targetNodeKey" column="target_node_key"/>
+        <result property="sourceAnchor" column="source_anchor"/>
+        <result property="targetAnchor" column="target_anchor"/>
+        <result property="edgeType" column="edge_type"/>
+        <result property="edgeColor" column="edge_color"/>
+        <result property="conditionExpr" column="condition_expr"/>
+        <result property="sortOrder" column="sort_order"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <select id="selectCompanyWorkflowEdgeById" parameterType="Long" resultMap="CompanyWorkflowEdgeResult">
+        select * from company_ai_workflow_edge where edge_id = #{edgeId}
+    </select>
+
+    <select id="selectCompanyWorkflowEdgeByWorkflowId" parameterType="Long" resultMap="CompanyWorkflowEdgeResult">
+        select * from company_ai_workflow_edge where workflow_id = #{workflowId} order by sort_order
+    </select>
+
+    <insert id="insertCompanyWorkflowEdge" parameterType="CompanyWorkflowEdge" useGeneratedKeys="true" keyProperty="edgeId">
+        insert into company_ai_workflow_edge (workflow_id, edge_key, edge_label, source_node_key, target_node_key,
+            source_anchor, target_anchor, edge_type, edge_color, condition_expr, sort_order, create_time)
+        values (#{workflowId}, #{edgeKey}, #{edgeLabel}, #{sourceNodeKey}, #{targetNodeKey},
+            #{sourceAnchor}, #{targetAnchor}, #{edgeType}, #{edgeColor}, #{conditionExpr}, #{sortOrder}, #{createTime})
+    </insert>
+
+    <insert id="batchInsertCompanyWorkflowEdge" parameterType="list">
+        insert into company_ai_workflow_edge (workflow_id, edge_key, edge_label, source_node_key, target_node_key,
+            source_anchor, target_anchor, edge_type, edge_color, condition_expr, sort_order, create_time)
+        values
+        <foreach collection="list" item="item" separator=",">
+            (#{item.workflowId}, #{item.edgeKey}, #{item.edgeLabel}, #{item.sourceNodeKey}, #{item.targetNodeKey},
+            #{item.sourceAnchor}, #{item.targetAnchor}, #{item.edgeType}, #{item.edgeColor}, #{item.conditionExpr}, #{item.sortOrder}, #{item.createTime})
+        </foreach>
+    </insert>
+
+    <update id="updateCompanyWorkflowEdge" parameterType="CompanyWorkflowEdge">
+        update company_ai_workflow_edge
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="edgeLabel != null">edge_label = #{edgeLabel},</if>
+            <if test="sourceNodeKey != null">source_node_key = #{sourceNodeKey},</if>
+            <if test="targetNodeKey != null">target_node_key = #{targetNodeKey},</if>
+            <if test="sourceAnchor != null">source_anchor = #{sourceAnchor},</if>
+            <if test="targetAnchor != null">target_anchor = #{targetAnchor},</if>
+            <if test="edgeType != null">edge_type = #{edgeType},</if>
+            <if test="edgeColor != null">edge_color = #{edgeColor},</if>
+            <if test="conditionExpr != null">condition_expr = #{conditionExpr},</if>
+            <if test="sortOrder != null">sort_order = #{sortOrder},</if>
+            update_time = now(),
+        </trim>
+        where edge_id = #{edgeId}
+    </update>
+
+    <delete id="deleteCompanyWorkflowEdgeById" parameterType="Long">
+        delete from company_ai_workflow_edge where edge_id = #{edgeId}
+    </delete>
+
+    <delete id="deleteCompanyWorkflowEdgeByWorkflowId" parameterType="Long">
+        delete from company_ai_workflow_edge where workflow_id = #{workflowId}
+    </delete>
+</mapper>

+ 194 - 0
fs-service/src/main/resources/mapper/company/CompanyWorkflowMapper.xml

@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.CompanyWorkflowMapper">
+
+    <resultMap type="CompanyWorkflow" id="CompanyWorkflowResult">
+        <result property="workflowId" column="workflow_id"/>
+        <result property="workflowName" column="workflow_name"/>
+        <result property="workflowDesc" column="workflow_desc"/>
+        <result property="workflowType" column="workflow_type"/>
+        <result property="status" column="status"/>
+        <result property="version" column="version"/>
+        <result property="canvasData" column="canvas_data"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+        <result property="remark" column="remark"/>
+        <result property="delFlag" column="del_flag"/>
+        <result property="companyUserId" column="company_user_id"/>
+    </resultMap>
+
+    <sql id="selectCompanyWorkflowVo">
+        select workflow_id, workflow_name, workflow_desc, workflow_type, status, version,
+               canvas_data, create_by, create_time, update_by, update_time, remark, del_flag, company_user_id
+        from company_ai_workflow
+    </sql>
+    <update id="deleteAiWorkflowCompanyUserVoice" >
+        update
+            company_ai_workflow_company_voice
+        set statu = 1
+        where
+            workflow_id = #{workflowId}
+            and node_key in
+            <foreach item="item" index="index" collection="nodeKeyDel" separator="," open="(" close=")">
+                #{item}
+            </foreach>
+
+    </update>
+
+
+    <select id="selectCompanyWorkflowList" parameterType="CompanyWorkflow" resultMap="CompanyWorkflowResult">
+        <include refid="selectCompanyWorkflowVo"/>
+        <where>
+            del_flag = 0
+            <if test="workflowName != null and workflowName != ''">
+                and workflow_name like concat('%', #{workflowName}, '%')
+            </if>
+            <if test="workflowType != null">and workflow_type = #{workflowType}</if>
+            <if test="status != null">and status = #{status}</if>
+            <if test="companyUserId != null">and company_user_id = #{companyUserId}</if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectCompanyWorkflowById" parameterType="Long" resultMap="CompanyWorkflowResult">
+        <include refid="selectCompanyWorkflowVo"/>
+        where workflow_id = #{workflowId} and del_flag = 0
+    </select>
+
+    <select id="selectCompanyWorkflowByCompanyUserId" parameterType="Long" resultMap="CompanyWorkflowResult">
+        <include refid="selectCompanyWorkflowVo"/>
+        where company_user_id = #{companyUserId} and del_flag = 0 and status = 1
+        order by create_time desc
+    </select>
+    <select id="selectCompanyWorkflowCountByCompanyUserId" resultType="java.lang.Integer">
+        select count(1) from company_ai_workflow where company_user_id = #{companyUserId} and del_flag = 0 and status = 1
+    </select>
+    <select id="getBindCompanyUserByWorkflowId" resultType="com.fs.company.domain.CompanyUser">
+        select
+           aw.company_user_id userId,cu.user_name,cu.nick_name
+        from company_ai_workflow_company_user aw
+        left join company_user cu
+            on aw.company_user_id = cu.user_id
+        where
+        cu.status = 0
+        and aw.del_flag = 0
+        and aw.workflow_id = #{workflowId}
+    </select>
+    <select id="selectCompanyWorkflowNodeByWorkflowId" resultType="java.lang.String">
+        select distinct
+            node_key
+        from
+            company_ai_workflow_company_voice
+        where
+            workflow_id = #{workflowId}
+    </select>
+    <select id="selectWorkflowCompanyUserByWfId" resultType="java.lang.Long">
+        select distinct
+            company_user_id
+        from
+            company_ai_workflow_company_voice
+        where
+            workflow_id = #{workflowId}
+    </select>
+    <select id="getMyWorkflowNodes" resultType="com.fs.company.vo.CompanyWorkflowNodeVoiceVo">
+        select
+            wcv.company_user_id,wcv.workflow_id,wcv.node_key,wcv.voice_url,aw.workflow_name,
+            awn.node_name,awn.node_type,awn.node_id
+        from
+            company_ai_workflow_company_voice wcv
+        left join
+            company_ai_workflow aw on aw.workflow_id = wcv.workflow_id
+        left join
+            company_ai_workflow_node awn on awn.node_key = wcv.node_key
+        where
+            wcv.company_user_id = #{companyUserId}
+          and wcv.statu = 0
+          and aw.del_flag = 0
+    </select>
+
+
+    <insert id="insertCompanyWorkflow" parameterType="CompanyWorkflow" useGeneratedKeys="true" keyProperty="workflowId">
+        insert into company_ai_workflow
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="workflowName != null and workflowName != ''">workflow_name,</if>
+            <if test="workflowDesc != null">workflow_desc,</if>
+            <if test="workflowType != null">workflow_type,</if>
+            <if test="status != null">status,</if>
+            <if test="version != null">version,</if>
+            <if test="canvasData != null">canvas_data,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="remark != null">remark,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="workflowName != null and workflowName != ''">#{workflowName},</if>
+            <if test="workflowDesc != null">#{workflowDesc},</if>
+            <if test="workflowType != null">#{workflowType},</if>
+            <if test="status != null">#{status},</if>
+            <if test="version != null">#{version},</if>
+            <if test="canvasData != null">#{canvasData},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="remark != null">#{remark},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+        </trim>
+    </insert>
+    <insert id="insertCompanyWorkflowCompanyUser">
+        insert into company_ai_workflow_company_user (workflow_id, company_user_id)
+        values
+        <foreach collection="companyUserIds" item="userId" separator=",">
+            (#{workflowId}, #{userId})
+        </foreach>
+    </insert>
+    <insert id="insertAiWorkflowCompanyUserVoice">
+        insert into company_ai_workflow_company_voice (workflow_id, company_user_id, node_key)
+        values
+        <foreach collection="fsAiWorkflowNodes" item="node" separator=",">
+            <foreach collection="companyUserIds" item="userId" separator=",">
+                (#{workflowId}, #{userId}, #{node.nodeKey})
+            </foreach>
+        </foreach>
+    </insert>
+    <insert id="addAiWorkflowCompanyUserVoice">
+        insert into company_ai_workflow_company_voice (workflow_id, company_user_id, node_key)
+            values
+        <foreach collection="companyUserIds" item="userId" separator=",">
+                <foreach collection="nodeKeyAdd" item="nodeKey" separator=",">
+                    (#{workflowId}, #{userId}, #{nodeKey})
+                </foreach>
+        </foreach>
+    </insert>
+
+    <update id="updateCompanyWorkflow" parameterType="CompanyWorkflow">
+        update company_ai_workflow
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="workflowName != null and workflowName != ''">workflow_name = #{workflowName},</if>
+            <if test="workflowDesc != null">workflow_desc = #{workflowDesc},</if>
+            <if test="workflowType != null">workflow_type = #{workflowType},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="version != null">version = #{version},</if>
+            <if test="canvasData != null">canvas_data = #{canvasData},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null">remark = #{remark},</if>
+        </trim>
+        where workflow_id = #{workflowId}
+    </update>
+
+    <update id="deleteCompanyWorkflowById" parameterType="Long">
+        update company_ai_workflow set del_flag = 1 where workflow_id = #{workflowId}
+    </update>
+
+    <update id="deleteCompanyWorkflowByIds" parameterType="Long">
+        update company_ai_workflow set del_flag = 1 where workflow_id in
+        <foreach item="workflowId" collection="array" open="(" separator="," close=")">
+            #{workflowId}
+        </foreach>
+    </update>
+    <update id="updateWorkflowBindCompanyUser">
+        update company_ai_workflow set company_user_id = #{companyUserId} where workflow_id = #{workflowId}
+    </update>
+</mapper>

+ 89 - 0
fs-service/src/main/resources/mapper/company/CompanyWorkflowNodeMapper.xml

@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.CompanyWorkflowNodeMapper">
+
+    <resultMap type="CompanyWorkflowNode" id="CompanyWorkflowNodeResult">
+        <result property="nodeId" column="node_id"/>
+        <result property="workflowId" column="workflow_id"/>
+        <result property="nodeKey" column="node_key"/>
+        <result property="nodeName" column="node_name"/>
+        <result property="nodeType" column="node_type"/>
+        <result property="nodeIcon" column="node_icon"/>
+        <result property="nodeColor" column="node_color"/>
+        <result property="posX" column="pos_x"/>
+        <result property="posY" column="pos_y"/>
+        <result property="width" column="width"/>
+        <result property="height" column="height"/>
+        <result property="nodeConfig" column="node_config"/>
+        <result property="sortOrder" column="sort_order"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateTime" column="update_time"/>
+        <result property="voiceUrl" column="voice_url"/>
+    </resultMap>
+
+    <select id="selectCompanyWorkflowNodeById" parameterType="Long" resultMap="CompanyWorkflowNodeResult">
+        select * from company_ai_workflow_node where node_id = #{nodeId}
+    </select>
+
+    <select id="selectCompanyWorkflowNodeByWorkflowId" parameterType="Long" resultMap="CompanyWorkflowNodeResult">
+        select * from company_ai_workflow_node where workflow_id = #{workflowId} order by sort_order
+    </select>
+
+    <select id="selectNodesByWorkflowIdAndTypes" resultMap="CompanyWorkflowNodeResult">
+        select * from company_ai_workflow_node
+        where workflow_id = #{workflowId}
+        and node_type in
+        <foreach collection="nodeTypes" item="nodeType" open="(" separator="," close=")">
+            #{nodeType}
+        </foreach>
+        order by sort_order
+    </select>
+
+    <insert id="insertCompanyWorkflowNode" parameterType="CompanyWorkflowNode" useGeneratedKeys="true" keyProperty="nodeId">
+        insert into company_ai_workflow_node (workflow_id, node_key, node_name, node_type, node_icon, node_color,
+            pos_x, pos_y, width, height, node_config, sort_order, create_time)
+        values (#{workflowId}, #{nodeKey}, #{nodeName}, #{nodeType}, #{nodeIcon}, #{nodeColor},
+            #{posX}, #{posY}, #{width}, #{height}, #{nodeConfig}, #{sortOrder}, #{createTime})
+    </insert>
+
+    <insert id="batchInsertCompanyWorkflowNode" parameterType="list">
+        insert into company_ai_workflow_node (workflow_id, node_key, node_name, node_type, node_icon, node_color,
+            pos_x, pos_y, width, height, node_config, sort_order, create_time)
+        values
+        <foreach collection="list" item="item" separator=",">
+            (#{item.workflowId}, #{item.nodeKey}, #{item.nodeName}, #{item.nodeType}, #{item.nodeIcon}, #{item.nodeColor},
+            #{item.posX}, #{item.posY}, #{item.width}, #{item.height}, #{item.nodeConfig}, #{item.sortOrder}, #{item.createTime})
+        </foreach>
+    </insert>
+
+    <update id="updateCompanyWorkflowNode" parameterType="CompanyWorkflowNode">
+        update company_ai_workflow_node
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="nodeName != null">node_name = #{nodeName},</if>
+            <if test="nodeType != null">node_type = #{nodeType},</if>
+            <if test="nodeIcon != null">node_icon = #{nodeIcon},</if>
+            <if test="nodeColor != null">node_color = #{nodeColor},</if>
+            <if test="posX != null">pos_x = #{posX},</if>
+            <if test="posY != null">pos_y = #{posY},</if>
+            <if test="width != null">width = #{width},</if>
+            <if test="height != null">height = #{height},</if>
+            <if test="nodeConfig != null">node_config = #{nodeConfig},</if>
+            <if test="sortOrder != null">sort_order = #{sortOrder},</if>
+            <if test="voiceUrl != null">voice_url = #{voiceUrl},</if>
+            update_time = now(),
+        </trim>
+        where node_id = #{nodeId}
+    </update>
+
+    <delete id="deleteCompanyWorkflowNodeById" parameterType="Long">
+        delete from company_ai_workflow_node where node_id = #{nodeId}
+    </delete>
+
+    <delete id="deleteCompanyWorkflowNodeByWorkflowId" parameterType="Long">
+        delete from company_ai_workflow_node where workflow_id = #{workflowId}
+    </delete>
+
+    <select id="selectNodeByWorkflowIdAndNodeKey" resultType="CompanyWorkflowNode">
+        select * from company_ai_workflow_node where workflow_id = #{workflowId} and node_key = #{nodeKey}
+    </select>
+</mapper>

+ 40 - 0
fs-service/src/main/resources/mapper/company/CompanyWorkflowNodeTypeMapper.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.CompanyWorkflowNodeTypeMapper">
+
+    <resultMap type="CompanyWorkflowNodeType" id="CompanyWorkflowNodeTypeResult">
+        <result property="typeId" column="type_id"/>
+        <result property="typeCode" column="type_code"/>
+        <result property="typeName" column="type_name"/>
+        <result property="typeIcon" column="type_icon"/>
+        <result property="typeColor" column="type_color"/>
+        <result property="typeCategory" column="type_category"/>
+        <result property="defaultConfig" column="default_config"/>
+        <result property="sortOrder" column="sort_order"/>
+        <result property="status" column="status"/>
+        <result property="createTime" column="create_time"/>
+    </resultMap>
+
+    <select id="selectCompanyWorkflowNodeTypeById" parameterType="Long" resultMap="CompanyWorkflowNodeTypeResult">
+        select * from company_ai_workflow_node_type where type_id = #{typeId}
+    </select>
+
+    <select id="selectCompanyWorkflowNodeTypeByCode" parameterType="String" resultMap="CompanyWorkflowNodeTypeResult">
+        select * from company_ai_workflow_node_type where type_code = #{typeCode}
+    </select>
+
+    <select id="selectCompanyWorkflowNodeTypeList" parameterType="CompanyWorkflowNodeType" resultMap="CompanyWorkflowNodeTypeResult">
+        select * from company_ai_workflow_node_type
+        <where>
+            <if test="typeCode != null and typeCode != ''">and type_code = #{typeCode}</if>
+            <if test="typeName != null and typeName != ''">and type_name like concat('%', #{typeName}, '%')</if>
+            <if test="typeCategory != null and typeCategory != ''">and type_category = #{typeCategory}</if>
+            <if test="status != null">and status = #{status}</if>
+        </where>
+        order by sort_order
+    </select>
+
+    <select id="selectAllEnabledNodeTypes" resultMap="CompanyWorkflowNodeTypeResult">
+        select * from company_ai_workflow_node_type where status = 1 order by sort_order
+    </select>
+</mapper>