1
0

2 Коммиты 9cbad0ff5f ... 6bdfca2050

Автор SHA1 Сообщение Дата
  lk 6bdfca2050 Merge remote-tracking branch 'origin/master' 4 дней назад
  lk 14ee7dca6a 益寿缘AI语音复刻接入豆包 4 дней назад

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

@@ -109,4 +109,13 @@ public class FsAiWorkflowController extends BaseController {
         List<FsAiWorkflowNodeType> list = fsAiWorkflowService.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(fsAiWorkflowService.exportWorkflowJson(workflowId));
+    }
 }

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

@@ -14,7 +14,7 @@ public class TtsConfig {
     // 默认参数
     private String defaultFormat = "mp3";
     private Integer defaultSampleRate = 24000;
-    private Integer defaultSpeed = 10;
+    private Integer defaultSpeed = 1;
     private Integer defaultVolume = 10;
     private Integer defaultPitch = 10;
     private String defaultCluster = "volcano_icl";

+ 2 - 2
fs-service/src/main/java/com/fs/aiSoundReplication/config/VoiceCloneConfig.java

@@ -8,8 +8,8 @@ import org.springframework.context.annotation.Configuration;
 @Configuration
 @ConfigurationProperties(prefix = "voice.clone")
 public class VoiceCloneConfig {
-    private String accessToken = "IVX_Rlt6r93upGb-_vy0QlxaK_dhQzDY";//正式环境需要换成公司豆包的信息
-    private String appId = "8243948690";//正式环境需要换成公司豆包的信息
+    private String accessToken = "NqLzbUypz6WgbXbHO2P5DpqxSE1t-I4V";//正式环境需要换成公司豆包的信息
+    private String appId = "8505877548";//正式环境需要换成公司豆包的信息
 
     // API地址
     private String uploadUrl = "https://openspeech.bytedance.com/api/v1/mega_tts/audio/upload";

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

@@ -62,4 +62,9 @@ public class FsAiWorkflowNode implements Serializable {
     /** 更新时间 */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date updateTime;
+
+    /**
+     * 语音URL
+     */
+    private String voiceUrl;
 }

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

@@ -3,6 +3,7 @@ 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.FsAiWorkflowExportVO;
 import com.fs.his.vo.FsAiWorkflowVO;
 
 import java.util.List;
@@ -49,4 +50,8 @@ public interface IFsAiWorkflowService {
      * 获取所有启用的节点类型
      */
     List<FsAiWorkflowNodeType> selectAllEnabledNodeTypes();
+    /**
+     * 导出工作流JSON(包含节点、连接顺序、节点类型)
+     */
+    FsAiWorkflowExportVO exportWorkflowJson(Long workflowId);
 }

+ 78 - 3
fs-service/src/main/java/com/fs/his/service/impl/FsAiWorkflowServiceImpl.java

@@ -11,15 +11,15 @@ 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.FsAiWorkflowExportVO;
 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;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * AI工作流Service业务层处理
@@ -178,4 +178,79 @@ public class FsAiWorkflowServiceImpl implements IFsAiWorkflowService {
     public List<FsAiWorkflowNodeType> selectAllEnabledNodeTypes() {
         return fsAiWorkflowNodeTypeMapper.selectAllEnabledNodeTypes();
     }
+
+    @Override
+    public FsAiWorkflowExportVO exportWorkflowJson(Long workflowId) {
+        FsAiWorkflowVO workflowVO = selectFsAiWorkflowById(workflowId);
+        if (workflowVO == null) {
+            return null;
+        }
+
+        // 获取节点类型映射
+        List<FsAiWorkflowNodeType> nodeTypes = fsAiWorkflowNodeTypeMapper.selectAllEnabledNodeTypes();
+        Map<String, String> typeNameMap = nodeTypes.stream()
+                .collect(Collectors.toMap(FsAiWorkflowNodeType::getTypeCode, FsAiWorkflowNodeType::getTypeName));
+
+        // 构建导出VO
+        FsAiWorkflowExportVO exportVO = new FsAiWorkflowExportVO();
+        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<FsAiWorkflowNode> nodes = workflowVO.getNodes();
+        Map<String, String> nodeNameMap = new HashMap<>();
+        List<FsAiWorkflowExportVO.NodeInfo> nodeInfoList = new ArrayList<>();
+        if (nodes != null) {
+            for (FsAiWorkflowNode node : nodes) {
+                nodeNameMap.put(node.getNodeKey(), node.getNodeName());
+                FsAiWorkflowExportVO.NodeInfo nodeInfo = new FsAiWorkflowExportVO.NodeInfo();
+                nodeInfo.setNodeKey(node.getNodeKey());
+                nodeInfo.setNodeName(node.getNodeName());
+                nodeInfo.setNodeType(node.getNodeType());
+                nodeInfo.setNodeTypeName(typeNameMap.getOrDefault(node.getNodeType(), node.getNodeType()));
+                nodeInfo.setNodeConfig(node.getNodeConfig());
+                nodeInfo.setPosX(node.getPosX());
+                nodeInfo.setPosY(node.getPosY());
+                nodeInfo.setSortOrder(node.getSortOrder());
+                nodeInfoList.add(nodeInfo);
+            }
+        }
+        exportVO.setNodes(nodeInfoList);
+
+        // 构建连接信息(按sortOrder排序)
+        List<FsAiWorkflowEdge> edges = workflowVO.getEdges();
+        List<FsAiWorkflowExportVO.EdgeInfo> edgeInfoList = new ArrayList<>();
+        if (edges != null) {
+            edges.sort(Comparator.comparing(e -> e.getSortOrder() != null ? e.getSortOrder() : 0));
+            for (FsAiWorkflowEdge edge : edges) {
+                FsAiWorkflowExportVO.EdgeInfo edgeInfo = new FsAiWorkflowExportVO.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;
+    }
+
+    private String getWorkflowTypeName(Integer workflowType) {
+        if (workflowType == null) return "";
+        switch (workflowType) {
+            case 1: return "对话流程";
+            case 2: return "任务流程";
+            case 3: return "审批流程";
+            default: return "";
+        }
+    }
 }

+ 101 - 0
fs-service/src/main/java/com/fs/his/vo/FsAiWorkflowExportVO.java

@@ -0,0 +1,101 @@
+package com.fs.his.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * AI工作流导出JSON VO
+ *
+ * @author fs
+ * @date 2026-01-06
+ */
+@Data
+public class FsAiWorkflowExportVO 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 String nodeType;
+
+        /** 节点类型名称 */
+        private String nodeTypeName;
+
+        /** 节点配置JSON */
+        private String nodeConfig;
+
+        /** X坐标 */
+        private Integer posX;
+
+        /** Y坐标 */
+        private Integer posY;
+
+        /** 排序 */
+        private Integer sortOrder;
+    }
+
+    /** 连接信息 */
+    @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;
+    }
+}

+ 4 - 2
fs-service/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java

@@ -374,17 +374,19 @@ public class WxWorkServiceImpl implements WxWorkService {
             if (vcCompanyUser == null)throw new RuntimeException("用户不存在");
         }catch (Exception e){
             log.error(String.format("用户id不存在于豆包: %s",companyUserId));
+            return null;
         }
 
         AudioVO audioVO = ttsServiceImpl.textToSpeech(new TtsRequest(null,null,vcCompanyUser.getSpeakerId(),content));
         vcCompanyUser.setLatestTextToSpeechUrl(audioVO.getUrl());
         companyUserMapper.updateVcCompanyUser(vcCompanyUser);
-        WxwSilkVoceDTO wxwSilkVoceDTO = new WxwSilkVoceDTO() ;
+        WxwSilkVoceDTO wxwSilkVoceDTO = new WxwSilkVoceDTO();
         WxwSilkVoceDTO.Data data = new WxwSilkVoceDTO.Data();
-        data.setDuration(audioVO.getDuration());
         data.setUrl(audioVO.getUrl());
+        data.setDuration(audioVO.getDuration());
         wxwSilkVoceDTO.setData(data);
         wxwSilkVoceDTO.setCode(200);
+        log.info("豆包语音生成成功,url: {}", (audioVO.getUrl()));
         return wxwSilkVoceDTO;
     }
 }

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

