Sfoglia il codice sorgente

益寿缘AI语音复刻接入豆包
-报错优化
AI外呼工作流绘制
-绘制工具初版编写

lk 6 giorni fa
parent
commit
4f28958eec
22 ha cambiato i file con 1037 aggiunte e 17 eliminazioni
  1. 112 0
      fs-admin/src/main/java/com/fs/his/controller/FsAiWorkflowController.java
  2. 1 1
      fs-service/src/main/java/com/fs/aiSoundReplication/VoiceCloneController.java
  3. 3 2
      fs-service/src/main/java/com/fs/aiSoundReplication/service/VoiceCloneService.java
  4. 2 1
      fs-service/src/main/java/com/fs/aiSoundReplication/service/impl/TtsServiceImpl.java
  5. 19 12
      fs-service/src/main/java/com/fs/aiSoundReplication/service/impl/VoiceCloneServiceImpl.java
  6. 44 0
      fs-service/src/main/java/com/fs/his/domain/FsAiWorkflow.java
  7. 62 0
      fs-service/src/main/java/com/fs/his/domain/FsAiWorkflowEdge.java
  8. 65 0
      fs-service/src/main/java/com/fs/his/domain/FsAiWorkflowNode.java
  9. 49 0
      fs-service/src/main/java/com/fs/his/domain/FsAiWorkflowNodeType.java
  10. 27 0
      fs-service/src/main/java/com/fs/his/mapper/FsAiWorkflowEdgeMapper.java
  11. 25 0
      fs-service/src/main/java/com/fs/his/mapper/FsAiWorkflowMapper.java
  12. 27 0
      fs-service/src/main/java/com/fs/his/mapper/FsAiWorkflowNodeMapper.java
  13. 21 0
      fs-service/src/main/java/com/fs/his/mapper/FsAiWorkflowNodeTypeMapper.java
  14. 40 0
      fs-service/src/main/java/com/fs/his/param/FsAiWorkflowSaveParam.java
  15. 52 0
      fs-service/src/main/java/com/fs/his/service/IFsAiWorkflowService.java
  16. 181 0
      fs-service/src/main/java/com/fs/his/service/impl/FsAiWorkflowServiceImpl.java
  17. 25 0
      fs-service/src/main/java/com/fs/his/vo/FsAiWorkflowVO.java
  18. 71 0
      fs-service/src/main/resources/mapper/his/FsAiWorkflowEdgeMapper.xml
  19. 97 0
      fs-service/src/main/resources/mapper/his/FsAiWorkflowMapper.xml
  20. 73 0
      fs-service/src/main/resources/mapper/his/FsAiWorkflowNodeMapper.xml
  21. 40 0
      fs-service/src/main/resources/mapper/his/FsAiWorkflowNodeTypeMapper.xml
  22. 1 1
      fs-user-app/src/main/java/com/fs/app/controller/CompanyUserController.java

+ 112 - 0
fs-admin/src/main/java/com/fs/his/controller/FsAiWorkflowController.java

@@ -0,0 +1,112 @@
+package com.fs.his.controller;
+
+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.his.domain.FsAiWorkflow;
+import com.fs.his.domain.FsAiWorkflowNodeType;
+import com.fs.his.param.FsAiWorkflowSaveParam;
+import com.fs.his.service.IFsAiWorkflowService;
+import com.fs.his.vo.FsAiWorkflowVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * AI工作流Controller
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+@RestController
+@RequestMapping("/his/aiWorkflow")
+public class FsAiWorkflowController extends BaseController {
+
+    @Autowired
+    private IFsAiWorkflowService fsAiWorkflowService;
+
+    /**
+     * 查询AI工作流列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(FsAiWorkflow fsAiWorkflow) {
+        startPage();
+        List<FsAiWorkflow> list = fsAiWorkflowService.selectFsAiWorkflowList(fsAiWorkflow);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出AI工作流列表
+     */
+    @Log(title = "AI工作流", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsAiWorkflow fsAiWorkflow) {
+        List<FsAiWorkflow> list = fsAiWorkflowService.selectFsAiWorkflowList(fsAiWorkflow);
+        ExcelUtil<FsAiWorkflow> util = new ExcelUtil<FsAiWorkflow>(FsAiWorkflow.class);
+        return util.exportExcel(list, "AI工作流数据");
+    }
+
+    /**
+     * 获取AI工作流详细信息(包含节点和连线)
+     */
+    @GetMapping(value = "/{workflowId}")
+    public AjaxResult getInfo(@PathVariable("workflowId") Long workflowId) {
+        return AjaxResult.success(fsAiWorkflowService.selectFsAiWorkflowById(workflowId));
+    }
+
+    /**
+     * 保存AI工作流(新增或更新)
+     */
+    @Log(title = "AI工作流", businessType = BusinessType.INSERT)
+    @PostMapping("/save")
+    public AjaxResult save(@RequestBody FsAiWorkflowSaveParam param) {
+        Long workflowId = fsAiWorkflowService.saveFsAiWorkflow(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(fsAiWorkflowService.updateFsAiWorkflowStatus(workflowId, status));
+    }
+
+    /**
+     * 删除AI工作流
+     */
+    @Log(title = "AI工作流", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{workflowIds}")
+    public AjaxResult remove(@PathVariable Long[] workflowIds) {
+        return toAjax(fsAiWorkflowService.deleteFsAiWorkflowByIds(workflowIds));
+    }
+
+    /**
+     * 复制AI工作流
+     */
+    @Log(title = "AI工作流", businessType = BusinessType.INSERT)
+    @PostMapping("/copy/{workflowId}")
+    public AjaxResult copy(@PathVariable("workflowId") Long workflowId) {
+        Long newWorkflowId = fsAiWorkflowService.copyFsAiWorkflow(workflowId);
+        if (newWorkflowId != null) {
+            return AjaxResult.success(newWorkflowId);
+        }
+        return AjaxResult.error("复制失败,工作流不存在");
+    }
+
+    /**
+     * 获取所有启用的节点类型
+     */
+    @GetMapping("/nodeTypes")
+    public AjaxResult getNodeTypes() {
+        List<FsAiWorkflowNodeType> list = fsAiWorkflowService.selectAllEnabledNodeTypes();
+        return AjaxResult.success(list);
+    }
+}

+ 1 - 1
fs-service/src/main/java/com/fs/aiSoundReplication/VoiceCloneController.java

@@ -137,7 +137,7 @@ public class VoiceCloneController {
     }
     }
     @PostMapping("/upload")
     @PostMapping("/upload")
     @ApiOperation("上传音频训练音色")
     @ApiOperation("上传音频训练音色")
-    public UploadResponse uploadVoice(
+    public R uploadVoice(
             @ApiParam(value = "音色ID", required = true) @RequestParam String speakerId,
             @ApiParam(value = "音色ID", required = true) @RequestParam String speakerId,
             @ApiParam(value = "音频文件", required = true) @RequestParam MultipartFile audioFile,
             @ApiParam(value = "音频文件", required = true) @RequestParam MultipartFile audioFile,
             @ApiParam(value = "模型类型(1-ICL1.0, 4-ICL2.0)", defaultValue = "4")
             @ApiParam(value = "模型类型(1-ICL1.0, 4-ICL2.0)", defaultValue = "4")

+ 3 - 2
fs-service/src/main/java/com/fs/aiSoundReplication/service/VoiceCloneService.java

@@ -3,6 +3,7 @@ package com.fs.aiSoundReplication.service;
 
 
 import com.fs.aiSoundReplication.param.StatusResponse;
 import com.fs.aiSoundReplication.param.StatusResponse;
 import com.fs.aiSoundReplication.param.UploadResponse;
 import com.fs.aiSoundReplication.param.UploadResponse;
+import com.fs.common.core.domain.R;
 import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartFile;
 
 
 public interface VoiceCloneService {
 public interface VoiceCloneService {
@@ -15,8 +16,8 @@ public interface VoiceCloneService {
      * @param language 语种 0-中文, 1-英文等
      * @param language 语种 0-中文, 1-英文等
      * @return 上传响应
      * @return 上传响应
      */
      */
-    UploadResponse uploadVoice(String speakerId, MultipartFile audioFile,
-                               Integer modelType, Integer language);
+    R uploadVoice(String speakerId, MultipartFile audioFile,
+                  Integer modelType, Integer language);
 
 
     /**
     /**
      * 上传音频训练音色(使用文件路径)
      * 上传音频训练音色(使用文件路径)

+ 2 - 1
fs-service/src/main/java/com/fs/aiSoundReplication/service/impl/TtsServiceImpl.java

@@ -73,7 +73,8 @@ public class TtsServiceImpl implements TtsService {
 
 
             // 6. 检查音频数据
             // 6. 检查音频数据
             if (bytes == null || bytes.length == 0) {
             if (bytes == null || bytes.length == 0) {
-                throw new VoiceCloneException(-1, "音频数据为空");
+                log.error("音频数据为空,VoiceType: {}", request.getVoiceType());
+                return null;
             }
             }
 
 
             // 7. 自动保存音频文件
             // 7. 自动保存音频文件

+ 19 - 12
fs-service/src/main/java/com/fs/aiSoundReplication/service/impl/VoiceCloneServiceImpl.java

@@ -9,6 +9,7 @@ import com.fs.aiSoundReplication.exception.VoiceCloneException;
 import com.fs.aiSoundReplication.param.*;
 import com.fs.aiSoundReplication.param.*;
 import com.fs.aiSoundReplication.service.VoiceCloneService;
 import com.fs.aiSoundReplication.service.VoiceCloneService;
 import com.fs.aiSoundReplication.util.FileUtil;
 import com.fs.aiSoundReplication.util.FileUtil;
+import com.fs.common.core.domain.R;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import okhttp3.*;
 import okhttp3.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -35,11 +36,12 @@ public class VoiceCloneServiceImpl implements VoiceCloneService {
     private static final String RESOURCE_ID_HEADER = "Resource-Id";
     private static final String RESOURCE_ID_HEADER = "Resource-Id";
 
 
     @Override
     @Override
-    public UploadResponse uploadVoice(String speakerId, MultipartFile audioFile,
-                                      Integer modelType, Integer language) {
+    public R uploadVoice(String speakerId, MultipartFile audioFile,
+                         Integer modelType, Integer language) {
         try {
         try {
             // 1. 参数校验
             // 1. 参数校验
-            validateUploadParams(speakerId, audioFile, modelType);
+            if (!validateUploadParams(speakerId, audioFile, modelType))
+                return R.error("参数错误");
 
 
             // 2. 构建请求
             // 2. 构建请求
             VoiceCloneRequest request = buildUploadRequest(speakerId, audioFile, modelType, language);
             VoiceCloneRequest request = buildUploadRequest(speakerId, audioFile, modelType, language);
@@ -56,15 +58,16 @@ public class VoiceCloneServiceImpl implements VoiceCloneService {
             checkResponse(response);
             checkResponse(response);
 
 
             log.info("音色上传成功,speakerId: {}", response.getSpeakerId());
             log.info("音色上传成功,speakerId: {}", response.getSpeakerId());
-            return response;
+            return R.ok();
 
 
         } catch (JsonProcessingException e) {
         } catch (JsonProcessingException e) {
-            log.error("JSON序列化失败", e);
-            throw new VoiceCloneException("JSON序列化失败", e);
+            log.error("JSON序列化失败,声音复刻发送豆包失败", e);
+            R.error("JSON序列化失败");
         } catch (IOException e) {
         } catch (IOException e) {
-            log.error("文件处理失败", e);
-            throw new VoiceCloneException("文件处理失败", e);
+            log.error("文件处理失败,声音复刻发送豆包失败", e);
+            R.error("文件处理失败");
         }
         }
+        return R.ok();
     }
     }
 
 
     @Override
     @Override
@@ -224,19 +227,23 @@ public class VoiceCloneServiceImpl implements VoiceCloneService {
 
 
     // ============ 私有方法 ============
     // ============ 私有方法 ============
 
 
-    private void validateUploadParams(String speakerId, MultipartFile audioFile,
+    private boolean validateUploadParams(String speakerId, MultipartFile audioFile,
                                       Integer modelType) {
                                       Integer modelType) {
         if (speakerId == null || speakerId.trim().isEmpty()) {
         if (speakerId == null || speakerId.trim().isEmpty()) {
-            throw new VoiceCloneException(1001, "speakerId不能为空");
+            log.error("参数 speakerId 不能为空");
+            return false;
         }
         }
 
 
         if (audioFile != null && audioFile.isEmpty()) {
         if (audioFile != null && audioFile.isEmpty()) {
-            throw new VoiceCloneException(1001, "音频文件不能为空");
+            log.error("参数 audioFile 不能为空");
+            return false;
         }
         }
 
 
         if (modelType != null && modelType < 0 || modelType > 4) {
         if (modelType != null && modelType < 0 || modelType > 4) {
-            throw new VoiceCloneException(1001, "modelType参数错误,应为1-4");
+            log.error("参数 modelType 错误,应为1-4");
+            return false;
         }
         }
+        return true;
     }
     }
 
 
     private VoiceCloneRequest buildUploadRequest(String speakerId, MultipartFile audioFile,
     private VoiceCloneRequest buildUploadRequest(String speakerId, MultipartFile audioFile,

+ 44 - 0
fs-service/src/main/java/com/fs/his/domain/FsAiWorkflow.java

@@ -0,0 +1,44 @@
+package com.fs.his.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 FsAiWorkflow 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;
+}

+ 62 - 0
fs-service/src/main/java/com/fs/his/domain/FsAiWorkflowEdge.java

@@ -0,0 +1,62 @@
+package com.fs.his.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 FsAiWorkflowEdge 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;
+}

+ 65 - 0
fs-service/src/main/java/com/fs/his/domain/FsAiWorkflowNode.java

@@ -0,0 +1,65 @@
+package com.fs.his.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 FsAiWorkflowNode 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 String 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;
+}

+ 49 - 0
fs-service/src/main/java/com/fs/his/domain/FsAiWorkflowNodeType.java

@@ -0,0 +1,49 @@
+package com.fs.his.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 FsAiWorkflowNodeType 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;
+}

+ 27 - 0
fs-service/src/main/java/com/fs/his/mapper/FsAiWorkflowEdgeMapper.java

@@ -0,0 +1,27 @@
+package com.fs.his.mapper;
+
+import com.fs.his.domain.FsAiWorkflowEdge;
+import java.util.List;
+
+/**
+ * AI工作流连线Mapper接口
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+public interface FsAiWorkflowEdgeMapper {
+
+    FsAiWorkflowEdge selectFsAiWorkflowEdgeById(Long edgeId);
+
+    List<FsAiWorkflowEdge> selectFsAiWorkflowEdgeByWorkflowId(Long workflowId);
+
+    int insertFsAiWorkflowEdge(FsAiWorkflowEdge edge);
+
+    int batchInsertFsAiWorkflowEdge(List<FsAiWorkflowEdge> edges);
+
+    int updateFsAiWorkflowEdge(FsAiWorkflowEdge edge);
+
+    int deleteFsAiWorkflowEdgeById(Long edgeId);
+
+    int deleteFsAiWorkflowEdgeByWorkflowId(Long workflowId);
+}

+ 25 - 0
fs-service/src/main/java/com/fs/his/mapper/FsAiWorkflowMapper.java

@@ -0,0 +1,25 @@
+package com.fs.his.mapper;
+
+import com.fs.his.domain.FsAiWorkflow;
+import java.util.List;
+
+/**
+ * AI工作流Mapper接口
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+public interface FsAiWorkflowMapper {
+
+    FsAiWorkflow selectFsAiWorkflowById(Long workflowId);
+
+    List<FsAiWorkflow> selectFsAiWorkflowList(FsAiWorkflow fsAiWorkflow);
+
+    int insertFsAiWorkflow(FsAiWorkflow fsAiWorkflow);
+
+    int updateFsAiWorkflow(FsAiWorkflow fsAiWorkflow);
+
+    int deleteFsAiWorkflowById(Long workflowId);
+
+    int deleteFsAiWorkflowByIds(Long[] workflowIds);
+}

+ 27 - 0
fs-service/src/main/java/com/fs/his/mapper/FsAiWorkflowNodeMapper.java

@@ -0,0 +1,27 @@
+package com.fs.his.mapper;
+
+import com.fs.his.domain.FsAiWorkflowNode;
+import java.util.List;
+
+/**
+ * AI工作流节点Mapper接口
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+public interface FsAiWorkflowNodeMapper {
+
+    FsAiWorkflowNode selectFsAiWorkflowNodeById(Long nodeId);
+
+    List<FsAiWorkflowNode> selectFsAiWorkflowNodeByWorkflowId(Long workflowId);
+
+    int insertFsAiWorkflowNode(FsAiWorkflowNode node);
+
+    int batchInsertFsAiWorkflowNode(List<FsAiWorkflowNode> nodes);
+
+    int updateFsAiWorkflowNode(FsAiWorkflowNode node);
+
+    int deleteFsAiWorkflowNodeById(Long nodeId);
+
+    int deleteFsAiWorkflowNodeByWorkflowId(Long workflowId);
+}

+ 21 - 0
fs-service/src/main/java/com/fs/his/mapper/FsAiWorkflowNodeTypeMapper.java

@@ -0,0 +1,21 @@
+package com.fs.his.mapper;
+
+import com.fs.his.domain.FsAiWorkflowNodeType;
+import java.util.List;
+
+/**
+ * 工作流节点类型Mapper接口
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+public interface FsAiWorkflowNodeTypeMapper {
+
+    FsAiWorkflowNodeType selectFsAiWorkflowNodeTypeById(Long typeId);
+
+    FsAiWorkflowNodeType selectFsAiWorkflowNodeTypeByCode(String typeCode);
+
+    List<FsAiWorkflowNodeType> selectFsAiWorkflowNodeTypeList(FsAiWorkflowNodeType nodeType);
+
+    List<FsAiWorkflowNodeType> selectAllEnabledNodeTypes();
+}

+ 40 - 0
fs-service/src/main/java/com/fs/his/param/FsAiWorkflowSaveParam.java

@@ -0,0 +1,40 @@
+package com.fs.his.param;
+
+import com.fs.his.domain.FsAiWorkflowEdge;
+import com.fs.his.domain.FsAiWorkflowNode;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * AI工作流保存参数
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+@Data
+public class FsAiWorkflowSaveParam 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<FsAiWorkflowNode> nodes;
+
+    /** 连线列表 */
+    private List<FsAiWorkflowEdge> edges;
+}

+ 52 - 0
fs-service/src/main/java/com/fs/his/service/IFsAiWorkflowService.java

@@ -0,0 +1,52 @@
+package com.fs.his.service;
+
+import com.fs.his.domain.FsAiWorkflow;
+import com.fs.his.domain.FsAiWorkflowNodeType;
+import com.fs.his.param.FsAiWorkflowSaveParam;
+import com.fs.his.vo.FsAiWorkflowVO;
+
+import java.util.List;
+
+/**
+ * AI工作流Service接口
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+public interface IFsAiWorkflowService {
+
+    /**
+     * 查询工作流
+     */
+    FsAiWorkflowVO selectFsAiWorkflowById(Long workflowId);
+
+    /**
+     * 查询工作流列表
+     */
+    List<FsAiWorkflow> selectFsAiWorkflowList(FsAiWorkflow fsAiWorkflow);
+
+    /**
+     * 保存工作流(新增或更新)
+     */
+    Long saveFsAiWorkflow(FsAiWorkflowSaveParam param);
+
+    /**
+     * 修改工作流状态
+     */
+    int updateFsAiWorkflowStatus(Long workflowId, Integer status);
+
+    /**
+     * 删除工作流
+     */
+    int deleteFsAiWorkflowByIds(Long[] workflowIds);
+
+    /**
+     * 复制工作流
+     */
+    Long copyFsAiWorkflow(Long workflowId);
+
+    /**
+     * 获取所有启用的节点类型
+     */
+    List<FsAiWorkflowNodeType> selectAllEnabledNodeTypes();
+}

+ 181 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsAiWorkflowServiceImpl.java

@@ -0,0 +1,181 @@
+package com.fs.his.service.impl;
+
+import com.fs.common.utils.DateUtils;
+import com.fs.his.domain.FsAiWorkflow;
+import com.fs.his.domain.FsAiWorkflowEdge;
+import com.fs.his.domain.FsAiWorkflowNode;
+import com.fs.his.domain.FsAiWorkflowNodeType;
+import com.fs.his.mapper.FsAiWorkflowEdgeMapper;
+import com.fs.his.mapper.FsAiWorkflowMapper;
+import com.fs.his.mapper.FsAiWorkflowNodeMapper;
+import com.fs.his.mapper.FsAiWorkflowNodeTypeMapper;
+import com.fs.his.param.FsAiWorkflowSaveParam;
+import com.fs.his.service.IFsAiWorkflowService;
+import com.fs.his.vo.FsAiWorkflowVO;
+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.Date;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * AI工作流Service业务层处理
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+@Service
+public class FsAiWorkflowServiceImpl implements IFsAiWorkflowService {
+
+    @Autowired
+    private FsAiWorkflowMapper fsAiWorkflowMapper;
+    @Autowired
+    private FsAiWorkflowNodeMapper fsAiWorkflowNodeMapper;
+    @Autowired
+    private FsAiWorkflowEdgeMapper fsAiWorkflowEdgeMapper;
+    @Autowired
+    private FsAiWorkflowNodeTypeMapper fsAiWorkflowNodeTypeMapper;
+
+    @Override
+    public FsAiWorkflowVO selectFsAiWorkflowById(Long workflowId) {
+        FsAiWorkflow workflow = fsAiWorkflowMapper.selectFsAiWorkflowById(workflowId);
+        if (workflow == null) {
+            return null;
+        }
+        FsAiWorkflowVO vo = new FsAiWorkflowVO();
+        BeanUtils.copyProperties(workflow, vo);
+        vo.setNodes(fsAiWorkflowNodeMapper.selectFsAiWorkflowNodeByWorkflowId(workflowId));
+        vo.setEdges(fsAiWorkflowEdgeMapper.selectFsAiWorkflowEdgeByWorkflowId(workflowId));
+        return vo;
+    }
+
+    @Override
+    public List<FsAiWorkflow> selectFsAiWorkflowList(FsAiWorkflow fsAiWorkflow) {
+        return fsAiWorkflowMapper.selectFsAiWorkflowList(fsAiWorkflow);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long saveFsAiWorkflow(FsAiWorkflowSaveParam param) {
+        Date now = DateUtils.getNowDate();
+        Long workflowId = param.getWorkflowId();
+
+        if (workflowId == null) {
+            // 新增
+            FsAiWorkflow workflow = new FsAiWorkflow();
+            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);
+            fsAiWorkflowMapper.insertFsAiWorkflow(workflow);
+            workflowId = workflow.getWorkflowId();
+        } else {
+            // 更新
+            FsAiWorkflow workflow = new FsAiWorkflow();
+            workflow.setWorkflowId(workflowId);
+            workflow.setWorkflowName(param.getWorkflowName());
+            workflow.setWorkflowDesc(param.getWorkflowDesc());
+            workflow.setWorkflowType(param.getWorkflowType());
+            workflow.setCanvasData(param.getCanvasData());
+            workflow.setUpdateTime(now);
+            fsAiWorkflowMapper.updateFsAiWorkflow(workflow);
+            // 删除旧的节点和连线
+            fsAiWorkflowNodeMapper.deleteFsAiWorkflowNodeByWorkflowId(workflowId);
+            fsAiWorkflowEdgeMapper.deleteFsAiWorkflowEdgeByWorkflowId(workflowId);
+        }
+
+        // 保存节点
+        List<FsAiWorkflowNode> nodes = param.getNodes();
+        if (nodes != null && !nodes.isEmpty()) {
+            for (FsAiWorkflowNode node : nodes) {
+                node.setWorkflowId(workflowId);
+                node.setCreateTime(now);
+            }
+            fsAiWorkflowNodeMapper.batchInsertFsAiWorkflowNode(nodes);
+        }
+
+        // 保存连线
+        List<FsAiWorkflowEdge> edges = param.getEdges();
+        if (edges != null && !edges.isEmpty()) {
+            for (FsAiWorkflowEdge edge : edges) {
+                edge.setWorkflowId(workflowId);
+                edge.setCreateTime(now);
+            }
+            fsAiWorkflowEdgeMapper.batchInsertFsAiWorkflowEdge(edges);
+        }
+
+        return workflowId;
+    }
+
+    @Override
+    public int updateFsAiWorkflowStatus(Long workflowId, Integer status) {
+        FsAiWorkflow workflow = new FsAiWorkflow();
+        workflow.setWorkflowId(workflowId);
+        workflow.setStatus(status);
+        workflow.setUpdateTime(DateUtils.getNowDate());
+        return fsAiWorkflowMapper.updateFsAiWorkflow(workflow);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int deleteFsAiWorkflowByIds(Long[] workflowIds) {
+        return fsAiWorkflowMapper.deleteFsAiWorkflowByIds(workflowIds);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long copyFsAiWorkflow(Long workflowId) {
+        FsAiWorkflowVO source = selectFsAiWorkflowById(workflowId);
+        if (source == null) {
+            return null;
+        }
+        Date now = DateUtils.getNowDate();
+
+        // 复制工作流
+        FsAiWorkflow workflow = new FsAiWorkflow();
+        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);
+        fsAiWorkflowMapper.insertFsAiWorkflow(workflow);
+        Long newWorkflowId = workflow.getWorkflowId();
+
+        // 复制节点
+        List<FsAiWorkflowNode> nodes = source.getNodes();
+        if (nodes != null && !nodes.isEmpty()) {
+            for (FsAiWorkflowNode node : nodes) {
+                node.setNodeId(null);
+                node.setWorkflowId(newWorkflowId);
+                node.setCreateTime(now);
+            }
+            fsAiWorkflowNodeMapper.batchInsertFsAiWorkflowNode(nodes);
+        }
+
+        // 复制连线
+        List<FsAiWorkflowEdge> edges = source.getEdges();
+        if (edges != null && !edges.isEmpty()) {
+            for (FsAiWorkflowEdge edge : edges) {
+                edge.setEdgeId(null);
+                edge.setWorkflowId(newWorkflowId);
+                edge.setCreateTime(now);
+            }
+            fsAiWorkflowEdgeMapper.batchInsertFsAiWorkflowEdge(edges);
+        }
+
+        return newWorkflowId;
+    }
+
+    @Override
+    public List<FsAiWorkflowNodeType> selectAllEnabledNodeTypes() {
+        return fsAiWorkflowNodeTypeMapper.selectAllEnabledNodeTypes();
+    }
+}

+ 25 - 0
fs-service/src/main/java/com/fs/his/vo/FsAiWorkflowVO.java

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

+ 71 - 0
fs-service/src/main/resources/mapper/his/FsAiWorkflowEdgeMapper.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.his.mapper.FsAiWorkflowEdgeMapper">
+
+    <resultMap type="FsAiWorkflowEdge" id="FsAiWorkflowEdgeResult">
+        <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="selectFsAiWorkflowEdgeById" parameterType="Long" resultMap="FsAiWorkflowEdgeResult">
+        select * from fs_ai_workflow_edge where edge_id = #{edgeId}
+    </select>
+
+    <select id="selectFsAiWorkflowEdgeByWorkflowId" parameterType="Long" resultMap="FsAiWorkflowEdgeResult">
+        select * from fs_ai_workflow_edge where workflow_id = #{workflowId} order by sort_order
+    </select>
+
+    <insert id="insertFsAiWorkflowEdge" parameterType="FsAiWorkflowEdge" useGeneratedKeys="true" keyProperty="edgeId">
+        insert into fs_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="batchInsertFsAiWorkflowEdge" parameterType="list">
+        insert into fs_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="updateFsAiWorkflowEdge" parameterType="FsAiWorkflowEdge">
+        update fs_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="deleteFsAiWorkflowEdgeById" parameterType="Long">
+        delete from fs_ai_workflow_edge where edge_id = #{edgeId}
+    </delete>
+
+    <delete id="deleteFsAiWorkflowEdgeByWorkflowId" parameterType="Long">
+        delete from fs_ai_workflow_edge where workflow_id = #{workflowId}
+    </delete>
+</mapper>

+ 97 - 0
fs-service/src/main/resources/mapper/his/FsAiWorkflowMapper.xml

@@ -0,0 +1,97 @@
+<?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.his.mapper.FsAiWorkflowMapper">
+
+    <resultMap type="FsAiWorkflow" id="FsAiWorkflowResult">
+        <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"/>
+    </resultMap>
+
+    <sql id="selectFsAiWorkflowVo">
+        select workflow_id, workflow_name, workflow_desc, workflow_type, status, version,
+               canvas_data, create_by, create_time, update_by, update_time, remark, del_flag
+        from fs_ai_workflow
+    </sql>
+
+    <select id="selectFsAiWorkflowList" parameterType="FsAiWorkflow" resultMap="FsAiWorkflowResult">
+        <include refid="selectFsAiWorkflowVo"/>
+        <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>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectFsAiWorkflowById" parameterType="Long" resultMap="FsAiWorkflowResult">
+        <include refid="selectFsAiWorkflowVo"/>
+        where workflow_id = #{workflowId} and del_flag = 0
+    </select>
+
+    <insert id="insertFsAiWorkflow" parameterType="FsAiWorkflow" useGeneratedKeys="true" keyProperty="workflowId">
+        insert into fs_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>
+        </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>
+        </trim>
+    </insert>
+
+    <update id="updateFsAiWorkflow" parameterType="FsAiWorkflow">
+        update fs_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="deleteFsAiWorkflowById" parameterType="Long">
+        update fs_ai_workflow set del_flag = 1 where workflow_id = #{workflowId}
+    </update>
+
+    <update id="deleteFsAiWorkflowByIds" parameterType="Long">
+        update fs_ai_workflow set del_flag = 1 where workflow_id in
+        <foreach item="workflowId" collection="array" open="(" separator="," close=")">
+            #{workflowId}
+        </foreach>
+    </update>
+</mapper>

+ 73 - 0
fs-service/src/main/resources/mapper/his/FsAiWorkflowNodeMapper.xml

@@ -0,0 +1,73 @@
+<?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.his.mapper.FsAiWorkflowNodeMapper">
+
+    <resultMap type="FsAiWorkflowNode" id="FsAiWorkflowNodeResult">
+        <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"/>
+    </resultMap>
+
+    <select id="selectFsAiWorkflowNodeById" parameterType="Long" resultMap="FsAiWorkflowNodeResult">
+        select * from fs_ai_workflow_node where node_id = #{nodeId}
+    </select>
+
+    <select id="selectFsAiWorkflowNodeByWorkflowId" parameterType="Long" resultMap="FsAiWorkflowNodeResult">
+        select * from fs_ai_workflow_node where workflow_id = #{workflowId} order by sort_order
+    </select>
+
+    <insert id="insertFsAiWorkflowNode" parameterType="FsAiWorkflowNode" useGeneratedKeys="true" keyProperty="nodeId">
+        insert into fs_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="batchInsertFsAiWorkflowNode" parameterType="list">
+        insert into fs_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="updateFsAiWorkflowNode" parameterType="FsAiWorkflowNode">
+        update fs_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>
+            update_time = now(),
+        </trim>
+        where node_id = #{nodeId}
+    </update>
+
+    <delete id="deleteFsAiWorkflowNodeById" parameterType="Long">
+        delete from fs_ai_workflow_node where node_id = #{nodeId}
+    </delete>
+
+    <delete id="deleteFsAiWorkflowNodeByWorkflowId" parameterType="Long">
+        delete from fs_ai_workflow_node where workflow_id = #{workflowId}
+    </delete>
+</mapper>

+ 40 - 0
fs-service/src/main/resources/mapper/his/FsAiWorkflowNodeTypeMapper.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.his.mapper.FsAiWorkflowNodeTypeMapper">
+
+    <resultMap type="FsAiWorkflowNodeType" id="FsAiWorkflowNodeTypeResult">
+        <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="selectFsAiWorkflowNodeTypeById" parameterType="Long" resultMap="FsAiWorkflowNodeTypeResult">
+        select * from fs_ai_workflow_node_type where type_id = #{typeId}
+    </select>
+
+    <select id="selectFsAiWorkflowNodeTypeByCode" parameterType="String" resultMap="FsAiWorkflowNodeTypeResult">
+        select * from fs_ai_workflow_node_type where type_code = #{typeCode}
+    </select>
+
+    <select id="selectFsAiWorkflowNodeTypeList" parameterType="FsAiWorkflowNodeType" resultMap="FsAiWorkflowNodeTypeResult">
+        select * from fs_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="FsAiWorkflowNodeTypeResult">
+        select * from fs_ai_workflow_node_type where status = 1 order by sort_order
+    </select>
+</mapper>

+ 1 - 1
fs-user-app/src/main/java/com/fs/app/controller/CompanyUserController.java

@@ -552,7 +552,7 @@ public class CompanyUserController extends AppBaseController {
             return R.error("用户没有声纹槽位,请联系管理员");
             return R.error("用户没有声纹槽位,请联系管理员");
         }
         }
         if (vcCompanyUser.getTimes() != null && vcCompanyUser.getTimes() >= 5)
         if (vcCompanyUser.getTimes() != null && vcCompanyUser.getTimes() >= 5)
-            throw new RuntimeException("用户已上传声纹达到上限");
+            return R.error("用户已上传声纹达到上限");
         vcCompanyUser.setUploadUrl(voicePrintUrl);
         vcCompanyUser.setUploadUrl(voicePrintUrl);
         File file = downloadFileFromUrl(voicePrintUrl);
         File file = downloadFileFromUrl(voicePrintUrl);
         /*获取文件时长*/
         /*获取文件时长*/