@@ -196,56 +196,58 @@ public class CompanyUserController extends AppBaseController {
         CompanyUser companyUser = new CompanyUser();
         companyUser.setUserId(userId);
         companyUser.setVoicePrintUrl(param.getVoicePrintUrl());
-        String wavUrl = null;
+
         //转换音频格式 mp3-wav
         String s = AudioUtils.audioWAVFromUrl(param.getVoicePrintUrl());
-
         //保存文件并且上传存储桶
         System.out.println(s);
         File file = new File(s);
         FileInputStream fileInputStream = new FileInputStream(file);
         CloudStorageService storage = OSSFactory.build();
-        wavUrl = storage.uploadSuffix(fileInputStream, ".wav");
+        String wavUrl = storage.uploadSuffix(fileInputStream, ".wav");
+        //更新销售员工声纹
+        companyUser.setVoicePrintUrl(wavUrl);
+        companyUserMapper.updateCompanyUser(companyUser);
         /*判断sys的声纹复刻的配置value是不是2*/
         JSONObject vcConfig = configUtil.generateConfigByKey(SysConfigEnum.VS_CONFIG.getKey());
         if (vcConfig != null && !vcConfig.isEmpty() &&
 //                !vcConfig.equals(new JSONObject()) &&
                 "2".equals(vcConfig.getString("type"))) {
-            uploadVoice(userId, wavUrl);
-        }
-        //更新销售员工声纹
-        companyUser.setVoicePrintUrl(wavUrl);
-        companyUserMapper.updateCompanyUser(companyUser);
-
-
-        try {
-            CloseableHttpClient httpClient = HttpClients.createDefault();
-            HttpPost httpPost = new HttpPost(aiHostProper.getCommonApi() + "/app/common/addCompanyAudio");
-            String json = "{\"url\":\"" + wavUrl + "\",\"id\":\"" + userId + "\"}";
-            StringEntity entity = new StringEntity(json);
-            httpPost.setEntity(entity);
-            httpPost.setHeader("Content-type", "application/json");
-            HttpResponse response = httpClient.execute(httpPost);
-
-            if (response.getStatusLine().getStatusCode() == 200) {
-                String responseBody = EntityUtils.toString(response.getEntity());
-                JSONObject jsonObject = JSON.parseObject(responseBody);
-                Integer code = (Integer) jsonObject.get("code");
-                if (code == 200) {
-                    voiceService.insertQwSopTempVoiceModel(userId);
-                    return R.ok();
-                }
-            } else {
-                return R.error();
+            /*走豆包直接不走原逻辑*/
+            AjaxResult ajaxResult = uploadVoice(userId, wavUrl);
+            if (!ajaxResult.get("code").equals(200)){
+                return R.error((String) ajaxResult.get("msg"));
             }
+            voiceService.insertQwSopTempVoiceModel(userId);
+            return R.ok();
+        }else {
+            try {
+                CloseableHttpClient httpClient = HttpClients.createDefault();
+                HttpPost httpPost = new HttpPost(aiHostProper.getCommonApi() + "/app/common/addCompanyAudio");
+                String json = "{\"url\":\"" + wavUrl + "\",\"id\":\"" + userId + "\"}";
+                StringEntity entity = new StringEntity(json);
+                httpPost.setEntity(entity);
+                httpPost.setHeader("Content-type", "application/json");
+                HttpResponse response = httpClient.execute(httpPost);
+
+                if (response.getStatusLine().getStatusCode() == 200) {
+                    String responseBody = EntityUtils.toString(response.getEntity());
+                    JSONObject jsonObject = JSON.parseObject(responseBody);
+                    Integer code = (Integer) jsonObject.get("code");
+                    if (code == 200) {
+                        voiceService.insertQwSopTempVoiceModel(userId);
+                        return R.ok();
+                    }
+                } else {
+                    return R.error();
+                }
 
-            httpClient.close();
-        } catch (Exception e) {
-            e.printStackTrace();
+                httpClient.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            return R.error();
         }
-
-        return R.error();
-
     }
 
     @ApiOperation("小程序销售绑定医生")
@@ -543,16 +545,16 @@ public class CompanyUserController extends AppBaseController {
      * @throws Exception
      */
     @PostMapping("/uploadVoice")
-    public R uploadVoice(
+    public AjaxResult uploadVoice(
             Long userId,
             String voicePrintUrl) throws Exception {
         if (userId == null) userId = 123L;
         VcCompanyUser vcCompanyUser = companyUserMapper.selectVcCompanyUserByCompanyUserId(userId);
         if (vcCompanyUser == null) {
-            return R.error("用户没有声纹槽位,请联系管理员");
+            return AjaxResult.error("用户没有声纹槽位,请联系管理员");
         }
         if (vcCompanyUser.getTimes() != null && vcCompanyUser.getTimes() >= 5)
-            return R.error("用户已上传声纹达到上限");
+            return AjaxResult.error("用户已上传声纹达到上限");
         vcCompanyUser.setUploadUrl(voicePrintUrl);
         File file = downloadFileFromUrl(voicePrintUrl);
         /*获取文件时长*/
@@ -567,7 +569,7 @@ public class CompanyUserController extends AppBaseController {
         vcCompanyUser.incrementTimes();
         vcCompanyUser.setUploadTime(duration);
         companyUserMapper.updateVcCompanyUser(vcCompanyUser);
-        return R.ok();
+        return AjaxResult.success();
     }
 //    private static MultipartFile downloadAsMultipartFile(String fileUrl) throws Exception {
 //        URL url = new URL(fileUrl);