From 0a115f289ee9235d138365c0d76addd9fc45b0d5 Mon Sep 17 00:00:00 2001 From: zengxb <648669796@qq.com> Date: Tue, 24 Feb 2026 10:34:26 +0800 Subject: [PATCH 1/9] =?UTF-8?q?context:=E9=80=9A=E4=B9=89=E4=B8=87?= =?UTF-8?q?=E7=9B=B8=E6=96=87=E7=94=9F=E5=9B=BE=E8=8A=82=E7=82=B9=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E4=BB=A5=E5=8F=8A=E5=8F=91=E9=80=81=E9=82=AE=E7=AE=B1?= =?UTF-8?q?=E5=92=8CHTTP=E8=AF=B7=E6=B1=82=E8=8A=82=E7=82=B9=E8=B0=83?= =?UTF-8?q?=E7=A0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/script/sql/workFlow-v3.0.sql | 16 ++- .../chat/Service/IImageGenerationService.java | 20 +++ .../domain/entity/image/ImageContext.java | 45 +++++++ .../chat/factory/ImageServiceFactory.java | 45 +++++++ .../workflow/workflow/WfNodeFactory.java | 4 +- .../ruoyi/workflow/workflow/WorkflowUtil.java | 32 +++++ .../node/answer/LLMAnswerNodeConfig.java | 6 - .../HumanFeedbackNode.java | 3 +- .../workflow/node/image/ImageNode.java | 62 +++++++++ .../workflow/node/image/ImageNodeConfig.java | 37 ++++++ .../java/org/ruoyi/enums/ImageModeType.java | 23 ++++ .../impl/provider/QianWenChatServiceImpl.java | 124 ++++++++++++------ .../image/AbstractImageGenerationService.java | 43 ++++++ .../provider/TongYiWanxImageServiceImpl.java | 73 +++++++++++ 14 files changed, 476 insertions(+), 57 deletions(-) create mode 100644 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IImageGenerationService.java create mode 100644 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/image/ImageContext.java create mode 100644 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ImageServiceFactory.java rename ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/{ => humanFeedBack}/HumanFeedbackNode.java (95%) create mode 100644 ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNode.java create mode 100644 ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNodeConfig.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/ImageModeType.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/image/AbstractImageGenerationService.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/image/provider/TongYiWanxImageServiceImpl.java diff --git a/docs/script/sql/workFlow-v3.0.sql b/docs/script/sql/workFlow-v3.0.sql index b97366f2..f7573338 100644 --- a/docs/script/sql/workFlow-v3.0.sql +++ b/docs/script/sql/workFlow-v3.0.sql @@ -1,6 +1,10 @@ -INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (17, '5cd68dccbbb411f0bb7840c2ba9a7fbc', 'Start', '开始', '流程由此开始', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); -INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (18, '5cd6ac69bbb411f0bb7840c2ba9a7fbc', 'End', '结束', '流程由此结束', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); -INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (19, '5cd6c8eabbb411f0bb7840c2ba9a7fbc', 'Answer', '生成回答', '调用大语言模型回答问题', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); -INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (25, '0b4369bb60dc46d6bd84ceb4e36184dc', 'KeywordExtractor', '关键词提取', '从文本中提取关键词', 0, 1, '2025-12-26 16:30:05', '2025-12-26 16:30:05', 0, '000000'); -INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (26, 'bb00fc2f52c74fec82ee3f99725b56bb', 'Switcher', '条件分支', '根据条件执行不同分支', 0, 1, '2025-12-26 16:30:46', '2025-12-26 16:30:46', 0, '000000'); -INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (36, 'f37dbcb8f0d5464d90fbb22774490a56', 'HumanFeedback', '人类', '人机沟通', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000'); +INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (17, '5cd68dccbbb411f0bb7840c2ba9a7fbc', 'Start', '开始', '流程由此开始', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); +INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (18, '5cd6ac69bbb411f0bb7840c2ba9a7fbc', 'End', '结束', '流程由此结束', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); +INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (19, '5cd6c8eabbb411f0bb7840c2ba9a7fbc', 'Answer', '生成回答', '调用大语言模型回答问题', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); +INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (20, '0b4369bb60dc46d6bd84ceb4e36184dc', 'KeywordExtractor', '关键词提取', '从文本中提取关键词', 0, 1, '2025-12-26 16:30:05', '2025-12-26 16:30:05', 0, '000000'); +INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (21, 'bb00fc2f52c74fec82ee3f99725b56bb', 'Switcher', '条件分支', '根据条件执行不同分支', 0, 1, '2025-12-26 16:30:46', '2025-12-26 16:30:46', 0, '000000'); +INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (22, 'f37dbcb8f0d5464d90fbb22774490a56', 'HumanFeedback', '人类', '人机沟通', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000'); +INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (23, 'af9d6d7b9c9b47f990ad25ec84912b73', 'Tongyiwanx', '阿里图像生成', '使用通义万相生成图像', 0, 1, '2025-12-26 16:32:25', '2025-12-26 16:32:25', 0, '000000'); +INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (24, 'a1e2c9d4b8f04e1a9c3d6f8e2a7b1c9d', 'MailSend', '发送邮箱', '发送邮箱', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000'); +INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (25, 'f1e2d3c4b5a67890f1e2d3c4b5a6f1e2', 'HttpRequest', '请求节点', '请求节点', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000'); +INSERT INTO `ruoyi-ai-v3`.`chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_price`, `model_type`, `model_show`, `model_free`, `priority`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`) VALUES (2022565766560468994, 'Tongyiwanx', 'wan2.5-t2i-preview', 'Tongyiwanx', 'wan2.5-t2i-preview', 1, '1', 'Y', 'Y', 1, 'https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation', 'Bearer sk-f4550b0e138c488cbfcafe3d61f800a5', 103, 1, '2026-02-14 14:57:11', 1, '2026-02-14 14:57:11', '通义万相文生图', 0); diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IImageGenerationService.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IImageGenerationService.java new file mode 100644 index 00000000..237afed1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IImageGenerationService.java @@ -0,0 +1,20 @@ +package org.ruoyi.common.chat.Service; + +import jakarta.validation.Valid; +import org.ruoyi.common.chat.domain.entity.image.ImageContext; + +/** + * 公共文生图接口 + */ +public interface IImageGenerationService { + + /** + * 根据文字生成图片 + */ + String generateImage(@Valid ImageContext imageContext); + + /** + * 获取服务提供商名称 + */ + String getProviderName(); +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/image/ImageContext.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/image/ImageContext.java new file mode 100644 index 00000000..4bf42c61 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/image/ImageContext.java @@ -0,0 +1,45 @@ +package org.ruoyi.common.chat.domain.entity.image; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; + +/** + * 文生图对话上下文对象 + * + * @author zengxb + * @date 2026-02-14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Builder +public class ImageContext { + + /** + * 模型管理视图对象 + */ + @NotNull(message = "模型管理视图对象不能为空") + private ChatModelVo chatModelVo; + + /** + * 提示词 + */ + @NotNull(message = "提示词不能为空") + private String prompt; + + /** + * 图片尺寸大小 + */ + private String size; + + /** + * 随机数种子 + */ + @Min(value = 0, message = "随机数种子不能小于0") + @Max(value = 2147483647, message = "随机数种子不能大于2147483647") + private Integer seed; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ImageServiceFactory.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ImageServiceFactory.java new file mode 100644 index 00000000..e7ba4160 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ImageServiceFactory.java @@ -0,0 +1,45 @@ +package org.ruoyi.common.chat.factory; + +import org.ruoyi.common.chat.Service.IImageGenerationService; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 文生图服务工厂类 + * + * @author zengxb + * @date 2026-02-14 + */ +@Component +public class ImageServiceFactory implements ApplicationContextAware { + + private final Map imageSerivceMap = new ConcurrentHashMap<>(); + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + // 初始化时收集所有IImageGenerationService的实现 + Map serviceMap = applicationContext.getBeansOfType(IImageGenerationService.class); + for (IImageGenerationService service : serviceMap.values()) { + if (service != null ) { + imageSerivceMap.put(service.getProviderName(), service); + } + } + } + + + /** + * 获取原始服务(不包装代理) + */ + public IImageGenerationService getOriginalService(String category) { + IImageGenerationService service = imageSerivceMap.get(category); + if (service == null) { + throw new IllegalArgumentException("不支持的模型类别: " + category); + } + return service; + } +} diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WfNodeFactory.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WfNodeFactory.java index 94aafe8b..ce22efca 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WfNodeFactory.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WfNodeFactory.java @@ -4,9 +4,10 @@ import org.ruoyi.workflow.entity.WorkflowComponent; import org.ruoyi.workflow.entity.WorkflowNode; import org.ruoyi.workflow.workflow.node.AbstractWfNode; import org.ruoyi.workflow.workflow.node.EndNode; -import org.ruoyi.workflow.workflow.node.HumanFeedbackNode; +import org.ruoyi.workflow.workflow.node.humanFeedBack.HumanFeedbackNode; import org.ruoyi.workflow.workflow.node.answer.LLMAnswerNode; import org.ruoyi.workflow.workflow.node.httpRequest.HttpRequestNode; +import org.ruoyi.workflow.workflow.node.image.ImageNode; import org.ruoyi.workflow.workflow.node.keywordExtractor.KeywordExtractorNode; import org.ruoyi.workflow.workflow.node.knowledgeRetrieval.KnowledgeRetrievalNode; import org.ruoyi.workflow.workflow.node.mailSend.MailSendNode; @@ -21,6 +22,7 @@ public class WfNodeFactory { case START -> wfNode = new StartNode(wfComponent, nodeDefinition, wfState, nodeState); case LLM_ANSWER -> wfNode = new LLMAnswerNode(wfComponent, nodeDefinition, wfState, nodeState); case KEYWORD_EXTRACTOR -> wfNode = new KeywordExtractorNode(wfComponent, nodeDefinition, wfState, nodeState); + case TONGYI_WANX -> wfNode = new ImageNode(wfComponent, nodeDefinition, wfState, nodeState); case KNOWLEDGE_RETRIEVER -> wfNode = new KnowledgeRetrievalNode(wfComponent, nodeDefinition, wfState, nodeState); case END -> wfNode = new EndNode(wfComponent, nodeDefinition, wfState, nodeState); case MAIL_SEND -> wfNode = new MailSendNode(wfComponent, nodeDefinition, wfState, nodeState); diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java index 176ed7d6..6715b3b7 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java @@ -13,10 +13,13 @@ import org.bsc.langgraph4j.langchain4j.generators.StreamingChatGenerator; import org.bsc.langgraph4j.state.AgentState; import org.ruoyi.common.chat.Service.IChatModelService; import org.ruoyi.common.chat.Service.IChatService; +import org.ruoyi.common.chat.Service.IImageGenerationService; import org.ruoyi.common.chat.domain.dto.request.ChatRequest; import org.ruoyi.common.chat.domain.entity.chat.ChatContext; +import org.ruoyi.common.chat.domain.entity.image.ImageContext; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.common.chat.factory.ChatServiceFactory; +import org.ruoyi.common.chat.factory.ImageServiceFactory; import org.ruoyi.workflow.base.NodeInputConfigTypeHandler; import org.ruoyi.workflow.entity.WorkflowNode; import org.ruoyi.workflow.enums.WfIODataTypeEnum; @@ -38,6 +41,9 @@ public class WorkflowUtil { @Resource private ChatServiceFactory chatServiceFactory; + @Resource + private ImageServiceFactory imageServiceFactory; + @Resource private IChatModelService chatModelService; @@ -213,4 +219,30 @@ public class WorkflowUtil { .filter(item -> nameSet.contains(item.getName())) .map(item -> getMessage("user", item.getContent().getValue().toString())).toList(); } + + /** + * 调用LLM 根据文字生成图片 + */ + public String buildTextToImage(String modelName, String prompt, String size, Integer seed){ + log.info("Generate image invoke, modelName: {}", modelName); + // 根据模型名称查询模型信息 + ChatModelVo chatModelVo = chatModelService.selectModelByName(modelName); + if (chatModelVo == null) { + throw new IllegalArgumentException("模型不存在: " + modelName); + } + // 根据模型名称找到模型实体 + String modelVoCategory = chatModelVo.getCategory(); + // 根据 category 获取对应的 IImageGenerationService(不使用计费代理,工作流场景单独计费) + IImageGenerationService imageService = imageServiceFactory.getOriginalService(modelVoCategory); + // 构建文生图上下文对象 + ImageContext imageContext = ImageContext.builder() + .chatModelVo(chatModelVo) + .prompt(prompt) + .size(size) + .seed(seed) + .build(); + // 调用LLM 生成图片 + return imageService.generateImage(imageContext); + } + } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/answer/LLMAnswerNodeConfig.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/answer/LLMAnswerNodeConfig.java index fc90009f..64b45b0a 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/answer/LLMAnswerNodeConfig.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/answer/LLMAnswerNodeConfig.java @@ -12,12 +12,6 @@ public class LLMAnswerNodeConfig { @NotBlank private String prompt; - /** - * TODO - */ - // @NotBlank - private String category; - @NotNull @JsonProperty("model_name") private String modelName; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/HumanFeedbackNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/humanFeedBack/HumanFeedbackNode.java similarity index 95% rename from ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/HumanFeedbackNode.java rename to ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/humanFeedBack/HumanFeedbackNode.java index f41e1938..ea5b918d 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/HumanFeedbackNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/humanFeedBack/HumanFeedbackNode.java @@ -1,4 +1,4 @@ -package org.ruoyi.workflow.workflow.node; +package org.ruoyi.workflow.workflow.node.humanFeedBack; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -8,6 +8,7 @@ import org.ruoyi.workflow.workflow.NodeProcessResult; import org.ruoyi.workflow.workflow.WfNodeState; import org.ruoyi.workflow.workflow.WfState; import org.ruoyi.workflow.workflow.data.NodeIOData; +import org.ruoyi.workflow.workflow.node.AbstractWfNode; import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.*; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNode.java new file mode 100644 index 00000000..095e4acd --- /dev/null +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNode.java @@ -0,0 +1,62 @@ +package org.ruoyi.workflow.workflow.node.image; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.ruoyi.workflow.entity.WorkflowComponent; +import org.ruoyi.workflow.entity.WorkflowNode; +import org.ruoyi.workflow.util.SpringUtil; +import org.ruoyi.workflow.workflow.NodeProcessResult; +import org.ruoyi.workflow.workflow.WfNodeState; +import org.ruoyi.workflow.workflow.WfState; +import org.ruoyi.workflow.workflow.WorkflowUtil; +import org.ruoyi.workflow.workflow.data.NodeIOData; +import org.ruoyi.workflow.workflow.node.AbstractWfNode; + +import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.NODE_PROCESS_STATUS_SUCCESS; + +/** + * 【节点】文生图
+ * 节点内容固定格式:ImageNodeConfig + */ +@Slf4j +public class ImageNode extends AbstractWfNode { + + public ImageNode(WorkflowComponent wfComponent, WorkflowNode nodeDef, WfState wfState, WfNodeState nodeState) { + super(wfComponent, nodeDef, wfState, nodeState); + } + + /** + * nodeConfig格式: + * {"prompt": "{input}","model_name":"wan2.5-t2i-preview","size":"1024*1024"} + * + * @return 图片地址URL + */ + @Override + public NodeProcessResult onProcess() { + ImageNodeConfig nodeConfigObj = checkAndGetConfig(ImageNodeConfig.class); + String inputText = getFirstInputText(); + log.info("Image node config:{}", nodeConfigObj); + String prompt = inputText; + if (StringUtils.isNotBlank(nodeConfigObj.getPrompt())) { + prompt = WorkflowUtil.renderTemplate(nodeConfigObj.getPrompt(), state.getInputs()); + } + log.info("Image prompt:{}", prompt); + // 获取工作流实例 + WorkflowUtil workflowUtil = SpringUtil.getBean(WorkflowUtil.class); + // 获取模型名称 + String modelName = nodeConfigObj.getModelName(); + // 获取图片大小 + String size = nodeConfigObj.getSize(); + // 获取随机数种子 + Integer seed = nodeConfigObj.getSeed(); + // 调用LLM生成图片(后续可以将图片保存到OSS中) + String imageUrl = workflowUtil.buildTextToImage(modelName, prompt, size, seed); + // 创建节点参数对象 + NodeIOData nodeIOData = NodeIOData.createByText("output", "image", imageUrl); + // 添加到输出列表以便给后续节点使用 + state.getOutputs().add(nodeIOData); + // 设置为成功状态 + state.setProcessStatus(NODE_PROCESS_STATUS_SUCCESS); + return new NodeProcessResult(); + } +} diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNodeConfig.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNodeConfig.java new file mode 100644 index 00000000..381c9016 --- /dev/null +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNodeConfig.java @@ -0,0 +1,37 @@ +package org.ruoyi.workflow.workflow.node.image; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode +@Data +public class ImageNodeConfig { + + /** + * 提示词 + */ + private String prompt; + + /** + * 模型名称 + */ + @NotNull + @JsonProperty("model_name") + private String modelName; + + /** + * 图片尺寸大小 + */ + private String size; + + /** + * 随机数种子 + */ + @Min(value = 0, message = "随机数种子不能小于0") + @Max(value = 2147483647, message = "随机数种子不能大于2147483647") + private Integer seed; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/ImageModeType.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/ImageModeType.java new file mode 100644 index 00000000..2faf77e7 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/ImageModeType.java @@ -0,0 +1,23 @@ +package org.ruoyi.enums; + +import lombok.Getter; + +/** + * 文生图模型分类 + * + * @author Zengxb + * @date 2026-02-14 + */ +@Getter +public enum ImageModeType { + + TONGYI_WANX("Tongyiwanx", "万相"); + + private final String code; + private final String description; + + ImageModeType(String code, String description) { + this.code = code; + this.description = description; + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java index ab583520..e962e2f7 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java @@ -1,6 +1,5 @@ package org.ruoyi.service.chat.impl.provider; -import dev.langchain4j.agent.tool.ToolSpecification; import dev.langchain4j.agentic.AgenticServices; import dev.langchain4j.agentic.supervisor.SupervisorAgent; import dev.langchain4j.agentic.supervisor.SupervisorResponseStrategy; @@ -30,6 +29,8 @@ import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; /** * qianWenAI服务调用 @@ -44,14 +45,20 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService { @Autowired private McpSseConfig mcpSseConfig; - /** - * 千问开发者默认地址 - */ - private static final String QWEN_API_HOST = "https://dashscope.aliyuncs.com/api/v1"; - // 添加文档解析的前缀字段 private static final String UPLOAD_FILE_API_PREFIX = "fileid"; + // 缓存不同API Key和模型的MCP智能体实例 + private final ConcurrentHashMap supervisorCache = new ConcurrentHashMap<>(); + + // 缓存不同API Key和模型的MCP客户端实例 + private final ConcurrentHashMap mcpClientCache = new ConcurrentHashMap<>(); + + // 缓存不同API Key和模型的MCP工具提供者实例 + private final ConcurrentHashMap toolProviderCache = new ConcurrentHashMap<>(); + // 用于线程安全的锁 + private final ReentrantLock cacheLock = new ReentrantLock(); + @Override protected StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo,ChatRequest chatRequest) { return QwenStreamingChatModel.builder() @@ -102,17 +109,16 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService { } /** - * 调用MCP服务(智能体) - * @param userMessage 用户信息 - * @param chatModelVo 模型信息 - * @return 返回LLM信息 + * 获取缓存键 */ - protected String doAgent(String userMessage,ChatModelVo chatModelVo) { - // 判断是否开启MCP服务 - if (!mcpSseConfig.isEnabled()) { - return ""; - } + private String getCacheKey(ChatModelVo chatModelVo) { + return chatModelVo.getApiKey() + ":" + chatModelVo.getModelName(); + } + /** + * 初始化MCP客户端连接 + */ + private McpClient initializeMcpClient() { // 步骤1:根据SSE对外暴露端点连接 McpTransport httpMcpTransport = new StreamableHttpMcpTransport.Builder(). url(mcpSseConfig.getUrl()). @@ -120,42 +126,74 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService { build(); // 步骤2:开启客户端连接 - McpClient mcpClient = new DefaultMcpClient.Builder() + return new DefaultMcpClient.Builder() .transport(httpMcpTransport) .build(); + } - // 获取所有mcp工具 - List toolSpecifications = mcpClient.listTools(); - System.out.println(toolSpecifications); + /** + * 调用MCP服务(智能体) + * @param userMessage 用户信息 + * @param chatModelVo 模型信息 + * @return 返回LLM信息 + */ + protected String doAgent(String userMessage, ChatModelVo chatModelVo) { + // 判断是否开启MCP服务 + if (!mcpSseConfig.isEnabled()) { + return ""; + } + // 生成缓存键 + String cacheKey = getCacheKey(chatModelVo); + // 尝试从缓存获取监督智能体 + SupervisorAgent cachedSupervisor = supervisorCache.get(cacheKey); + if (cachedSupervisor != null) { + // 如果已存在缓存的监督智能体,直接使用 + return cachedSupervisor.invoke(userMessage); + } + cacheLock.lock(); + try { + // 双重检查,防止并发情况下的重复初始化 + cachedSupervisor = supervisorCache.get(cacheKey); + if (cachedSupervisor != null) { + return cachedSupervisor.invoke(userMessage); + } - // 步骤3:将mcp对象包装 - ToolProvider toolProvider = McpToolProvider.builder() - .mcpClients(List.of(mcpClient)) - .build(); + // 获取或初始化MCP客户端 + McpClient mcpClient = mcpClientCache.computeIfAbsent(cacheKey, k -> initializeMcpClient()); - // 步骤4:加载LLM模型对话 - QwenChatModel qwenChatModel = QwenChatModel.builder() - .baseUrl(QWEN_API_HOST) - .apiKey(chatModelVo.getApiKey()) - .modelName(chatModelVo.getModelName()) - .build(); + // 步骤3:将mcp对象包装 + ToolProvider toolProvider = toolProviderCache.computeIfAbsent(cacheKey, k -> McpToolProvider.builder() + .mcpClients(List.of(mcpClient)) + .build()); - // 步骤5:将MCP对象由智能体Agent管控 - McpAgent mcpAgent = AgenticServices.agentBuilder(McpAgent.class) - .chatModel(qwenChatModel) - .toolProvider(toolProvider) - .build(); + // 步骤4:加载LLM模型对话 + QwenChatModel qwenChatModel = QwenChatModel.builder() + .apiKey(chatModelVo.getApiKey()) + .modelName(chatModelVo.getModelName()) + .build(); - // 步骤6:将所有MCP对象由超级智能体管控 - SupervisorAgent supervisor = AgenticServices - .supervisorBuilder() - .chatModel(qwenChatModel) - .subAgents(mcpAgent) - .responseStrategy(SupervisorResponseStrategy.LAST) - .build(); + // 步骤5:将MCP对象由智能体Agent管控 + McpAgent mcpAgent = AgenticServices.agentBuilder(McpAgent.class) + .chatModel(qwenChatModel) + .toolProvider(toolProvider) + .build(); - // 步骤7:调用大模型LLM - return supervisor.invoke(userMessage); + // 步骤6:将所有MCP对象由超级智能体管控 + SupervisorAgent supervisor = AgenticServices + .supervisorBuilder() + .chatModel(qwenChatModel) + .subAgents(mcpAgent) + .responseStrategy(SupervisorResponseStrategy.LAST) + .build(); + + // 缓存监督智能体 + supervisorCache.put(cacheKey, supervisor); + + // 步骤7:调用大模型LLM + return supervisor.invoke(userMessage); + } finally { + cacheLock.unlock(); + } } @Override diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/image/AbstractImageGenerationService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/image/AbstractImageGenerationService.java new file mode 100644 index 00000000..3312239e --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/image/AbstractImageGenerationService.java @@ -0,0 +1,43 @@ +package org.ruoyi.service.image; + +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.Service.IImageGenerationService; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.common.chat.domain.entity.image.ImageContext; +import org.springframework.validation.annotation.Validated; + +@Slf4j +@Validated +public abstract class AbstractImageGenerationService implements IImageGenerationService { + + /** + * 根据文字生成图片 + * @param imageContext 文生图上下文对象 + * @return 生成的图片URL + */ + @Override + public String generateImage(ImageContext imageContext){ + // 获取模型管理视图对象 + ChatModelVo chatModelVo = imageContext.getChatModelVo(); + // 获取提示词 + String prompt = imageContext.getPrompt(); + // 获取图片尺寸大小 + String size = imageContext.getSize(); + // 获取随机数种子 + Integer seed = imageContext.getSeed(); + return doGenerateImage(chatModelVo, prompt, size, seed); + } + + /** + * 执行生成图片(钩子方法 - 子类必须实现) + * + * @param prompt 提示词 + */ + protected abstract String doGenerateImage(ChatModelVo chatModelVo, String prompt, String size, Integer seed); + + /** + * 构建具体厂商的 ImageModel(原生SDK 非langchain4j-dashscope版) + * 子类必须实现此方法,返回对应厂商的模型实例 + */ + protected abstract Object buildImageModel(ChatModelVo chatModelVo); +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/image/provider/TongYiWanxImageServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/image/provider/TongYiWanxImageServiceImpl.java new file mode 100644 index 00000000..090ca327 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/image/provider/TongYiWanxImageServiceImpl.java @@ -0,0 +1,73 @@ +package org.ruoyi.service.image.provider; + +import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis; +import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisParam; +import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisResult; +import com.alibaba.dashscope.exception.ApiException; +import com.alibaba.dashscope.exception.NoApiKeyException; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.enums.ImageModeType; +import org.ruoyi.service.image.AbstractImageGenerationService; +import org.springframework.stereotype.Service; + +/** + * 万相文生图AI调用 + * + * @author Zengxb + * @date 2026/02/14 + */ +@Service +@Slf4j +public class TongYiWanxImageServiceImpl extends AbstractImageGenerationService { + + /** + * 默认图片数量(1张) + */ + private final static int IMAGE_DEFAULT_SIZE = 1; + + /** + * 默认图片分辨率(1280*1280) + */ + private final static String IMAGE_DEFAULT_RESOLUTION = "1280*1280"; + + @Override + protected String doGenerateImage(ChatModelVo chatModelVo, String prompt, String size, Integer seed) { + // 构建万相模型对象 + var param = (ImageSynthesisParam) buildImageModel(chatModelVo); + // 设置图片大小和提示词以及随机数种子 + param.setSize(StringUtils.isEmpty(size) ? IMAGE_DEFAULT_RESOLUTION : size); + param.setPrompt(prompt); + param.setSeed(seed); + // 同步调用 AI 大模型,生成图片 + var imageSynthesis = new ImageSynthesis(); + ImageSynthesisResult result; + try { + log.info("同步调用通义万相文生图接口中...."); + result = imageSynthesis.call(param); + } catch (ApiException | NoApiKeyException e) { + log.error("同步调用通义万相文生图接口失败", e); + return ""; + } + // 直接提取图片URL + var output = result.getOutput(); + var results = output.getResults(); + return results.isEmpty() ? "" : results.get(0).get("url"); + } + + @Override + protected Object buildImageModel(ChatModelVo chatModelVo) { + return ImageSynthesisParam.builder() + .prompt("") + .apiKey(chatModelVo.getApiKey()) + .model(chatModelVo.getModelName()) + .n(IMAGE_DEFAULT_SIZE) + .build(); + } + + @Override + public String getProviderName() { + return ImageModeType.TONGYI_WANX.getCode(); + } +} From d6e4a50d6e3320dc13709811e9314bddcef74140 Mon Sep 17 00:00:00 2001 From: zengxb <648669796@qq.com> Date: Tue, 24 Feb 2026 10:58:11 +0800 Subject: [PATCH 2/9] =?UTF-8?q?context:=E6=A0=B9=E6=8D=AE=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=88=86=E7=B1=BB=E6=89=BE=E5=88=B0=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E7=B1=BB=E4=BF=AE=E6=94=B9=E4=B8=BA=E7=94=B1=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E4=BE=9B=E5=BA=94=E5=95=86=E6=89=BE=E5=88=B0=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E7=B1=BB=EF=BC=88=E4=BF=9D=E6=8C=81=E5=B7=A5=E4=BD=9C=E6=B5=81?= =?UTF-8?q?=E5=92=8C=E5=AF=B9=E8=AF=9D=E6=B5=81=E7=A8=8B=E4=B8=80=E8=87=B4?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java index 6715b3b7..63a13845 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java @@ -118,10 +118,10 @@ public class WorkflowUtil { throw new IllegalArgumentException("模型不存在: " + modelName); } - // 根据模型名称找到模型实体 - String modelVoCategory = chatModelVo.getCategory(); + // 路由服务提供商 + String category = chatModelVo.getProviderCode(); // 根据 category 获取对应的 ChatService(不使用计费代理,工作流场景单独计费) - IChatService chatService = chatServiceFactory.getOriginalService(modelVoCategory); + IChatService chatService = chatServiceFactory.getOriginalService(category); StreamingChatGenerator streamingGenerator = StreamingChatGenerator.builder() .mapResult(response -> { From 26bcfbba8a56af19397dff3c4c876d63e269a503 Mon Sep 17 00:00:00 2001 From: zhang <823772544@qq.com> Date: Tue, 24 Feb 2026 16:07:18 +0800 Subject: [PATCH 3/9] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E8=AF=BB=E5=8F=96=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-dev.yml | 18 ++-- .../ruoyi/agent/config/AgentMysqlConfig.java | 80 ++++++++--------- .../ruoyi/agent/domain/TableStructure.java | 3 +- .../agent/manager/TableSchemaInitializer.java | 6 +- .../agent/manager/TableSchemaManager.java | 41 ++++++--- .../ruoyi/agent/tool/ExecuteSqlQueryTool.java | 31 ++++--- .../ruoyi/agent/tool/QueryAllTablesTool.java | 85 ++++++++---------- .../agent/tool/QueryTableSchemaTool.java | 86 +++++++------------ .../impl/AbstractStreamingChatService.java | 75 +++++++++------- 9 files changed, 214 insertions(+), 211 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 8bcc34bb..91498093 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -58,9 +58,16 @@ spring: driverClassName: com.mysql.cj.jdbc.Driver # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) - url: jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + url: jdbc:mysql://127.0.0.1:3306/ruoyi_ai_agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true username: root - password: 123456 + password: Qzhang450000 + agent: + url: jdbc:mysql://127.0.0.1:3306/yunding?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + # url: jdbc:mysql://localhost:3306/agent_db + username: root + password: Qzhang450000 + driverClassName: com.mysql.cj.jdbc.Driver + hikari: # 最大连接池数量 maxPoolSize: 20 @@ -77,11 +84,7 @@ spring: # 多久检查一次连接的活性 keepaliveTime: 30000 -agent: - mysql: - url: jdbc:mysql://localhost:3306/ruoyi-ai-agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true - username: root - password: root + --- # 上传文件地址 sys: @@ -265,3 +268,4 @@ justauth: client-secret: 1f7d08**********5b7**********29e redirect-uri: ${justauth.address}/social-callback?source=gitea +AGENT_ALLOWED_TABLES: "abtest_rule,abtest_project,agent_ban_log,agent_ban_logs,agent_install_sub_task,agent_install_sum_task,agent_install_task" \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/config/AgentMysqlConfig.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/config/AgentMysqlConfig.java index b5d2f9e0..dfcc28b6 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/config/AgentMysqlConfig.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/config/AgentMysqlConfig.java @@ -1,44 +1,44 @@ -package org.ruoyi.agent.config; +// package org.ruoyi.agent.config; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import javax.sql.DataSource; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; +// import com.zaxxer.hikari.HikariConfig; +// import com.zaxxer.hikari.HikariDataSource; +// import javax.sql.DataSource; +// import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +// import org.springframework.boot.context.properties.EnableConfigurationProperties; +// import org.springframework.context.annotation.Bean; +// import org.springframework.context.annotation.Configuration; -/** - * Agent MySQL 数据源配置 - * 为 Agent 配置独立的 MySQL 数据库连接池(HikariCP) - * - * 仅在 agent.mysql.enabled=true 时启用 - */ -@Configuration -@EnableConfigurationProperties(AgentMysqlProperties.class) -@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") -public class AgentMysqlConfig { +// /** +// * Agent MySQL 数据源配置 +// * 为 Agent 配置独立的 MySQL 数据库连接池(HikariCP) +// * +// * 仅在 agent.mysql.enabled=true 时启用 +// */ +// @Configuration +// @EnableConfigurationProperties(AgentMysqlProperties.class) +// @ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") +// public class AgentMysqlConfig { - /** - * 创建 Agent 专用的数据源 - * 与项目主数据源隔离,独立管理 - * - * @param properties Agent MySQL 配置属性 - * @return HikariCP 数据源 - */ - @Bean("agentDataSource") - public DataSource agentDataSource(AgentMysqlProperties properties) { - HikariConfig config = new HikariConfig(); - config.setJdbcUrl(properties.getUrl()); - config.setUsername(properties.getUsername()); - config.setPassword(properties.getPassword()); - config.setDriverClassName("com.mysql.cj.jdbc.Driver"); - config.setMaximumPoolSize(properties.getMaxPoolSize()); - config.setMinimumIdle(properties.getMinIdle()); - config.setConnectionTimeout(30000); - config.setIdleTimeout(600000); - config.setMaxLifetime(1800000); +// /** +// * 创建 Agent 专用的数据源 +// * 与项目主数据源隔离,独立管理 +// * +// * @param properties Agent MySQL 配置属性 +// * @return HikariCP 数据源 +// */ +// @Bean("agentDataSource") +// public DataSource agentDataSource(AgentMysqlProperties properties) { +// HikariConfig config = new HikariConfig(); +// config.setJdbcUrl(properties.getUrl()); +// config.setUsername(properties.getUsername()); +// config.setPassword(properties.getPassword()); +// config.setDriverClassName("com.mysql.cj.jdbc.Driver"); +// config.setMaximumPoolSize(properties.getMaxPoolSize()); +// config.setMinimumIdle(properties.getMinIdle()); +// config.setConnectionTimeout(30000); +// config.setIdleTimeout(600000); +// config.setMaxLifetime(1800000); - return new HikariDataSource(config); - } -} +// return new HikariDataSource(config); +// } +// } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/domain/TableStructure.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/domain/TableStructure.java index d1c3bca2..7ff66c21 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/domain/TableStructure.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/domain/TableStructure.java @@ -16,7 +16,8 @@ public class TableStructure { * 表名 */ private String tableName; - + + private String tableType; // 添加此字段:BASE TABLE 或 VIEW /** * 表注释/说明 */ diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaInitializer.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaInitializer.java index 14bd86e3..ce75074e 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaInitializer.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaInitializer.java @@ -1,19 +1,19 @@ package org.ruoyi.agent.manager; -import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; +import lombok.extern.slf4j.Slf4j; + /** * 架构初始化器 * 在应用启动完成后自动初始化表结构缓存 */ @Slf4j @Component -@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") +// @ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") public class TableSchemaInitializer { @Autowired(required = false) diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaManager.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaManager.java index 9bdab429..a02eb78d 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaManager.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaManager.java @@ -1,16 +1,30 @@ package org.ruoyi.agent.manager; -import lombok.extern.slf4j.Slf4j; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import javax.sql.DataSource; + import org.ruoyi.agent.domain.ColumnInfo; import org.ruoyi.agent.domain.TableStructure; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import javax.sql.DataSource; -import java.sql.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; + +import com.baomidou.dynamic.datasource.annotation.DS; + +import lombok.extern.slf4j.Slf4j; /** * 表结构管理器 @@ -24,12 +38,15 @@ import java.util.stream.Collectors; */ @Slf4j @Component -@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") +@DS("agent") public class TableSchemaManager { - + @Autowired(required = false) private DataSource agentDataSource; + @Value("${AGENT_ALLOWED_TABLES}") + private String allowedTables; + /** * 表结构缓存 (表名 -> 表结构) * 使用 ConcurrentHashMap 支持高并发访问 @@ -55,12 +72,12 @@ public class TableSchemaManager { if (initialized) { return; } - try { log.info("Initializing database schema cache..."); loadAllowedTableSchemas(); initialized = true; log.info("Schema cache initialized with {} tables", schemaCache.size()); + } catch (Exception e) { log.error("Failed to initialize schema cache", e); } @@ -103,6 +120,7 @@ public class TableSchemaManager { try (ResultSet tableRs = metaData.getTables(conn.getCatalog(), null, tableName, new String[]{"TABLE"})) { if (tableRs.next()) { table.setTableComment(tableRs.getString("REMARKS")); + table.setTableType(tableRs.getString("TABLE_TYPE")); } } @@ -183,7 +201,6 @@ public class TableSchemaManager { * 获取所有允许的表名 */ public List getAllowedTableNames() { - String allowedTables = System.getenv("AGENT_ALLOWED_TABLES"); if (allowedTables == null || allowedTables.trim().isEmpty()) { log.warn("AGENT_ALLOWED_TABLES not configured"); return new ArrayList<>(); @@ -224,7 +241,7 @@ public class TableSchemaManager { * 检查表是否在允许列表中 */ private boolean isTableAllowed(String tableName) { - String allowedTables = System.getenv("AGENT_ALLOWED_TABLES"); + // String allowedTables = System.getenv("AGENT_ALLOWED_TABLES"); if (allowedTables == null || allowedTables.trim().isEmpty()) { return false; } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java index 914fa409..2cc33a67 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java @@ -1,12 +1,5 @@ package org.ruoyi.agent.tool; -import dev.langchain4j.agent.tool.Tool; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Component; - -import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -16,17 +9,26 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; + +import dev.langchain4j.agent.tool.Tool; +import lombok.extern.slf4j.Slf4j; + /** * 执行 SQL 查询的 Tool * 执行指定的 SELECT SQL 查询并返回结果 */ @Slf4j @Component -@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") public class ExecuteSqlQueryTool { @Autowired(required = false) - private DataSource agentDataSource; + private DataSource dataSource; /** * 执行 SELECT SQL 查询 @@ -37,6 +39,8 @@ public class ExecuteSqlQueryTool { */ @Tool("Execute a SELECT SQL query and return the results. Example: SELECT * FROM sys_user") public String executeSql(String sql) { + // 2. 手动推入数据源上下文 + DynamicDataSourceContextHolder.push("agent"); if (sql == null || sql.trim().isEmpty()) { return "Error: SQL query cannot be empty"; } @@ -48,11 +52,11 @@ public class ExecuteSqlQueryTool { } try { - if (agentDataSource == null) { + if (dataSource == null) { return "Error: Database datasource not configured"; } - try (Connection connection = agentDataSource.getConnection()) { + try (Connection connection = dataSource.getConnection()) { try (PreparedStatement preparedStatement = connection.prepareStatement(sql); ResultSet resultSet = preparedStatement.executeQuery()) { @@ -82,7 +86,12 @@ public class ExecuteSqlQueryTool { } } catch (Exception e) { log.error("Error executing SQL: {}", sql, e); + // 3. 必须在 finally 中清除上下文,防止污染其他请求 + DynamicDataSourceContextHolder.clear(); return "Error: " + e.getMessage(); + } finally { + // 3. 必须在 finally 中清除上下文,防止污染其他请求 + DynamicDataSourceContextHolder.clear(); } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryAllTablesTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryAllTablesTool.java index 9e490dc3..d9ed0666 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryAllTablesTool.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryAllTablesTool.java @@ -1,20 +1,14 @@ package org.ruoyi.agent.tool; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import dev.langchain4j.agent.tool.Tool; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.agent.config.AgentMysqlProperties; +import java.util.List; + +import org.ruoyi.agent.domain.TableStructure; +import org.ruoyi.agent.manager.TableSchemaManager; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.ResultSet; -import java.util.ArrayList; -import java.util.List; +import dev.langchain4j.agent.tool.Tool; +import lombok.extern.slf4j.Slf4j; /** * 查询数据库所有表的 Tool @@ -22,12 +16,11 @@ import java.util.List; */ @Slf4j @Component -@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") public class QueryAllTablesTool { - @Autowired(required = false) - private DataSource agentDataSource; - + + @Autowired + private TableSchemaManager tableSchemaManager; // 注入管理器 /** * 查询数据库中所有表 * 返回数据库中存在的所有表的列表 @@ -37,44 +30,36 @@ public class QueryAllTablesTool { @Tool("Query all tables in the database and return table names and basic information") public String queryAllTables() { try { - if (agentDataSource == null) { - return "Error: Database datasource not configured"; - } - - try (Connection connection = agentDataSource.getConnection()) { - DatabaseMetaData databaseMetaData = connection.getMetaData(); - ResultSet resultSet = databaseMetaData.getTables(null, null, null, new String[]{"TABLE"}); - - List tableNames = new ArrayList<>(); - List tableDetails = new ArrayList<>(); - - while (resultSet.next()) { - String tableName = resultSet.getString("TABLE_NAME"); - String tableComment = resultSet.getString("REMARKS"); - String tableType = resultSet.getString("TABLE_TYPE"); - - tableNames.add(tableName); - tableDetails.add(String.format("- %s (%s) - %s", - tableName, tableType, tableComment != null ? tableComment : "No comment")); + // 1. 从管理器获取所有允许的表结构信息(内部已包含初始化/缓存逻辑) + List tableSchemas = tableSchemaManager.getAllowedTableSchemas(); + + if (tableSchemas == null || tableSchemas.isEmpty()) { + return "No tables found in database or cache is empty."; } - resultSet.close(); - - if (tableNames.isEmpty()) { - return "No tables found in database"; - } - + + // 2. 格式化结果 StringBuilder result = new StringBuilder(); - result.append("Found ").append(tableNames.size()).append(" tables:\n"); - for (String detail : tableDetails) { - result.append(detail).append("\n"); + result.append("Found ").append(tableSchemas.size()).append(" tables in cache:\n"); + + for (TableStructure schema : tableSchemas) { + String tableName = schema.getTableName(); + String tableType = schema.getTableType() != null ? schema.getTableType() : "TABLE"; + String tableComment = schema.getTableComment(); + + result.append(String.format("- %s (%s) - %s\n", + tableName, + tableType, + tableComment != null ? tableComment : "No comment")); } - - log.info("Successfully queried {} tables", tableNames.size()); + + log.info("Successfully retrieved {} tables from schema cache", tableSchemas.size()); return result.toString(); + + } catch (Exception e) { + log.error("Error retrieving tables from cache", e); + return "Error: " + e.getMessage(); } - } catch (Exception e) { - log.error("Error querying all tables", e); - return "Error: " + e.getMessage(); - } + + } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryTableSchemaTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryTableSchemaTool.java index b7872322..33d06b27 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryTableSchemaTool.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryTableSchemaTool.java @@ -1,83 +1,57 @@ package org.ruoyi.agent.tool; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import dev.langchain4j.agent.tool.Tool; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Component; - -import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; -/** - * 查询表建表详情的 Tool - * 根据表名查询该表的建表 SQL 语句 - */ -@Slf4j +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; + +import dev.langchain4j.agent.tool.Tool; +import lombok.extern.slf4j.Slf4j; + @Component -@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") +@Slf4j public class QueryTableSchemaTool { @Autowired(required = false) - private DataSource agentDataSource; + private DataSource dataSource; - /** - * 根据表名查询建表详情 - * 返回指定表的 CREATE TABLE 语句 - * - * @param tableName 表名 - * @return 包含建表 SQL 的结果 - */ @Tool("Query the CREATE TABLE statement (DDL) for a specific table by table name") public String queryTableSchema(String tableName) { + // 2. 手动推入数据源上下文 + DynamicDataSourceContextHolder.push("agent"); if (tableName == null || tableName.trim().isEmpty()) { return "Error: Table name cannot be empty"; } - // 验证表名有效性,防止 SQL 注入 - if (!isValidIdentifier(tableName)) { + if (!tableName.matches("^[a-zA-Z0-9_]+$")) { return "Error: Invalid table name format"; } - try { - if (agentDataSource == null) { - return "Error: Database datasource not configured"; + String sql = "SHOW CREATE TABLE `" + tableName + "`"; + + try (Connection connection = dataSource.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql); + ResultSet rs = ps.executeQuery()) { + + if (rs.next()) { + return rs.getString("Create Table"); } - try (Connection connection = agentDataSource.getConnection()) { - String sql = "SHOW CREATE TABLE " + tableName; - PreparedStatement preparedStatement = connection.prepareStatement(sql); - ResultSet resultSet = preparedStatement.executeQuery(); + return "Table not found: " + tableName; - if (resultSet.next()) { - String createTableSql = resultSet.getString("Create Table"); - resultSet.close(); - preparedStatement.close(); - - log.info("Successfully queried schema for table: {}", tableName); - return "CREATE TABLE DDL for " + tableName + ":\n\n" + createTableSql; - } - - resultSet.close(); - preparedStatement.close(); - return "Error: Table not found or not accessible: " + tableName; - } } catch (Exception e) { - log.error("Error querying table schema for table: {}", tableName, e); + // 3. 必须在 finally 中清除上下文,防止污染其他请求 + DynamicDataSourceContextHolder.clear(); + log.error("Error querying table schema: {}", tableName, e); return "Error: " + e.getMessage(); + } finally { + // 3. 必须在 finally 中清除上下文,防止污染其他请求 + DynamicDataSourceContextHolder.clear(); } } - - /** - * 验证是否为有效的 SQL 标识符 - */ - private boolean isValidIdentifier(String identifier) { - if (identifier == null || identifier.isEmpty()) { - return false; - } - return identifier.matches("^[a-zA-Z0-9_\\.]+$"); - } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java index 75894c9a..fa2b8f70 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java @@ -3,6 +3,7 @@ package org.ruoyi.service.chat.impl; import dev.langchain4j.agentic.AgenticServices; import dev.langchain4j.agentic.supervisor.SupervisorAgent; import dev.langchain4j.agentic.supervisor.SupervisorResponseStrategy; +import dev.langchain4j.community.model.dashscope.QwenChatModel; import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.UserMessage; import dev.langchain4j.mcp.McpToolProvider; @@ -325,26 +326,26 @@ public abstract class AbstractStreamingChatService implements IChatService { protected String doAgent(String userMessage, ChatModelVo chatModelVo) { // 步骤1: 配置MCP传输层 - 连接到bing-cn-mcp服务器 // 该服务提供两个工具: bing_search (必应搜索) 和 crawl_webpage (网页抓取) - McpTransport transport = new StdioMcpTransport.Builder() - .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y", - "bing-cn-mcp" - )) - .logEvents(true) - .build(); + // McpTransport transport = new StdioMcpTransport.Builder() + // .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y", + // "bing-cn-mcp" + // )) + // .logEvents(true) + // .build(); - // 步骤2: 创建MCP客户端 - McpClient mcpClient = new DefaultMcpClient.Builder() - .transport(transport) - .build(); + // // 步骤2: 创建MCP客户端 + // McpClient mcpClient = new DefaultMcpClient.Builder() + // .transport(transport) + // .build(); - // 步骤3: 配置工具提供者 - ToolProvider toolProvider = McpToolProvider.builder() - .mcpClients(List.of(mcpClient)) - .build(); + // // 步骤3: 配置工具提供者 + // ToolProvider toolProvider = McpToolProvider.builder() + // .mcpClients(List.of(mcpClient)) + // .build(); McpTransport transport1 = new StdioMcpTransport.Builder() - .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y", + .command(List.of("npx", "-y", "mcp-echarts" )) .logEvents(true) @@ -361,40 +362,52 @@ public abstract class AbstractStreamingChatService implements IChatService { .build(); // 步骤4: 配置OpenAI模型 - OpenAiChatModel PLANNER_MODEL = OpenAiChatModel.builder() - .baseUrl(chatModelVo.getApiHost()) + // OpenAiChatModel PLANNER_MODEL = OpenAiChatModel.builder() + // .baseUrl(chatModelVo.getApiHost()) + // .apiKey(chatModelVo.getApiKey()) + // .modelName(chatModelVo.getModelName()) + // .build(); + + + QwenChatModel qwenChatModel = QwenChatModel.builder() + // .baseUrl(chatModelVo.getApiHost()) .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) - .build(); - + .build(); + SqlAgent sqlAgent = AgenticServices.agentBuilder(SqlAgent.class) - .chatModel(PLANNER_MODEL) + .chatModel( + qwenChatModel) .tools( - new QueryAllTablesTool(), - new QueryTableSchemaTool(), - new ExecuteSqlQueryTool() + SpringUtils.getBean(QueryAllTablesTool.class), // 必须通过 getBean 获取 + SpringUtils.getBean(QueryTableSchemaTool.class), + SpringUtils.getBean(ExecuteSqlQueryTool.class) ) .build(); - WebSearchAgent searchAgent = AgenticServices.agentBuilder(WebSearchAgent.class) - .chatModel(PLANNER_MODEL) - .toolProvider(toolProvider) - .build(); + // WebSearchAgent searchAgent = AgenticServices.agentBuilder(WebSearchAgent.class) + // .chatModel(PLANNER_MODEL) + // .toolProvider(toolProvider) + // .build(); ChartGenerationAgent chartGenerationAgent = AgenticServices.agentBuilder(ChartGenerationAgent.class) - .chatModel(PLANNER_MODEL) + .chatModel( + qwenChatModel) .toolProvider(toolProvider1) .build(); - + String res = sqlAgent.getData(userMessage); + String res1 = chartGenerationAgent.generateChart(res); + System.out.println(res1); + System.out.println(res); SupervisorAgent supervisor = AgenticServices .supervisorBuilder() - .chatModel(PLANNER_MODEL) + .chatModel(qwenChatModel) .subAgents(sqlAgent, chartGenerationAgent) .responseStrategy(SupervisorResponseStrategy.LAST) .build(); String invoke = supervisor.invoke(userMessage); System.out.println(invoke); - return invoke; + return res1; } } From d059f50ba691f9367b71056c5246883eeeba9ec2 Mon Sep 17 00:00:00 2001 From: zhang <823772544@qq.com> Date: Tue, 24 Feb 2026 16:09:37 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-admin/src/main/resources/application-dev.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 91498093..03b7bc24 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -60,12 +60,12 @@ spring: # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) url: jdbc:mysql://127.0.0.1:3306/ruoyi_ai_agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true username: root - password: Qzhang450000 + password: root agent: - url: jdbc:mysql://127.0.0.1:3306/yunding?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # url: jdbc:mysql://localhost:3306/agent_db username: root - password: Qzhang450000 + password: root driverClassName: com.mysql.cj.jdbc.Driver hikari: From 8954f59cd7b846c2a4c57f1e1d796b305f2da56f Mon Sep 17 00:00:00 2001 From: zengxb <648669796@qq.com> Date: Thu, 26 Feb 2026 14:36:33 +0800 Subject: [PATCH 5/9] =?UTF-8?q?context:=E5=B7=A5=E4=BD=9C=E6=B5=81?= =?UTF-8?q?=E5=92=8CAi=20Chat=E5=AF=B9=E8=AF=9D=E6=B6=88=E6=81=AF=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=95=B4=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/script/sql/ruoyi-ai-v3_mysql8.sql | 14 ++ docs/script/sql/workFlow-v3.0.sql | 10 -- ruoyi-common/ruoyi-common-chat/pom.xml | 6 + .../common/chat}/base/ThreadContext.java | 8 +- .../chat}/domain/bo/chat/ChatMessageBo.java | 13 +- .../chat/domain/bo/chat/ChatModelBo.java | 2 +- .../chat/domain/dto/request/ChatRequest.java | 20 +++ .../chat/domain/dto/request/ReSumeRunner.java | 19 +++ .../domain/dto/request/WorkFlowRunner.java | 15 ++ .../chat}/domain/vo/chat/ChatMessageVo.java | 9 +- .../chat/domain/vo/chat/ChatModelVo.java | 2 +- .../ruoyi/common/chat}/entity/BaseEntity.java | 2 +- .../org/ruoyi/common/chat}/entity/User.java | 4 +- .../{domain => }/entity/chat/ChatContext.java | 2 +- .../common/chat}/entity/chat/ChatMessage.java | 7 +- .../{domain => }/entity/chat/ChatModel.java | 2 +- .../entity/image/ImageContext.java | 2 +- .../ruoyi/common/chat}/enums/BaseEnum.java | 2 +- .../ruoyi/common/chat}/enums/ErrorEnum.java | 2 +- .../ruoyi/common/chat}/enums/RoleType.java | 3 +- .../common/chat}/enums/UserStatusEnum.java | 2 +- .../chat/factory/ChatServiceFactory.java | 2 +- .../chat/factory/ImageServiceFactory.java | 2 +- .../chat}/IChatModelService.java | 2 +- .../chat}/IChatService.java | 4 +- .../AbstractChatMessageService.java | 59 ++++++++ .../chatMessage}/IChatMessageService.java | 6 +- .../image}/IImageGenerationService.java | 4 +- .../workFlow/IWorkFlowStarterService.java | 33 +++++ .../common/sse/core/SseEmitterManager.java | 2 +- ruoyi-modules/ruoyi-aiflow/pom.xml | 6 - .../org/ruoyi/workflow/base/BaseResponse.java | 2 +- .../controller/WorkflowController.java | 4 +- .../controller/WorkflowRuntimeController.java | 2 +- .../dto/workflow/WorkflowResumeReq.java | 2 + .../workflow/dto/workflow/WorkflowRunReq.java | 1 + .../org/ruoyi/workflow/entity/Workflow.java | 1 + .../workflow/entity/WorkflowComponent.java | 1 + .../ruoyi/workflow/entity/WorkflowEdge.java | 1 + .../ruoyi/workflow/entity/WorkflowNode.java | 1 + .../workflow/entity/WorkflowRuntime.java | 1 + .../workflow/entity/WorkflowRuntimeNode.java | 1 + .../ruoyi/workflow/enums/AiModelStatus.java | 1 + .../workflow/enums/WfIODataTypeEnum.java | 1 + .../workflow/helper/SSEEmitterHelper.java | 2 +- .../service/WorkflowComponentService.java | 4 +- .../workflow/service/WorkflowEdgeService.java | 2 +- .../workflow/service/WorkflowNodeService.java | 2 +- .../service/WorkflowRuntimeNodeService.java | 4 +- .../service/WorkflowRuntimeService.java | 6 +- .../workflow/service/WorkflowService.java | 6 +- .../ruoyi/workflow/util/PrivilegeUtil.java | 4 +- .../workflow/util/WorkflowMessageUtil.java | 37 +++++ .../workflow/workflow/WfNodeIODataUtil.java | 2 +- .../org/ruoyi/workflow/workflow/WfState.java | 6 +- .../workflow/workflow/WorkflowEngine.java | 21 ++- .../workflow/WorkflowGraphBuilder.java | 2 +- .../workflow/workflow/WorkflowStarter.java | 27 ++-- .../ruoyi/workflow/workflow/WorkflowUtil.java | 66 +++++---- .../workflow/node/AbstractWfNode.java | 30 +++- .../node/httpRequest/HttpRequestNode.java | 10 ++ .../node/humanFeedBack/HumanFeedbackNode.java | 1 + .../workflow/node/image/ImageNode.java | 5 + .../workflow/node/mailSend/MailSendNode.java | 138 ++++++++++++------ .../workflow/node/start/StartNode.java | 4 +- .../chat/ChatMessageController.java | 6 +- .../controller/chat/ChatModelController.java | 2 +- .../ruoyi/factory/EmbeddingModelFactory.java | 2 +- .../ruoyi/mapper/chat/ChatMessageMapper.java | 4 +- .../ruoyi/mapper/chat/ChatModelMapper.java | 2 +- .../impl/AbstractStreamingChatService.java | 91 ++++++------ .../chat/impl/ChatMessageServiceImpl.java | 8 +- .../chat/impl/ChatModelServiceImpl.java | 4 +- .../service/chat/impl/ChatServiceFacade.java | 7 +- .../impl/memory/ChatMemoryUsageExample.java | 2 +- .../memory/PersistentChatMemoryStore.java | 2 +- .../impl/GraphExtractionServiceImpl.java | 2 +- .../image/AbstractImageGenerationService.java | 4 +- .../impl/KnowledgeAttachServiceImpl.java | 2 +- 79 files changed, 548 insertions(+), 254 deletions(-) delete mode 100644 docs/script/sql/workFlow-v3.0.sql rename {ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow => ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat}/base/ThreadContext.java (94%) rename {ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi => ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat}/domain/bo/chat/ChatMessageBo.java (90%) create mode 100644 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ReSumeRunner.java create mode 100644 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/WorkFlowRunner.java rename {ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi => ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat}/domain/vo/chat/ChatMessageVo.java (94%) rename {ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow => ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat}/entity/BaseEntity.java (95%) rename {ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow => ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat}/entity/User.java (95%) rename ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/{domain => }/entity/chat/ChatContext.java (96%) rename {ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain => ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat}/entity/chat/ChatMessage.java (87%) rename ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/{domain => }/entity/chat/ChatModel.java (96%) rename ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/{domain => }/entity/image/ImageContext.java (94%) rename {ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow => ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat}/enums/BaseEnum.java (84%) rename {ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow => ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat}/enums/ErrorEnum.java (99%) rename {ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi => ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat}/enums/RoleType.java (84%) rename {ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow => ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat}/enums/UserStatusEnum.java (93%) rename ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/{Service => service/chat}/IChatModelService.java (97%) rename ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/{Service => service/chat}/IChatService.java (79%) create mode 100644 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chatMessage/AbstractChatMessageService.java rename {ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat => ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chatMessage}/IChatMessageService.java (92%) rename ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/{Service => service/image}/IImageGenerationService.java (74%) create mode 100644 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/workFlow/IWorkFlowStarterService.java create mode 100644 ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/util/WorkflowMessageUtil.java diff --git a/docs/script/sql/ruoyi-ai-v3_mysql8.sql b/docs/script/sql/ruoyi-ai-v3_mysql8.sql index e7aec981..6d85a2ef 100644 --- a/docs/script/sql/ruoyi-ai-v3_mysql8.sql +++ b/docs/script/sql/ruoyi-ai-v3_mysql8.sql @@ -3647,3 +3647,17 @@ INSERT INTO `test_tree` VALUES (12, '000000', 10, 108, 3, '子节点88', 0, 103, INSERT INTO `test_tree` VALUES (13, '000000', 10, 108, 3, '子节点99', 0, 103, '2026-02-03 05:14:54', 1, NULL, NULL, 0); SET FOREIGN_KEY_CHECKS = 1; + +INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (17, '5cd68dccbbb411f0bb7840c2ba9a7fbc', 'Start', '开始', '流程由此开始', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); +INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (18, '5cd6ac69bbb411f0bb7840c2ba9a7fbc', 'End', '结束', '流程由此结束', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); +INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (19, '5cd6c8eabbb411f0bb7840c2ba9a7fbc', 'Answer', '生成回答', '调用大语言模型回答问题', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); +INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (20, '0b4369bb60dc46d6bd84ceb4e36184dc', 'KeywordExtractor', '关键词提取', '从文本中提取关键词', 0, 1, '2025-12-26 16:30:05', '2025-12-26 16:30:05', 0, '000000'); +INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (21, 'bb00fc2f52c74fec82ee3f99725b56bb', 'Switcher', '条件分支', '根据条件执行不同分支', 0, 1, '2025-12-26 16:30:46', '2025-12-26 16:30:46', 0, '000000'); +INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (22, 'f37dbcb8f0d5464d90fbb22774490a56', 'HumanFeedback', '人类', '人机沟通', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000'); +INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (23, 'af9d6d7b9c9b47f990ad25ec84912b73', 'Tongyiwanx', '阿里图像生成', '使用通义万相生成图像', 0, 1, '2025-12-26 16:32:25', '2025-12-26 16:32:25', 0, '000000'); +INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (24, 'a1e2c9d4b8f04e1a9c3d6f8e2a7b1c9d', 'MailSend', '发送邮箱', '发送邮箱', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000'); +INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (25, 'f1e2d3c4b5a67890f1e2d3c4b5a6f1e2', 'HttpRequest', '请求节点', '请求节点', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000'); +INSERT INTO `chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_price`, `model_type`, `model_show`, `model_free`, `priority`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`) VALUES (2022565766560468994, 'Tongyiwanx', 'wan2.5-t2i-preview', 'Tongyiwanx', 'wan2.5-t2i-preview', 1, '1', 'Y', 'Y', 1, 'https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation', 'skxxxx', 103, 1, '2026-02-14 14:57:11', 1, '2026-02-14 14:57:11', '通义万相文生图', 0); +INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2021046920636690433, '流程管理', 0, 0, 'flow', '', NULL, 1, 0, 'M', '0', '0', NULL, 'ph:user-fill', 103, 1, '2026-02-10 10:21:50', 1, '2026-02-10 15:59:28', ''); +INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2021047050391678978, '工作流编排', 2021046920636690433, 0, 'aiflowengine', 'aiflow/index', NULL, 1, 0, 'C', '0', '0', '', 'ph:user-fill', 103, 1, '2026-02-10 10:22:21', 1, '2026-02-10 16:04:41', ''); + diff --git a/docs/script/sql/workFlow-v3.0.sql b/docs/script/sql/workFlow-v3.0.sql deleted file mode 100644 index f7573338..00000000 --- a/docs/script/sql/workFlow-v3.0.sql +++ /dev/null @@ -1,10 +0,0 @@ -INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (17, '5cd68dccbbb411f0bb7840c2ba9a7fbc', 'Start', '开始', '流程由此开始', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); -INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (18, '5cd6ac69bbb411f0bb7840c2ba9a7fbc', 'End', '结束', '流程由此结束', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); -INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (19, '5cd6c8eabbb411f0bb7840c2ba9a7fbc', 'Answer', '生成回答', '调用大语言模型回答问题', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); -INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (20, '0b4369bb60dc46d6bd84ceb4e36184dc', 'KeywordExtractor', '关键词提取', '从文本中提取关键词', 0, 1, '2025-12-26 16:30:05', '2025-12-26 16:30:05', 0, '000000'); -INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (21, 'bb00fc2f52c74fec82ee3f99725b56bb', 'Switcher', '条件分支', '根据条件执行不同分支', 0, 1, '2025-12-26 16:30:46', '2025-12-26 16:30:46', 0, '000000'); -INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (22, 'f37dbcb8f0d5464d90fbb22774490a56', 'HumanFeedback', '人类', '人机沟通', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000'); -INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (23, 'af9d6d7b9c9b47f990ad25ec84912b73', 'Tongyiwanx', '阿里图像生成', '使用通义万相生成图像', 0, 1, '2025-12-26 16:32:25', '2025-12-26 16:32:25', 0, '000000'); -INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (24, 'a1e2c9d4b8f04e1a9c3d6f8e2a7b1c9d', 'MailSend', '发送邮箱', '发送邮箱', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000'); -INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (25, 'f1e2d3c4b5a67890f1e2d3c4b5a6f1e2', 'HttpRequest', '请求节点', '请求节点', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000'); -INSERT INTO `ruoyi-ai-v3`.`chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_price`, `model_type`, `model_show`, `model_free`, `priority`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`) VALUES (2022565766560468994, 'Tongyiwanx', 'wan2.5-t2i-preview', 'Tongyiwanx', 'wan2.5-t2i-preview', 1, '1', 'Y', 'Y', 1, 'https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation', 'Bearer sk-f4550b0e138c488cbfcafe3d61f800a5', 103, 1, '2026-02-14 14:57:11', 1, '2026-02-14 14:57:11', '通义万相文生图', 0); diff --git a/ruoyi-common/ruoyi-common-chat/pom.xml b/ruoyi-common/ruoyi-common-chat/pom.xml index 0db3c41c..06e38185 100644 --- a/ruoyi-common/ruoyi-common-chat/pom.xml +++ b/ruoyi-common/ruoyi-common-chat/pom.xml @@ -62,6 +62,12 @@ org.ruoyi ruoyi-common-tenant + + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations.version} + diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/base/ThreadContext.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/base/ThreadContext.java similarity index 94% rename from ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/base/ThreadContext.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/base/ThreadContext.java index d74eb104..7605307d 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/base/ThreadContext.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/base/ThreadContext.java @@ -1,14 +1,14 @@ -package org.ruoyi.workflow.base; +package org.ruoyi.common.chat.base; import cn.dev33.satoken.stp.StpUtil; import org.apache.commons.lang3.StringUtils; +import org.ruoyi.common.chat.entity.User; +import org.ruoyi.common.chat.enums.UserStatusEnum; import org.ruoyi.common.core.domain.model.LoginUser; import org.ruoyi.common.core.exception.base.BaseException; import org.ruoyi.common.satoken.utils.LoginHelper; -import org.ruoyi.workflow.entity.User; -import org.ruoyi.workflow.enums.UserStatusEnum; -import static org.ruoyi.workflow.enums.ErrorEnum.A_USER_NOT_FOUND; +import static org.ruoyi.common.chat.enums.ErrorEnum.A_USER_NOT_FOUND; /** * 线程上下文适配器,统一接入 Sa-Token 登录态。 diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/chat/ChatMessageBo.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatMessageBo.java similarity index 90% rename from ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/chat/ChatMessageBo.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatMessageBo.java index 9d44fcaa..2cbb051b 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/chat/ChatMessageBo.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatMessageBo.java @@ -1,13 +1,14 @@ -package org.ruoyi.domain.bo.chat; +package org.ruoyi.common.chat.domain.bo.chat; -import org.ruoyi.common.core.validate.AddGroup; -import org.ruoyi.common.core.validate.EditGroup; -import org.ruoyi.domain.entity.chat.ChatMessage; -import org.ruoyi.common.mybatis.core.domain.BaseEntity; import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; -import jakarta.validation.constraints.*; +import org.ruoyi.common.chat.entity.chat.ChatMessage; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + /** * 聊天消息业务对象 chat_message diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatModelBo.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatModelBo.java index 8807503c..4d97dd05 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatModelBo.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatModelBo.java @@ -4,7 +4,7 @@ import io.github.linpeilie.annotations.AutoMapper; import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; -import org.ruoyi.common.chat.domain.entity.chat.ChatModel; +import org.ruoyi.common.chat.entity.chat.ChatModel; import org.ruoyi.common.core.validate.EditGroup; import org.ruoyi.common.mybatis.core.domain.BaseEntity; diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ChatRequest.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ChatRequest.java index 1b36ae42..8d13339e 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ChatRequest.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ChatRequest.java @@ -21,6 +21,21 @@ public class ChatRequest { @NotEmpty(message = "传入的模型不能为空") private String model; + /** + * 工作流请求体 + */ + private WorkFlowRunner workFlowRunner; + + /** + * 人机交互信息体 + */ + private ReSumeRunner reSumeRunner; + + /** + * 是否启用工作流 + */ + private Boolean enableWorkFlow; + /** * 会话id */ @@ -41,6 +56,11 @@ public class ChatRequest { */ private Long uuid; + /** + * 是否为人机交互用户继续输入 + */ + private Boolean isResume; + /** * 是否启用深度思考 */ diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ReSumeRunner.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ReSumeRunner.java new file mode 100644 index 00000000..65b39fd0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ReSumeRunner.java @@ -0,0 +1,19 @@ +package org.ruoyi.common.chat.domain.dto.request; + +import lombok.Data; + +/** + * 人机交互输入信息 + */ +@Data +public class ReSumeRunner { + /** + * 运行节点UUID + */ + private String runtimeUuid; + + /** + * 人机交互用户输入信息 + */ + private String feedbackContent; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/WorkFlowRunner.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/WorkFlowRunner.java new file mode 100644 index 00000000..fff727ec --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/WorkFlowRunner.java @@ -0,0 +1,15 @@ +package org.ruoyi.common.chat.domain.dto.request; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Data; + +import java.util.List; + +/** + * 工作流请求体信息 + */ +@Data +public class WorkFlowRunner { + private List inputs; + private String uuid; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/chat/ChatMessageVo.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatMessageVo.java similarity index 94% rename from ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/chat/ChatMessageVo.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatMessageVo.java index 7a548865..17f4d707 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/chat/ChatMessageVo.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatMessageVo.java @@ -1,18 +1,17 @@ -package org.ruoyi.domain.vo.chat; +package org.ruoyi.common.chat.domain.vo.chat; -import org.ruoyi.domain.entity.chat.ChatMessage; import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import cn.idev.excel.annotation.ExcelProperty; -import org.ruoyi.common.excel.annotation.ExcelDictFormat; -import org.ruoyi.common.excel.convert.ExcelDictConvert; import io.github.linpeilie.annotations.AutoMapper; import lombok.Data; +import org.ruoyi.common.chat.entity.chat.ChatMessage; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; import java.io.Serial; import java.io.Serializable; - /** * 聊天消息视图对象 chat_message * diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatModelVo.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatModelVo.java index 77ba3328..f79f9f23 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatModelVo.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatModelVo.java @@ -5,7 +5,7 @@ import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import cn.idev.excel.annotation.ExcelProperty; import io.github.linpeilie.annotations.AutoMapper; import lombok.Data; -import org.ruoyi.common.chat.domain.entity.chat.ChatModel; +import org.ruoyi.common.chat.entity.chat.ChatModel; import java.io.Serial; import java.io.Serializable; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/BaseEntity.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/BaseEntity.java similarity index 95% rename from ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/BaseEntity.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/BaseEntity.java index b657eb16..e9630ff2 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/BaseEntity.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/BaseEntity.java @@ -1,4 +1,4 @@ -package org.ruoyi.workflow.entity; +package org.ruoyi.common.chat.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/User.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/User.java similarity index 95% rename from ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/User.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/User.java index 20ac4dc0..dadedf9a 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/User.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/User.java @@ -1,11 +1,11 @@ -package org.ruoyi.workflow.entity; +package org.ruoyi.common.chat.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; -import org.ruoyi.workflow.enums.UserStatusEnum; +import org.ruoyi.common.chat.enums.UserStatusEnum; import java.time.LocalDateTime; diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/chat/ChatContext.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatContext.java similarity index 96% rename from ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/chat/ChatContext.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatContext.java index 13a5b4b9..f90abd82 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/chat/ChatContext.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatContext.java @@ -1,4 +1,4 @@ -package org.ruoyi.common.chat.domain.entity.chat; +package org.ruoyi.common.chat.entity.chat; import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; import jakarta.validation.constraints.NotNull; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/chat/ChatMessage.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatMessage.java similarity index 87% rename from ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/chat/ChatMessage.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatMessage.java index 905f74fb..c0055bd4 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/chat/ChatMessage.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatMessage.java @@ -1,9 +1,10 @@ -package org.ruoyi.domain.entity.chat; +package org.ruoyi.common.chat.entity.chat; -import org.ruoyi.common.tenant.core.TenantEntity; -import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import lombok.EqualsAndHashCode; +import org.ruoyi.common.tenant.core.TenantEntity; import java.io.Serial; diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/chat/ChatModel.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatModel.java similarity index 96% rename from ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/chat/ChatModel.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatModel.java index 9edee32f..4fc63bfd 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/chat/ChatModel.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatModel.java @@ -1,4 +1,4 @@ -package org.ruoyi.common.chat.domain.entity.chat; +package org.ruoyi.common.chat.entity.chat; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/image/ImageContext.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/image/ImageContext.java similarity index 94% rename from ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/image/ImageContext.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/image/ImageContext.java index 4bf42c61..60be2739 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/image/ImageContext.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/image/ImageContext.java @@ -1,4 +1,4 @@ -package org.ruoyi.common.chat.domain.entity.image; +package org.ruoyi.common.chat.entity.image; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/BaseEnum.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/enums/BaseEnum.java similarity index 84% rename from ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/BaseEnum.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/enums/BaseEnum.java index 66a2fb63..902adf45 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/BaseEnum.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/enums/BaseEnum.java @@ -1,4 +1,4 @@ -package org.ruoyi.workflow.enums; +package org.ruoyi.common.chat.enums; import com.baomidou.mybatisplus.annotation.IEnum; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/ErrorEnum.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/enums/ErrorEnum.java similarity index 99% rename from ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/ErrorEnum.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/enums/ErrorEnum.java index f9de801e..06095cec 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/ErrorEnum.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/enums/ErrorEnum.java @@ -1,4 +1,4 @@ -package org.ruoyi.workflow.enums; +package org.ruoyi.common.chat.enums; import lombok.Getter; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/RoleType.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/enums/RoleType.java similarity index 84% rename from ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/RoleType.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/enums/RoleType.java index cb550975..daaaaaa2 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/RoleType.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/enums/RoleType.java @@ -1,4 +1,4 @@ -package org.ruoyi.enums; +package org.ruoyi.common.chat.enums; import lombok.AllArgsConstructor; import lombok.Getter; @@ -18,6 +18,7 @@ public enum RoleType { ASSISTANT("assistant"), FUNCTION("function"), TOOL("tool"), + WORKFLOW("workFlow") ; private final String name; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/UserStatusEnum.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/enums/UserStatusEnum.java similarity index 93% rename from ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/UserStatusEnum.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/enums/UserStatusEnum.java index 2be9a950..2b2cca43 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/UserStatusEnum.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/enums/UserStatusEnum.java @@ -1,4 +1,4 @@ -package org.ruoyi.workflow.enums; +package org.ruoyi.common.chat.enums; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ChatServiceFactory.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ChatServiceFactory.java index ee884357..6e91254a 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ChatServiceFactory.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ChatServiceFactory.java @@ -1,6 +1,6 @@ package org.ruoyi.common.chat.factory; -import org.ruoyi.common.chat.Service.IChatService; +import org.ruoyi.common.chat.service.chat.IChatService; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ImageServiceFactory.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ImageServiceFactory.java index e7ba4160..52d3731b 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ImageServiceFactory.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ImageServiceFactory.java @@ -1,6 +1,6 @@ package org.ruoyi.common.chat.factory; -import org.ruoyi.common.chat.Service.IImageGenerationService; +import org.ruoyi.common.chat.service.image.IImageGenerationService; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IChatModelService.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chat/IChatModelService.java similarity index 97% rename from ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IChatModelService.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chat/IChatModelService.java index edb7ed52..3c362a52 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IChatModelService.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chat/IChatModelService.java @@ -1,4 +1,4 @@ -package org.ruoyi.common.chat.Service; +package org.ruoyi.common.chat.service.chat; import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IChatService.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chat/IChatService.java similarity index 79% rename from ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IChatService.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chat/IChatService.java index 01a02567..de9f13ca 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IChatService.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chat/IChatService.java @@ -1,7 +1,7 @@ -package org.ruoyi.common.chat.Service; +package org.ruoyi.common.chat.service.chat; import jakarta.validation.Valid; -import org.ruoyi.common.chat.domain.entity.chat.ChatContext; +import org.ruoyi.common.chat.entity.chat.ChatContext; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; /** diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chatMessage/AbstractChatMessageService.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chatMessage/AbstractChatMessageService.java new file mode 100644 index 00000000..687605eb --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chatMessage/AbstractChatMessageService.java @@ -0,0 +1,59 @@ +package org.ruoyi.common.chat.service.chatMessage; + +import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo; +import org.ruoyi.common.chat.domain.dto.request.ChatRequest; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * 聊天信息抽象基类 - 保存聊天信息 + * + * @author Zengxb + * @date 2026-02-24 + */ +public abstract class AbstractChatMessageService { + + /** + * 创建日志对象 + */ + Logger log = LoggerFactory.getLogger(AbstractChatMessageService.class); + + @Autowired + private IChatMessageService chatMessageService; + + /** + * 保存聊天信息 + */ + public void saveChatMessage(ChatRequest chatRequest, Long userId, String content, String role, ChatModelVo chatModelVo){ + try { + // 验证必要的上下文信息 + if (chatRequest == null || userId == null) { + log.warn("缺少必要的聊天上下文信息,无法保存消息"); + return; + } + + // 创建ChatMessageBo对象 + ChatMessageBo messageBO = new ChatMessageBo(); + messageBO.setUserId(userId); + messageBO.setSessionId(chatRequest.getSessionId()); + messageBO.setContent(content); + messageBO.setRole(role); + messageBO.setModelName(chatRequest.getModel()); + messageBO.setBillingType(chatModelVo.getModelType()); + messageBO.setRemark(null); + + chatMessageService.insertByBo(messageBO); + } catch (Exception e) { + log.error("保存{}聊天消息时出错: {}", getProviderName(), e.getMessage(), e); + } + } + + /** + * 获取服务提供商名称 + */ + protected String getProviderName(){ + return "默认工作流大模型"; + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/IChatMessageService.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chatMessage/IChatMessageService.java similarity index 92% rename from ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/IChatMessageService.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chatMessage/IChatMessageService.java index 3289b5f2..98e31a40 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/IChatMessageService.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chatMessage/IChatMessageService.java @@ -1,10 +1,10 @@ -package org.ruoyi.service.chat; +package org.ruoyi.common.chat.service.chatMessage; +import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo; import org.ruoyi.common.chat.domain.dto.ChatMessageDTO; +import org.ruoyi.common.chat.domain.vo.chat.ChatMessageVo; import org.ruoyi.common.mybatis.core.page.PageQuery; import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.ruoyi.domain.bo.chat.ChatMessageBo; -import org.ruoyi.domain.vo.chat.ChatMessageVo; import java.util.Collection; import java.util.List; diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IImageGenerationService.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/image/IImageGenerationService.java similarity index 74% rename from ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IImageGenerationService.java rename to ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/image/IImageGenerationService.java index 237afed1..3be0a84f 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IImageGenerationService.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/image/IImageGenerationService.java @@ -1,7 +1,7 @@ -package org.ruoyi.common.chat.Service; +package org.ruoyi.common.chat.service.image; import jakarta.validation.Valid; -import org.ruoyi.common.chat.domain.entity.image.ImageContext; +import org.ruoyi.common.chat.entity.image.ImageContext; /** * 公共文生图接口 diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/workFlow/IWorkFlowStarterService.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/workFlow/IWorkFlowStarterService.java new file mode 100644 index 00000000..4001b67f --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/workFlow/IWorkFlowStarterService.java @@ -0,0 +1,33 @@ +package org.ruoyi.common.chat.service.workFlow; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.ruoyi.common.chat.entity.User; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.List; + +/** + * 工作流启动Service接口 + * + * @author Zengxb + * @date 2026-02-24 + */ +public interface IWorkFlowStarterService { + + /** + * 启动工作流 + * @param user 用户 + * @param workflowUuid 工作流UUID + * @param userInputs 用户输入信息 + * @return 流式输出结果 + */ + SseEmitter streaming(User user, String workflowUuid, List userInputs, Long sessionId); + + /** + * 恢复工作流 + * @param runtimeUuid 运行时UUID + * @param userInput 用户输入 + * @param sseEmitter SSE连接对象 + */ + void resumeFlow(String runtimeUuid, String userInput, SseEmitter sseEmitter); +} diff --git a/ruoyi-common/ruoyi-common-sse/src/main/java/org/ruoyi/common/sse/core/SseEmitterManager.java b/ruoyi-common/ruoyi-common-sse/src/main/java/org/ruoyi/common/sse/core/SseEmitterManager.java index d111c875..4e7a5481 100644 --- a/ruoyi-common/ruoyi-common-sse/src/main/java/org/ruoyi/common/sse/core/SseEmitterManager.java +++ b/ruoyi-common/ruoyi-common-sse/src/main/java/org/ruoyi/common/sse/core/SseEmitterManager.java @@ -65,7 +65,7 @@ public class SseEmitterManager { emitter.onCompletion(() -> { SseEmitter remove = emitters.remove(token); if (remove != null) { - remove.complete(); +// remove.complete(); } }); emitter.onTimeout(() -> { diff --git a/ruoyi-modules/ruoyi-aiflow/pom.xml b/ruoyi-modules/ruoyi-aiflow/pom.xml index 34d46d62..d7a147c8 100644 --- a/ruoyi-modules/ruoyi-aiflow/pom.xml +++ b/ruoyi-modules/ruoyi-aiflow/pom.xml @@ -81,12 +81,6 @@ - - io.swagger.core.v3 - swagger-annotations - ${swagger-annotations.version} - - com.google.api-client google-api-client diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/base/BaseResponse.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/base/BaseResponse.java index d6220abd..c84389bc 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/base/BaseResponse.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/base/BaseResponse.java @@ -1,7 +1,7 @@ package org.ruoyi.workflow.base; import lombok.Data; -import org.ruoyi.workflow.enums.ErrorEnum; +import org.ruoyi.common.chat.enums.ErrorEnum; import java.io.Serializable; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/controller/WorkflowController.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/controller/WorkflowController.java index d92f79d8..61037520 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/controller/WorkflowController.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/controller/WorkflowController.java @@ -5,8 +5,8 @@ import io.swagger.v3.oas.annotations.Operation; import jakarta.annotation.Resource; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; +import org.ruoyi.common.chat.base.ThreadContext; import org.ruoyi.common.core.domain.R; -import org.ruoyi.workflow.base.ThreadContext; import org.ruoyi.workflow.dto.workflow.*; import org.ruoyi.workflow.entity.WorkflowComponent; import org.ruoyi.workflow.service.WorkflowComponentService; @@ -72,7 +72,7 @@ public class WorkflowController { @Operation(summary = "流式响应") @PostMapping(value = "/run", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter sseAsk(@RequestBody WorkflowRunReq runReq) { - return workflowStarter.streaming(ThreadContext.getCurrentUser(), runReq.getUuid(), runReq.getInputs()); + return workflowStarter.streaming(ThreadContext.getCurrentUser(), runReq.getUuid(), runReq.getInputs(),runReq.getSessionId()); } @GetMapping("/mine/search") diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/controller/WorkflowRuntimeController.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/controller/WorkflowRuntimeController.java index 34fc0065..2d384aaa 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/controller/WorkflowRuntimeController.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/controller/WorkflowRuntimeController.java @@ -30,7 +30,7 @@ public class WorkflowRuntimeController { @Operation(summary = "接收用户输入以继续执行剩余流程") @PostMapping(value = "/resume/{runtimeUuid}") public R resume(@PathVariable String runtimeUuid, @RequestBody WorkflowResumeReq resumeReq) { - workflowStarter.resumeFlow(runtimeUuid, resumeReq.getFeedbackContent()); + workflowStarter.resumeFlow(runtimeUuid, resumeReq.getFeedbackContent(), resumeReq.getSseEmitter()); return R.ok(); } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowResumeReq.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowResumeReq.java index 27680932..c1b64829 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowResumeReq.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowResumeReq.java @@ -1,8 +1,10 @@ package org.ruoyi.workflow.dto.workflow; import lombok.Data; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; @Data public class WorkflowResumeReq { private String feedbackContent; + private SseEmitter sseEmitter; } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowRunReq.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowRunReq.java index 54a32a03..40ca0fcc 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowRunReq.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowRunReq.java @@ -9,6 +9,7 @@ import java.util.List; public class WorkflowRunReq { private List inputs; private String uuid; + private Long sessionId; } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/Workflow.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/Workflow.java index 4334b069..4a43e674 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/Workflow.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/Workflow.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import org.ruoyi.common.chat.entity.BaseEntity; import java.io.Serial; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowComponent.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowComponent.java index d75e92c8..c60b06bc 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowComponent.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowComponent.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import org.ruoyi.common.chat.entity.BaseEntity; import java.io.Serial; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowEdge.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowEdge.java index 0a9bffda..6c44c70d 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowEdge.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowEdge.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import org.ruoyi.common.chat.entity.BaseEntity; import java.io.Serial; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowNode.java index cc15504a..e4261aab 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowNode.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import org.ruoyi.common.chat.entity.BaseEntity; import java.io.Serial; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntime.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntime.java index 557de3f4..dada5dea 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntime.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntime.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import org.ruoyi.common.chat.entity.BaseEntity; import java.io.Serial; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntimeNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntimeNode.java index 271d9eb7..a472b5a1 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntimeNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntimeNode.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import org.ruoyi.common.chat.entity.BaseEntity; import java.io.Serial; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/AiModelStatus.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/AiModelStatus.java index 0dec4a94..f49c3f88 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/AiModelStatus.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/AiModelStatus.java @@ -2,6 +2,7 @@ package org.ruoyi.workflow.enums; import lombok.AllArgsConstructor; import lombok.Getter; +import org.ruoyi.common.chat.enums.BaseEnum; @Getter @AllArgsConstructor diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/WfIODataTypeEnum.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/WfIODataTypeEnum.java index 8f8989c6..2ca638dc 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/WfIODataTypeEnum.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/enums/WfIODataTypeEnum.java @@ -2,6 +2,7 @@ package org.ruoyi.workflow.enums; import lombok.AllArgsConstructor; import lombok.Getter; +import org.ruoyi.common.chat.enums.BaseEnum; import java.util.Arrays; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/helper/SSEEmitterHelper.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/helper/SSEEmitterHelper.java index 4d432c07..61b3dda0 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/helper/SSEEmitterHelper.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/helper/SSEEmitterHelper.java @@ -5,9 +5,9 @@ import com.google.common.cache.CacheBuilder; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.ruoyi.common.chat.entity.User; import org.ruoyi.workflow.cosntant.AdiConstant; import org.ruoyi.workflow.cosntant.RedisKeyConstant; -import org.ruoyi.workflow.entity.User; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowComponentService.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowComponentService.java index 04e3b03c..c1790738 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowComponentService.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowComponentService.java @@ -7,11 +7,11 @@ import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.ruoyi.common.chat.enums.ErrorEnum; import org.ruoyi.common.core.exception.base.BaseException; import org.ruoyi.workflow.dto.workflow.WfComponentReq; import org.ruoyi.workflow.dto.workflow.WfComponentSearchReq; import org.ruoyi.workflow.entity.WorkflowComponent; -import org.ruoyi.workflow.enums.ErrorEnum; import org.ruoyi.workflow.mapper.WorkflowComponentMapper; import org.ruoyi.workflow.util.PrivilegeUtil; import org.ruoyi.workflow.util.UuidUtil; @@ -26,7 +26,7 @@ import java.util.List; import static org.ruoyi.workflow.cosntant.RedisKeyConstant.WORKFLOW_COMPONENTS; import static org.ruoyi.workflow.cosntant.RedisKeyConstant.WORKFLOW_COMPONENT_START_KEY; -import static org.ruoyi.workflow.enums.ErrorEnum.C_WF_COMPONENT_DELETED_FAIL_BY_USED; +import static org.ruoyi.common.chat.enums.ErrorEnum.C_WF_COMPONENT_DELETED_FAIL_BY_USED; @Slf4j @Service diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowEdgeService.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowEdgeService.java index 63cc3530..624482b8 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowEdgeService.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowEdgeService.java @@ -5,10 +5,10 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.enums.ErrorEnum; import org.ruoyi.common.core.exception.base.BaseException; import org.ruoyi.workflow.dto.workflow.WfEdgeReq; import org.ruoyi.workflow.entity.WorkflowEdge; -import org.ruoyi.workflow.enums.ErrorEnum; import org.ruoyi.workflow.mapper.WorkflowEdgeMapper; import org.ruoyi.workflow.util.MPPageUtil; import org.ruoyi.workflow.util.UuidUtil; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowNodeService.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowNodeService.java index e99bd8fd..f56ac479 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowNodeService.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowNodeService.java @@ -6,12 +6,12 @@ import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers; import com.fasterxml.jackson.databind.node.ObjectNode; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.enums.ErrorEnum; import org.ruoyi.common.core.exception.base.BaseException; import org.ruoyi.workflow.dto.workflow.WfNodeDto; import org.ruoyi.workflow.entity.Workflow; import org.ruoyi.workflow.entity.WorkflowComponent; import org.ruoyi.workflow.entity.WorkflowNode; -import org.ruoyi.workflow.enums.ErrorEnum; import org.ruoyi.workflow.enums.WfIODataTypeEnum; import org.ruoyi.workflow.mapper.WorkflowNodeMapper; import org.ruoyi.workflow.util.JsonUtil; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowRuntimeNodeService.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowRuntimeNodeService.java index 43c73207..63aa6d4b 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowRuntimeNodeService.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowRuntimeNodeService.java @@ -4,9 +4,9 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; -import org.ruoyi.workflow.base.ThreadContext; +import org.ruoyi.common.chat.base.ThreadContext; +import org.ruoyi.common.chat.entity.User; import org.ruoyi.workflow.dto.workflow.WfRuntimeNodeDto; -import org.ruoyi.workflow.entity.User; import org.ruoyi.workflow.entity.WorkflowRuntimeNode; import org.ruoyi.workflow.mapper.WorkflowRuntimeNodeMapper; import org.ruoyi.workflow.util.JsonUtil; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowRuntimeService.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowRuntimeService.java index 330974a9..ddd3f3fb 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowRuntimeService.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowRuntimeService.java @@ -7,13 +7,13 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.ruoyi.workflow.base.ThreadContext; +import org.ruoyi.common.chat.base.ThreadContext; +import org.ruoyi.common.chat.entity.User; +import org.ruoyi.common.chat.enums.ErrorEnum; import org.ruoyi.workflow.dto.workflow.WfRuntimeNodeDto; import org.ruoyi.workflow.dto.workflow.WfRuntimeResp; -import org.ruoyi.workflow.entity.User; import org.ruoyi.workflow.entity.Workflow; import org.ruoyi.workflow.entity.WorkflowRuntime; -import org.ruoyi.workflow.enums.ErrorEnum; import org.ruoyi.workflow.mapper.WorkflowRunMapper; import org.ruoyi.workflow.util.JsonUtil; import org.ruoyi.workflow.util.MPPageUtil; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowService.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowService.java index 439ffba4..926dc12b 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowService.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/service/WorkflowService.java @@ -6,15 +6,15 @@ import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.ruoyi.common.chat.base.ThreadContext; +import org.ruoyi.common.chat.entity.User; +import org.ruoyi.common.chat.enums.ErrorEnum; import org.ruoyi.common.core.exception.base.BaseException; -import org.ruoyi.workflow.base.ThreadContext; import org.ruoyi.workflow.dto.workflow.WfEdgeReq; import org.ruoyi.workflow.dto.workflow.WfNodeDto; import org.ruoyi.workflow.dto.workflow.WorkflowResp; import org.ruoyi.workflow.dto.workflow.WorkflowUpdateReq; -import org.ruoyi.workflow.entity.User; import org.ruoyi.workflow.entity.Workflow; -import org.ruoyi.workflow.enums.ErrorEnum; import org.ruoyi.workflow.mapper.WorkflowMapper; import org.ruoyi.workflow.util.MPPageUtil; import org.ruoyi.workflow.util.PrivilegeUtil; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/util/PrivilegeUtil.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/util/PrivilegeUtil.java index 7ad0dc7a..7480cbfe 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/util/PrivilegeUtil.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/util/PrivilegeUtil.java @@ -1,9 +1,9 @@ package org.ruoyi.workflow.util; import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper; +import org.ruoyi.common.chat.base.ThreadContext; +import org.ruoyi.common.chat.enums.ErrorEnum; import org.ruoyi.common.core.exception.base.BaseException; -import org.ruoyi.workflow.base.ThreadContext; -import org.ruoyi.workflow.enums.ErrorEnum; import static org.ruoyi.workflow.cosntant.AdiConstant.*; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/util/WorkflowMessageUtil.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/util/WorkflowMessageUtil.java new file mode 100644 index 00000000..4ae71e3b --- /dev/null +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/util/WorkflowMessageUtil.java @@ -0,0 +1,37 @@ +package org.ruoyi.workflow.util; + +import org.ruoyi.common.chat.domain.dto.request.ChatRequest; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.common.chat.enums.RoleType; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.workflow.workflow.WfState; +import org.ruoyi.workflow.workflow.WorkflowUtil; + +/** + * 工作流消息工具类 + * + * @author Zengxb + * @date 2026-02-26 + */ +@Slf4j +public class WorkflowMessageUtil { + + /** + * 保存工作流消息公共方法(对话使用) + * @param wfState 工作流实例状态 + * @param message 消息 + */ + public static void saveWorkflowMessage(WfState wfState, String message) { + Long sessionId = wfState.getSessionId(); + Long userId = wfState.getUserId(); + + if (sessionId != null && userId != null) { + ChatRequest chatRequest = new ChatRequest(); + chatRequest.setSessionId(sessionId); + WorkflowUtil workflowUtil = SpringUtils.getBean(WorkflowUtil.class); + workflowUtil.saveChatMessage(chatRequest, userId, message, RoleType.WORKFLOW.getName(), new ChatModelVo()); + } + } + +} diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WfNodeIODataUtil.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WfNodeIODataUtil.java index 61828568..9bedc711 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WfNodeIODataUtil.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WfNodeIODataUtil.java @@ -4,8 +4,8 @@ import cn.hutool.core.collection.CollUtil; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.collections4.CollectionUtils; +import org.ruoyi.common.chat.enums.ErrorEnum; import org.ruoyi.common.core.exception.base.BaseException; -import org.ruoyi.workflow.enums.ErrorEnum; import org.ruoyi.workflow.enums.WfIODataTypeEnum; import org.ruoyi.workflow.util.JsonUtil; import org.ruoyi.workflow.workflow.data.NodeIOData; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WfState.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WfState.java index 97962a71..d0595803 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WfState.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WfState.java @@ -4,8 +4,8 @@ import lombok.Getter; import lombok.Setter; import org.bsc.langgraph4j.langchain4j.generators.StreamingChatGenerator; import org.bsc.langgraph4j.state.AgentState; +import org.ruoyi.common.chat.entity.User; import org.ruoyi.workflow.dto.workflow.WfRuntimeNodeDto; -import org.ruoyi.workflow.entity.User; import org.ruoyi.workflow.entity.WorkflowNode; import org.ruoyi.workflow.workflow.data.NodeIOData; import org.ruoyi.workflow.workflow.node.AbstractWfNode; @@ -28,6 +28,7 @@ public class WfState { private Long userId; private String tokenValue; private SseEmitter sseEmitter; + private Long sessionId; //Source node uuid => target node uuid list private Map> edges = new HashMap<>(); @@ -59,13 +60,14 @@ public class WfState { */ private Set interruptNodes = new HashSet<>(); - public WfState(User user, List input, String uuid, Long userId, String tokenValue, SseEmitter sseEmitter) { + public WfState(User user, List input, String uuid, Long userId, String tokenValue, SseEmitter sseEmitter, Long sessionId) { this.input = input; this.user = user; this.uuid = uuid; this.userId = userId; this.tokenValue = tokenValue; this.sseEmitter = sseEmitter; + this.sessionId = sessionId; } /** diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java index f84b2164..20a3e783 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java @@ -3,6 +3,8 @@ package org.ruoyi.workflow.workflow; import cn.hutool.core.collection.CollStreamUtil; import cn.hutool.core.collection.CollUtil; import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -13,16 +15,18 @@ import org.bsc.langgraph4j.langchain4j.generators.StreamingChatGenerator; import org.bsc.langgraph4j.state.AgentState; import org.bsc.langgraph4j.state.StateSnapshot; import org.bsc.langgraph4j.streaming.StreamingOutput; +import org.ruoyi.common.chat.entity.User; +import org.ruoyi.common.chat.enums.ErrorEnum; import org.ruoyi.common.core.exception.base.BaseException; import org.ruoyi.workflow.base.NodeInputConfigTypeHandler; import org.ruoyi.workflow.dto.workflow.WfRuntimeNodeDto; import org.ruoyi.workflow.dto.workflow.WfRuntimeResp; import org.ruoyi.workflow.entity.*; -import org.ruoyi.workflow.enums.ErrorEnum; import org.ruoyi.workflow.helper.SSEEmitterHelper; import org.ruoyi.workflow.service.WorkflowRuntimeNodeService; import org.ruoyi.workflow.service.WorkflowRuntimeService; import org.ruoyi.workflow.util.JsonUtil; +import org.ruoyi.workflow.util.WorkflowMessageUtil; import org.ruoyi.workflow.workflow.data.NodeIOData; import org.ruoyi.workflow.workflow.def.WfNodeIO; import org.ruoyi.workflow.workflow.def.WfNodeParamRef; @@ -34,7 +38,7 @@ import java.util.function.Function; import static org.bsc.langgraph4j.StateGraph.END; import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.*; -import static org.ruoyi.workflow.enums.ErrorEnum.*; +import static org.ruoyi.common.chat.enums.ErrorEnum.*; @Slf4j public class WorkflowEngine { @@ -45,7 +49,9 @@ public class WorkflowEngine { private final SSEEmitterHelper sseEmitterHelper; private final WorkflowRuntimeService workflowRuntimeService; private final WorkflowRuntimeNodeService workflowRuntimeNodeService; + @Getter private CompiledGraph app; + @Setter private SseEmitter sseEmitter; private User user; private WfState wfState; @@ -68,7 +74,7 @@ public class WorkflowEngine { this.workflowRuntimeNodeService = workflowRuntimeNodeService; } - public void run(User user, List userInputs, SseEmitter sseEmitter, Long userId, String tokenValue) { + public void run(User user, List userInputs, SseEmitter sseEmitter, Long userId, String tokenValue, Long sessionId) { this.user = user; this.sseEmitter = sseEmitter; log.info("WorkflowEngine run,userId:{},workflowUuid:{},userInputs:{}", user.getId(), workflow.getUuid(), userInputs); @@ -86,7 +92,7 @@ public class WorkflowEngine { Pair> startAndEnds = findStartAndEndNode(); WorkflowNode startNode = startAndEnds.getLeft(); List wfInputs = getAndCheckUserInput(userInputs, startNode); - this.wfState = new WfState(user, wfInputs, runtimeUuid,userId, tokenValue, sseEmitter); + this.wfState = new WfState(user, wfInputs, runtimeUuid,userId, tokenValue, sseEmitter, sessionId); workflowRuntimeService.updateInput(this.wfRuntimeResp.getId(), wfState); @@ -122,6 +128,8 @@ public class WorkflowEngine { String intTip = WorkflowUtil.getHumanFeedbackTip(nextNode, wfNodes); //将等待输入信息[事件与提示词]发送到到客户端 SSEEmitterHelper.parseAndSendPartialMsg(sseEmitter, "[NODE_WAIT_FEEDBACK_BY_" + nextNode + "]", intTip); + // 保存提示信息到Chat信息记录中(对话使用) + WorkflowMessageUtil.saveWorkflowMessage(wfState, intTip); InterruptedFlow.RUNTIME_TO_GRAPH.put(wfState.getUuid(), this); //更新状�? wfState.setProcessStatus(WORKFLOW_PROCESS_STATUS_WAITING_INPUT); @@ -241,6 +249,7 @@ public class WorkflowEngine { Map strMap = new HashMap<>(); strMap.put("ck", chunk); // SSEEmitterHelper.parseAndSendPartialMsg(sseEmitter, "[NODE_CHUNK_" + node + "]", strMap.toString()); + SSEEmitterHelper.parseAndSendPartialMsg(sseEmitter, "[NODE_CHUNK_" + node + "]", chunk); } else { AbstractWfNode abstractWfNode = wfState.getCompletedNodes().stream() @@ -349,8 +358,4 @@ public class WorkflowEngine { return Pair.of(startNode, endNodes); } - - public CompiledGraph getApp() { - return app; - } } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowGraphBuilder.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowGraphBuilder.java index aa11b4b5..55241376 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowGraphBuilder.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowGraphBuilder.java @@ -5,11 +5,11 @@ import org.apache.commons.lang3.StringUtils; import org.bsc.langgraph4j.GraphStateException; import org.bsc.langgraph4j.StateGraph; import org.bsc.langgraph4j.serializer.std.ObjectStreamStateSerializer; +import org.ruoyi.common.chat.enums.ErrorEnum; import org.ruoyi.common.core.exception.base.BaseException; import org.ruoyi.workflow.entity.WorkflowComponent; import org.ruoyi.workflow.entity.WorkflowEdge; import org.ruoyi.workflow.entity.WorkflowNode; -import org.ruoyi.workflow.enums.ErrorEnum; import java.util.*; import java.util.function.Function; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowStarter.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowStarter.java index 7865af51..95885ed1 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowStarter.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowStarter.java @@ -4,6 +4,8 @@ import cn.dev33.satoken.stp.StpUtil; import com.fasterxml.jackson.databind.node.ObjectNode; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.entity.User; +import org.ruoyi.common.chat.service.workFlow.IWorkFlowStarterService; import org.ruoyi.common.core.exception.base.BaseException; import org.ruoyi.common.satoken.utils.LoginHelper; import org.ruoyi.common.sse.core.SseEmitterManager; @@ -12,17 +14,16 @@ import org.ruoyi.workflow.helper.SSEEmitterHelper; import org.ruoyi.workflow.service.*; import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.util.List; -import static org.ruoyi.workflow.cosntant.AdiConstant.SSE_TIMEOUT; -import static org.ruoyi.workflow.enums.ErrorEnum.*; +import static org.ruoyi.common.chat.enums.ErrorEnum.*; @Slf4j -@Component -public class WorkflowStarter { +@Service +public class WorkflowStarter implements IWorkFlowStarterService { @Lazy @Resource @@ -52,8 +53,7 @@ public class WorkflowStarter { @Resource private SseEmitterManager sseEmitterManager; - - public SseEmitter streaming(User user, String workflowUuid, List userInputs) { + public SseEmitter streaming(User user, String workflowUuid, List userInputs, Long sessionId) { // 获取用户ID Long userId = LoginHelper.getUserId(); // 获取登录Token @@ -71,12 +71,12 @@ public class WorkflowStarter { sseEmitterHelper.sendErrorAndComplete(user.getId(), sseEmitter, A_WF_DISABLED.getInfo()); return sseEmitter; } - self.asyncRun(user, workflow, userInputs, sseEmitter, userId, tokenValue); + self.asyncRun(user, workflow, userInputs, sseEmitter, userId, tokenValue, sessionId); return sseEmitter; } @Async - public void asyncRun(User user, Workflow workflow, List userInputs, SseEmitter sseEmitter, Long userId, String tokenValue) { + public void asyncRun(User user, Workflow workflow, List userInputs, SseEmitter sseEmitter, Long userId, String tokenValue, Long sessionId) { log.info("WorkflowEngine run,userId:{},workflowUuid:{},userInputs:{}", user.getId(), workflow.getUuid(), userInputs); List components = workflowComponentService.getAllEnable(); List nodes = workflowNodeService.lambdaQuery() @@ -90,17 +90,20 @@ public class WorkflowStarter { WorkflowEngine workflowEngine = new WorkflowEngine(workflow, sseEmitterHelper, components, nodes, edges, workflowRuntimeService, workflowRuntimeNodeService); - workflowEngine.run(user, userInputs, sseEmitter, userId, tokenValue); + workflowEngine.run(user, userInputs, sseEmitter, userId, tokenValue, sessionId); } @Async - public void resumeFlow(String runtimeUuid, String userInput) { + public void resumeFlow(String runtimeUuid, String userInput, SseEmitter sseEmitter) { WorkflowEngine workflowEngine = InterruptedFlow.RUNTIME_TO_GRAPH.get(runtimeUuid); if (null == workflowEngine) { log.error("工作流恢复执行时失败,runtime:{}", runtimeUuid); throw new BaseException(A_WF_RESUME_FAIL.getInfo()); } + // 如果SSE连接对象不为空传入该对象(Chat调用工作流对话使用) + if (null != sseEmitter){ + workflowEngine.setSseEmitter(sseEmitter); + } workflowEngine.resume(userInput); } - } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java index 63a13845..c34962c0 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java @@ -11,12 +11,14 @@ import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.bsc.langgraph4j.langchain4j.generators.StreamingChatGenerator; import org.bsc.langgraph4j.state.AgentState; -import org.ruoyi.common.chat.Service.IChatModelService; -import org.ruoyi.common.chat.Service.IChatService; -import org.ruoyi.common.chat.Service.IImageGenerationService; +import org.ruoyi.common.chat.enums.RoleType; +import org.ruoyi.common.chat.service.chat.IChatModelService; +import org.ruoyi.common.chat.service.chat.IChatService; +import org.ruoyi.common.chat.service.chatMessage.AbstractChatMessageService; +import org.ruoyi.common.chat.service.image.IImageGenerationService; import org.ruoyi.common.chat.domain.dto.request.ChatRequest; -import org.ruoyi.common.chat.domain.entity.chat.ChatContext; -import org.ruoyi.common.chat.domain.entity.image.ImageContext; +import org.ruoyi.common.chat.entity.chat.ChatContext; +import org.ruoyi.common.chat.entity.image.ImageContext; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.common.chat.factory.ChatServiceFactory; import org.ruoyi.common.chat.factory.ImageServiceFactory; @@ -28,6 +30,7 @@ import org.ruoyi.workflow.workflow.data.NodeIOData; import org.ruoyi.workflow.workflow.data.NodeIODataContent; import org.ruoyi.workflow.workflow.def.WfNodeParamRef; import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.util.*; @@ -35,8 +38,8 @@ import java.util.*; import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.DEFAULT_OUTPUT_PARAM_NAME; @Slf4j -@Component -public class WorkflowUtil { +@Service +public class WorkflowUtil extends AbstractChatMessageService { @Resource private ChatServiceFactory chatServiceFactory; @@ -123,11 +126,38 @@ public class WorkflowUtil { // 根据 category 获取对应的 ChatService(不使用计费代理,工作流场景单独计费) IChatService chatService = chatServiceFactory.getOriginalService(category); + // 获取用户信息和Token以及SSe连接对象(对话接口需要使用) + Long sessionId = wfState.getSessionId(); + Long userId = wfState.getUserId(); + String tokenValue = wfState.getTokenValue(); + SseEmitter sseEmitter = wfState.getSseEmitter(); + + // 构建 ruoyi-ai 的 ChatRequest + List chatMessages = new ArrayList<>(); + addUserMessage(node, state.getInputs(), chatMessages); + chatMessages.addAll(systemMessage); + + // 定义模型调用对象 + ChatRequest chatRequest = new ChatRequest(); + // 目前工作流深度思考成员变量只能写死 + chatRequest.setSessionId(sessionId); + chatRequest.setEnableThinking(false); + chatRequest.setModel(modelName); + chatRequest.setChatMessages(chatMessages); + + // 构建流式生成器 StreamingChatGenerator streamingGenerator = StreamingChatGenerator.builder() .mapResult(response -> { String responseTxt = response.aiMessage().text(); log.info("llm response:{}", responseTxt); + // 会话ID不为空时插入数据库 + if (sessionId != null){ + // 保存助手回复消息 + saveChatMessage(chatRequest, userId, responseTxt, RoleType.ASSISTANT.getName(), chatModelVo); + log.info("{}消息结束,已保存到数据库", getProviderName()); + } + // 传递所有输入数据 + 添加 LLM 输出 wfState.getNodeStateByNodeUuid(node.getUuid()).ifPresent(item -> { List outputs = new ArrayList<>(item.getInputs()); @@ -142,24 +172,9 @@ public class WorkflowUtil { .startingState(state) .build(); - // 获取用户信息和Token以及SSe连接对象(对话接口需要使用) - Long userId = wfState.getUserId(); - String tokenValue = wfState.getTokenValue(); - SseEmitter sseEmitter = wfState.getSseEmitter(); + // 构建流式回调响应器 StreamingChatResponseHandler handler = streamingGenerator.handler(); - // 构建 ruoyi-ai 的 ChatRequest - List chatMessages = new ArrayList<>(); - addUserMessage(node, state.getInputs(), chatMessages); - chatMessages.addAll(systemMessage); - - // 定义模型调用对象 - ChatRequest chatRequest = new ChatRequest(); - // 目前工作流深度思考成员变量只能写死 - chatRequest.setEnableThinking(false); - chatRequest.setModel(modelName); - chatRequest.setChatMessages(chatMessages); - //构建聊天对话上下文参数 ChatContext chatContext = ChatContext.builder() .chatModelVo(chatModelVo) @@ -231,9 +246,9 @@ public class WorkflowUtil { throw new IllegalArgumentException("模型不存在: " + modelName); } // 根据模型名称找到模型实体 - String modelVoCategory = chatModelVo.getCategory(); + String category = chatModelVo.getProviderCode(); // 根据 category 获取对应的 IImageGenerationService(不使用计费代理,工作流场景单独计费) - IImageGenerationService imageService = imageServiceFactory.getOriginalService(modelVoCategory); + IImageGenerationService imageService = imageServiceFactory.getOriginalService(category); // 构建文生图上下文对象 ImageContext imageContext = ImageContext.builder() .chatModelVo(chatModelVo) @@ -244,5 +259,4 @@ public class WorkflowUtil { // 调用LLM 生成图片 return imageService.generateImage(imageContext); } - } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/AbstractWfNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/AbstractWfNode.java index d3262971..700071f0 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/AbstractWfNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/AbstractWfNode.java @@ -6,18 +6,19 @@ import lombok.Data; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.SerializationUtils; -import org.apache.commons.lang3.StringUtils; +import org.ruoyi.common.chat.domain.dto.request.ChatRequest; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.common.chat.enums.RoleType; import org.ruoyi.common.core.exception.base.BaseException; import org.ruoyi.workflow.base.NodeInputConfigTypeHandler; import org.ruoyi.workflow.entity.WorkflowComponent; import org.ruoyi.workflow.entity.WorkflowNode; import org.ruoyi.workflow.enums.WfIODataTypeEnum; +import org.ruoyi.workflow.helper.SSEEmitterHelper; import org.ruoyi.workflow.util.JsonUtil; import org.ruoyi.workflow.util.SpringUtil; -import org.ruoyi.workflow.workflow.NodeProcessResult; -import org.ruoyi.workflow.workflow.WfNodeInputConfig; -import org.ruoyi.workflow.workflow.WfNodeState; -import org.ruoyi.workflow.workflow.WfState; +import org.ruoyi.workflow.util.WorkflowMessageUtil; +import org.ruoyi.workflow.workflow.*; import org.ruoyi.workflow.workflow.data.NodeIOData; import org.ruoyi.workflow.workflow.def.WfNodeIO; import org.ruoyi.workflow.workflow.def.WfNodeParamRef; @@ -31,8 +32,8 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.*; -import static org.ruoyi.workflow.enums.ErrorEnum.A_WF_NODE_CONFIG_ERROR; -import static org.ruoyi.workflow.enums.ErrorEnum.A_WF_NODE_CONFIG_NOT_FOUND; +import static org.ruoyi.common.chat.enums.ErrorEnum.A_WF_NODE_CONFIG_ERROR; +import static org.ruoyi.common.chat.enums.ErrorEnum.A_WF_NODE_CONFIG_NOT_FOUND; /** * 节点实例-运行时 @@ -225,4 +226,19 @@ public abstract class AbstractWfNode { return nodeConfig; } + /** + * 会话消息保存方法 + */ + public void saveSessionMessage(WfState wfState, String message) { + WorkflowMessageUtil.saveWorkflowMessage(wfState, message); + } + + /** + * 发送SSe消息 + * @param message 信息 + */ + public void sendSseEvent(String message){ + String nodeUuid = node.getUuid(); + SSEEmitterHelper.parseAndSendPartialMsg(wfState.getSseEmitter(), "[NODE_CHUNK_" + nodeUuid + "]", message); + } } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/httpRequest/HttpRequestNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/httpRequest/HttpRequestNode.java index 16fc5f71..10248aeb 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/httpRequest/HttpRequestNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/httpRequest/HttpRequestNode.java @@ -63,6 +63,11 @@ public class HttpRequestNode extends AbstractWfNode { List outputs = new ArrayList<>(); outputs.add(NodeIOData.createByText("output", "HTTP响应", response)); + // 保存成功会话信息 + String message = "HTTP响应:" + response; + saveSessionMessage(wfState, message); + // 发送驱动消息事件 + sendSseEvent(message); return NodeProcessResult.builder().content(outputs).build(); } catch (Exception e) { @@ -73,6 +78,11 @@ public class HttpRequestNode extends AbstractWfNode { errorOutputs.add(NodeIOData.createByText("output", "错误", "")); errorOutputs.add(NodeIOData.createByText("error", "HTTP请求错误", e.getMessage())); + // 保存失败会话信息 + String message = "HTTP响应失败:" + e.getMessage(); + saveSessionMessage(wfState, message); + // 发送驱动消息事件 + sendSseEvent(message); return NodeProcessResult.builder().content(errorOutputs).build(); } } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/humanFeedBack/HumanFeedbackNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/humanFeedBack/HumanFeedbackNode.java index ea5b918d..bf14b716 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/humanFeedBack/HumanFeedbackNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/humanFeedBack/HumanFeedbackNode.java @@ -7,6 +7,7 @@ import org.ruoyi.workflow.entity.WorkflowNode; import org.ruoyi.workflow.workflow.NodeProcessResult; import org.ruoyi.workflow.workflow.WfNodeState; import org.ruoyi.workflow.workflow.WfState; +import org.ruoyi.workflow.workflow.WorkflowUtil; import org.ruoyi.workflow.workflow.data.NodeIOData; import org.ruoyi.workflow.workflow.node.AbstractWfNode; diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNode.java index 095e4acd..4b887ad7 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNode.java @@ -51,6 +51,11 @@ public class ImageNode extends AbstractWfNode { Integer seed = nodeConfigObj.getSeed(); // 调用LLM生成图片(后续可以将图片保存到OSS中) String imageUrl = workflowUtil.buildTextToImage(modelName, prompt, size, seed); + // 保存成功信息 + String message = "图片生成地址:" + imageUrl; + saveSessionMessage(wfState, message); + // 发送驱动消息事件 + sendSseEvent(message); // 创建节点参数对象 NodeIOData nodeIOData = NodeIOData.createByText("output", "image", imageUrl); // 添加到输出列表以便给后续节点使用 diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/mailSend/MailSendNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/mailSend/MailSendNode.java index c006cbe4..0ae6a197 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/mailSend/MailSendNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/mailSend/MailSendNode.java @@ -1,5 +1,7 @@ package org.ruoyi.workflow.workflow.node.mailSend; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.JSONValidator; import jakarta.mail.internet.MimeMessage; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -31,6 +33,12 @@ public class MailSendNode extends AbstractWfNode { try { MailSendNodeConfig config = checkAndGetConfig(MailSendNodeConfig.class); List inputs = state.getInputs(); + // 获取输入信息 + String input = getDataFromInput(inputs); + // 判断是否为JSON格式(LLM输出转换 由LLM生成格式) + if (StringUtils.isNotBlank(input) && isJson(input)) { + config = JSONObject.parseObject(input, MailSendNodeConfig.class); + } // 安全获取模板(使用 defaultString 避免 null) String subjectTemplate = StringUtils.defaultString(config.getSubject()); @@ -49,15 +57,7 @@ public class MailSendNode extends AbstractWfNode { content = WorkflowUtil.renderTemplate(contentTemplate, inputs); } else { // 优先使用 output,如果没有则使用 input - content = inputs.stream() - .filter(item -> "output".equals(item.getName())) - .map(NodeIOData::valueToString) - .findFirst() - .orElseGet(() -> inputs.stream() - .filter(item -> "input".equals(item.getName())) - .map(NodeIOData::valueToString) - .findFirst() - .orElse("")); + content = getDataFromInput(inputs); } // 将换行符转换为 HTML 换行 @@ -84,9 +84,9 @@ public class MailSendNode extends AbstractWfNode { // 设置收件人 String[] toArray = Arrays.stream(toMails.split(",")) - .map(String::trim) - .filter(StringUtils::isNotBlank) - .toArray(String[]::new); + .map(String::trim) + .filter(StringUtils::isNotBlank) + .toArray(String[]::new); if (toArray.length == 0) { throw new IllegalArgumentException("收件人邮箱列表为空"); } @@ -95,9 +95,9 @@ public class MailSendNode extends AbstractWfNode { // 设置抄送(如有) if (StringUtils.isNotBlank(ccMails)) { String[] ccArray = Arrays.stream(ccMails.split(",")) - .map(String::trim) - .filter(StringUtils::isNotBlank) - .toArray(String[]::new); + .map(String::trim) + .filter(StringUtils::isNotBlank) + .toArray(String[]::new); if (ccArray.length > 0) { helper.setCc(ccArray); } @@ -111,25 +111,31 @@ public class MailSendNode extends AbstractWfNode { mailSender.send(message); log.info("Email sent successfully to: {}", toMails); + // 保存成功会话信息 + String resultMessage = "发送邮箱成功"; + saveSessionMessage(wfState, resultMessage); + // 发送驱动消息事件 + sendSseEvent(resultMessage); + // 构造输出:统一输出为 output 参数 List outputs = new java.util.ArrayList<>(); // 优先使用 output,如果没有则使用 input(但重命名为 output) inputs.stream() - .filter(item -> "output".equals(item.getName())) - .findFirst() - .ifPresentOrElse( - outputs::add, - () -> inputs.stream() - .filter(item -> "input".equals(item.getName())) - .findFirst() - .ifPresent(inputParam -> { - String title = inputParam.getContent() != null && inputParam.getContent().getTitle() != null - ? inputParam.getContent().getTitle() : ""; - NodeIOData outputParam = NodeIOData.createByText("output", title, inputParam.valueToString()); - outputs.add(outputParam); - }) - ); + .filter(item -> "output".equals(item.getName())) + .findFirst() + .ifPresentOrElse( + outputs::add, + () -> inputs.stream() + .filter(item -> "input".equals(item.getName())) + .findFirst() + .ifPresent(inputParam -> { + String title = inputParam.getContent() != null && inputParam.getContent().getTitle() != null + ? inputParam.getContent().getTitle() : ""; + NodeIOData outputParam = NodeIOData.createByText("output", title, resultMessage); + outputs.add(outputParam); + }) + ); return NodeProcessResult.builder().content(outputs).build(); @@ -138,23 +144,29 @@ public class MailSendNode extends AbstractWfNode { // 异常时也统一输出为 output 参数,添加错误信息 List errorOutputs = new java.util.ArrayList<>(); - state.getInputs().stream() - .filter(item -> "output".equals(item.getName())) - .findFirst() - .ifPresentOrElse( - errorOutputs::add, - () -> state.getInputs().stream() - .filter(item -> "input".equals(item.getName())) - .findFirst() - .ifPresent(inputParam -> { - String title = inputParam.getContent() != null && inputParam.getContent().getTitle() != null - ? inputParam.getContent().getTitle() : ""; - NodeIOData outputParam = NodeIOData.createByText("output", title, inputParam.valueToString()); - errorOutputs.add(outputParam); - }) - ); + // 保存失败会话信息 + String resultMessage = "发送邮箱失败: " + e.getMessage(); + saveSessionMessage(wfState, resultMessage); + // 发送驱动消息事件 + sendSseEvent(resultMessage); - errorOutputs.add(NodeIOData.createByText("error", "mail", e.getMessage())); + state.getInputs().stream() + .filter(item -> "output".equals(item.getName())) + .findFirst() + .ifPresentOrElse( + errorOutputs::add, + () -> state.getInputs().stream() + .filter(item -> "input".equals(item.getName())) + .findFirst() + .ifPresent(inputParam -> { + String title = inputParam.getContent() != null && inputParam.getContent().getTitle() != null + ? inputParam.getContent().getTitle() : ""; + NodeIOData outputParam = NodeIOData.createByText("output", title, resultMessage); + errorOutputs.add(outputParam); + }) + ); + + errorOutputs.add(NodeIOData.createByText("error", "mail", resultMessage)); return NodeProcessResult.builder().content(errorOutputs).build(); } } @@ -174,4 +186,40 @@ public class MailSendNode extends AbstractWfNode { return sender; } + + /** + * 获取信息 + * @param inputs 用户输入 + * @return 返回输入信息 + */ + public String getDataFromInput(List inputs) { + return inputs.stream() + .filter(item -> "output".equals(item.getName())) + .map(NodeIOData::valueToString) + .findFirst() + .orElseGet(() -> inputs.stream() + .filter(item -> "input".equals(item.getName())) + .map(NodeIOData::valueToString) + .findFirst() + .orElse("")); + } + + /** + * 判断字符串是否为合法的 JSON 格式 + * + * @param str 待检测的字符串 + * @return true 表示是合法 JSON (包括 JSONObject, JSONArray, 或基本类型值) + */ + public static boolean isJson(String str) { + if (str == null || str.trim().isEmpty()) { + return false; + } + // 使用 try-with-resources 正确处理 JSONValidator 资源关闭 + try (JSONValidator validator = JSONValidator.from(str.trim())) { + return validator.getType() == JSONValidator.Type.Object; + } catch (Exception e) { + log.warn("JSON格式校验失败: {}", e.getMessage()); + return false; + } + } } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/start/StartNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/start/StartNode.java index 6f5df29e..e9ccc2be 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/start/StartNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/start/StartNode.java @@ -16,8 +16,8 @@ import org.ruoyi.workflow.workflow.node.AbstractWfNode; import java.util.List; import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.DEFAULT_OUTPUT_PARAM_NAME; -import static org.ruoyi.workflow.enums.ErrorEnum.A_WF_NODE_CONFIG_ERROR; -import static org.ruoyi.workflow.enums.ErrorEnum.A_WF_NODE_CONFIG_NOT_FOUND; +import static org.ruoyi.common.chat.enums.ErrorEnum.A_WF_NODE_CONFIG_ERROR; +import static org.ruoyi.common.chat.enums.ErrorEnum.A_WF_NODE_CONFIG_NOT_FOUND; @Slf4j public class StartNode extends AbstractWfNode { diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/ChatMessageController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/ChatMessageController.java index c24de577..7d85cdb1 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/ChatMessageController.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/ChatMessageController.java @@ -6,7 +6,9 @@ import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; -import org.ruoyi.service.chat.IChatMessageService; +import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo; +import org.ruoyi.common.chat.domain.vo.chat.ChatMessageVo; +import org.ruoyi.common.chat.service.chatMessage.IChatMessageService; import org.springframework.web.bind.annotation.*; import org.springframework.validation.annotation.Validated; import org.ruoyi.common.idempotent.annotation.RepeatSubmit; @@ -18,8 +20,6 @@ import org.ruoyi.common.core.validate.AddGroup; import org.ruoyi.common.core.validate.EditGroup; import org.ruoyi.common.log.enums.BusinessType; import org.ruoyi.common.excel.utils.ExcelUtil; -import org.ruoyi.domain.vo.chat.ChatMessageVo; -import org.ruoyi.domain.bo.chat.ChatMessageBo; import org.ruoyi.common.mybatis.core.page.TableDataInfo; /** diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/ChatModelController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/ChatModelController.java index 06212c1d..15884158 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/ChatModelController.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/ChatModelController.java @@ -6,7 +6,7 @@ import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; -import org.ruoyi.common.chat.Service.IChatModelService; +import org.ruoyi.common.chat.service.chat.IChatModelService; import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.enums.ModelType; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java index 0aecf556..41ae8160 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java @@ -2,7 +2,7 @@ package org.ruoyi.factory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ruoyi.common.chat.Service.IChatModelService; +import org.ruoyi.common.chat.service.chat.IChatModelService; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.service.embed.BaseEmbedModelService; import org.ruoyi.service.embed.MultiModalEmbedModelService; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/chat/ChatMessageMapper.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/chat/ChatMessageMapper.java index 63793d19..51ad8651 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/chat/ChatMessageMapper.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/chat/ChatMessageMapper.java @@ -1,7 +1,7 @@ package org.ruoyi.mapper.chat; -import org.ruoyi.domain.entity.chat.ChatMessage; -import org.ruoyi.domain.vo.chat.ChatMessageVo; +import org.ruoyi.common.chat.domain.vo.chat.ChatMessageVo; +import org.ruoyi.common.chat.entity.chat.ChatMessage; import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; /** diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/chat/ChatModelMapper.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/chat/ChatModelMapper.java index 50c296a4..ee93917a 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/chat/ChatModelMapper.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/chat/ChatModelMapper.java @@ -1,6 +1,6 @@ package org.ruoyi.mapper.chat; -import org.ruoyi.common.chat.domain.entity.chat.ChatModel; +import org.ruoyi.common.chat.entity.chat.ChatModel; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java index 75894c9a..7d8b1a30 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java @@ -24,17 +24,20 @@ import org.ruoyi.agent.WebSearchAgent; import org.ruoyi.agent.tool.ExecuteSqlQueryTool; import org.ruoyi.agent.tool.QueryAllTablesTool; import org.ruoyi.agent.tool.QueryTableSchemaTool; -import org.ruoyi.common.chat.Service.IChatService; +import org.ruoyi.common.chat.base.ThreadContext; +import org.ruoyi.common.chat.domain.dto.request.ReSumeRunner; +import org.ruoyi.common.chat.domain.dto.request.WorkFlowRunner; +import org.ruoyi.common.chat.enums.RoleType; +import org.ruoyi.common.chat.service.chat.IChatService; import org.ruoyi.common.chat.domain.dto.request.ChatRequest; -import org.ruoyi.common.chat.domain.entity.chat.ChatContext; +import org.ruoyi.common.chat.entity.chat.ChatContext; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.common.chat.service.chatMessage.AbstractChatMessageService; +import org.ruoyi.common.chat.service.workFlow.IWorkFlowStarterService; import org.ruoyi.common.core.utils.ObjectUtils; import org.ruoyi.common.core.utils.SpringUtils; import org.ruoyi.common.core.utils.StringUtils; import org.ruoyi.common.sse.utils.SseMessageUtils; -import org.ruoyi.domain.bo.chat.ChatMessageBo; -import org.ruoyi.enums.RoleType; -import org.ruoyi.service.chat.IChatMessageService; import org.ruoyi.service.chat.impl.memory.PersistentChatMemoryStore; import org.springframework.util.CollectionUtils; import org.springframework.validation.annotation.Validated; @@ -58,7 +61,7 @@ import java.util.concurrent.ConcurrentHashMap; */ @Slf4j @Validated -public abstract class AbstractStreamingChatService implements IChatService { +public abstract class AbstractStreamingChatService extends AbstractChatMessageService implements IChatService { /** * 默认保留的消息窗口大小(用于长期记忆) @@ -76,6 +79,11 @@ public abstract class AbstractStreamingChatService implements IChatService { */ private static final Map memoryCache = new ConcurrentHashMap<>(); + /** + * 获取工作流启用Bean对象 + */ + private static final IWorkFlowStarterService starterService = SpringUtils.getBean(IWorkFlowStarterService.class); + /** * 定义聊天流程骨架 */ @@ -108,9 +116,28 @@ public abstract class AbstractStreamingChatService implements IChatService { // 保存用户消息 saveChatMessage(chatRequest, userId, content, RoleType.USER.getName(), chatModelVo); + + // 判断用户是否重新输入 + boolean isResume = chatRequest.getIsResume() != null && chatRequest.getIsResume(); + if (isResume){ + ReSumeRunner reSumeRunner = chatRequest.getReSumeRunner(); + if (ObjectUtils.isNotEmpty(reSumeRunner)){ + starterService.resumeFlow(reSumeRunner.getRuntimeUuid(), reSumeRunner.getFeedbackContent(), emitter); + return emitter; + } + } + + // 判断用户是否开启工作流 + boolean enableWorkFlow = chatRequest.getEnableWorkFlow() != null && chatRequest.getEnableWorkFlow(); + if (enableWorkFlow) { + WorkFlowRunner runner = chatRequest.getWorkFlowRunner(); + if (ObjectUtils.isNotEmpty(runner)){ + return starterService.streaming(ThreadContext.getCurrentUser(), runner.getUuid(), runner.getInputs(), chatRequest.getSessionId()); + } + } + // 使用长期记忆增强的消息列表 List messagesWithMemory = buildMessagesWithMemory(chatRequest); - if (chatRequest.getEnableThinking()) { String msg = doAgent(content, chatModelVo); SseMessageUtils.sendMessage(userId, msg); @@ -119,13 +146,10 @@ public abstract class AbstractStreamingChatService implements IChatService { saveChatMessage(chatRequest, userId, msg, RoleType.ASSISTANT.getName(), chatModelVo); } else { // 创建包含内存管理的响应处理器 - if (ObjectUtils.isEmpty(handler)) { - handler = createResponseHandler(chatRequest, userId, tokenValue, chatModelVo); - } + handler = ObjectUtils.isEmpty(handler) ? createResponseHandler(chatRequest, userId, tokenValue, chatModelVo) : handler; // 调用具体实现的聊天方法 doChat(chatModelVo, chatRequest, messagesWithMemory, handler); } - } catch (Exception e) { SseMessageUtils.sendMessage(userId, "对话出错:" + e.getMessage()); SseMessageUtils.completeConnection(userId, tokenValue); @@ -144,6 +168,12 @@ public abstract class AbstractStreamingChatService implements IChatService { */ protected List buildMessagesWithMemory(ChatRequest chatRequest) { List messages = new ArrayList<>(); + // 工作流对话消息 + List chatMessages = chatRequest.getChatMessages(); + if (!CollectionUtils.isEmpty(chatMessages)){ + messages.addAll(chatMessages); + } + // 开启长期记忆 if (enablePersistentMemory && chatRequest.getSessionId() != null) { MessageWindowChatMemory memory = createChatMemory(chatRequest.getSessionId()); if (memory != null) { @@ -155,11 +185,6 @@ public abstract class AbstractStreamingChatService implements IChatService { } return messages; } - // 工作流方式 - List chatMessages = chatRequest.getChatMessages(); - if (!CollectionUtils.isEmpty(chatMessages)){ - messages.addAll(chatMessages); - } return messages; } @@ -276,40 +301,6 @@ public abstract class AbstractStreamingChatService implements IChatService { }; } - /** - * 保存聊天消息到数据库 - * - * @param chatRequest 聊天请求 - * @param userId 用户ID - * @param content 消息内容 - * @param role 消息角色 - * @param chatModelVo 模型配置 - */ - private void saveChatMessage(ChatRequest chatRequest, Long userId, String content, String role, ChatModelVo chatModelVo) { - try { - // 验证必要的上下文信息 - if (chatRequest == null || userId == null) { - log.warn("缺少必要的聊天上下文信息,无法保存消息"); - return; - } - - // 创建ChatMessageBo对象 - ChatMessageBo messageBO = new ChatMessageBo(); - messageBO.setUserId(userId); - messageBO.setSessionId(chatRequest.getSessionId()); - messageBO.setContent(content); - messageBO.setRole(role); - messageBO.setModelName(chatRequest.getModel()); - messageBO.setBillingType(chatModelVo.getModelType()); - messageBO.setRemark(null); - - IChatMessageService chatMessageService = SpringUtils.getBean(IChatMessageService.class); - chatMessageService.insertByBo(messageBO); - } catch (Exception e) { - log.error("保存{}聊天消息时出错: {}", getProviderName(), e.getMessage(), e); - } - } - /** * 构建具体厂商的 StreamingChatModel * 子类必须实现此方法,返回对应厂商的模型实例 diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatMessageServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatMessageServiceImpl.java index 0804f010..95f681fd 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatMessageServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatMessageServiceImpl.java @@ -1,6 +1,10 @@ package org.ruoyi.service.chat.impl; +import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo; import org.ruoyi.common.chat.domain.dto.ChatMessageDTO; +import org.ruoyi.common.chat.domain.vo.chat.ChatMessageVo; +import org.ruoyi.common.chat.entity.chat.ChatMessage; +import org.ruoyi.common.chat.service.chatMessage.IChatMessageService; import org.ruoyi.common.core.utils.MapstructUtils; import org.ruoyi.common.core.utils.StringUtils; import org.ruoyi.common.mybatis.core.page.TableDataInfo; @@ -10,11 +14,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ruoyi.service.chat.IChatMessageService; import org.springframework.stereotype.Service; -import org.ruoyi.domain.bo.chat.ChatMessageBo; -import org.ruoyi.domain.vo.chat.ChatMessageVo; -import org.ruoyi.domain.entity.chat.ChatMessage; import org.ruoyi.mapper.chat.ChatMessageMapper; import java.util.List; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatModelServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatModelServiceImpl.java index 32c8f85a..127d66a5 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatModelServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatModelServiceImpl.java @@ -1,8 +1,8 @@ package org.ruoyi.service.chat.impl; -import org.ruoyi.common.chat.Service.IChatModelService; +import org.ruoyi.common.chat.service.chat.IChatModelService; import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo; -import org.ruoyi.common.chat.domain.entity.chat.ChatModel; +import org.ruoyi.common.chat.entity.chat.ChatModel; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.common.core.utils.MapstructUtils; import org.ruoyi.common.core.utils.StringUtils; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java index 5151c35f..c97dcf95 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java @@ -4,11 +4,11 @@ import cn.dev33.satoken.stp.StpUtil; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ruoyi.common.chat.Service.IChatModelService; -import org.ruoyi.common.chat.Service.IChatService; +import org.ruoyi.common.chat.service.chat.IChatModelService; +import org.ruoyi.common.chat.service.chat.IChatService; import org.ruoyi.common.chat.domain.dto.ChatMessageDTO; import org.ruoyi.common.chat.domain.dto.request.ChatRequest; -import org.ruoyi.common.chat.domain.entity.chat.ChatContext; +import org.ruoyi.common.chat.entity.chat.ChatContext; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.common.chat.factory.ChatServiceFactory; import org.ruoyi.common.satoken.utils.LoginHelper; @@ -52,7 +52,6 @@ public class ChatServiceFacade { * @return SseEmitter */ public SseEmitter sseChat(ChatRequest chatRequest, HttpServletRequest request) { - // 1. 根据模型名称查询完整配置 ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel()); if (chatModelVo == null) { diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/memory/ChatMemoryUsageExample.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/memory/ChatMemoryUsageExample.java index 2b9a7da4..ca719572 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/memory/ChatMemoryUsageExample.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/memory/ChatMemoryUsageExample.java @@ -108,7 +108,7 @@ public class ChatMemoryUsageExample { log.info("=== 示例4:清理过期消息 ==="); /* // 假设已有IChatMessageService实例 - IChatMessageService chatMessageService = getBean(IChatMessageService.class); + AbstractChatMessageService chatMessageService = getBean(AbstractChatMessageService.class); // 场景:用户要求"忘记我们之前的对话" Long sessionId = 789L; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/memory/PersistentChatMemoryStore.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/memory/PersistentChatMemoryStore.java index 63d9e151..a10ccfb6 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/memory/PersistentChatMemoryStore.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/memory/PersistentChatMemoryStore.java @@ -4,8 +4,8 @@ import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.store.memory.chat.ChatMemoryStore; import lombok.extern.slf4j.Slf4j; import org.ruoyi.common.chat.domain.dto.ChatMessageDTO; +import org.ruoyi.common.chat.service.chatMessage.IChatMessageService; import org.ruoyi.common.core.utils.SpringUtils; -import org.ruoyi.service.chat.IChatMessageService; import java.util.ArrayList; import java.util.List; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphExtractionServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphExtractionServiceImpl.java index 890bdc12..bdceb35f 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphExtractionServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphExtractionServiceImpl.java @@ -3,7 +3,7 @@ package org.ruoyi.service.graph.impl; import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ruoyi.common.chat.Service.IChatModelService; +import org.ruoyi.common.chat.service.chat.IChatModelService; import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.config.GraphExtractPrompt; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/image/AbstractImageGenerationService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/image/AbstractImageGenerationService.java index 3312239e..4a9065a7 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/image/AbstractImageGenerationService.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/image/AbstractImageGenerationService.java @@ -1,9 +1,9 @@ package org.ruoyi.service.image; import lombok.extern.slf4j.Slf4j; -import org.ruoyi.common.chat.Service.IImageGenerationService; +import org.ruoyi.common.chat.service.image.IImageGenerationService; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; -import org.ruoyi.common.chat.domain.entity.image.ImageContext; +import org.ruoyi.common.chat.entity.image.ImageContext; import org.springframework.validation.annotation.Validated; @Slf4j diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/knowledge/impl/KnowledgeAttachServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/knowledge/impl/KnowledgeAttachServiceImpl.java index b7106120..ce02785e 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/knowledge/impl/KnowledgeAttachServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/knowledge/impl/KnowledgeAttachServiceImpl.java @@ -2,7 +2,7 @@ package org.ruoyi.service.knowledge.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.RandomUtil; -import org.ruoyi.common.chat.Service.IChatModelService; +import org.ruoyi.common.chat.service.chat.IChatModelService; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.common.core.domain.dto.OssDTO; import org.ruoyi.common.core.service.OssService; From e97bd4e20176a5a9dbf46bf3c22af5eee7853ec8 Mon Sep 17 00:00:00 2001 From: zhang <823772544@qq.com> Date: Fri, 27 Feb 2026 10:14:53 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E5=88=86=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java | 1 - 1 file changed, 1 deletion(-) diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java index c97dcf95..af71f54a 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java @@ -15,7 +15,6 @@ import org.ruoyi.common.satoken.utils.LoginHelper; import org.ruoyi.common.sse.core.SseEmitterManager; import org.ruoyi.domain.bo.vector.QueryVectorBo; import org.ruoyi.domain.vo.knowledge.KnowledgeInfoVo; -; import org.ruoyi.service.knowledge.IKnowledgeInfoService; import org.ruoyi.service.vector.VectorStoreService; import org.springframework.stereotype.Service; From 20d531c0dbecf48e43a66733c06c65da536d0bd4 Mon Sep 17 00:00:00 2001 From: zengxb <648669796@qq.com> Date: Fri, 27 Feb 2026 14:53:07 +0800 Subject: [PATCH 7/9] =?UTF-8?q?context:=E6=96=B0=E5=A2=9E=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E8=8A=82=E7=82=B9=E6=8F=90=E4=BE=9B=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E6=B6=88=E6=81=AF=E8=BE=93=E5=87=BA=E4=BB=A5=E5=8F=8A?= =?UTF-8?q?=E6=99=BA=E8=B0=B1=E5=A4=A7=E6=A8=A1=E5=9E=8BChat=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E6=8E=A5=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/script/sql/ruoyi-ai-v3_mysql8.sql | 11 ++++- pom.xml | 1 + .../dto/node/LLmMailSendNodeConfigDto.java | 38 ++++++++++++++++ .../workflow/util/WorkflowMessageUtil.java | 45 +++++++++++++++++++ .../workflow/workflow/WorkflowEngine.java | 19 +++++++- .../workflow/workflow/WorkflowStarter.java | 2 + .../ruoyi/workflow/workflow/WorkflowUtil.java | 6 ++- .../workflow/node/AbstractWfNode.java | 18 +++----- .../workflow/node/answer/LLMAnswerNode.java | 8 +++- .../node/enmus/NodeMessageTemplateEnum.java | 25 +++++++++++ .../node/httpRequest/HttpRequestNode.java | 19 ++++---- .../workflow/node/image/ImageNode.java | 11 ++--- .../KeywordExtractorNode.java | 9 +++- .../KnowledgeRetrievalNode.java | 3 +- .../workflow/node/mailSend/MailSendNode.java | 25 ++++++----- .../workflow/node/switcher/SwitcherNode.java | 34 ++++++++++++-- ruoyi-modules/ruoyi-chat/pom.xml | 6 +++ .../impl/provider/ZhiPuChatServiceImpl.java | 43 ++++++++++++++++++ 18 files changed, 274 insertions(+), 49 deletions(-) create mode 100644 ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/node/LLmMailSendNodeConfigDto.java create mode 100644 ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/enmus/NodeMessageTemplateEnum.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/ZhiPuChatServiceImpl.java diff --git a/docs/script/sql/ruoyi-ai-v3_mysql8.sql b/docs/script/sql/ruoyi-ai-v3_mysql8.sql index 6d85a2ef..58d14837 100644 --- a/docs/script/sql/ruoyi-ai-v3_mysql8.sql +++ b/docs/script/sql/ruoyi-ai-v3_mysql8.sql @@ -3657,7 +3657,16 @@ INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `di INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (23, 'af9d6d7b9c9b47f990ad25ec84912b73', 'Tongyiwanx', '阿里图像生成', '使用通义万相生成图像', 0, 1, '2025-12-26 16:32:25', '2025-12-26 16:32:25', 0, '000000'); INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (24, 'a1e2c9d4b8f04e1a9c3d6f8e2a7b1c9d', 'MailSend', '发送邮箱', '发送邮箱', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000'); INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (25, 'f1e2d3c4b5a67890f1e2d3c4b5a6f1e2', 'HttpRequest', '请求节点', '请求节点', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000'); -INSERT INTO `chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_price`, `model_type`, `model_show`, `model_free`, `priority`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`) VALUES (2022565766560468994, 'Tongyiwanx', 'wan2.5-t2i-preview', 'Tongyiwanx', 'wan2.5-t2i-preview', 1, '1', 'Y', 'Y', 1, 'https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation', 'skxxxx', 103, 1, '2026-02-14 14:57:11', 1, '2026-02-14 14:57:11', '通义万相文生图', 0); +INSERT INTO `chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_price`, `model_type`, `model_show`, `model_free`, `priority`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`) VALUES (2022565766560468994, 'image', 'wan2.5-t2i-preview', 'Tongyiwanx', 'wan2.5-t2i-preview', 1, '1', 'Y', 'Y', 1, 'https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation', 'skxxxx', 103, 1, '2026-02-14 14:57:11', 1, '2026-02-14 14:57:11', '通义万相文生图', 0); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2021046920636690433, '流程管理', 0, 0, 'flow', '', NULL, 1, 0, 'M', '0', '0', NULL, 'ph:user-fill', 103, 1, '2026-02-10 10:21:50', 1, '2026-02-10 15:59:28', ''); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2021047050391678978, '工作流编排', 2021046920636690433, 0, 'aiflowengine', 'aiflow/index', NULL, 1, 0, 'C', '0', '0', '', 'ph:user-fill', 103, 1, '2026-02-10 10:22:21', 1, '2026-02-10 16:04:41', ''); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027192921483309058, '000000', 'HTTP请求节点响应模板', 'node.httpRequest.template', '✅ HTTP请求节点:结束响应 - ', 'Y', 103, 1, '2026-02-27 09:23:51', 1, '2026-02-27 09:31:41', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027193296990957569, '000000', '文生图节点响应模板', 'node.image.template', '🎨 文生图节点:结束响应 - 图片URL: ', 'Y', 103, 1, '2026-02-27 09:25:20', 1, '2026-02-27 09:31:52', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027193820393959425, '000000', '发送邮箱节点响应模板', 'node.mailsend.template', '📧 发送邮箱节点:结束响应 - ', 'Y', 103, 1, '2026-02-27 09:27:25', 1, '2026-02-27 09:32:05', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027194134438277122, '000000', '结束节点响应模板', 'node.end.template', '🔚 流程已执行完毕,如果您有其他需求,请随时重新发起请求。', 'Y', 103, 1, '2026-02-27 09:28:40', 1, '2026-02-27 09:32:53', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027206492573335554, '000000', '人机交互节点响应模板', 'node.humanFeedback.template', '👤 人机交互节点:等待用户操作 - ', 'Y', 103, 1, '2026-02-27 10:17:46', 1, '2026-02-27 10:17:46', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027208880369647617, '000000', '条件分支节点响应模板', 'node.switch.template', '🔀 条件分支节点:触发 -> 跳转到节点 ', 'Y', 103, 1, '2026-02-27 10:27:15', 1, '2026-02-27 10:35:54', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027213914603995137, '000000', '大模型回答节点响应模板', 'node.llmAnswer.template', '🤖 LLM 节点 生成回答:', 'Y', 103, 1, '2026-02-27 10:47:16', 1, '2026-02-27 10:52:40', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027214387000066050, '000000', '关键词提取响应模板', 'node.keywordExtractor.template', '🔑 关键词提取节点 处理完成 : ', 'Y', 103, 1, '2026-02-27 10:49:08', 1, '2026-02-27 10:52:08', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027217577397391361, '000000', '工作流异常响应模板', 'node.exception.template', '🛑 工作流发生异常:', 'N', 103, 1, '2026-02-27 11:01:49', 1, '2026-02-27 11:02:01', NULL); diff --git a/pom.xml b/pom.xml index 2e5a27ab..4fda3ba8 100644 --- a/pom.xml +++ b/pom.xml @@ -56,6 +56,7 @@ 1.11.0 1.11.0-beta19 + 1.1.0-beta7 1.5.3 1.19.6 1.0.7 diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/node/LLmMailSendNodeConfigDto.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/node/LLmMailSendNodeConfigDto.java new file mode 100644 index 00000000..277c4424 --- /dev/null +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/node/LLmMailSendNodeConfigDto.java @@ -0,0 +1,38 @@ +package org.ruoyi.workflow.dto.node; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 为大模型返回信息封装的信息DTO(发送邮箱) + */ +@Data +public class LLmMailSendNodeConfigDto { + + /** + * 主题 + */ + private String subject; + + /** + * 内容 + */ + private String content; + + /** + * 收件邮箱 + */ + @JsonProperty("to_mails") + private String toMails; + + /** + * 抄送邮箱 + */ + @JsonProperty("cc_mails") + private String ccMails; + + /** + * 发送类型 + */ + private Integer senderType; +} diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/util/WorkflowMessageUtil.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/util/WorkflowMessageUtil.java index 4ae71e3b..9fef1230 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/util/WorkflowMessageUtil.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/util/WorkflowMessageUtil.java @@ -4,9 +4,15 @@ import org.ruoyi.common.chat.domain.dto.request.ChatRequest; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.common.chat.enums.RoleType; import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.service.ConfigService; import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.workflow.entity.WorkflowNode; +import org.ruoyi.workflow.helper.SSEEmitterHelper; import org.ruoyi.workflow.workflow.WfState; import org.ruoyi.workflow.workflow.WorkflowUtil; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; /** * 工作流消息工具类 @@ -17,6 +23,34 @@ import org.ruoyi.workflow.workflow.WorkflowUtil; @Slf4j public class WorkflowMessageUtil { + + /** + * 通知并存储消息(对话使用) + * @param wfState 工作流实例状态 + * @param sseEmitter SSE连接对象 + * @param node 工作流节点 + * @param message 消息 + */ + public static void notifyAndStoreMessage(WfState wfState, SseEmitter sseEmitter, WorkflowNode node, String message){ + saveWorkflowMessage(wfState, message); + sendEmitterMessage(sseEmitter, node, message); + } + + + /** + * 获取节点的响应模板 + * @param configKey 参数Key + * @return 返回模板样式 + */ + public static String getNodeMessageTemplate(String configKey){ + ConfigService configService = SpringUtil.getBean(ConfigService.class); + String configValue = configService.getConfigValue(configKey); + if (StringUtils.isEmpty(configValue)) { + throw new ServiceException("请先配置该节点的响应模板"); + } + return configValue; + } + /** * 保存工作流消息公共方法(对话使用) * @param wfState 工作流实例状态 @@ -34,4 +68,15 @@ public class WorkflowMessageUtil { } } + /** + * 发送SSE消息 + * @param sseEmitter 连接对象 + * @param node 工作流定义 + * @param message 消息 + */ + public static void sendEmitterMessage(SseEmitter sseEmitter, WorkflowNode node, String message) { + String nodeUuid = node.getUuid(); + SSEEmitterHelper.parseAndSendPartialMsg(sseEmitter,"[NODE_CHUNK_" + nodeUuid + "]", message); + } + } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java index 20a3e783..379bbb2b 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java @@ -31,6 +31,7 @@ import org.ruoyi.workflow.workflow.data.NodeIOData; import org.ruoyi.workflow.workflow.def.WfNodeIO; import org.ruoyi.workflow.workflow.def.WfNodeParamRef; import org.ruoyi.workflow.workflow.node.AbstractWfNode; +import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.util.*; @@ -54,6 +55,7 @@ public class WorkflowEngine { @Setter private SseEmitter sseEmitter; private User user; + @Getter private WfState wfState; private WfRuntimeResp wfRuntimeResp; @@ -125,7 +127,10 @@ public class WorkflowEngine { String nextNode = stateSnapshot.config().nextNode().orElse(""); //还有下个节点,表示进入中断状态,等待用户输入后继续执�? if (StringUtils.isNotBlank(nextNode) && !nextNode.equalsIgnoreCase(END)) { - String intTip = WorkflowUtil.getHumanFeedbackTip(nextNode, wfNodes); + // 获取提示模板 + String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.HUMAN_FEED_BACK.getValue()); + // 获取人机交互提示信息 + String intTip = nodeMessageTemplate + WorkflowUtil.getHumanFeedbackTip(nextNode, wfNodes); //将等待输入信息[事件与提示词]发送到到客户端 SSEEmitterHelper.parseAndSendPartialMsg(sseEmitter, "[NODE_WAIT_FEEDBACK_BY_" + nextNode + "]", intTip); // 保存提示信息到Chat信息记录中(对话使用) @@ -136,7 +141,17 @@ public class WorkflowEngine { workflowRuntimeService.updateOutput(wfRuntimeResp.getId(), wfState); } else { WorkflowRuntime updatedRuntime = workflowRuntimeService.updateOutput(wfRuntimeResp.getId(), wfState); + // 保存成功会话信息 + wfNodes.stream().filter(item -> stateSnapshot.node().equals(item.getUuid())) + .findFirst().ifPresent(wfNode -> { + // 获取节点模板提示词信息 + String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.END.getValue()); + // 发送SSE消息驱动事件和保存会话 + WorkflowMessageUtil.notifyAndStoreMessage(wfState, sseEmitter, wfNode, nodeMessageTemplate); + }); + // 发送结束消息 sseEmitterHelper.sendComplete(user.getId(), sseEmitter, updatedRuntime.getOutput()); + // 发送驱动消息事件 InterruptedFlow.RUNTIME_TO_GRAPH.remove(wfState.getUuid()); } } @@ -163,10 +178,12 @@ public class WorkflowEngine { private void errorWhenExe(Exception e) { log.error("error", e); + String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.EXCEPTION.getValue()); String errorMsg = e.getMessage(); if (errorMsg.contains("parallel node doesn't support conditional branch")) { errorMsg = "并行节点中不能包含条件分�?"; } + errorMsg = nodeMessageTemplate + errorMsg; sseEmitterHelper.sendErrorAndComplete(user.getId(), sseEmitter, errorMsg); workflowRuntimeService.updateStatus(wfRuntimeResp.getId(), WORKFLOW_PROCESS_STATUS_FAIL, errorMsg); } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowStarter.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowStarter.java index 95885ed1..bb99f201 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowStarter.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowStarter.java @@ -103,6 +103,8 @@ public class WorkflowStarter implements IWorkFlowStarterService { // 如果SSE连接对象不为空传入该对象(Chat调用工作流对话使用) if (null != sseEmitter){ workflowEngine.setSseEmitter(sseEmitter); + // 为了让每个节点都可以发送模板消息 保持SSE对象一致(以防出现向已关闭的SSE对象发送消息) + workflowEngine.getWfState().setSseEmitter(sseEmitter); } workflowEngine.resume(userInput); } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java index c34962c0..53eb41cf 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java @@ -112,7 +112,7 @@ public class WorkflowUtil extends AbstractChatMessageService { } public void streamingInvokeLLM(WfState wfState, WfNodeState state, WorkflowNode node, String modelName, - List systemMessage) { + List systemMessage, String nodeMessageTemplate) { log.info("stream invoke, modelName: {}", modelName); // 根据模型名称查询模型信息 @@ -153,8 +153,10 @@ public class WorkflowUtil extends AbstractChatMessageService { // 会话ID不为空时插入数据库 if (sessionId != null){ + // 获取模板消息拼接信息体 + String message = nodeMessageTemplate + responseTxt; // 保存助手回复消息 - saveChatMessage(chatRequest, userId, responseTxt, RoleType.ASSISTANT.getName(), chatModelVo); + saveChatMessage(chatRequest, userId, message, RoleType.ASSISTANT.getName(), chatModelVo); log.info("{}消息结束,已保存到数据库", getProviderName()); } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/AbstractWfNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/AbstractWfNode.java index 700071f0..80f8aa60 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/AbstractWfNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/AbstractWfNode.java @@ -6,15 +6,11 @@ import lombok.Data; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.SerializationUtils; -import org.ruoyi.common.chat.domain.dto.request.ChatRequest; -import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; -import org.ruoyi.common.chat.enums.RoleType; import org.ruoyi.common.core.exception.base.BaseException; import org.ruoyi.workflow.base.NodeInputConfigTypeHandler; import org.ruoyi.workflow.entity.WorkflowComponent; import org.ruoyi.workflow.entity.WorkflowNode; import org.ruoyi.workflow.enums.WfIODataTypeEnum; -import org.ruoyi.workflow.helper.SSEEmitterHelper; import org.ruoyi.workflow.util.JsonUtil; import org.ruoyi.workflow.util.SpringUtil; import org.ruoyi.workflow.util.WorkflowMessageUtil; @@ -229,16 +225,16 @@ public abstract class AbstractWfNode { /** * 会话消息保存方法 */ - public void saveSessionMessage(WfState wfState, String message) { - WorkflowMessageUtil.saveWorkflowMessage(wfState, message); + public void notifyAndStoreMessage(WfState wfState, String message) { + WorkflowMessageUtil.notifyAndStoreMessage(wfState, wfState.getSseEmitter(), node, message); } /** - * 发送SSe消息 - * @param message 信息 + * 获取节点的响应模板 + * @param configKey 参数Key + * @return 返回模板样式 */ - public void sendSseEvent(String message){ - String nodeUuid = node.getUuid(); - SSEEmitterHelper.parseAndSendPartialMsg(wfState.getSseEmitter(), "[NODE_CHUNK_" + nodeUuid + "]", message); + public String getNodeMessageTemplate(String configKey){ + return WorkflowMessageUtil.getNodeMessageTemplate(configKey); } } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/answer/LLMAnswerNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/answer/LLMAnswerNode.java index e0c2f31c..2539226b 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/answer/LLMAnswerNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/answer/LLMAnswerNode.java @@ -6,11 +6,13 @@ import org.apache.commons.lang3.StringUtils; import org.ruoyi.workflow.entity.WorkflowComponent; import org.ruoyi.workflow.entity.WorkflowNode; import org.ruoyi.workflow.util.SpringUtil; +import org.ruoyi.workflow.util.WorkflowMessageUtil; import org.ruoyi.workflow.workflow.NodeProcessResult; import org.ruoyi.workflow.workflow.WfNodeState; import org.ruoyi.workflow.workflow.WfState; import org.ruoyi.workflow.workflow.WorkflowUtil; import org.ruoyi.workflow.workflow.node.AbstractWfNode; +import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum; import java.util.List; @@ -46,7 +48,11 @@ public class LLMAnswerNode extends AbstractWfNode { String modelName = nodeConfigObj.getModelName(); // 转换系统信息结构 List systemMessage = List.of(new SystemMessage(prompt)); - workflowUtil.streamingInvokeLLM(wfState, state, node, modelName, systemMessage); + // 获取节点模板提示词信息 + String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.LLM_RESPONSE.getValue()); + // 发送SSE驱动事件消息 + WorkflowMessageUtil.sendEmitterMessage(wfState.getSseEmitter(), node, nodeMessageTemplate); + workflowUtil.streamingInvokeLLM(wfState, state, node, modelName, systemMessage, nodeMessageTemplate); return new NodeProcessResult(); } } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/enmus/NodeMessageTemplateEnum.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/enmus/NodeMessageTemplateEnum.java new file mode 100644 index 00000000..1b39443f --- /dev/null +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/enmus/NodeMessageTemplateEnum.java @@ -0,0 +1,25 @@ +package org.ruoyi.workflow.workflow.node.enmus; + +import lombok.Getter; + +/** + * 节点消息模板ConfigKey枚举 + */ +@Getter +public enum NodeMessageTemplateEnum { + HTTP_REQUEST("node.httpRequest.template"), + MAIL_SEND("node.mailsend.template"), + IMAGE("node.image.template"), + HUMAN_FEED_BACK("node.humanFeedback.template"), + SWITCH("node.switch.template"), + LLM_RESPONSE("node.llmAnswer.template"), + KEYWORD_EXTRACTOR("node.keywordExtractor.template"), + EXCEPTION("node.exception.template"), + END("node.end.template"); + + private final String value; + + NodeMessageTemplateEnum(String value) { + this.value = value; + } +} diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/httpRequest/HttpRequestNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/httpRequest/HttpRequestNode.java index 10248aeb..8cd360c2 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/httpRequest/HttpRequestNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/httpRequest/HttpRequestNode.java @@ -12,6 +12,7 @@ import org.ruoyi.workflow.workflow.WfNodeState; import org.ruoyi.workflow.workflow.WfState; import org.ruoyi.workflow.workflow.data.NodeIOData; import org.ruoyi.workflow.workflow.node.AbstractWfNode; +import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum; import org.springframework.http.*; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -32,6 +33,8 @@ public class HttpRequestNode extends AbstractWfNode { @Override public NodeProcessResult onProcess() { + // 获取节点模板提示词信息 + String nodeMessageTemplate = getNodeMessageTemplate(NodeMessageTemplateEnum.HTTP_REQUEST.getValue()); try { HttpRequestNodeConfig config = checkAndGetConfig(HttpRequestNodeConfig.class); List inputs = state.getInputs(); @@ -63,11 +66,9 @@ public class HttpRequestNode extends AbstractWfNode { List outputs = new ArrayList<>(); outputs.add(NodeIOData.createByText("output", "HTTP响应", response)); - // 保存成功会话信息 - String message = "HTTP响应:" + response; - saveSessionMessage(wfState, message); - // 发送驱动消息事件 - sendSseEvent(message); + // 保存成功会话信息且发送驱动消息事件 + String message = nodeMessageTemplate + response; + notifyAndStoreMessage(wfState, message); return NodeProcessResult.builder().content(outputs).build(); } catch (Exception e) { @@ -78,11 +79,9 @@ public class HttpRequestNode extends AbstractWfNode { errorOutputs.add(NodeIOData.createByText("output", "错误", "")); errorOutputs.add(NodeIOData.createByText("error", "HTTP请求错误", e.getMessage())); - // 保存失败会话信息 - String message = "HTTP响应失败:" + e.getMessage(); - saveSessionMessage(wfState, message); - // 发送驱动消息事件 - sendSseEvent(message); + // 保存失败会话信息且发送驱动消息事件 + String message = nodeMessageTemplate + e.getMessage(); + notifyAndStoreMessage(wfState, message); return NodeProcessResult.builder().content(errorOutputs).build(); } } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNode.java index 4b887ad7..6f5b639b 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/image/ImageNode.java @@ -11,6 +11,7 @@ import org.ruoyi.workflow.workflow.WfState; import org.ruoyi.workflow.workflow.WorkflowUtil; import org.ruoyi.workflow.workflow.data.NodeIOData; import org.ruoyi.workflow.workflow.node.AbstractWfNode; +import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum; import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.NODE_PROCESS_STATUS_SUCCESS; @@ -51,11 +52,11 @@ public class ImageNode extends AbstractWfNode { Integer seed = nodeConfigObj.getSeed(); // 调用LLM生成图片(后续可以将图片保存到OSS中) String imageUrl = workflowUtil.buildTextToImage(modelName, prompt, size, seed); - // 保存成功信息 - String message = "图片生成地址:" + imageUrl; - saveSessionMessage(wfState, message); - // 发送驱动消息事件 - sendSseEvent(message); + // 获取节点模板提示词信息 + String nodeMessageTemplate = getNodeMessageTemplate(NodeMessageTemplateEnum.IMAGE.getValue()); + // 保存成功信息且发送驱动消息事件 + String message = nodeMessageTemplate + imageUrl; + notifyAndStoreMessage(wfState, message); // 创建节点参数对象 NodeIOData nodeIOData = NodeIOData.createByText("output", "image", imageUrl); // 添加到输出列表以便给后续节点使用 diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/keywordExtractor/KeywordExtractorNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/keywordExtractor/KeywordExtractorNode.java index 5ad7147a..a75c79f3 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/keywordExtractor/KeywordExtractorNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/keywordExtractor/KeywordExtractorNode.java @@ -1,18 +1,19 @@ package org.ruoyi.workflow.workflow.node.keywordExtractor; import dev.langchain4j.data.message.SystemMessage; -import dev.langchain4j.data.message.UserMessage; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.ruoyi.workflow.entity.WorkflowComponent; import org.ruoyi.workflow.entity.WorkflowNode; import org.ruoyi.workflow.util.SpringUtil; +import org.ruoyi.workflow.util.WorkflowMessageUtil; import org.ruoyi.workflow.workflow.NodeProcessResult; import org.ruoyi.workflow.workflow.WfNodeState; import org.ruoyi.workflow.workflow.WfState; import org.ruoyi.workflow.workflow.WorkflowUtil; import org.ruoyi.workflow.workflow.data.NodeIOData; import org.ruoyi.workflow.workflow.node.AbstractWfNode; +import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum; import java.util.ArrayList; import java.util.List; @@ -67,8 +68,12 @@ public class KeywordExtractorNode extends AbstractWfNode { WorkflowUtil workflowUtil = SpringUtil.getBean(WorkflowUtil.class); String modelName = config.getModelName(); List systemMessage = List.of(new SystemMessage(prompt)); + // 获取节点模板提示词信息 + String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.KEYWORD_EXTRACTOR.getValue()); + // 发送SSE事件消息 + WorkflowMessageUtil.sendEmitterMessage(wfState.getSseEmitter(), node, nodeMessageTemplate); // 使用流式调用 - workflowUtil.streamingInvokeLLM(wfState, state, node, modelName, systemMessage); + workflowUtil.streamingInvokeLLM(wfState, state, node, modelName, systemMessage, nodeMessageTemplate); return new NodeProcessResult(); } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/knowledgeRetrieval/KnowledgeRetrievalNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/knowledgeRetrieval/KnowledgeRetrievalNode.java index f17dc744..d934c79b 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/knowledgeRetrieval/KnowledgeRetrievalNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/knowledgeRetrieval/KnowledgeRetrievalNode.java @@ -161,7 +161,8 @@ public class KnowledgeRetrievalNode extends AbstractWfNode { tempState, tempNode, modelName, - systemMessage + systemMessage, + "" ); // 等待LLM响应完成(最多等待30秒) diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/mailSend/MailSendNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/mailSend/MailSendNode.java index 0ae6a197..71688064 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/mailSend/MailSendNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/mailSend/MailSendNode.java @@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONValidator; import jakarta.mail.internet.MimeMessage; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.ruoyi.workflow.dto.node.LLmMailSendNodeConfigDto; import org.ruoyi.workflow.entity.WorkflowComponent; import org.ruoyi.workflow.entity.WorkflowNode; import org.ruoyi.workflow.workflow.NodeProcessResult; @@ -13,6 +14,8 @@ import org.ruoyi.workflow.workflow.WfState; import org.ruoyi.workflow.workflow.WorkflowUtil; import org.ruoyi.workflow.workflow.data.NodeIOData; import org.ruoyi.workflow.workflow.node.AbstractWfNode; +import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum; +import org.springframework.beans.BeanUtils; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; @@ -30,6 +33,8 @@ public class MailSendNode extends AbstractWfNode { @Override public NodeProcessResult onProcess() { + // 获取节点模板提示词信息 + String nodeMessageTemplate = getNodeMessageTemplate(NodeMessageTemplateEnum.MAIL_SEND.getValue()); try { MailSendNodeConfig config = checkAndGetConfig(MailSendNodeConfig.class); List inputs = state.getInputs(); @@ -37,7 +42,9 @@ public class MailSendNode extends AbstractWfNode { String input = getDataFromInput(inputs); // 判断是否为JSON格式(LLM输出转换 由LLM生成格式) if (StringUtils.isNotBlank(input) && isJson(input)) { - config = JSONObject.parseObject(input, MailSendNodeConfig.class); + LLmMailSendNodeConfigDto lLmMailSendNodeConfigDto = JSONObject.parseObject(input, LLmMailSendNodeConfigDto.class); + // 保留原本Sender和Smtp对象 + BeanUtils.copyProperties(lLmMailSendNodeConfigDto, config); } // 安全获取模板(使用 defaultString 避免 null) @@ -111,11 +118,9 @@ public class MailSendNode extends AbstractWfNode { mailSender.send(message); log.info("Email sent successfully to: {}", toMails); - // 保存成功会话信息 - String resultMessage = "发送邮箱成功"; - saveSessionMessage(wfState, resultMessage); - // 发送驱动消息事件 - sendSseEvent(resultMessage); + // 保存成功会话信息且发送驱动消息事件 + String resultMessage = nodeMessageTemplate + "发送邮箱成功"; + notifyAndStoreMessage(wfState, resultMessage); // 构造输出:统一输出为 output 参数 List outputs = new java.util.ArrayList<>(); @@ -144,11 +149,9 @@ public class MailSendNode extends AbstractWfNode { // 异常时也统一输出为 output 参数,添加错误信息 List errorOutputs = new java.util.ArrayList<>(); - // 保存失败会话信息 - String resultMessage = "发送邮箱失败: " + e.getMessage(); - saveSessionMessage(wfState, resultMessage); - // 发送驱动消息事件 - sendSseEvent(resultMessage); + // 保存失败会话信息且发送驱动消息事件 + String resultMessage = nodeMessageTemplate + "发送邮箱失败: " + e.getMessage(); + notifyAndStoreMessage(wfState, resultMessage); state.getInputs().stream() .filter(item -> "output".equals(item.getName())) diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/switcher/SwitcherNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/switcher/SwitcherNode.java index 6c27509d..f67d1585 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/switcher/SwitcherNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/switcher/SwitcherNode.java @@ -15,6 +15,7 @@ import org.ruoyi.workflow.workflow.WfNodeState; import org.ruoyi.workflow.workflow.WfState; import org.ruoyi.workflow.workflow.data.NodeIOData; import org.ruoyi.workflow.workflow.node.AbstractWfNode; +import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum; import java.math.BigDecimal; import java.util.List; @@ -43,6 +44,9 @@ public class SwitcherNode extends AbstractWfNode { log.info("条件分支节点处理中,分支数量: {}", config.getCases() != null ? config.getCases().size() : 0); + // 获取提示模板 + String nodeMessageTemplate = getNodeMessageTemplate(NodeMessageTemplateEnum.SWITCH.getValue()); + // 按顺序评估每个分支 if (config.getCases() != null) { for (int i = 0; i < config.getCases().size(); i++) { @@ -52,13 +56,17 @@ public class SwitcherNode extends AbstractWfNode { if (evaluateCase(switcherCase, inputs)) { // 检查目标节点UUID是否为空 - if (StringUtils.isBlank(switcherCase.getTargetNodeUuid())) { + String targetNodeUuid = switcherCase.getTargetNodeUuid(); + if (StringUtils.isBlank(targetNodeUuid)) { log.warn("分支 {} 匹配但目标节点UUID为空,跳过到下一个分支", i + 1); continue; } + // 根据目标节点UUID获取对应节点名称 + findNodeAndNotify(targetNodeUuid, nodeMessageTemplate); + log.info("分支 {} 匹配,跳转到节点: {}", - i + 1, switcherCase.getTargetNodeUuid()); + i + 1, targetNodeUuid); // 构造输出:只保留 output 和其他非 input 参数 + 添加分支匹配信息 List outputs = new java.util.ArrayList<>(); @@ -85,12 +93,12 @@ public class SwitcherNode extends AbstractWfNode { outputs.add(NodeIOData.createByText("matched_case", "switcher", String.valueOf(i + 1))); outputs.add(NodeIOData.createByText("case_uuid", "switcher", switcherCase.getUuid())); - outputs.add(NodeIOData.createByText("target_node", "switcher", switcherCase.getTargetNodeUuid())); + outputs.add(NodeIOData.createByText("target_node", "switcher", targetNodeUuid)); // WorkflowEngine 会自动将 nextNodeUuid 放入 resultMap 的 "next" 键中 return NodeProcessResult.builder() .content(outputs) - .nextNodeUuid(switcherCase.getTargetNodeUuid()) + .nextNodeUuid(targetNodeUuid) .build(); } } @@ -99,6 +107,9 @@ public class SwitcherNode extends AbstractWfNode { // 所有分支都不满足,使用默认分支 log.info("没有分支匹配,使用默认分支: {}", config.getDefaultTargetNodeUuid()); + // 根据默认目标节点UUID获取对应节点名称 + findNodeAndNotify(config.getDefaultTargetNodeUuid(), nodeMessageTemplate); + if (StringUtils.isBlank(config.getDefaultTargetNodeUuid())) { log.warn("默认目标节点UUID为空,工作流可能在此停止"); } @@ -154,6 +165,21 @@ public class SwitcherNode extends AbstractWfNode { } } + /** + * 根据节点ID查询对应节点 + * @param targetNodeUuid 节点UUID + * @param nodeMessageTemplate 节点消息模板 + */ + private void findNodeAndNotify(String targetNodeUuid, String nodeMessageTemplate) { + // 根据目标节点UUID获取对应节点名称 + WorkflowNode workflowNode = workflowNodeService.lambdaQuery().eq(WorkflowNode::getUuid, targetNodeUuid).one(); + if (null != workflowNode){ + // 获取节点名称 + String message = nodeMessageTemplate + workflowNode.getTitle(); + notifyAndStoreMessage(wfState, message); + } + } + /** * 评估单个分支的条件 * diff --git a/ruoyi-modules/ruoyi-chat/pom.xml b/ruoyi-modules/ruoyi-chat/pom.xml index ae85abd9..35a3535f 100644 --- a/ruoyi-modules/ruoyi-chat/pom.xml +++ b/ruoyi-modules/ruoyi-chat/pom.xml @@ -48,6 +48,12 @@ ${langchain4j.community.version} + + dev.langchain4j + langchain4j-community-zhipu-ai + ${langchain4j.community.zhipu.ai.version} + + diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/ZhiPuChatServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/ZhiPuChatServiceImpl.java new file mode 100644 index 00000000..222ee3cc --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/ZhiPuChatServiceImpl.java @@ -0,0 +1,43 @@ +package org.ruoyi.service.chat.impl.provider; + +import dev.langchain4j.community.model.zhipu.ZhipuAiStreamingChatModel; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.model.chat.StreamingChatModel; +import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.domain.dto.request.ChatRequest; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.enums.ChatModeType; +import org.ruoyi.service.chat.impl.AbstractStreamingChatService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 智谱AI服务调用 + * + * @author zengxb + * @date 2026/02/26 + */ +@Service +@Slf4j +public class ZhiPuChatServiceImpl extends AbstractStreamingChatService { + @Override + protected void doChat(ChatModelVo chatModelVo, ChatRequest chatRequest, List messagesWithMemory, StreamingChatResponseHandler handler) { + StreamingChatModel streamingChatModel = buildStreamingChatModel(chatModelVo,chatRequest); + streamingChatModel.chat(messagesWithMemory, handler); + } + + @Override + protected StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) { + return ZhipuAiStreamingChatModel.builder() + .apiKey(chatModelVo.getApiKey()) + .model(chatModelVo.getModelName()) + .build(); + } + + @Override + public String getProviderName() { + return ChatModeType.ZHI_PU.getCode(); + } +} From fd38d2030ee17f5ba14a153e4f28cbaf91481219 Mon Sep 17 00:00:00 2001 From: zhang <823772544@qq.com> Date: Sat, 28 Feb 2026 11:53:36 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/node/LLmMailSendNodeConfigDto.java | 38 ------------------- .../workflow/workflow/WorkflowEngine.java | 2 + .../workflow/node/mailSend/MailSendNode.java | 10 ++--- 3 files changed, 7 insertions(+), 43 deletions(-) delete mode 100644 ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/node/LLmMailSendNodeConfigDto.java diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/node/LLmMailSendNodeConfigDto.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/node/LLmMailSendNodeConfigDto.java deleted file mode 100644 index 277c4424..00000000 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/dto/node/LLmMailSendNodeConfigDto.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.ruoyi.workflow.dto.node; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -/** - * 为大模型返回信息封装的信息DTO(发送邮箱) - */ -@Data -public class LLmMailSendNodeConfigDto { - - /** - * 主题 - */ - private String subject; - - /** - * 内容 - */ - private String content; - - /** - * 收件邮箱 - */ - @JsonProperty("to_mails") - private String toMails; - - /** - * 抄送邮箱 - */ - @JsonProperty("cc_mails") - private String ccMails; - - /** - * 发送类型 - */ - private Integer senderType; -} diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java index 379bbb2b..87ab1baf 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java @@ -184,6 +184,8 @@ public class WorkflowEngine { errorMsg = "并行节点中不能包含条件分�?"; } errorMsg = nodeMessageTemplate + errorMsg; + // 保存会话信息且发送驱动消息事件 + WorkflowMessageUtil.saveWorkflowMessage(wfState, errorMsg); sseEmitterHelper.sendErrorAndComplete(user.getId(), sseEmitter, errorMsg); workflowRuntimeService.updateStatus(wfRuntimeResp.getId(), WORKFLOW_PROCESS_STATUS_FAIL, errorMsg); } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/mailSend/MailSendNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/mailSend/MailSendNode.java index 71688064..f2482096 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/mailSend/MailSendNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/mailSend/MailSendNode.java @@ -1,11 +1,11 @@ package org.ruoyi.workflow.workflow.node.mailSend; +import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONValidator; import jakarta.mail.internet.MimeMessage; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.ruoyi.workflow.dto.node.LLmMailSendNodeConfigDto; import org.ruoyi.workflow.entity.WorkflowComponent; import org.ruoyi.workflow.entity.WorkflowNode; import org.ruoyi.workflow.workflow.NodeProcessResult; @@ -15,7 +15,6 @@ import org.ruoyi.workflow.workflow.WorkflowUtil; import org.ruoyi.workflow.workflow.data.NodeIOData; import org.ruoyi.workflow.workflow.node.AbstractWfNode; import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum; -import org.springframework.beans.BeanUtils; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; @@ -42,9 +41,10 @@ public class MailSendNode extends AbstractWfNode { String input = getDataFromInput(inputs); // 判断是否为JSON格式(LLM输出转换 由LLM生成格式) if (StringUtils.isNotBlank(input) && isJson(input)) { - LLmMailSendNodeConfigDto lLmMailSendNodeConfigDto = JSONObject.parseObject(input, LLmMailSendNodeConfigDto.class); - // 保留原本Sender和Smtp对象 - BeanUtils.copyProperties(lLmMailSendNodeConfigDto, config); + JSONObject inputJson = JSON.parseObject(input); + JSONObject configJson = (JSONObject) JSON.toJSON(config); + configJson.putAll(inputJson); + config = configJson.toJavaObject(MailSendNodeConfig.class); } // 安全获取模板(使用 defaultString 避免 null) From c1e2a03178421563a95ddfb6f2570f6ecaa92791 Mon Sep 17 00:00:00 2001 From: zhang <823772544@qq.com> Date: Sun, 1 Mar 2026 11:29:58 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/script/sql/ruoyi-ai-v3_mysql8.sql | 267 ++++++--- pom.xml | 7 - ruoyi-admin/pom.xml | 6 - ruoyi-modules/pom.xml | 1 - ruoyi-modules/ruoyi-aihuman/pom.xml | 101 ---- .../org/ruoyi/aihuman/config/WebConfig.java | 16 - .../controller/AihumanConfigController.java | 126 ----- .../controller/AihumanInfoController.java | 80 --- .../AihumanRealConfigController.java | 157 ----- .../AihumanVolcengineController.java | 502 ---------------- .../ruoyi/aihuman/domain/AihumanConfig.java | 74 --- .../org/ruoyi/aihuman/domain/AihumanInfo.java | 52 -- .../aihuman/domain/AihumanRealConfig.java | 104 ---- .../ruoyi/aihuman/domain/VoiceRequest.java | 22 - .../aihuman/domain/bo/AihumanConfigBo.java | 65 --- .../aihuman/domain/bo/AihumanInfoBo.java | 44 -- .../domain/bo/AihumanRealConfigBo.java | 87 --- .../aihuman/domain/vo/AihumanConfigVo.java | 75 --- .../aihuman/domain/vo/AihumanInfoVo.java | 44 -- .../domain/vo/AihumanRealConfigVo.java | 109 ---- .../aihuman/mapper/AihumanConfigMapper.java | 17 - .../aihuman/mapper/AihumanInfoMapper.java | 16 - .../mapper/AihumanRealConfigMapper.java | 17 - .../aihuman/protocol/CompressionBits.java | 26 - .../org/ruoyi/aihuman/protocol/EventType.java | 90 --- .../aihuman/protocol/HeaderSizeBits.java | 27 - .../org/ruoyi/aihuman/protocol/Message.java | 220 -------- .../org/ruoyi/aihuman/protocol/MsgType.java | 29 - .../aihuman/protocol/MsgTypeFlagBits.java | 27 - .../aihuman/protocol/SerializationBits.java | 27 - .../protocol/SpeechWebSocketClient.java | 115 ---- .../ruoyi/aihuman/protocol/VersionBits.java | 27 - .../aihuman/service/AihumanConfigService.java | 48 -- .../service/AihumanRealConfigService.java | 56 -- .../service/AihumanVolcengineService.java | 4 - .../aihuman/service/IAihumanInfoService.java | 47 -- .../impl/AihumanConfigServiceImpl.java | 115 ---- .../service/impl/AihumanInfoServiceImpl.java | 96 ---- .../impl/AihumanRealConfigServiceImpl.java | 534 ------------------ .../impl/AihumanVolcengineServiceImpl.java | 4 - .../ruoyi/aihuman/volcengine/Bidirection.java | 160 ------ .../resources/mapper/AihumanInfoMapper.xml | 9 - .../mapper/aihuman/AihumanConfigMapper.xml | 7 - .../aihuman/AihumanRealConfigMapper.xml | 7 - 44 files changed, 174 insertions(+), 3490 deletions(-) delete mode 100644 ruoyi-modules/ruoyi-aihuman/pom.xml delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/config/WebConfig.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanConfigController.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanInfoController.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanRealConfigController.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanVolcengineController.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/AihumanConfig.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/AihumanInfo.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/AihumanRealConfig.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/VoiceRequest.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/bo/AihumanConfigBo.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/bo/AihumanInfoBo.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/bo/AihumanRealConfigBo.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/vo/AihumanConfigVo.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/vo/AihumanInfoVo.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/vo/AihumanRealConfigVo.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/mapper/AihumanConfigMapper.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/mapper/AihumanInfoMapper.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/mapper/AihumanRealConfigMapper.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/CompressionBits.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/EventType.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/HeaderSizeBits.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/Message.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/MsgType.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/MsgTypeFlagBits.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/SerializationBits.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/SpeechWebSocketClient.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/VersionBits.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/AihumanConfigService.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/AihumanRealConfigService.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/AihumanVolcengineService.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/IAihumanInfoService.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanConfigServiceImpl.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanInfoServiceImpl.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanRealConfigServiceImpl.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanVolcengineServiceImpl.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/volcengine/Bidirection.java delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/resources/mapper/AihumanInfoMapper.xml delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/resources/mapper/aihuman/AihumanConfigMapper.xml delete mode 100644 ruoyi-modules/ruoyi-aihuman/src/main/resources/mapper/aihuman/AihumanRealConfigMapper.xml diff --git a/docs/script/sql/ruoyi-ai-v3_mysql8.sql b/docs/script/sql/ruoyi-ai-v3_mysql8.sql index 58d14837..805c932d 100644 --- a/docs/script/sql/ruoyi-ai-v3_mysql8.sql +++ b/docs/script/sql/ruoyi-ai-v3_mysql8.sql @@ -1,58 +1,22 @@ +/* + Navicat MySQL Dump SQL + + Source Server : 本地 + Source Server Type : MySQL + Source Server Version : 80045 (8.0.45) + Source Host : localhost:3306 + Source Schema : ruoyi-ai + + Target Server Type : MySQL + Target Server Version : 80045 (8.0.45) + File Encoding : 65001 + + Date: 27/02/2026 13:59:20 +*/ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; --- ---------------------------- --- Table structure for aihuman_config --- ---------------------------- -DROP TABLE IF EXISTS `aihuman_config`; -CREATE TABLE `aihuman_config` ( - `id` int NOT NULL AUTO_INCREMENT, - `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `model_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `model_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `model_params` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `agent_params` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `create_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - `update_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - `status` int NULL DEFAULT NULL, - `publish` int NULL DEFAULT NULL, - `create_dept` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `create_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `update_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id', - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '数字人配置信息' ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Records of aihuman_config --- ---------------------------- -INSERT INTO `aihuman_config` VALUES (9, '关爱老婆数字人(梅朵)', '梅朵吉祥物', '/Live2D/models/梅朵吉祥物/梅朵吉祥物.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"梅朵吉祥物.moc3\",\n \"Textures\": [\n \"梅朵吉祥物.4096/texture_00.png\",\n \"梅朵吉祥物.4096/texture_01.png\"\n ],\n \"Physics\": \"梅朵吉祥物.physics3.json\",\n \"DisplayInfo\": \"梅朵吉祥物.cdi3.json\",\n \"MotionSync\": \"梅朵吉祥物.motionsync3.json\",\n \"Expressions\": [\n {\n \"Name\": \"kaixin\",\n \"File\": \"kaixin.exp3.json\"\n },\n {\n \"Name\": \"maozi\",\n \"File\": \"maozi.exp3.json\"\n },\n {\n \"Name\": \"mouth open\",\n \"File\": \"mouth open.exp3.json\"\n },\n {\n \"Name\": \"shibai\",\n \"File\": \"shibai.exp3.json\"\n },\n {\n \"Name\": \"yinchen\",\n \"File\": \"yinchen.exp3.json\"\n }\n ],\n \"Motions\": {\n \"\": [\n {\n \"File\": \"mouth.motion3.json\"\n }\n ]\n }\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": [\n \"ParamMouthForm\",\n \"ParamMouthOpenY\"\n ]\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": [\n \"ParamEyeLOpen\",\n \"ParamEyeROpen\"\n ]\n }\n ],\n \"HitAreas\": []\n}', '{\n \"bot_id\": \"7504596188201746470\",\n \"user_id\": \"7376476310010937396\",\n \"stream\": true,\n \"auto_save_history\": true\n}', '2025-09-29 16:36:46', '2025-09-29 16:36:46', 0, 1, NULL, NULL, '1', 0); -INSERT INTO `aihuman_config` VALUES (10, '关爱老婆数字人(K)', 'kei_vowels_pro', '/Live2D/models/kei_vowels_pro/kei_vowels_pro.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"kei_vowels_pro.moc3\",\n \"Textures\": [\n \"kei_vowels_pro.2048/texture_00.png\"\n ],\n \"Physics\": \"kei_vowels_pro.physics3.json\",\n \"DisplayInfo\": \"kei_vowels_pro.cdi3.json\",\n \"MotionSync\": \"kei_vowels_pro.motionsync3.json\",\n \"Motions\": {\n \"\": [\n {\n \"File\": \"motions/01_kei_en.motion3.json\",\n \"Sound\": \"sounds/01_kei_en.wav\",\n \"MotionSync\": \"Vowels_CRI\"\n },\n {\n \"File\": \"motions/01_kei_jp.motion3.json\",\n \"Sound\": \"sounds/01_kei_jp.wav\",\n \"MotionSync\": \"Vowels_CRI\"\n },\n {\n \"File\": \"motions/01_kei_ko.motion3.json\",\n \"Sound\": \"sounds/01_kei_ko.wav\",\n \"MotionSync\": \"Vowels_CRI\"\n },\n {\n \"File\": \"motions/01_kei_zh.motion3.json\",\n \"Sound\": \"sounds/01_kei_zh.wav\",\n \"MotionSync\": \"Vowels_CRI\"\n }\n ]\n }\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": []\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": [\n \"ParamEyeLOpen\",\n \"ParamEyeROpen\"\n ]\n }\n ],\n \"HitAreas\": [\n {\n \"Id\": \"HitAreaHead\",\n \"Name\": \"Head\"\n }\n ]\n}', '3', '2025-09-29 16:35:27', '2025-09-29 16:35:27', 0, 1, NULL, NULL, '1', 0); -INSERT INTO `aihuman_config` VALUES (11, '关爱老婆数字人(March 7th)', 'March 7th', '/Live2D/models/March 7th/March 7th.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"March 7th.moc3\",\n \"Textures\": [\n \"March 7th.4096/texture_00.png\",\n \"March 7th.4096/texture_01.png\"\n ],\n \"Physics\": \"March 7th.physics3.json\",\n \"DisplayInfo\": \"March 7th.cdi3.json\",\n \"Expressions\": [\n {\n \"Name\": \"捂脸\",\n \"File\": \"1.exp3.json\"\n },\n {\n \"Name\": \"比耶\",\n \"File\": \"2.exp3.json\"\n },\n {\n \"Name\": \"照相\",\n \"File\": \"3.exp3.json\"\n },\n {\n \"Name\": \"脸红\",\n \"File\": \"4.exp3.json\"\n },\n {\n \"Name\": \"黑脸\",\n \"File\": \"5.exp3.json\"\n },\n {\n \"Name\": \"哭\",\n \"File\": \"6.exp3.json\"\n },\n {\n \"Name\": \"流汗\",\n \"File\": \"7.exp3.json\"\n },\n {\n \"Name\": \"星星\",\n \"File\": \"8.exp3.json\"\n }\n ]\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": [\n \"ParamEyeLOpen\",\n \"ParamEyeROpen\"\n ]\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": [\n \"ParamMouthOpenY\"\n ]\n }\n ],\n \"HitAreas\": []\n}', '3', '2025-09-29 21:09:26', '2025-09-29 21:09:28', 0, 1, NULL, NULL, NULL, 0); -INSERT INTO `aihuman_config` VALUES (12, '关爱老婆数字人(pachan)', 'pachan', '/Live2D/models/pachan/pachan.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"pachirisu anime girl - top half.moc3\",\n \"Textures\": [\n \"pachirisu anime girl - top half.4096/texture_00.png\"\n ],\n \"Physics\": \"pachirisu anime girl - top half.physics3.json\",\n \"DisplayInfo\": \"pachirisu anime girl - top half.cdi3.json\"\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": []\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": []\n }\n ]\n}', NULL, '2025-10-05 19:49:56', '2025-10-05 19:49:56', 0, 1, NULL, NULL, NULL, 0); -INSERT INTO `aihuman_config` VALUES (13, '关爱老婆数字人(230108)', '230108', '/Live2D/models/230108/230108.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"230108.moc3\",\n \"Textures\": [\n \"230108.4096/texture_00.png\"\n ],\n \"Physics\": \"230108.physics3.json\",\n \"DisplayInfo\": \"230108.cdi3.json\"\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": [\n \"ParamMouthOpenY\"\n ]\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": [\n \"ParamEyeLOpen\",\n \"ParamEyeROpen\"\n ]\n }\n ]\n}', NULL, '2025-10-06 19:28:20', '2025-10-06 19:28:23', 0, 1, NULL, NULL, NULL, 0); - --- ---------------------------- --- Table structure for aihuman_info --- ---------------------------- -DROP TABLE IF EXISTS `aihuman_info`; -CREATE TABLE `aihuman_info` ( - `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '交互名称', - `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '交互内容', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', - `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id', - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'AI人类交互信息表' ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Records of aihuman_info --- ---------------------------- -INSERT INTO `aihuman_info` VALUES (1, '1', '1', '2025-09-26 18:02:00', '2025-09-26 18:02:02', '0', 0); - -- ---------------------------- -- Table structure for chat_config -- ---------------------------- @@ -139,8 +103,8 @@ CREATE TABLE `chat_model` ( -- ---------------------------- -- Records of chat_model -- ---------------------------- -INSERT INTO `chat_model` VALUES (2000585866022060033, 'chat', 'deepseek/deepseek-v3.2', 'openai', 'deepseek', 1, '1', 'Y', 'Y', 1, 'https://api.ppinfra.com/openai', 'sk_xx', 103, 1, '2025-12-15 23:16:54', 1, '2026-02-06 01:02:31', 'DeepSeek-V3.2 是一款在高效推理、复杂推理能力与智能体场景中表现突出的领先模型。其基于 DeepSeek Sparse Attention(DSA)稀疏注意力机制,在显著降低计算开销的同时优化长上下文性能;通过可扩展强化学习框架,整体能力达到 GPT-5 同级,高算力版本 V3.2-Speciale 更在推理表现上接近 Gemini-3.0-Pro;同时,模型依托大型智能体任务合成管线,具备更强的工具调用与多步骤决策能力,并在 2025 年 IMO 与 IOI 中取得金牌级表现。作为 MaaS 平台,我们已对 DeepSeek-V3.2 完成深度适配,通过动态调度、批处理加速、低延迟推理与企业级 SLA 保障,进一步增强其在企业生产环境中的稳定性、性价比与可控性,适用于搜索、问答、智能体、代码、数据处理等多类高价值场景。', 0); -INSERT INTO `chat_model` VALUES (2007528268536287233, 'vector', 'baai/bge-m3', 'openai', 'bge-m3', 0, '1', 'N', 'Y', 1, 'https://api.ppinfra.com/openai', 'sk_xx', 103, 1, '2026-01-04 03:03:32', 1, '2026-02-06 01:02:35', 'bge-large-zh-v1.5', 0); +INSERT INTO `chat_model` VALUES (2000585866022060033, 'chat', 'deepseek/deepseek-v3.2', 'ppio', 'deepseek', 1, '1', 'Y', 'Y', 1, 'https://api.ppinfra.com/openai', 'sk_xx', 103, 1, '2025-12-15 23:16:54', 1, '2026-02-25 21:46:08', 'DeepSeek-V3.2 是一款在高效推理、复杂推理能力与智能体场景中表现突出的领先模型。其基于 DeepSeek Sparse Attention(DSA)稀疏注意力机制,在显著降低计算开销的同时优化长上下文性能;通过可扩展强化学习框架,整体能力达到 GPT-5 同级,高算力版本 V3.2-Speciale 更在推理表现上接近 Gemini-3.0-Pro;同时,模型依托大型智能体任务合成管线,具备更强的工具调用与多步骤决策能力,并在 2025 年 IMO 与 IOI 中取得金牌级表现。作为 MaaS 平台,我们已对 DeepSeek-V3.2 完成深度适配,通过动态调度、批处理加速、低延迟推理与企业级 SLA 保障,进一步增强其在企业生产环境中的稳定性、性价比与可控性,适用于搜索、问答、智能体、代码、数据处理等多类高价值场景。', 0); +INSERT INTO `chat_model` VALUES (2007528268536287233, 'vector', 'baai/bge-m3', 'ppio', 'bge-m3', 0, '1', 'N', 'Y', 1, 'https://api.ppinfra.com/openai', 'sk_xx', 103, 1, '2026-01-04 03:03:32', 1, '2026-02-25 21:15:14', 'bge-large-zh-v1.5', 0); -- ---------------------------- -- Table structure for chat_provider @@ -173,11 +137,11 @@ CREATE TABLE `chat_provider` ( -- ---------------------------- -- Records of chat_provider -- ---------------------------- -INSERT INTO `chat_provider` VALUES (1, 'OpenAI', 'openai', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/9d944a6abfcd46e2bd6e364f07202589.png', 'OpenAI官方API服务商', 'https://api.openai.com', '0', 1, NULL, '2025-12-14 21:48:11', '1', '1', '2026-01-22 15:05:55', 'OpenAI厂商', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (2, '阿里云百炼', 'qianwen', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/039ad13f690649f0ade139f8c803727b.png', '阿里云百炼大模型服务', 'https://dashscope.aliyuncs.com', '0', 2, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-06 00:58:22', '阿里云厂商', NULL, '0', NULL, 0); +INSERT INTO `chat_provider` VALUES (1, 'OpenAI', 'openai', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/01091be272334383a1efd9bc22b73ee6.png', 'OpenAI官方API服务商', 'https://api.openai.com', '0', 1, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:46:59', 'OpenAI厂商', NULL, '0', NULL, 0); +INSERT INTO `chat_provider` VALUES (2, '阿里云百炼', 'qianwen', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', '阿里云百炼大模型服务', 'https://dashscope.aliyuncs.com', '0', 2, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:49:13', '阿里云厂商', NULL, '0', NULL, 0); INSERT INTO `chat_provider` VALUES (3, '智谱AI', 'zhipu', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/a43e98fb7b3b4861b8caa6184e6fa40a.png', '智谱AI大模型服务', 'https://open.bigmodel.cn', '0', 3, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-06 00:49:14', '智谱AI厂商', NULL, '1', NULL, 0); -INSERT INTO `chat_provider` VALUES (5, 'ollama', 'ollama', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/2ff984bc9e4249df992733b31959056b.png', 'ollama大模型', 'http://127.0.0.1:11434', '0', 5, NULL, '2025-12-14 21:48:11', '1', '1', '2025-12-15 00:49:05', 'ollama厂商', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (2000585060904435714, 'PPIO', 'ppio', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/c4f8e304ce7740029b0024934d4625bc.png', 'api聚合厂商', 'https://api.ppinfra.com/openai', '0', 5, 103, '2025-12-15 23:13:42', '1', '1', '2026-01-02 00:54:45', 'api聚合厂商', NULL, '0', NULL, 0); +INSERT INTO `chat_provider` VALUES (5, 'ollama', 'ollama', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', 'ollama大模型', 'http://127.0.0.1:11434', '0', 5, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:48:48', 'ollama厂商', NULL, '0', NULL, 0); +INSERT INTO `chat_provider` VALUES (2000585060904435714, 'PPIO', 'ppio', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', 'api聚合厂商', 'https://api.ppinfra.com/openai', '0', 5, 103, '2025-12-15 23:13:42', '1', '1', '2026-02-25 20:49:01', 'api聚合厂商', NULL, '0', NULL, 0); -- ---------------------------- -- Table structure for chat_session @@ -1448,6 +1412,90 @@ CREATE TABLE `knowledge_info` ( -- Records of knowledge_info -- ---------------------------- +-- ---------------------------- +-- Table structure for mcp_market_info +-- ---------------------------- +DROP TABLE IF EXISTS `mcp_market_info`; +CREATE TABLE `mcp_market_info` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '市场ID', + `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '市场名称', + `url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '市场URL', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '市场描述', + `auth_config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '认证配置(JSON格式)', + `status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'ENABLED' COMMENT '状态:ENABLED-启用, DISABLED-禁用', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '000000' COMMENT '租户编号', + `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_name`(`name` ASC) USING BTREE, + INDEX `idx_status`(`status` ASC) USING BTREE, + INDEX `idx_tenant_id`(`tenant_id` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'MCP市场表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of mcp_market_info +-- ---------------------------- + +-- ---------------------------- +-- Table structure for mcp_market_tool +-- ---------------------------- +DROP TABLE IF EXISTS `mcp_market_tool`; +CREATE TABLE `mcp_market_tool` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID', + `market_id` bigint NOT NULL COMMENT '市场ID', + `tool_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '工具名称', + `tool_description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '工具描述', + `tool_version` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '工具版本', + `tool_metadata` json NULL COMMENT '工具元数据(JSON格式)', + `is_loaded` tinyint(1) NULL DEFAULT 0 COMMENT '是否已加载到本地', + `local_tool_id` bigint NULL DEFAULT NULL COMMENT '关联的本地工具ID', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_market_id`(`market_id` ASC) USING BTREE, + INDEX `idx_tool_name`(`tool_name` ASC) USING BTREE, + INDEX `idx_is_loaded`(`is_loaded` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'MCP市场工具关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of mcp_market_tool +-- ---------------------------- + +-- ---------------------------- +-- Table structure for mcp_tool_info +-- ---------------------------- +DROP TABLE IF EXISTS `mcp_tool_info`; +CREATE TABLE `mcp_tool_info` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '工具ID', + `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '工具名称', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '工具描述', + `type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'LOCAL' COMMENT '工具类型:LOCAL-本地, REMOTE-远程, BUILTIN-内置', + `status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'ENABLED' COMMENT '状态:ENABLED-启用, DISABLED-禁用', + `config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '配置信息(JSON格式)', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '000000' COMMENT '租户编号', + `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_name`(`name` ASC) USING BTREE, + INDEX `idx_type`(`type` ASC) USING BTREE, + INDEX `idx_status`(`status` ASC) USING BTREE, + INDEX `idx_tenant_id`(`tenant_id` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'MCP工具表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of mcp_tool_info +-- ---------------------------- +INSERT INTO `mcp_tool_info` VALUES (1, 'edit_file', 'Edits a file by applying a diff. Use this tool when you need to make specific changes to a file. The tool will show the diff before applying changes. Use absolute paths within the workspace directory.', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-02-24 20:19:41', -1, '2026-02-24 20:19:41', '0'); +INSERT INTO `mcp_tool_info` VALUES (2, 'list_directory', 'Lists files and directories in the specified path. Supports recursive listing and filtering. Shows file sizes, modification times, and types. Use absolute paths within the workspace directory.', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-02-24 20:19:41', -1, '2026-02-24 20:19:41', '0'); +INSERT INTO `mcp_tool_info` VALUES (3, 'read_file', 'Reads the contents of a file. Use absolute paths within the workspace directory. Returns the complete file content as a string.', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-02-24 20:19:41', -1, '2026-02-24 20:19:41', '0'); + -- ---------------------------- -- Table structure for sj_distributed_lock -- ---------------------------- @@ -1460,7 +1508,7 @@ CREATE TABLE `sj_distributed_lock` ( `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`name`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '锁定表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '锁定表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_distributed_lock @@ -1485,7 +1533,7 @@ CREATE TABLE `sj_group_config` ( `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `uk_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '组配置' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '组配置' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_group_config @@ -1531,7 +1579,7 @@ CREATE TABLE `sj_job` ( INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE, INDEX `idx_job_status_bucket_index`(`job_status` ASC, `bucket_index` ASC) USING BTREE, INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务信息' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务信息' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_job @@ -1553,7 +1601,7 @@ CREATE TABLE `sj_job_executor` ( PRIMARY KEY (`id`) USING BTREE, INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE, INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务执行器信息' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务执行器信息' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_job_executor @@ -1579,7 +1627,7 @@ CREATE TABLE `sj_job_log_message` ( INDEX `idx_task_batch_id_task_id`(`task_batch_id` ASC, `task_id` ASC) USING BTREE, INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE, INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '调度日志' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '调度日志' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_job_log_message @@ -1608,7 +1656,7 @@ CREATE TABLE `sj_job_summary` ( PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `uk_trigger_at_system_task_type_business_id`(`trigger_at` ASC, `system_task_type` ASC, `business_id` ASC) USING BTREE, INDEX `idx_namespace_id_group_name_business_id`(`namespace_id` ASC, `group_name` ASC, `business_id` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DashBoard_Job' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DashBoard_Job' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_job_summary @@ -1642,7 +1690,7 @@ CREATE TABLE `sj_job_task` ( INDEX `idx_task_batch_id_task_status`(`task_batch_id` ASC, `task_status` ASC) USING BTREE, INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE, INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务实例' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务实例' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_job_task @@ -1674,7 +1722,7 @@ CREATE TABLE `sj_job_task_batch` ( INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE, INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE, INDEX `idx_workflow_task_batch_id_workflow_node_id`(`workflow_task_batch_id` ASC, `workflow_node_id` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务批次' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务批次' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_job_task_batch @@ -1695,7 +1743,7 @@ CREATE TABLE `sj_namespace` ( PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `uk_unique_id`(`unique_id` ASC) USING BTREE, INDEX `idx_name`(`name` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '命名空间' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '命名空间' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_namespace @@ -1724,7 +1772,7 @@ CREATE TABLE `sj_notify_config` ( `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_namespace_id_group_name_scene_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '通知配置' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '通知配置' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_notify_config @@ -1745,7 +1793,7 @@ CREATE TABLE `sj_notify_recipient` ( `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_namespace_id`(`namespace_id` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '告警通知接收人' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '告警通知接收人' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_notify_recipient @@ -1784,7 +1832,7 @@ CREATE TABLE `sj_retry` ( INDEX `idx_retry_status_bucket_index`(`retry_status` ASC, `bucket_index` ASC) USING BTREE, INDEX `idx_parent_id`(`parent_id` ASC) USING BTREE, INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '重试信息表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '重试信息表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_retry @@ -1813,7 +1861,7 @@ CREATE TABLE `sj_retry_dead_letter` ( INDEX `idx_idempotent_id`(`idempotent_id` ASC) USING BTREE, INDEX `idx_biz_no`(`biz_no` ASC) USING BTREE, INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '死信队列表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '死信队列表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_retry_dead_letter @@ -1848,7 +1896,7 @@ CREATE TABLE `sj_retry_scene_config` ( `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `uk_namespace_id_group_name_scene_name`(`namespace_id` ASC, `group_name` ASC, `scene_name` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '场景配置' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '场景配置' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_retry_scene_config @@ -1873,7 +1921,7 @@ CREATE TABLE `sj_retry_summary` ( PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `uk_scene_name_trigger_at`(`namespace_id` ASC, `group_name` ASC, `scene_name` ASC, `trigger_at` ASC) USING BTREE, INDEX `idx_trigger_at`(`trigger_at` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DashBoard_Retry' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DashBoard_Retry' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_retry_summary @@ -1901,7 +1949,7 @@ CREATE TABLE `sj_retry_task` ( INDEX `task_status`(`task_status` ASC) USING BTREE, INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE, INDEX `idx_retry_id`(`retry_id` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '重试任务表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '重试任务表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_retry_task @@ -1924,7 +1972,7 @@ CREATE TABLE `sj_retry_task_log_message` ( PRIMARY KEY (`id`) USING BTREE, INDEX `idx_namespace_id_group_name_retry_task_id`(`namespace_id` ASC, `group_name` ASC, `retry_task_id` ASC) USING BTREE, INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务调度日志信息记录表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务调度日志信息记录表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_retry_task_log_message @@ -1951,7 +1999,7 @@ CREATE TABLE `sj_server_node` ( UNIQUE INDEX `uk_host_id_host_ip`(`host_id` ASC, `host_ip` ASC) USING BTREE, INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE, INDEX `idx_expire_at_node_type`(`expire_at` ASC, `node_type` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '服务器节点' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '服务器节点' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_server_node @@ -1970,7 +2018,7 @@ CREATE TABLE `sj_system_user` ( `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `uk_username`(`username` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统用户表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统用户表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_system_user @@ -1990,7 +2038,7 @@ CREATE TABLE `sj_system_user_permission` ( `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `uk_namespace_id_group_name_system_user_id`(`namespace_id` ASC, `group_name` ASC, `system_user_id` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统用户权限表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统用户权限表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_system_user_permission @@ -2025,7 +2073,7 @@ CREATE TABLE `sj_workflow` ( PRIMARY KEY (`id`) USING BTREE, INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE, INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_workflow @@ -2056,7 +2104,7 @@ CREATE TABLE `sj_workflow_node` ( PRIMARY KEY (`id`) USING BTREE, INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE, INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流节点' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流节点' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_workflow_node @@ -2085,7 +2133,7 @@ CREATE TABLE `sj_workflow_task_batch` ( INDEX `idx_job_id_task_batch_status`(`workflow_id` ASC, `task_batch_status` ASC) USING BTREE, INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE, INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流批次' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流批次' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sj_workflow_task_batch @@ -2288,6 +2336,11 @@ INSERT INTO `sys_dict_data` VALUES (2018858143757504522, '154726', 0, 'PC', 'pc' INSERT INTO `sys_dict_data` VALUES (2018858143761698817, '154726', 0, '安卓', 'android', 'sys_device_type', '', 'default', 'N', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '安卓'); INSERT INTO `sys_dict_data` VALUES (2018858143761698818, '154726', 0, 'iOS', 'ios', 'sys_device_type', '', 'default', 'N', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', 'iOS'); INSERT INTO `sys_dict_data` VALUES (2018858143761698819, '154726', 0, '小程序', 'xcx', 'sys_device_type', '', 'default', 'N', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '小程序'); +INSERT INTO `sys_dict_data` VALUES (2026642472673288194, '000000', 0, '对话', 'chat', 'chat_model_category', NULL, 'cyan', 'N', 103, 1, '2026-02-25 20:56:33', 1, '2026-02-25 21:01:42', NULL); +INSERT INTO `sys_dict_data` VALUES (2026642525081116674, '000000', 1, '图像', 'image', 'chat_model_category', NULL, 'success', 'N', 103, 1, '2026-02-25 20:56:46', 1, '2026-02-25 21:01:37', NULL); +INSERT INTO `sys_dict_data` VALUES (2026643983713247233, '000000', 1, '次数计费', '1', 'sys_model_billing', NULL, 'green', 'N', 103, 1, '2026-02-25 21:02:34', 1, '2026-02-25 21:02:56', NULL); +INSERT INTO `sys_dict_data` VALUES (2026644058522853378, '000000', 2, 'token计费', '2', 'sys_model_billing', NULL, 'primary', 'N', 103, 1, '2026-02-25 21:02:51', 1, '2026-02-25 21:02:51', NULL); +INSERT INTO `sys_dict_data` VALUES (2027261114955931650, '000000', 2, '向量', 'vector', 'chat_model_category', NULL, 'default', 'N', 103, 1, '2026-02-27 13:54:49', 1, '2026-02-27 13:54:54', NULL); -- ---------------------------- -- Table structure for sys_dict_type @@ -2331,6 +2384,8 @@ INSERT INTO `sys_dict_type` VALUES (2018858143723950085, '154726', '操作类型 INSERT INTO `sys_dict_type` VALUES (2018858143723950086, '154726', '系统状态', 'sys_common_status', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '登录状态列表'); INSERT INTO `sys_dict_type` VALUES (2018858143723950087, '154726', '授权类型', 'sys_grant_type', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '认证授权类型'); INSERT INTO `sys_dict_type` VALUES (2018858143723950088, '154726', '设备类型', 'sys_device_type', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '客户端设备类型'); +INSERT INTO `sys_dict_type` VALUES (2026642112982360066, '000000', '模型分类', 'chat_model_category', 103, 1, '2026-02-25 20:55:08', 1, '2026-02-25 20:55:08', '模型分类'); +INSERT INTO `sys_dict_type` VALUES (2026642183606050817, '000000', '计费方式', 'sys_model_billing', 103, 1, '2026-02-25 20:55:24', 1, '2026-02-25 20:55:24', '计费方式'); -- ---------------------------- -- Table structure for sys_logininfor @@ -2569,6 +2624,15 @@ INSERT INTO `sys_logininfor` VALUES (2019224998575153153, '000000', 'admin', 'pc INSERT INTO `sys_logininfor` VALUES (2019225059417726977, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-05 09:42:24'); INSERT INTO `sys_logininfor` VALUES (2019240817392693249, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-05 10:45:01'); INSERT INTO `sys_logininfor` VALUES (2019447979716972545, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-06 00:28:13'); +INSERT INTO `sys_logininfor` VALUES (2026536636865163265, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 13:56:00'); +INSERT INTO `sys_logininfor` VALUES (2026556949535502337, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 15:16:43'); +INSERT INTO `sys_logininfor` VALUES (2026578433112911874, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 16:42:05'); +INSERT INTO `sys_logininfor` VALUES (2026638437400518657, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 20:40:31'); +INSERT INTO `sys_logininfor` VALUES (2026647463072952321, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 21:16:23'); +INSERT INTO `sys_logininfor` VALUES (2026653919016968194, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '退出成功', '2026-02-25 21:42:02'); +INSERT INTO `sys_logininfor` VALUES (2026654082020204546, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 21:42:41'); +INSERT INTO `sys_logininfor` VALUES (2026654455514587138, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 21:44:10'); +INSERT INTO `sys_logininfor` VALUES (2027260957187186689, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-27 13:54:12'); -- ---------------------------- -- Table structure for sys_menu @@ -2604,7 +2668,7 @@ CREATE TABLE `sys_menu` ( INSERT INTO `sys_menu` VALUES (1, '系统管理', 0, 3, 'system', '', '', 1, 0, 'M', '0', '0', '', 'eos-icons:system-group', 103, 1, '2025-12-14 16:11:49', 1, '2026-01-01 19:06:19', '系统管理目录'); INSERT INTO `sys_menu` VALUES (2, '系统监控', 0, 3, 'monitor', '', '', 1, 0, 'M', '0', '0', '', 'solar:monitor-camera-outline', 103, 1, '2025-12-14 16:11:49', 1, '2025-12-14 17:56:44', '系统监控目录'); INSERT INTO `sys_menu` VALUES (3, '系统工具', 0, 4, 'tool', NULL, '', 1, 0, 'M', '0', '0', '', 'ant-design:tool-outlined', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '系统工具目录'); -INSERT INTO `sys_menu` VALUES (6, '租户管理', 0, 2, 'tenant', NULL, '', 1, 0, 'M', '0', '0', '', 'ph:users-light', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '租户管理目录'); +INSERT INTO `sys_menu` VALUES (6, '租户管理', 0, 8, 'tenant', '', '', 1, 0, 'M', '0', '0', '', 'ph:users-light', 103, 1, '2025-12-14 16:11:49', 1, '2026-02-25 20:42:14', '租户管理目录'); INSERT INTO `sys_menu` VALUES (100, '用户管理', 1, 1, 'user', 'system/user/index', '', 1, 0, 'C', '0', '0', 'system:user:list', 'ant-design:user-outlined', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '用户管理菜单'); INSERT INTO `sys_menu` VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', '', 1, 0, 'C', '0', '0', 'system:role:list', 'eos-icons:role-binding-outlined', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '角色管理菜单'); INSERT INTO `sys_menu` VALUES (102, '菜单管理', 1, 3, 'menu', 'system/menu/index', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'ic:sharp-menu', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '菜单管理菜单'); @@ -2708,6 +2772,22 @@ INSERT INTO `sys_menu` VALUES (1620, '配置列表', 118, 5, '#', '', '', 1, 0, INSERT INTO `sys_menu` VALUES (1621, '配置添加', 118, 6, '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:add', '#', 103, 1, '2025-12-14 16:11:49', NULL, NULL, ''); INSERT INTO `sys_menu` VALUES (1622, '配置编辑', 118, 6, '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:edit', '#', 103, 1, '2025-12-14 16:11:49', NULL, NULL, ''); INSERT INTO `sys_menu` VALUES (1623, '配置删除', 118, 6, '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:remove', '#', 103, 1, '2025-12-14 16:11:49', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (2000, 'MCP管理', 0, 2, 'mcp', '', '', 1, 0, 'M', '0', '0', '', 'mdi:robot-industrial', 103, 1, '2026-02-24 20:02:47', 1, '2026-02-25 20:41:54', 'MCP模块管理菜单'); +INSERT INTO `sys_menu` VALUES (2001, 'MCP工具管理', 2000, 1, 'tool', 'mcp/tool/index', '', 1, 0, 'C', '0', '0', 'mcp:tool:list', 'octicon:tools-24', 103, 1, '2026-02-24 20:02:47', 1, '2026-02-25 20:41:27', 'MCP工具管理菜单'); +INSERT INTO `sys_menu` VALUES (2002, 'MCP工具查询', 2001, 1, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:query', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (2003, 'MCP工具新增', 2001, 2, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:add', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (2004, 'MCP工具修改', 2001, 3, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:edit', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (2005, 'MCP工具删除', 2001, 4, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:remove', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (2006, 'MCP工具测试', 2001, 5, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:test', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (2007, 'MCP工具导出', 2001, 6, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:export', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (2010, 'MCP市场管理', 2000, 2, 'market', 'mcp/market/index', '', 1, 0, 'C', '0', '0', 'mcp:market:list', 'mdi:storefront-outline', 103, 1, '2026-02-24 20:02:47', NULL, NULL, 'MCP市场管理菜单'); +INSERT INTO `sys_menu` VALUES (2011, 'MCP市场查询', 2010, 1, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:query', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (2012, 'MCP市场新增', 2010, 2, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:add', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (2013, 'MCP市场修改', 2010, 3, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:edit', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (2014, 'MCP市场删除', 2010, 4, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:remove', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (2015, 'MCP市场刷新', 2010, 5, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:refresh', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (2016, 'MCP工具加载', 2010, 6, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:load', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (2017, 'MCP市场导出', 2010, 7, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:export', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); INSERT INTO `sys_menu` VALUES (11616, '工作流', 0, 6, 'workflow', '', '', 1, 0, 'M', '0', '0', '', 'mdi:workflow-outline', 103, 1, '2026-01-05 14:39:33', 1, '2026-01-05 14:56:07', ''); INSERT INTO `sys_menu` VALUES (11618, '我的任务', 0, 7, 'task', '', '', 1, 0, 'M', '0', '0', '', 'carbon:task-approved', 103, 1, '2026-01-05 14:39:33', NULL, NULL, ''); INSERT INTO `sys_menu` VALUES (11619, '我的待办', 11618, 2, 'taskWaiting', 'workflow/task/taskWaiting', '', 1, 1, 'C', '0', '0', '', 'ri:todo-line', 103, 1, '2026-01-05 14:39:33', NULL, NULL, ''); @@ -2732,13 +2812,6 @@ INSERT INTO `sys_menu` VALUES (11803, '流程达式定义新增', 11801, 2, '#', INSERT INTO `sys_menu` VALUES (11804, '流程达式定义修改', 11801, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, '2026-01-05 14:39:33', NULL, NULL, ''); INSERT INTO `sys_menu` VALUES (11805, '流程达式定义删除', 11801, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, '2026-01-05 14:39:33', NULL, NULL, ''); INSERT INTO `sys_menu` VALUES (11806, '流程达式定义导出', 11801, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, '2026-01-05 14:39:33', NULL, NULL, ''); -INSERT INTO `sys_menu` VALUES (1971546066781597696, '数字人体验', 2019459914910994434, 10, 'aihumanPublish', 'aihuman/aihumanPublish/index', NULL, 1, 0, 'C', '0', '0', '', 'mdi:human-child', 103, 1, '2026-02-06 01:13:22', 1, '2026-02-06 01:29:34', '数字人信息管理菜单'); -INSERT INTO `sys_menu` VALUES (1971546066781597697, '数字人信息管理查询', 1971546066781597696, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:query', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, ''); -INSERT INTO `sys_menu` VALUES (1971546066781597698, '数字人信息管理新增', 1971546066781597696, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:add', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, ''); -INSERT INTO `sys_menu` VALUES (1971546066781597699, '数字人信息管理修改', 1971546066781597696, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:edit', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, ''); -INSERT INTO `sys_menu` VALUES (1971546066781597700, '数字人信息管理删除', 1971546066781597696, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:remove', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, ''); -INSERT INTO `sys_menu` VALUES (1971546066781597701, '数字人信息管理导出', 1971546066781597696, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:export', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, ''); -INSERT INTO `sys_menu` VALUES (1980480880138051584, '数字人配置', 2019459914910994434, 1, 'aihumanConfig', 'aihuman/aihumanConfig/index', NULL, 1, 0, 'C', '0', '0', 'aihuman:aihumanConfig:list', 'mdi:human-child', 103, 1, '2026-02-06 01:13:35', 1, '2026-02-06 01:28:12', ''); INSERT INTO `sys_menu` VALUES (2000209300188356609, '对话管理', 0, 0, 'chat', '', NULL, 1, 0, 'M', '0', '0', NULL, 'material-symbols:chat-outline', 103, 1, '2025-12-14 22:20:34', 1, '2025-12-14 22:21:24', ''); INSERT INTO `sys_menu` VALUES (2000210913451892738, '厂商管理', 2000209300188356609, 1, 'provider', 'chat/provider/index', NULL, 1, 0, 'C', '0', '0', 'system:provider:list', 'tabler:cube-spark', 103, 1, '2025-12-14 22:28:05', 1, '2025-12-14 23:42:55', '厂商管理菜单'); INSERT INTO `sys_menu` VALUES (2000210913451892739, '厂商管理查询', 2000210913451892738, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:provider:query', '#', 103, 1, '2025-12-14 22:28:05', NULL, NULL, ''); @@ -2771,7 +2844,6 @@ INSERT INTO `sys_menu` VALUES (2006681261898813444, '知识库修改', 200668126 INSERT INTO `sys_menu` VALUES (2006681261898813445, '知识库删除', 2006681261898813441, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:info:remove', '#', 103, 1, '2026-01-01 18:59:06', NULL, NULL, ''); INSERT INTO `sys_menu` VALUES (2006681261898813446, '知识库导出', 2006681261898813441, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:info:export', '#', 103, 1, '2026-01-01 18:59:06', NULL, NULL, ''); INSERT INTO `sys_menu` VALUES (2006683336984580098, '知识管理', 0, 2, 'knowledge', '', NULL, 1, 0, 'M', '0', '0', NULL, 'bx:book', 103, 1, '2026-01-01 19:06:05', 1, '2026-01-01 19:06:05', ''); -INSERT INTO `sys_menu` VALUES (2019459914910994434, '数字人管理', 0, 2, 'human', '', NULL, 1, 0, 'M', '0', '0', NULL, 'tdesign:user', 103, 1, '2026-02-06 01:15:38', 1, '2026-02-06 01:16:58', ''); INSERT INTO `sys_menu` VALUES (2019464280262905857, '图谱实例', 2019464531388469250, 1, 'graphInstance', 'graph/graphInstance/index', NULL, 1, 0, 'C', '0', '0', 'operator:graph:list', 'ant-design:node-index-outlined', 103, 1, '2026-02-06 01:32:59', 1, '2026-02-06 01:40:06', ''); INSERT INTO `sys_menu` VALUES (2019464531388469250, '知识图谱', 2006683336984580098, 15, 'graph', '', NULL, 1, 0, 'M', '0', '0', NULL, 'carbon:chart-relationship', 103, 1, '2026-02-06 01:33:59', 1, '2026-02-06 01:33:59', ''); INSERT INTO `sys_menu` VALUES (2019464779217309697, '图谱可视化', 2019464531388469250, 2, 'graphVisualization', 'graph/graphVisualization/index', NULL, 1, 0, 'C', '0', '0', 'operator:graph:view', 'carbon:chart-network', 103, 1, '2026-02-06 01:34:58', 1, '2026-02-06 01:40:14', ''); @@ -2860,6 +2932,12 @@ CREATE TABLE `sys_oss` ( -- ---------------------------- -- Records of sys_oss -- ---------------------------- +INSERT INTO `sys_oss` VALUES (2026580908423340033, '000000', '2026/02/25/9219d6d71a6d45e19192014609d92dc9.png', 'logo.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/9219d6d71a6d45e19192014609d92dc9.png', '{\"fileSize\":\"183613\",\"contentType\":\"image/png\"}', 103, '2026-02-25 16:51:55', 1, '2026-02-25 16:51:55', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2026640059920883713, '000000', '2026/02/25/01091be272334383a1efd9bc22b73ee6.png', 'openai.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/01091be272334383a1efd9bc22b73ee6.png', '{\"fileSize\":\"11297\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:46:58', 1, '2026-02-25 20:46:58', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2026640515967557633, '000000', '2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', 'ollama.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', '{\"fileSize\":\"8746\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:48:47', 1, '2026-02-25 20:48:47', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2026640548213366785, '000000', '2026/02/25/e16429a462e54e14a1d36673146b9e3c.png', 'ppio-color.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/e16429a462e54e14a1d36673146b9e3c.png', '{\"fileSize\":\"7382\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:48:55', 1, '2026-02-25 20:48:55', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2026640572443860993, '000000', '2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', 'ppio-color.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', '{\"fileSize\":\"7382\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:49:00', 1, '2026-02-25 20:49:00', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2026640621945036802, '000000', '2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', 'bailian-color.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', '{\"fileSize\":\"5901\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:49:12', 1, '2026-02-25 20:49:12', 1, 'qcloud'); -- ---------------------------- -- Table structure for sys_oss_config @@ -2892,10 +2970,10 @@ CREATE TABLE `sys_oss_config` ( -- ---------------------------- -- Records of sys_oss_config -- ---------------------------- -INSERT INTO `sys_oss_config` VALUES (1, '000000', 'minio', 'ruoyi', 'ruoyi123', 'ruoyi', '', '127.0.0.1:9000', '', 'N', '', '1', '0', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-03 05:14:52', NULL); +INSERT INTO `sys_oss_config` VALUES (1, '000000', 'minio', 'ruoyi', 'ruoyi123', 'ruoyi', '', '127.0.0.1:9000', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-25 15:44:13', NULL); INSERT INTO `sys_oss_config` VALUES (2, '000000', 'qiniu', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 's3-cn-north-1.qiniucs.com', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-03 05:14:52', NULL); INSERT INTO `sys_oss_config` VALUES (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 'oss-cn-beijing.aliyuncs.com', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-03 05:14:52', NULL); -INSERT INTO `sys_oss_config` VALUES (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi-1240000000', '', 'cos.ap-beijing.myqcloud.com', '', 'N', 'ap-beijing', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-03 05:14:52', NULL); +INSERT INTO `sys_oss_config` VALUES (4, '000000', 'qcloud', 'xx', 'xx', 'ruoyiai-1254149996', '', 'cos.ap-guangzhou.myqcloud.com', '', 'Y', 'ap-guangzhou', '1', '0', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-25 16:51:41', ''); INSERT INTO `sys_oss_config` VALUES (5, '000000', 'image', 'ruoyi', 'ruoyi123', 'ruoyi', 'image', '127.0.0.1:9000', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:53', 1, '2026-02-03 05:14:53', NULL); -- ---------------------------- @@ -3342,7 +3420,7 @@ CREATE TABLE `sys_user` ( -- ---------------------------- -- Records of sys_user -- ---------------------------- -INSERT INTO `sys_user` VALUES (1, '000000', 103, 'admin', 'admin', 'sys_user', 'ageerle@163.com', '15888888888', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2026-02-06 00:28:13', 103, 1, '2026-02-05 09:22:12', -1, '2026-02-06 00:28:13', '管理员', NULL, 0.00); +INSERT INTO `sys_user` VALUES (1, '000000', 103, 'admin', 'admin', 'sys_user', 'ageerle@163.com', '15888888888', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2026-02-27 13:54:12', 103, 1, '2026-02-05 09:22:12', -1, '2026-02-27 13:54:12', '管理员', NULL, 0.00); INSERT INTO `sys_user` VALUES (3, '000000', 108, 'test', '本部门及以下 密码666666', 'sys_user', '', '', '0', NULL, '$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', '0', '0', '127.0.0.1', '2026-02-05 09:22:12', 103, 1, '2026-02-05 09:22:12', 3, '2026-02-05 09:22:12', NULL, NULL, 0.00); INSERT INTO `sys_user` VALUES (4, '000000', 102, 'test1', '仅本人 密码666666', 'sys_user', '', '', '0', NULL, '$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', '0', '0', '127.0.0.1', '2026-02-05 09:22:12', 103, 1, '2026-02-05 09:22:12', 4, '2026-02-05 09:22:12', NULL, NULL, 0.00); @@ -3421,7 +3499,7 @@ CREATE TABLE `t_workflow_component` ( `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '000000' COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_display_order`(`display_order` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流组件库 | Workflow Component' ROW_FORMAT = DYNAMIC; +) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流组件库 | Workflow Component' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of t_workflow_component @@ -3429,6 +3507,9 @@ CREATE TABLE `t_workflow_component` ( INSERT INTO `t_workflow_component` VALUES (17, '5cd68dccbbb411f0bb7840c2ba9a7fbc', 'Start', '开始', '流程由此开始', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); INSERT INTO `t_workflow_component` VALUES (18, '5cd6ac69bbb411f0bb7840c2ba9a7fbc', 'End', '结束', '流程由此结束', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); INSERT INTO `t_workflow_component` VALUES (19, '5cd6c8eabbb411f0bb7840c2ba9a7fbc', 'Answer', '生成回答', '调用大语言模型回答问题', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000'); +INSERT INTO `t_workflow_component` VALUES (25, '0b4369bb60dc46d6bd84ceb4e36184dc', 'KeywordExtractor', '关键词提取', '从文本中提取关键词', 0, 1, '2025-12-26 16:30:05', '2025-12-26 16:30:05', 0, '000000'); +INSERT INTO `t_workflow_component` VALUES (26, 'bb00fc2f52c74fec82ee3f99725b56bb', 'Switcher', '条件分支', '根据条件执行不同分支', 0, 1, '2025-12-26 16:30:46', '2025-12-26 16:30:46', 0, '000000'); +INSERT INTO `t_workflow_component` VALUES (36, 'f37dbcb8f0d5464d90fbb22774490a56', 'HumanFeedback', '人类', '人机沟通', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000'); -- ---------------------------- -- Table structure for t_workflow_edge diff --git a/pom.xml b/pom.xml index 4fda3ba8..372c2770 100644 --- a/pom.xml +++ b/pom.xml @@ -389,13 +389,6 @@ ${revision} - - - org.ruoyi - ruoyi-aihuman - ${revision} - - org.ruoyi diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 28ce987a..4675462c 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -104,12 +104,6 @@ ruoyi-wechat - - - org.ruoyi - ruoyi-aihuman - - org.ruoyi diff --git a/ruoyi-modules/pom.xml b/ruoyi-modules/pom.xml index 17b9a97e..5db99df1 100644 --- a/ruoyi-modules/pom.xml +++ b/ruoyi-modules/pom.xml @@ -10,7 +10,6 @@ 4.0.0 - ruoyi-aihuman ruoyi-aiflow ruoyi-chat ruoyi-demo diff --git a/ruoyi-modules/ruoyi-aihuman/pom.xml b/ruoyi-modules/ruoyi-aihuman/pom.xml deleted file mode 100644 index baaf8619..00000000 --- a/ruoyi-modules/ruoyi-aihuman/pom.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - 4.0.0 - - org.ruoyi - ruoyi-modules - ${revision} - ../pom.xml - - - ruoyi-aihuman - - - aihuman模块 - - - - 3.2.1 - 5.13.0 - 1.5.5 - - - - - - - org.ruoyi - ruoyi-common-core - - - - org.ruoyi - ruoyi-common-doc - - - - org.ruoyi - ruoyi-common-mybatis - - - - org.ruoyi - ruoyi-common-web - - - - org.ruoyi - ruoyi-common-log - - - - org.ruoyi - ruoyi-common-idempotent - - - - - org.apache.velocity - velocity-engine-core - - - - - - - - - - - - - - - - org.ruoyi - ruoyi-common-excel - - - - net.java.dev.jna - jna - ${jna.version} - - - - net.java.dev.jna - jna-platform - ${jna.version} - - - - org.java-websocket - Java-WebSocket - ${java-websocket.version} - - - - - diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/config/WebConfig.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/config/WebConfig.java deleted file mode 100644 index 5ccaf134..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/config/WebConfig.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.ruoyi.aihuman.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class WebConfig implements WebMvcConfigurer { - @Override - public void addResourceHandlers(ResourceHandlerRegistry registry) { - // 映射/voice/**路径到classpath:/voice/目录 - registry.addResourceHandler("/voice/**") - .addResourceLocations("classpath:/voice/") - .setCachePeriod(3600); - } -} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanConfigController.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanConfigController.java deleted file mode 100644 index c4397df8..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanConfigController.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.ruoyi.aihuman.controller; - -import cn.dev33.satoken.annotation.SaCheckPermission; -import cn.dev33.satoken.annotation.SaIgnore; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import org.ruoyi.aihuman.domain.bo.AihumanConfigBo; -import org.ruoyi.aihuman.domain.vo.AihumanConfigVo; -import org.ruoyi.aihuman.service.AihumanConfigService; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.core.validate.AddGroup; -import org.ruoyi.common.core.validate.EditGroup; -import org.ruoyi.common.excel.utils.ExcelUtil; -import org.ruoyi.common.idempotent.annotation.RepeatSubmit; -import org.ruoyi.common.log.annotation.Log; -import org.ruoyi.common.log.enums.BusinessType; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.ruoyi.common.web.core.BaseController; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -/** - * 交互数字人配置 - * - * @author ageerle - * @date Fri Sep 26 22:27:00 GMT+08:00 2025 - */ - -//临时免登录 -@SaIgnore - -@Validated -@RequiredArgsConstructor -@RestController -@RequestMapping("/aihuman/aihumanConfig") -public class AihumanConfigController extends BaseController { - - private final AihumanConfigService aihumanConfigService; - - /** - * 查询交互数字人配置列表 - */ - @SaCheckPermission("aihuman:aihumanConfig:list") - @GetMapping("/list") - public TableDataInfo list(AihumanConfigBo bo, PageQuery pageQuery) { - return aihumanConfigService.queryPageList(bo, pageQuery); - } - - /** - * 导出交互数字人配置列表 - */ - @SaCheckPermission("aihuman:aihumanConfig:export") - @Log(title = "交互数字人配置", businessType = BusinessType.EXPORT) - @PostMapping("/export") - public void export(AihumanConfigBo bo, HttpServletResponse response) { - List list = aihumanConfigService.queryList(bo); - ExcelUtil.exportExcel(list, "交互数字人配置", AihumanConfigVo.class, response); - } - - /** - * 获取交互数字人配置详细信息 - * - * @param id 主键 - */ - @SaCheckPermission("aihuman:aihumanConfig:query") - @GetMapping("/{id}") - public R getInfo(@NotNull(message = "主键不能为空") - @PathVariable Integer id) { - return R.ok(aihumanConfigService.queryById(id)); - } - - /** - * 新增交互数字人配置 - */ - @SaCheckPermission("aihuman:aihumanConfig:add") - @Log(title = "交互数字人配置", businessType = BusinessType.INSERT) - @RepeatSubmit() - @PostMapping() - public R add(@Validated(AddGroup.class) @RequestBody AihumanConfigBo bo) { - return toAjax(aihumanConfigService.insertByBo(bo)); - } - - /** - * 修改交互数字人配置 - */ - @SaCheckPermission("aihuman:aihumanConfig:edit") - @Log(title = "交互数字人配置", businessType = BusinessType.UPDATE) - @RepeatSubmit() - @PutMapping() - public R edit(@Validated(EditGroup.class) @RequestBody AihumanConfigBo bo) { - return toAjax(aihumanConfigService.updateByBo(bo)); - } - - /** - * 删除交互数字人配置 - * - * @param ids 主键串 - */ - @SaCheckPermission("aihuman:aihumanConfig:remove") - @Log(title = "交互数字人配置", businessType = BusinessType.DELETE) - @DeleteMapping("/{ids}") - public R remove(@NotEmpty(message = "主键不能为空") - @PathVariable Integer[] ids) { - return toAjax(aihumanConfigService.deleteWithValidByIds(List.of(ids), true)); - } - - /** - * 查询已发布的交互数字人配置列表 - * 只返回 publish = 1 的数据 - */ - @GetMapping("/publishedList") - public TableDataInfo publishedList(PageQuery pageQuery) { - // 创建查询条件对象并设置publish=1 - AihumanConfigBo bo = new AihumanConfigBo(); - bo.setPublish(1); - // 调用现有的查询方法,传入预设了publish=1条件的bo对象 - return aihumanConfigService.queryPageList(bo, pageQuery); - } - - -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanInfoController.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanInfoController.java deleted file mode 100644 index 2f0e9628..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanInfoController.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.ruoyi.aihuman.controller; - -import cn.dev33.satoken.annotation.SaIgnore; -import lombok.RequiredArgsConstructor; -import org.ruoyi.aihuman.domain.AihumanInfo; -import org.ruoyi.aihuman.domain.vo.AihumanInfoVo; -import org.ruoyi.aihuman.service.IAihumanInfoService; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.Arrays; - -/** - * AI人类交互信息Controller - * 免登录接口,方便验证 - * - * @author QingYunAI - */ -@SaIgnore -@Validated -@RequiredArgsConstructor -@RestController -@RequestMapping("/aihuman/info") -public class AihumanInfoController { - - private final IAihumanInfoService aihumanInfoService; - - /** - * 获取AI人类交互信息详情 - */ - @GetMapping("/{id}") - public R getInfo(@PathVariable Long id) { - return R.ok(aihumanInfoService.queryById(id)); - } - - /** - * 查询AI人类交互信息列表 - */ - @GetMapping("/list") - public R> list(AihumanInfo aihumanInfo, PageQuery pageQuery) { - TableDataInfo tableDataInfo = aihumanInfoService.queryPageList(aihumanInfo, pageQuery); - return R.ok(tableDataInfo); - } - - /** - * 新增AI人类交互信息 - */ - @PostMapping - public R add(@Validated @RequestBody AihumanInfo aihumanInfo) { - return R.ok(aihumanInfoService.insert(aihumanInfo)); - } - - /** - * 修改AI人类交互信息 - */ - @PutMapping - public R edit(@Validated @RequestBody AihumanInfo aihumanInfo) { - return R.ok(aihumanInfoService.update(aihumanInfo)); - } - - /** - * 删除AI人类交互信息 - */ - @DeleteMapping("/{ids}") - public R remove(@PathVariable Long[] ids) { - return R.ok(aihumanInfoService.deleteWithValidByIds(Arrays.asList(ids), true)); - } - - /** - * 测试接口 - * 提供一个简单的GET接口用于快速验证控制器是否正常工作 - */ - @GetMapping("/test") - public R test() { - return R.ok("AI Human Controller is working!"); - } -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanRealConfigController.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanRealConfigController.java deleted file mode 100644 index 46e47198..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanRealConfigController.java +++ /dev/null @@ -1,157 +0,0 @@ -package org.ruoyi.aihuman.controller; - -import cn.dev33.satoken.annotation.SaCheckPermission; -import cn.dev33.satoken.annotation.SaIgnore; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import org.ruoyi.aihuman.domain.bo.AihumanRealConfigBo; -import org.ruoyi.aihuman.domain.vo.AihumanRealConfigVo; -import org.ruoyi.aihuman.service.AihumanRealConfigService; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.core.validate.AddGroup; -import org.ruoyi.common.core.validate.EditGroup; -import org.ruoyi.common.excel.utils.ExcelUtil; -import org.ruoyi.common.idempotent.annotation.RepeatSubmit; -import org.ruoyi.common.log.annotation.Log; -import org.ruoyi.common.log.enums.BusinessType; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.ruoyi.common.web.core.BaseController; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -/** - * 真人交互数字人配置 - * - * @author ageerle - * @date Tue Oct 21 11:46:52 GMT+08:00 2025 - */ -//临时免登录 -@SaIgnore - -@Validated -@RequiredArgsConstructor -@RestController -@RequestMapping("/aihuman/aihumanRealConfig") -public class AihumanRealConfigController extends BaseController { - - private final AihumanRealConfigService aihumanRealConfigService; - - /** - * 查询真人交互数字人配置列表 - */ - @SaCheckPermission("aihuman:aihumanRealConfig:list") - @GetMapping("/list") - public TableDataInfo list(AihumanRealConfigBo bo, PageQuery pageQuery) { - return aihumanRealConfigService.queryPageList(bo, pageQuery); - } - - /** - * 导出真人交互数字人配置列表 - */ - @SaCheckPermission("aihuman:aihumanRealConfig:export") - @Log(title = "真人交互数字人配置", businessType = BusinessType.EXPORT) - @PostMapping("/export") - public void export(AihumanRealConfigBo bo, HttpServletResponse response) { - List list = aihumanRealConfigService.queryList(bo); - ExcelUtil.exportExcel(list, "真人交互数字人配置", AihumanRealConfigVo.class, response); - } - - /** - * 获取真人交互数字人配置详细信息 - * - * @param id 主键 - */ - @SaCheckPermission("aihuman:aihumanRealConfig:query") - @GetMapping("/{id}") - public R getInfo(@NotNull(message = "主键不能为空") - @PathVariable Integer id) { - return R.ok(aihumanRealConfigService.queryById(id)); - } - - /** - * 新增真人交互数字人配置 - */ - @SaCheckPermission("aihuman:aihumanRealConfig:add") - @Log(title = "真人交互数字人配置", businessType = BusinessType.INSERT) - @RepeatSubmit() - @PostMapping() - public R add(@Validated(AddGroup.class) @RequestBody AihumanRealConfigBo bo) { - return toAjax(aihumanRealConfigService.insertByBo(bo)); - } - - /** - * 修改真人交互数字人配置 - */ - @SaCheckPermission("aihuman:aihumanRealConfig:edit") - @Log(title = "真人交互数字人配置", businessType = BusinessType.UPDATE) - @RepeatSubmit() - @PutMapping() - public R edit(@Validated(EditGroup.class) @RequestBody AihumanRealConfigBo bo) { - return toAjax(aihumanRealConfigService.updateByBo(bo)); - } - - /** - * 删除真人交互数字人配置 - * - * @param ids 主键串 - */ - @SaCheckPermission("aihuman:aihumanRealConfig:remove") - @Log(title = "真人交互数字人配置", businessType = BusinessType.DELETE) - @DeleteMapping("/{ids}") - public R remove(@NotEmpty(message = "主键不能为空") - @PathVariable Integer[] ids) { - return toAjax(aihumanRealConfigService.deleteWithValidByIds(List.of(ids), true)); - } - - /** - * 1.执行以下命令: - * cd F:\Projects\AI-Human\LiveTalking - * conda activate D:\zg117\C\Users\zg117\.conda\envs\livetalking_new - * python app.py --transport webrtc --model wav2lip --avatar_id wav2lip256_avatar1 - *

- * 2.监听 python app.py --transport webrtc --model wav2lip --avatar_id wav2lip256_avatar1 执行情况 - *

- * 3.返回执行结果并打开页面 - * http://127.0.0.1:8010/webrtcapi-diy.html - */ - @SaCheckPermission("aihuman:aihumanRealConfig:run") - //@Log(title = "真人交互数字人配置", businessType = BusinessType.UPDATE, operatorType = OperatorType.OTHER) - @RepeatSubmit() - @PutMapping("/run") - public R run(@Validated(EditGroup.class) @RequestBody AihumanRealConfigBo bo) { - boolean result = aihumanRealConfigService.runByBo(bo); - if (result) { - // 返回前端页面URL,前端可以根据这个URL跳转或打开新页面 - // http://127.0.0.1:8010/webrtcapi-diy.html 其中的 http://127.0.0.1 获取当前java服务的IP地址 - // return R.ok("http://127.0.0.1:8010/webrtcapi-diy.html"); - // 运行状态 - bo.setRunStatus("1"); - return R.ok("http://127.0.0.1:8010/webrtcapi-diy.html"); - } else { - return R.fail("启动真人交互数字人失败"); - } - } - - /** - * 停止真人交互数字人配置任务 - */ - @SaCheckPermission("aihuman:aihumanRealConfig:stop") - //@Log(title = "真人交互数字人配置", businessType = BusinessType.UPDATE, operatorType = OperatorType.OTHER) - @RepeatSubmit() - @PutMapping("/stop") - public R stop(@Validated(EditGroup.class) @RequestBody AihumanRealConfigBo bo) { - boolean result = aihumanRealConfigService.stopByBo(bo); - if (result) { - // 运行状态 - bo.setRunStatus("0"); - return R.ok("真人交互数字人任务已停止"); - } else { - return R.fail("停止真人交互数字人任务失败或没有正在运行的任务"); - } - } -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanVolcengineController.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanVolcengineController.java deleted file mode 100644 index e9b3e99a..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/controller/AihumanVolcengineController.java +++ /dev/null @@ -1,502 +0,0 @@ -package org.ruoyi.aihuman.controller; - -import cn.dev33.satoken.annotation.SaIgnore; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.aihuman.domain.VoiceRequest; -import org.ruoyi.aihuman.protocol.EventType; -import org.ruoyi.aihuman.protocol.Message; -import org.ruoyi.aihuman.protocol.MsgType; -import org.ruoyi.aihuman.protocol.SpeechWebSocketClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.ResourceLoader; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -/** - * 火山引擎相关接口 - * - * @author ruoyi - */ -// 临时免登录 -@SaIgnore - -@Validated -@RequiredArgsConstructor -@Slf4j -@RestController -@RequestMapping("/aihuman/volcengine") -public class AihumanVolcengineController { - - private static final ObjectMapper objectMapper = new ObjectMapper(); - private static final Logger logger = LoggerFactory.getLogger(AihumanVolcengineController.class); - @Autowired - private ResourceLoader resourceLoader; - - @PostMapping("/generate-voice-direct") - public ResponseEntity generateVoiceDirect(@RequestBody VoiceRequest request) { - try { - // 生成唯一的语音ID - String voiceId = UUID.randomUUID().toString().replace("-", ""); - - log.info("开始生成语音,voiceId: {}", voiceId); - - // 调用火山引擎TTS API获取音频数据 - byte[] audioData = generateVoiceData(request, voiceId); - - // 设置响应头,返回音频数据 - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.parseMediaType("audio/wav")); - headers.setContentDispositionFormData("attachment", "voice_" + System.currentTimeMillis() + ".wav"); - headers.setContentLength(audioData.length); - - log.info("语音生成成功并返回,长度: {} bytes", audioData.length); - return new ResponseEntity<>(audioData, headers, HttpStatus.OK); - } catch (Exception e) { - log.error("生成语音失败", e); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } - } - - private byte[] generateVoiceData(VoiceRequest request, String voiceId) { - try { - // 这里是调用火山引擎TTS API的核心逻辑 - // 您需要根据火山引擎的API文档实现具体的调用逻辑 - // 注意:这只是一个示例框架,您需要根据实际情况进行实现 - - // 调用火山引擎API并获取音频数据 - // 假设您已经有现有的调用逻辑,这里保留原有的实现 - String endpoint = request.getEndpoint(); - String appId = request.getAppId(); - String accessToken = request.getAccessToken(); - String resourceId = request.getResourceId(); - String voice = request.getVoice(); - String text = request.getText(); - String encoding = request.getEncoding(); - - // 调用原有的火山引擎API调用方法(如果有) - // 或者直接在这里实现API调用逻辑 - byte[] audioData = callVolcengineTtsApiByte(endpoint, appId, accessToken, - resourceId, voice, text, encoding); - - log.info("成功生成语音数据,大小: {} bytes", audioData.length); - return audioData; - } catch (Exception e) { - log.error("生成语音数据失败", e); - throw new RuntimeException("生成语音数据失败", e); - } - - } - - private byte[] callVolcengineTtsApiByte(String endpoint, String appId, String accessToken, - String resourceId, String voice, String text, String encoding) { - try { - // 确保resourceId不为空,如果为空则根据voice类型获取默认值 - if (resourceId == null || resourceId.isEmpty()) { - resourceId = voiceToResourceId(voice); - } - - // 设置请求头 - Map headers = new HashMap<>(); - headers.put("X-Api-App-Key", appId); - headers.put("X-Api-Access-Key", accessToken); - headers.put("X-Api-Resource-Id", resourceId); - headers.put("X-Api-Connect-Id", UUID.randomUUID().toString()); - - // 创建WebSocket客户端 - SpeechWebSocketClient client = new SpeechWebSocketClient(new URI(endpoint), headers); - ByteArrayOutputStream totalAudioStream = new ByteArrayOutputStream(); - boolean audioReceived = false; - - try { - // 连接WebSocket - client.connectBlocking(); - - // 构建请求参数 - Map request = new HashMap<>(); - request.put("user", Map.of("uid", UUID.randomUUID().toString())); - request.put("namespace", "BidirectionalTTS"); - - Map reqParams = new HashMap<>(); - reqParams.put("speaker", voice); - - Map audioParams = new HashMap<>(); - audioParams.put("format", encoding); - audioParams.put("sample_rate", 24000); - audioParams.put("enable_timestamp", true); - - reqParams.put("audio_params", audioParams); - reqParams.put("additions", objectMapper.writeValueAsString(Map.of("disable_markdown_filter", false))); - - request.put("req_params", reqParams); - - // 开始连接 - client.sendStartConnection(); - // 等待连接成功 - client.waitForMessage(MsgType.FULL_SERVER_RESPONSE, EventType.CONNECTION_STARTED); - - // 处理每个句子 - String[] sentences = text.split("。"); - for (int i = 0; i < sentences.length; i++) { - if (sentences[i].trim().isEmpty()) { - continue; - } - - String sessionId = UUID.randomUUID().toString(); - ByteArrayOutputStream sentenceAudioStream = new ByteArrayOutputStream(); - - // 开始会话 - Map startReq = new HashMap<>(); - startReq.put("user", request.get("user")); - startReq.put("namespace", request.get("namespace")); - startReq.put("req_params", request.get("req_params")); - startReq.put("event", EventType.START_SESSION.getValue()); - client.sendStartSession(objectMapper.writeValueAsBytes(startReq), sessionId); - // 等待会话开始 - client.waitForMessage(MsgType.FULL_SERVER_RESPONSE, EventType.SESSION_STARTED); - - // 发送文本内容 - for (char c : sentences[i].toCharArray()) { - @SuppressWarnings("unchecked") - Map currentReqParams = new HashMap<>((Map) request.get("req_params")); - currentReqParams.put("text", String.valueOf(c)); - - Map currentRequest = new HashMap<>(); - currentRequest.put("user", request.get("user")); - currentRequest.put("namespace", request.get("namespace")); - currentRequest.put("req_params", currentReqParams); - currentRequest.put("event", EventType.TASK_REQUEST.getValue()); - - client.sendTaskRequest(objectMapper.writeValueAsBytes(currentRequest), sessionId); - } - - // 结束会话 - client.sendFinishSession(sessionId); - - // 接收响应 - while (true) { - Message msg = client.receiveMessage(); - switch (msg.getType()) { - case FULL_SERVER_RESPONSE: - break; - case AUDIO_ONLY_SERVER: - if (!audioReceived && sentenceAudioStream.size() > 0) { - audioReceived = true; - } - if (msg.getPayload() != null) { - sentenceAudioStream.write(msg.getPayload()); - } - break; - default: - // 不抛出异常,记录日志并继续处理 - log.warn("Unexpected message type: {}", msg.getType()); - } - if (msg.getEvent() == EventType.SESSION_FINISHED) { - break; - } - } - - // 将当前句子的音频追加到总音频流 - if (sentenceAudioStream.size() > 0) { - totalAudioStream.write(sentenceAudioStream.toByteArray()); - } - } - - // 验证是否收到音频数据 - if (totalAudioStream.size() > 0) { - log.info("Audio data generated successfully, size: {} bytes", totalAudioStream.size()); - return totalAudioStream.toByteArray(); - } else { - throw new RuntimeException("No audio data received"); - } - } finally { - // 结束连接 - client.sendFinishConnection(); - client.closeBlocking(); - } - } catch (Exception e) { - log.error("Error calling Volcengine TTS API: {}", e.getMessage(), e); - throw new RuntimeException("Failed to generate voice", e); - } - } - - - /** - * 生成语音文件接口 - * 用户传入JSON参数,返回音频文件的播放地址 - */ - @PostMapping("/generate-voice") - public ResponseEntity generateVoice(@RequestBody VoiceRequest request) { - try { - // 1. 解析请求参数 - String endpoint = request.getEndpoint(); - String appId = request.getAppId(); - String accessToken = request.getAccessToken(); - String resourceId = request.getResourceId(); - String voice = request.getVoice(); - String text = request.getText(); - String encoding = request.getEncoding(); - - // 1.1 验证必要参数 - if (endpoint == null || endpoint.isEmpty()) { - return ResponseEntity.badRequest().body(Map.of("error", "endpoint cannot be null or empty")); - } - if (appId == null || appId.isEmpty()) { - return ResponseEntity.badRequest().body(Map.of("error", "appId cannot be null or empty")); - } - if (accessToken == null || accessToken.isEmpty()) { - return ResponseEntity.badRequest().body(Map.of("error", "accessToken cannot be null or empty")); - } - if (text == null || text.isEmpty()) { - return ResponseEntity.badRequest().body(Map.of("error", "text cannot be null or empty")); - } - - // 1.2 设置默认值 - if (encoding == null || encoding.isEmpty()) { - encoding = "mp3"; - } - - // 2. 调用火山引擎API生成音频文件 - String audioUrl = callVolcengineTtsApi(endpoint, appId, accessToken, resourceId, voice, text, encoding); - - // 3. 构造并返回响应 - Map response = new HashMap<>(); - response.put("audioUrl", audioUrl); - - return ResponseEntity.ok(response); - } catch (Exception e) { - // 处理异常情况 - Map errorResponse = new HashMap<>(); - errorResponse.put("error", "生成音频文件失败: " + e.getMessage()); - return ResponseEntity.status(500).body(errorResponse); - } - } - - /** - * 调用火山引擎TTS API生成音频文件 - */ - private String callVolcengineTtsApi(String endpoint, String appId, String accessToken, - String resourceId, String voice, String text, String encoding) { - try { - // 确保resourceId不为空,如果为空则根据voice类型获取默认值 - if (resourceId == null || resourceId.isEmpty()) { - resourceId = voiceToResourceId(voice); - } - - // 生成唯一的文件名 - String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); - String randomId = UUID.randomUUID().toString().substring(0, 8); - String fileName = "voice_" + timestamp + "_" + randomId + "." + encoding; - - // 获取resources/voice目录路径 - String voiceDirPath = getVoiceDirectoryPath(); - File voiceDir = new File(voiceDirPath); - if (!voiceDir.exists()) { - voiceDir.mkdirs(); - } - - String filePath = voiceDirPath + File.separator + fileName; - - // 设置请求头 - Map headers = new HashMap<>(); - headers.put("X-Api-App-Key", appId); - headers.put("X-Api-Access-Key", accessToken); - headers.put("X-Api-Resource-Id", resourceId); - headers.put("X-Api-Connect-Id", UUID.randomUUID().toString()); - - // 创建WebSocket客户端 - SpeechWebSocketClient client = new SpeechWebSocketClient(new URI(endpoint), headers); - ByteArrayOutputStream totalAudioStream = new ByteArrayOutputStream(); - boolean audioReceived = false; - - try { - // 连接WebSocket - client.connectBlocking(); - - // 构建请求参数 - Map request = new HashMap<>(); - request.put("user", Map.of("uid", UUID.randomUUID().toString())); - request.put("namespace", "BidirectionalTTS"); - - Map reqParams = new HashMap<>(); - reqParams.put("speaker", voice); - - Map audioParams = new HashMap<>(); - audioParams.put("format", encoding); - audioParams.put("sample_rate", 24000); - audioParams.put("enable_timestamp", true); - - reqParams.put("audio_params", audioParams); - reqParams.put("additions", objectMapper.writeValueAsString(Map.of("disable_markdown_filter", false))); - - request.put("req_params", reqParams); - - // 开始连接 - client.sendStartConnection(); - // 等待连接成功 - client.waitForMessage(MsgType.FULL_SERVER_RESPONSE, EventType.CONNECTION_STARTED); - - // 处理每个句子 - String[] sentences = text.split("。"); - for (int i = 0; i < sentences.length; i++) { - if (sentences[i].trim().isEmpty()) { - continue; - } - - String sessionId = UUID.randomUUID().toString(); - ByteArrayOutputStream sentenceAudioStream = new ByteArrayOutputStream(); - - // 开始会话 - Map startReq = new HashMap<>(); - startReq.put("user", request.get("user")); - startReq.put("namespace", request.get("namespace")); - startReq.put("req_params", request.get("req_params")); - startReq.put("event", EventType.START_SESSION.getValue()); - client.sendStartSession(objectMapper.writeValueAsBytes(startReq), sessionId); - // 等待会话开始 - client.waitForMessage(MsgType.FULL_SERVER_RESPONSE, EventType.SESSION_STARTED); - - // 发送文本内容 - for (char c : sentences[i].toCharArray()) { - @SuppressWarnings("unchecked") - Map currentReqParams = new HashMap<>((Map) request.get("req_params")); - currentReqParams.put("text", String.valueOf(c)); - - Map currentRequest = new HashMap<>(); - currentRequest.put("user", request.get("user")); - currentRequest.put("namespace", request.get("namespace")); - currentRequest.put("req_params", currentReqParams); - currentRequest.put("event", EventType.TASK_REQUEST.getValue()); - - client.sendTaskRequest(objectMapper.writeValueAsBytes(currentRequest), sessionId); - } - - // 结束会话 - client.sendFinishSession(sessionId); - - // 接收响应 - while (true) { - Message msg = client.receiveMessage(); - switch (msg.getType()) { - case FULL_SERVER_RESPONSE: - break; - case AUDIO_ONLY_SERVER: - if (!audioReceived && sentenceAudioStream.size() > 0) { - audioReceived = true; - } - if (msg.getPayload() != null) { - sentenceAudioStream.write(msg.getPayload()); - } - break; - default: - // 不抛出异常,记录日志并继续处理 - log.warn("Unexpected message type: {}", msg.getType()); - } - if (msg.getEvent() == EventType.SESSION_FINISHED) { - break; - } - } - - // 将当前句子的音频追加到总音频流 - if (sentenceAudioStream.size() > 0) { - totalAudioStream.write(sentenceAudioStream.toByteArray()); - } - } - - // 保存音频文件 - if (totalAudioStream.size() > 0) { - Files.write(Paths.get(filePath), totalAudioStream.toByteArray(), StandardOpenOption.CREATE); - log.info("Audio saved to file: {}", filePath); - } else { - throw new RuntimeException("No audio data received"); - } - - // 结束连接 - client.sendFinishConnection(); - } finally { - client.closeBlocking(); - } - - // 返回音频文件的访问路径 - return "/voice/" + fileName; - } catch (Exception e) { - log.error("Error calling Volcengine TTS API: {}", e.getMessage(), e); - throw new RuntimeException("Failed to generate voice", e); - } - } - - /** - * 根据voice类型获取resourceId - */ - private String voiceToResourceId(String voice) { - if (voice != null && voice.startsWith("S_")) { - return "volc.megatts.default"; - } - return "volc.service_type.10029"; - } - - /** - * 获取voice目录路径 - */ - private String getVoiceDirectoryPath() { - try { - // 获取当前项目根目录 - String projectRoot = System.getProperty("user.dir"); - - // 构建目标目录路径:ruoyi-ai/ruoyi-modules/ruoyi-aihuman/src/main/resources/voice - File targetDir = new File(projectRoot, "ruoyi-modules/ruoyi-aihuman/src/main/resources/voice"); - - // 确保目录存在 - if (!targetDir.exists()) { - boolean created = targetDir.mkdirs(); - if (created) { - logger.info("成功创建目录: {}", targetDir.getAbsolutePath()); - } else { - logger.warn("无法创建目录: {}", targetDir.getAbsolutePath()); - - // 降级方案:直接使用项目根目录下的voice文件夹 - File fallbackDir = new File(projectRoot, "voice"); - if (!fallbackDir.exists()) { - fallbackDir.mkdirs(); - } - return fallbackDir.getAbsolutePath(); - } - } - - return targetDir.getAbsolutePath(); - } catch (Exception e) { - logger.error("获取音频目录路径失败", e); - - // 异常情况下的安全降级 - File safeDir = new File("voice"); - if (!safeDir.exists()) { - safeDir.mkdirs(); - } - return safeDir.getAbsolutePath(); - } - } -} - - diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/AihumanConfig.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/AihumanConfig.java deleted file mode 100644 index 5e9fadbb..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/AihumanConfig.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.ruoyi.aihuman.domain; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -import java.io.Serializable; -import java.time.LocalDateTime; - -/** - * 交互数字人配置对象 aihuman_config - * - * @author ageerle - * @date Fri Sep 26 22:27:00 GMT+08:00 2025 - */ -@Data -@TableName("aihuman_config") -public class AihumanConfig implements Serializable { - - - /** - * id - */ - @TableId(value = "id", type = IdType.AUTO) - private Integer id; - - /** - * name - */ - private String name; - - /** - * modelName - */ - private String modelName; - - /** - * modelPath - */ - private String modelPath; - - /** - * modelParams - */ - private String modelParams; - - /** - * agentParams - */ - private String agentParams; - - /** - * createTime - */ - private LocalDateTime createTime; - - /** - * updateTime - */ - private LocalDateTime updateTime; - - /** - * status - */ - private Integer status; - - /** - * publish - */ - private Integer publish; - - -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/AihumanInfo.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/AihumanInfo.java deleted file mode 100644 index 9be681f2..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/AihumanInfo.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.ruoyi.aihuman.domain; - -import com.baomidou.mybatisplus.annotation.*; -import lombok.Data; - -import java.io.Serializable; -import java.util.Date; - -/** - * AI人类交互信息实体类 - * - * @author QingYunAI - */ -@Data -@TableName("aihuman_info") -public class AihumanInfo implements Serializable { - private static final long serialVersionUID = 1L; - - /** - * 主键ID - */ - @TableId(value = "id", type = IdType.AUTO) - private Long id; - - /** - * 交互名称 - */ - private String name; - - /** - * 交互内容 - */ - private String content; - - /** - * 创建时间 - */ - @TableField(fill = FieldFill.INSERT) - private Date createTime; - - /** - * 更新时间 - */ - @TableField(fill = FieldFill.INSERT_UPDATE) - private Date updateTime; - - /** - * 删除标志(0代表存在 2代表删除) - */ - @TableLogic - private String delFlag; -} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/AihumanRealConfig.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/AihumanRealConfig.java deleted file mode 100644 index c87f9bcd..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/AihumanRealConfig.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.ruoyi.aihuman.domain; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -import java.io.Serializable; -import java.time.LocalDateTime; - -/** - * 真人交互数字人配置对象 aihuman_real_config - * - * @author ageerle - * @date Tue Oct 21 11:46:52 GMT+08:00 2025 - */ -@Data -@TableName("aihuman_real_config") -public class AihumanRealConfig implements Serializable { - - - /** - * 主键id - */ - @TableId(value = "id", type = IdType.AUTO) - private Integer id; - - /** - * 场景名称 - */ - private String name; - - /** - * 真人形象名称 - */ - private String avatars; - - /** - * 模型名称 - */ - private String models; - - /** - * 形象参数(预留) - */ - private String avatarsParams; - - /** - * 模型参数(预留) - */ - private String modelsParams; - - /** - * 智能体参数(扣子) - */ - private String agentParams; - - /** - * 创建时间 - */ - private LocalDateTime createTime; - - /** - * 更新时间 - */ - private LocalDateTime updateTime; - - /** - * 状态 - */ - private Integer status; - - /** - * 发布状态 - */ - private Integer publish; - - /** - * 运行参数 - */ - private String runParams; - - /** - * 运行状态 - */ - private String runStatus; - - /** - * 创建部门 - */ - private String createDept; - - /** - * 创建用户 - */ - private String createBy; - - /** - * 更新用户 - */ - private String updateBy; - - -} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/VoiceRequest.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/VoiceRequest.java deleted file mode 100644 index 4ed0d017..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/VoiceRequest.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.ruoyi.aihuman.domain; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - - -/** - * 语音请求参数实体类 - */ -@Data -public class VoiceRequest { - - @JsonProperty("ENDPOINT") - private String endpoint; - private String appId; - private String accessToken; - private String resourceId; - private String voice; - private String text; - private String encoding; - -} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/bo/AihumanConfigBo.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/bo/AihumanConfigBo.java deleted file mode 100644 index ac9ffad0..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/bo/AihumanConfigBo.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.ruoyi.aihuman.domain.bo; - -import io.github.linpeilie.annotations.AutoMapper; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import org.ruoyi.aihuman.domain.AihumanConfig; -import org.ruoyi.common.core.validate.AddGroup; -import org.ruoyi.common.core.validate.EditGroup; - -import java.io.Serializable; -import java.time.LocalDateTime; - -/** - * 交互数字人配置业务对象 aihuman_config - * - * @author ageerle - * @date Fri Sep 26 22:27:00 GMT+08:00 2025 - */ -@Data - -@AutoMapper(target = AihumanConfig.class, reverseConvertGenerate = false) -public class AihumanConfigBo implements Serializable { - - private Integer id; - - /** - * name - */ - private String name; - /** - * modelName - */ - private String modelName; - /** - * modelPath - */ - private String modelPath; - /** - * modelParams - */ - private String modelParams; - /** - * agentParams - */ - private String agentParams; - /** - * createTime - */ - private LocalDateTime createTime; - /** - * updateTime - */ - private LocalDateTime updateTime; - /** - * status - */ - @NotNull(message = "status不能为空", groups = {AddGroup.class, EditGroup.class}) - private Integer status; - /** - * publish - */ - @NotNull(message = "publish不能为空", groups = {AddGroup.class, EditGroup.class}) - private Integer publish; - -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/bo/AihumanInfoBo.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/bo/AihumanInfoBo.java deleted file mode 100644 index c6e49989..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/bo/AihumanInfoBo.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.ruoyi.aihuman.domain.bo; - -import io.github.linpeilie.annotations.AutoMapper; -import lombok.Data; -import org.ruoyi.aihuman.domain.AihumanInfo; - -import java.io.Serializable; -import java.time.LocalDateTime; - -/** - * 数字人信息管理业务对象 aihuman_info - * - * @author ageerle - * @date Fri Sep 26 20:03:06 GMT+08:00 2025 - */ -@Data - -@AutoMapper(target = AihumanInfo.class, reverseConvertGenerate = false) -public class AihumanInfoBo implements Serializable { - - private Long id; - - /** - * 交互名称 - */ - private String name; - /** - * 交互内容 - */ - private String content; - /** - * 创建时间 - */ - private LocalDateTime createTime; - /** - * 更新时间 - */ - private LocalDateTime updateTime; - /** - * 删除标志(0代表存在 2代表删除) - */ - private String delFlag; - -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/bo/AihumanRealConfigBo.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/bo/AihumanRealConfigBo.java deleted file mode 100644 index 2008c97c..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/bo/AihumanRealConfigBo.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.ruoyi.aihuman.domain.bo; - -import io.github.linpeilie.annotations.AutoMapper; -import lombok.Data; -import org.ruoyi.aihuman.domain.AihumanRealConfig; - -import java.io.Serializable; -import java.time.LocalDateTime; - -/** - * 真人交互数字人配置业务对象 aihuman_real_config - * - * @author ageerle - * @date Tue Oct 21 11:46:52 GMT+08:00 2025 - */ -@Data - -@AutoMapper(target = AihumanRealConfig.class, reverseConvertGenerate = false) -public class AihumanRealConfigBo implements Serializable { - - private Integer id; - - /** - * 场景名称 - */ - private String name; - /** - * 真人形象名称 - */ - private String avatars; - /** - * 模型名称 - */ - private String models; - /** - * 形象参数(预留) - */ - private String avatarsParams; - /** - * 模型参数(预留) - */ - private String modelsParams; - /** - * 智能体参数(扣子) - */ - private String agentParams; - /** - * 创建时间 - */ - private LocalDateTime createTime; - /** - * 更新时间 - */ - private LocalDateTime updateTime; - /** - * 状态 - */ - private Integer status; - /** - * 发布状态 - */ - private Integer publish; - - /** - * 运行参数 - */ - private String runParams; - - /** - * 运行状态 - */ - private String runStatus; - - /** - * 创建部门 - */ - private String createDept; - /** - * 创建用户 - */ - private String createBy; - /** - * 更新用户 - */ - private String updateBy; - -} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/vo/AihumanConfigVo.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/vo/AihumanConfigVo.java deleted file mode 100644 index 93fffa76..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/vo/AihumanConfigVo.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.ruoyi.aihuman.domain.vo; - -import cn.idev.excel.annotation.ExcelIgnoreUnannotated; -import cn.idev.excel.annotation.ExcelProperty; -import io.github.linpeilie.annotations.AutoMapper; -import lombok.Data; -import org.ruoyi.aihuman.domain.AihumanConfig; -import org.ruoyi.common.excel.annotation.ExcelDictFormat; -import org.ruoyi.common.excel.convert.ExcelDictConvert; - -import java.io.Serializable; -import java.time.LocalDateTime; - - -/** - * 交互数字人配置视图对象 aihuman_config - * - * @author ageerle - * @date Fri Sep 26 22:27:00 GMT+08:00 2025 - */ -@Data -@ExcelIgnoreUnannotated -@AutoMapper(target = AihumanConfig.class) -public class AihumanConfigVo implements Serializable { - - private Integer id; - /** - * name - */ - @ExcelProperty(value = "name") - private String name; - /** - * modelName - */ - @ExcelProperty(value = "modelName") - private String modelName; - /** - * modelPath - */ - @ExcelProperty(value = "modelPath") - private String modelPath; - /** - * modelParams - */ - @ExcelProperty(value = "modelParams") - private String modelParams; - /** - * agentParams - */ - @ExcelProperty(value = "agentParams") - private String agentParams; - /** - * createTime - */ - @ExcelProperty(value = "createTime") - private LocalDateTime createTime; - /** - * updateTime - */ - @ExcelProperty(value = "updateTime") - private LocalDateTime updateTime; - /** - * status - */ - @ExcelProperty(value = "status", converter = ExcelDictConvert.class) - @ExcelDictFormat(dictType = "sys_common_status") - private Integer status; - /** - * publish - */ - @ExcelProperty(value = "publish", converter = ExcelDictConvert.class) - @ExcelDictFormat(dictType = "sys_common_status") - private Integer publish; - -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/vo/AihumanInfoVo.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/vo/AihumanInfoVo.java deleted file mode 100644 index a36d7fbd..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/vo/AihumanInfoVo.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.ruoyi.aihuman.domain.vo; - -import io.github.linpeilie.annotations.AutoMapper; -import lombok.Data; -import org.ruoyi.aihuman.domain.AihumanInfo; - -import java.io.Serializable; -import java.util.Date; - -/** - * AI人类交互信息视图对象 - * - * @author QingYunAI - */ -@Data -@AutoMapper(target = AihumanInfo.class) -public class AihumanInfoVo implements Serializable { - private static final long serialVersionUID = 1L; - - /** - * 主键ID - */ - private Long id; - - /** - * 交互名称 - */ - private String name; - - /** - * 交互内容 - */ - private String content; - - /** - * 创建时间 - */ - private Date createTime; - - /** - * 更新时间 - */ - private Date updateTime; -} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/vo/AihumanRealConfigVo.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/vo/AihumanRealConfigVo.java deleted file mode 100644 index f6fe020e..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/domain/vo/AihumanRealConfigVo.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.ruoyi.aihuman.domain.vo; - -import cn.idev.excel.annotation.ExcelIgnoreUnannotated; -import cn.idev.excel.annotation.ExcelProperty; -import io.github.linpeilie.annotations.AutoMapper; -import lombok.Data; -import org.ruoyi.aihuman.domain.AihumanRealConfig; -import org.ruoyi.common.excel.annotation.ExcelDictFormat; -import org.ruoyi.common.excel.convert.ExcelDictConvert; - -import java.io.Serializable; -import java.time.LocalDateTime; - - -/** - * 真人交互数字人配置视图对象 aihuman_real_config - * - * @author ageerle - * @date Tue Oct 21 11:46:52 GMT+08:00 2025 - */ -@Data -@ExcelIgnoreUnannotated -@AutoMapper(target = AihumanRealConfig.class) -public class AihumanRealConfigVo implements Serializable { - - private Integer id; - /** - * 场景名称 - */ - @ExcelProperty(value = "场景名称") - private String name; - /** - * 真人形象名称 - */ - @ExcelProperty(value = "真人形象名称") - private String avatars; - /** - * 模型名称 - */ - @ExcelProperty(value = "模型名称") - private String models; - /** - * 形象参数(预留) - */ - @ExcelProperty(value = "形象参数", converter = ExcelDictConvert.class) - @ExcelDictFormat(readConverterExp = "$column.readConverterExp()") - private String avatarsParams; - /** - * 模型参数(预留) - */ - @ExcelProperty(value = "模型参数", converter = ExcelDictConvert.class) - @ExcelDictFormat(readConverterExp = "$column.readConverterExp()") - private String modelsParams; - /** - * 智能体参数(扣子) - */ - @ExcelProperty(value = "智能体参数", converter = ExcelDictConvert.class) - @ExcelDictFormat(readConverterExp = "$column.readConverterExp()") - private String agentParams; - /** - * 创建时间 - */ - @ExcelProperty(value = "创建时间") - private LocalDateTime createTime; - /** - * 更新时间 - */ - @ExcelProperty(value = "更新时间") - private LocalDateTime updateTime; - /** - * 状态 - */ - @ExcelProperty(value = "状态") - private Integer status; - /** - * 发布状态 - */ - @ExcelProperty(value = "发布状态") - private Integer publish; - - /** - * 运行参数 - */ - @ExcelProperty(value = "运行参数") - private String runParams; - - /** - * 运行状态 - */ - @ExcelProperty(value = "运行状态") - private String runStatus; - - /** - * 创建部门 - */ - @ExcelProperty(value = "创建部门") - private String createDept; - /** - * 创建用户 - */ - @ExcelProperty(value = "创建用户") - private String createBy; - /** - * 更新用户 - */ - @ExcelProperty(value = "更新用户") - private String updateBy; - -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/mapper/AihumanConfigMapper.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/mapper/AihumanConfigMapper.java deleted file mode 100644 index 3a26ef0b..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/mapper/AihumanConfigMapper.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.ruoyi.aihuman.mapper; - -import org.apache.ibatis.annotations.Mapper; -import org.ruoyi.aihuman.domain.AihumanConfig; -import org.ruoyi.aihuman.domain.vo.AihumanConfigVo; -import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; - -/** - * 交互数字人配置Mapper接口 - * - * @author ageerle - * @date Fri Sep 26 22:27:00 GMT+08:00 2025 - */ -@Mapper -public interface AihumanConfigMapper extends BaseMapperPlus { - -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/mapper/AihumanInfoMapper.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/mapper/AihumanInfoMapper.java deleted file mode 100644 index a64d0b13..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/mapper/AihumanInfoMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.ruoyi.aihuman.mapper; - -import org.apache.ibatis.annotations.Mapper; -import org.ruoyi.aihuman.domain.AihumanInfo; -import org.ruoyi.aihuman.domain.vo.AihumanInfoVo; -import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; - -/** - * AI人类交互信息Mapper接口 - * - * @author QingYunAI - */ -@Mapper -public interface AihumanInfoMapper extends BaseMapperPlus { - -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/mapper/AihumanRealConfigMapper.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/mapper/AihumanRealConfigMapper.java deleted file mode 100644 index 9ae4768e..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/mapper/AihumanRealConfigMapper.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.ruoyi.aihuman.mapper; - -import org.apache.ibatis.annotations.Mapper; -import org.ruoyi.aihuman.domain.AihumanRealConfig; -import org.ruoyi.aihuman.domain.vo.AihumanRealConfigVo; -import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; - -/** - * 真人交互数字人配置Mapper接口 - * - * @author ageerle - * @date Tue Oct 21 11:46:52 GMT+08:00 2025 - */ -@Mapper -public interface AihumanRealConfigMapper extends BaseMapperPlus { - -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/CompressionBits.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/CompressionBits.java deleted file mode 100644 index 78794aa4..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/CompressionBits.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.ruoyi.aihuman.protocol; - -import lombok.Getter; - -@Getter -public enum CompressionBits { - None_((byte) 0), - Gzip((byte) 0b1), - Custom((byte) 0b11), - ; - - private final byte value; - - CompressionBits(byte b) { - this.value = b; - } - - public static CompressionBits fromValue(int value) { - for (CompressionBits type : CompressionBits.values()) { - if (type.value == value) { - return type; - } - } - throw new IllegalArgumentException("Unknown CompressionBits value: " + value); - } -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/EventType.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/EventType.java deleted file mode 100644 index ece69af3..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/EventType.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.ruoyi.aihuman.protocol; - -import lombok.Getter; - -@Getter -public enum EventType { - // Default event - NONE(0), - - // Upstream Connection events (1-49) - START_CONNECTION(1), - START_TASK(1), - FINISH_CONNECTION(2), - FINISH_TASK(2), - - // Downstream Connection events (50-99) - CONNECTION_STARTED(50), - TASK_STARTED(50), - CONNECTION_FAILED(51), - TASK_FAILED(51), - CONNECTION_FINISHED(52), - TASK_FINISHED(52), - - // Upstream Session events (100-149) - START_SESSION(100), - CANCEL_SESSION(101), - FINISH_SESSION(102), - - // Downstream Session events (150-199) - SESSION_STARTED(150), - SESSION_CANCELED(151), - SESSION_FINISHED(152), - SESSION_FAILED(153), - USAGE_RESPONSE(154), - CHARGE_DATA(154), - - // Upstream General events (200-249) - TASK_REQUEST(200), - UPDATE_CONFIG(201), - - // Downstream General events (250-299) - AUDIO_MUTED(250), - - // Upstream TTS events (300-349) - SAY_HELLO(300), - - // Downstream TTS events (350-399) - TTS_SENTENCE_START(350), - TTS_SENTENCE_END(351), - TTS_RESPONSE(352), - TTS_ENDED(359), - PODCAST_ROUND_START(360), - PODCAST_ROUND_RESPONSE(361), - PODCAST_ROUND_END(362), - - // Downstream ASR events (450-499) - ASR_INFO(450), - ASR_RESPONSE(451), - ASR_ENDED(459), - - // Upstream Chat events (500-549) - CHAT_TTS_TEXT(500), - - // Downstream Chat events (550-599) - CHAT_RESPONSE(550), - CHAT_ENDED(559), - - // Subtitle events (650-699) - SOURCE_SUBTITLE_START(650), - SOURCE_SUBTITLE_RESPONSE(651), - SOURCE_SUBTITLE_END(652), - TRANSLATION_SUBTITLE_START(653), - TRANSLATION_SUBTITLE_RESPONSE(654), - TRANSLATION_SUBTITLE_END(655); - - private final int value; - - EventType(int value) { - this.value = value; - } - - public static EventType fromValue(int value) { - for (EventType type : EventType.values()) { - if (type.value == value) { - return type; - } - } - throw new IllegalArgumentException("Unknown EventType value: " + value); - } -} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/HeaderSizeBits.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/HeaderSizeBits.java deleted file mode 100644 index 6c09a20d..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/HeaderSizeBits.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.ruoyi.aihuman.protocol; - -import lombok.Getter; - -@Getter -public enum HeaderSizeBits { - HeaderSize4((byte) 1), - HeaderSize8((byte) 2), - HeaderSize12((byte) 3), - HeaderSize16((byte) 4), - ; - - private final byte value; - - HeaderSizeBits(byte b) { - this.value = b; - } - - public static HeaderSizeBits fromValue(int value) { - for (HeaderSizeBits type : HeaderSizeBits.values()) { - if (type.value == value) { - return type; - } - } - throw new IllegalArgumentException("Unknown HeaderSizeBits value: " + value); - } -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/Message.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/Message.java deleted file mode 100644 index d3605edc..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/Message.java +++ /dev/null @@ -1,220 +0,0 @@ -package org.ruoyi.aihuman.protocol; - -import lombok.Data; -import lombok.extern.slf4j.Slf4j; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; - -@Slf4j -@Data -public class Message { - private byte version = VersionBits.Version1.getValue(); - private byte headerSize = HeaderSizeBits.HeaderSize4.getValue(); - private MsgType type; - private MsgTypeFlagBits flag; - private byte serialization = SerializationBits.JSON.getValue(); - private byte compression = 0; - - private EventType event; - private String sessionId; - private String connectId; - private int sequence; - private int errorCode; - - private byte[] payload; - - public Message(MsgType type, MsgTypeFlagBits flag) { - this.type = type; - this.flag = flag; - } - - public static Message unmarshal(byte[] data) throws Exception { - ByteBuffer buffer = ByteBuffer.wrap(data); - - byte type_and_flag = data[1]; - MsgType type = MsgType.fromValue((type_and_flag >> 4) & 0x0F); - MsgTypeFlagBits flag = MsgTypeFlagBits.fromValue(type_and_flag & 0x0F); - - // Read version and header size - int versionAndHeaderSize = buffer.get(); - VersionBits version = VersionBits.fromValue((versionAndHeaderSize >> 4) & 0x0F); - HeaderSizeBits headerSize = HeaderSizeBits.fromValue(versionAndHeaderSize & 0x0F); - - // Skip second byte - buffer.get(); - - // Read serialization and compression method - int serializationCompression = buffer.get(); - SerializationBits serialization = SerializationBits.fromValue((serializationCompression >> 4) & 0x0F); - CompressionBits compression = CompressionBits.fromValue(serializationCompression & 0x0F); - - // Skip padding bytes - int headerSizeInt = 4 * (int) headerSize.getValue(); - int paddingSize = headerSizeInt - 3; - while (paddingSize > 0) { - buffer.get(); - paddingSize -= 1; - } - - Message message = new Message(type, flag); - message.setVersion(version.getValue()); - message.setHeaderSize(headerSize.getValue()); - message.setSerialization(serialization.getValue()); - message.setCompression(compression.getValue()); - - // Read sequence if present - if (flag == MsgTypeFlagBits.POSITIVE_SEQ || flag == MsgTypeFlagBits.NEGATIVE_SEQ) { - // Read 4 bytes from ByteBuffer and parse as int (big-endian) - byte[] sequeueBytes = new byte[4]; - if (buffer.remaining() >= 4) { - buffer.get(sequeueBytes); // Read 4 bytes into array - ByteBuffer wrapper = ByteBuffer.wrap(sequeueBytes); - wrapper.order(ByteOrder.BIG_ENDIAN); // Set big-endian order - message.setSequence(wrapper.getInt()); - } - } - - // Read event if present - if (flag == MsgTypeFlagBits.WITH_EVENT) { - // Read 4 bytes from ByteBuffer and parse as int (big-endian) - byte[] eventBytes = new byte[4]; - if (buffer.remaining() >= 4) { - buffer.get(eventBytes); // Read 4 bytes into array - ByteBuffer wrapper = ByteBuffer.wrap(eventBytes); - wrapper.order(ByteOrder.BIG_ENDIAN); // Set big-endian order - message.setEvent(EventType.fromValue(wrapper.getInt())); - } - - if (type != MsgType.ERROR && !(message.event == EventType.START_CONNECTION - || message.event == EventType.FINISH_CONNECTION || - message.event == EventType.CONNECTION_STARTED - || message.event == EventType.CONNECTION_FAILED || - message.event == EventType.CONNECTION_FINISHED)) { - // Read sessionId if present - int sessionIdLength = buffer.getInt(); - if (sessionIdLength > 0) { - byte[] sessionIdBytes = new byte[sessionIdLength]; - buffer.get(sessionIdBytes); - message.setSessionId(new String(sessionIdBytes, StandardCharsets.UTF_8)); - } - } - - if (message.event == EventType.CONNECTION_STARTED || message.event == EventType.CONNECTION_FAILED - || message.event == EventType.CONNECTION_FINISHED) { - // Read connectId if present - int connectIdLength = buffer.getInt(); - if (connectIdLength > 0) { - byte[] connectIdBytes = new byte[connectIdLength]; - buffer.get(connectIdBytes); - message.setConnectId(new String(connectIdBytes, StandardCharsets.UTF_8)); - } - } - } - - // Read errorCode if present - if (type == MsgType.ERROR) { - // Read 4 bytes from ByteBuffer and parse as int (big-endian) - byte[] errorCodeBytes = new byte[4]; - if (buffer.remaining() >= 4) { - buffer.get(errorCodeBytes); // Read 4 bytes into array - ByteBuffer wrapper = ByteBuffer.wrap(errorCodeBytes); - wrapper.order(ByteOrder.BIG_ENDIAN); // Set big-endian order - message.setErrorCode(wrapper.getInt()); - } - } - - // Read remaining bytes as payload - if (buffer.remaining() > 0) { - // 4 bytes length - int payloadLength = buffer.getInt(); - if (payloadLength > 0) { - byte[] payloadBytes = new byte[payloadLength]; - buffer.get(payloadBytes); - message.setPayload(payloadBytes); - } - } - - return message; - } - - public byte[] marshal() throws Exception { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - - // Write header - buffer.write((version & 0x0F) << 4 | (headerSize & 0x0F)); - buffer.write((type.getValue() & 0x0F) << 4 | (flag.getValue() & 0x0F)); - buffer.write((serialization & 0x0F) << 4 | (compression & 0x0F)); - - int headerSizeInt = 4 * (int) headerSize; - int padding = headerSizeInt - buffer.size(); - while (padding > 0) { - buffer.write(0); - padding -= 1; - } - - // Write event if present - if (event != null) { - byte[] eventBytes = ByteBuffer.allocate(4).putInt(event.getValue()).array(); - buffer.write(eventBytes); - } - - // Write sessionId if present - if (sessionId != null) { - byte[] sessionIdBytes = sessionId.getBytes(StandardCharsets.UTF_8); - buffer.write(ByteBuffer.allocate(4).putInt(sessionIdBytes.length).array()); - buffer.write(sessionIdBytes); - } - - // Write connectId if present - if (connectId != null) { - byte[] connectIdBytes = connectId.getBytes(StandardCharsets.UTF_8); - buffer.write(ByteBuffer.allocate(4).putInt(connectIdBytes.length).array()); - buffer.write(connectIdBytes); - } - - // Write sequence if present - if (sequence != 0) { - buffer.write(ByteBuffer.allocate(4).putInt(sequence).array()); - } - - // Write errorCode if present - if (errorCode != 0) { - buffer.write(ByteBuffer.allocate(4).putInt(errorCode).array()); - } - - // Write payload if present - if (payload != null && payload.length > 0) { - buffer.write(ByteBuffer.allocate(4).putInt(payload.length).array()); - buffer.write(payload); - } - return buffer.toByteArray(); - } - - @Override - public String toString() { - switch (this.type) { - case AUDIO_ONLY_SERVER: - case AUDIO_ONLY_CLIENT: - if (this.flag == MsgTypeFlagBits.POSITIVE_SEQ || this.flag == MsgTypeFlagBits.NEGATIVE_SEQ) { - return String.format("MsgType: %s, EventType: %s, Sequence: %d, PayloadSize: %d", this.type, this.event, this.sequence, - this.payload != null ? this.payload.length : 0); - } - return String.format("MsgType: %s, EventType: %s, PayloadSize: %d", this.type, this.event, - this.payload != null ? this.payload.length : 0); - case ERROR: - return String.format("MsgType: %s, EventType: %s, ErrorCode: %d, Payload: %s", this.type, this.event, this.errorCode, - this.payload != null ? new String(this.payload) : "null"); - default: - if (this.flag == MsgTypeFlagBits.POSITIVE_SEQ || this.flag == MsgTypeFlagBits.NEGATIVE_SEQ) { - return String.format("MsgType: %s, EventType: %s, Sequence: %d, Payload: %s", - this.type, this.event, this.sequence, - this.payload != null ? new String(this.payload) : "null"); - } - return String.format("MsgType: %s, EventType: %s, Payload: %s", this.type, this.event, - this.payload != null ? new String(this.payload) : "null"); - } - } -} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/MsgType.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/MsgType.java deleted file mode 100644 index 965f40e8..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/MsgType.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.ruoyi.aihuman.protocol; - -import lombok.Getter; - -@Getter -public enum MsgType { - INVALID((byte) 0), - FULL_CLIENT_REQUEST((byte) 0b1), - AUDIO_ONLY_CLIENT((byte) 0b10), - FULL_SERVER_RESPONSE((byte) 0b1001), - AUDIO_ONLY_SERVER((byte) 0b1011), - FRONT_END_RESULT_SERVER((byte) 0b1100), - ERROR((byte) 0b1111); - - private final byte value; - - MsgType(byte value) { - this.value = value; - } - - public static MsgType fromValue(int value) { - for (MsgType type : MsgType.values()) { - if (type.value == value) { - return type; - } - } - throw new IllegalArgumentException("Unknown MsgType value: " + value); - } -} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/MsgTypeFlagBits.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/MsgTypeFlagBits.java deleted file mode 100644 index 393a24dc..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/MsgTypeFlagBits.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.ruoyi.aihuman.protocol; - -import lombok.Getter; - -@Getter -public enum MsgTypeFlagBits { - NO_SEQ((byte) 0), // Non-terminating packet without sequence number - POSITIVE_SEQ((byte) 0b1), // Non-terminating packet with positive sequence number - LAST_NO_SEQ((byte) 0b10), // Terminating packet without sequence number - NEGATIVE_SEQ((byte) 0b11), // Terminating packet with negative sequence number - WITH_EVENT((byte) 0b100); // Packet containing event number - - private final byte value; - - MsgTypeFlagBits(byte value) { - this.value = value; - } - - public static MsgTypeFlagBits fromValue(int value) { - for (MsgTypeFlagBits flag : MsgTypeFlagBits.values()) { - if (flag.value == value) { - return flag; - } - } - throw new IllegalArgumentException("Unknown MsgTypeFlagBits value: " + value); - } -} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/SerializationBits.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/SerializationBits.java deleted file mode 100644 index 01e8f102..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/SerializationBits.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.ruoyi.aihuman.protocol; - -import lombok.Getter; - -@Getter -public enum SerializationBits { - Raw((byte) 0), - JSON((byte) 0b1), - Thrift((byte) 0b11), - Custom((byte) 0b1111), - ; - - private final byte value; - - SerializationBits(byte b) { - this.value = b; - } - - public static SerializationBits fromValue(int value) { - for (SerializationBits type : SerializationBits.values()) { - if (type.value == value) { - return type; - } - } - throw new IllegalArgumentException("Unknown SerializationBits value: " + value); - } -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/SpeechWebSocketClient.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/SpeechWebSocketClient.java deleted file mode 100644 index c41e57b3..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/SpeechWebSocketClient.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.ruoyi.aihuman.protocol; - -import lombok.extern.slf4j.Slf4j; -import org.java_websocket.client.WebSocketClient; -import org.java_websocket.handshake.ServerHandshake; - -import java.net.URI; -import java.nio.ByteBuffer; -import java.util.Map; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -@Slf4j -public class SpeechWebSocketClient extends WebSocketClient { - private final BlockingQueue messageQueue = new LinkedBlockingQueue<>(); - - public SpeechWebSocketClient(URI serverUri, Map headers) { - super(serverUri, headers); - } - - @Override - public void onOpen(ServerHandshake handshakedata) { - log.info("WebSocket connection established, Logid: {}", handshakedata.getFieldValue("x-tt-logid")); - } - - @Override - public void onMessage(String message) { - log.warn("Received unexpected text message: {}", message); - } - - @Override - public void onMessage(ByteBuffer bytes) { - try { - Message message = Message.unmarshal(bytes.array()); - messageQueue.put(message); - } catch (Exception e) { - log.error("Failed to parse message", e); - } - } - - @Override - public void onClose(int code, String reason, boolean remote) { - log.info("WebSocket connection closed: code={}, reason={}, remote={}", code, reason, remote); - } - - @Override - public void onError(Exception ex) { - log.error("WebSocket error", ex); - } - - public void sendStartConnection() throws Exception { - Message message = new Message(MsgType.FULL_CLIENT_REQUEST, MsgTypeFlagBits.WITH_EVENT); - message.setEvent(EventType.START_CONNECTION); - message.setPayload("{}".getBytes()); - sendMessage(message); - } - - public void sendFinishConnection() throws Exception { - Message message = new Message(MsgType.FULL_CLIENT_REQUEST, MsgTypeFlagBits.WITH_EVENT); - message.setEvent(EventType.FINISH_CONNECTION); - sendMessage(message); - } - - public void sendStartSession(byte[] payload, String sessionId) throws Exception { - Message message = new Message(MsgType.FULL_CLIENT_REQUEST, MsgTypeFlagBits.WITH_EVENT); - message.setEvent(EventType.START_SESSION); - message.setSessionId(sessionId); - message.setPayload(payload); - sendMessage(message); - } - - public void sendFinishSession(String sessionId) throws Exception { - Message message = new Message(MsgType.FULL_CLIENT_REQUEST, MsgTypeFlagBits.WITH_EVENT); - message.setEvent(EventType.FINISH_SESSION); - message.setSessionId(sessionId); - message.setPayload("{}".getBytes()); - sendMessage(message); - } - - public void sendTaskRequest(byte[] payload, String sessionId) throws Exception { - Message message = new Message(MsgType.FULL_CLIENT_REQUEST, MsgTypeFlagBits.WITH_EVENT); - message.setEvent(EventType.TASK_REQUEST); - message.setSessionId(sessionId); - message.setPayload(payload); - sendMessage(message); - } - - public void sendFullClientMessage(byte[] payload) throws Exception { - Message message = new Message(MsgType.FULL_CLIENT_REQUEST, MsgTypeFlagBits.NO_SEQ); - message.setPayload(payload); - sendMessage(message); - } - - public void sendMessage(Message message) throws Exception { - log.info("Send: {}", message); - send(message.marshal()); - } - - public Message receiveMessage() throws InterruptedException { - Message message = messageQueue.take(); - log.info("Receive: {}", message); - return message; - } - - public Message waitForMessage(MsgType type, EventType event) throws InterruptedException { - while (true) { - Message message = receiveMessage(); - if (message.getType() == type && message.getEvent() == event) { - return message; - } else { - throw new RuntimeException("Unexpected message: " + message); - } - } - } -} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/VersionBits.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/VersionBits.java deleted file mode 100644 index 6f4a8174..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/protocol/VersionBits.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.ruoyi.aihuman.protocol; - -import lombok.Getter; - -@Getter -public enum VersionBits { - Version1((byte) 1), - Version2((byte) 2), - Version3((byte) 3), - Version4((byte) 4), - ; - - private final byte value; - - VersionBits(byte b) { - this.value = b; - } - - public static VersionBits fromValue(int value) { - for (VersionBits type : VersionBits.values()) { - if (type.value == value) { - return type; - } - } - throw new IllegalArgumentException("Unknown VersionBits value: " + value); - } -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/AihumanConfigService.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/AihumanConfigService.java deleted file mode 100644 index b70367cb..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/AihumanConfigService.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.ruoyi.aihuman.service; - -import org.ruoyi.aihuman.domain.bo.AihumanConfigBo; -import org.ruoyi.aihuman.domain.vo.AihumanConfigVo; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; - -import java.util.Collection; -import java.util.List; - -/** - * 交互数字人配置Service接口 - * - * @author ageerle - * @date Fri Sep 26 22:27:00 GMT+08:00 2025 - */ -public interface AihumanConfigService { - - /** - * 查询交互数字人配置 - */ - AihumanConfigVo queryById(Integer id); - - /** - * 查询交互数字人配置列表 - */ - TableDataInfo queryPageList(AihumanConfigBo bo, PageQuery pageQuery); - - /** - * 查询交互数字人配置列表 - */ - List queryList(AihumanConfigBo bo); - - /** - * 新增交互数字人配置 - */ - Boolean insertByBo(AihumanConfigBo bo); - - /** - * 修改交互数字人配置 - */ - Boolean updateByBo(AihumanConfigBo bo); - - /** - * 校验并批量删除交互数字人配置信息 - */ - Boolean deleteWithValidByIds(Collection ids, Boolean isValid); -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/AihumanRealConfigService.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/AihumanRealConfigService.java deleted file mode 100644 index 56398190..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/AihumanRealConfigService.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.ruoyi.aihuman.service; - -import org.ruoyi.aihuman.domain.bo.AihumanRealConfigBo; -import org.ruoyi.aihuman.domain.vo.AihumanRealConfigVo; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; - -import java.util.Collection; -import java.util.List; - -/** - * 真人交互数字人配置Service接口 - * - * @author ageerle - * @date Tue Oct 21 11:46:52 GMT+08:00 2025 - */ -public interface AihumanRealConfigService { - - /** - * 查询真人交互数字人配置 - */ - AihumanRealConfigVo queryById(Integer id); - - /** - * 查询真人交互数字人配置列表 - */ - TableDataInfo queryPageList(AihumanRealConfigBo bo, PageQuery pageQuery); - - /** - * 查询真人交互数字人配置列表 - */ - List queryList(AihumanRealConfigBo bo); - - /** - * 新增真人交互数字人配置 - */ - Boolean insertByBo(AihumanRealConfigBo bo); - - /** - * 修改真人交互数字人配置 - */ - Boolean updateByBo(AihumanRealConfigBo bo); - - /** - * 执行真人交互数字人配置 - */ - Boolean runByBo(AihumanRealConfigBo bo); - - /** - * 校验并批量删除真人交互数字人配置信息 - */ - Boolean deleteWithValidByIds(Collection ids, Boolean isValid); - - // 在AihumanRealConfigService接口中添加 - Boolean stopByBo(AihumanRealConfigBo bo); -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/AihumanVolcengineService.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/AihumanVolcengineService.java deleted file mode 100644 index 6c99f013..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/AihumanVolcengineService.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.ruoyi.aihuman.service; - -public interface AihumanVolcengineService { -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/IAihumanInfoService.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/IAihumanInfoService.java deleted file mode 100644 index a07001d6..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/IAihumanInfoService.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.ruoyi.aihuman.service; - -import org.ruoyi.aihuman.domain.AihumanInfo; -import org.ruoyi.aihuman.domain.vo.AihumanInfoVo; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; - -import java.util.Collection; -import java.util.List; - -/** - * AI人类交互信息Service接口 - * - * @author QingYunAI - */ -public interface IAihumanInfoService { - - /** - * 查询AI人类交互信息 - */ - AihumanInfoVo queryById(Long id); - - /** - * 查询AI人类交互信息列表 - */ - TableDataInfo queryPageList(AihumanInfo record, PageQuery pageQuery); - - /** - * 查询AI人类交互信息列表 - */ - List queryList(AihumanInfo record); - - /** - * 新增AI人类交互信息 - */ - int insert(AihumanInfo record); - - /** - * 修改AI人类交互信息 - */ - int update(AihumanInfo record); - - /** - * 批量删除AI人类交互信息 - */ - int deleteWithValidByIds(Collection ids, Boolean isValid); -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanConfigServiceImpl.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanConfigServiceImpl.java deleted file mode 100644 index 6031d692..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanConfigServiceImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.ruoyi.aihuman.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import lombok.RequiredArgsConstructor; -import org.ruoyi.aihuman.domain.AihumanConfig; -import org.ruoyi.aihuman.domain.bo.AihumanConfigBo; -import org.ruoyi.aihuman.domain.vo.AihumanConfigVo; -import org.ruoyi.aihuman.mapper.AihumanConfigMapper; -import org.ruoyi.aihuman.service.AihumanConfigService; -import org.ruoyi.common.core.utils.MapstructUtils; -import org.ruoyi.common.core.utils.StringUtils; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.springframework.stereotype.Service; - -import java.util.Collection; -import java.util.List; - -/** - * 交互数字人配置Service业务层处理 - * - * @author ageerle - * @date Fri Sep 26 22:27:00 GMT+08:00 2025 - */ -@RequiredArgsConstructor -@Service -public class AihumanConfigServiceImpl implements AihumanConfigService { - - private final AihumanConfigMapper baseMapper; - - /** - * 查询交互数字人配置 - */ - @Override - public AihumanConfigVo queryById(Integer id) { - return baseMapper.selectVoById(id); - } - - /** - * 查询交互数字人配置列表 - */ - @Override - public TableDataInfo queryPageList(AihumanConfigBo bo, PageQuery pageQuery) { - LambdaQueryWrapper lqw = buildQueryWrapper(bo); - Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); - return TableDataInfo.build(result); - } - - /** - * 查询交互数字人配置列表 - */ - @Override - public List queryList(AihumanConfigBo bo) { - LambdaQueryWrapper lqw = buildQueryWrapper(bo); - return baseMapper.selectVoList(lqw); - } - - private LambdaQueryWrapper buildQueryWrapper(AihumanConfigBo bo) { - LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.eq(StringUtils.isNotBlank(bo.getName()), AihumanConfig::getName, bo.getName()); - lqw.eq(StringUtils.isNotBlank(bo.getModelName()), AihumanConfig::getModelName, bo.getModelName()); - lqw.eq(StringUtils.isNotBlank(bo.getModelPath()), AihumanConfig::getModelPath, bo.getModelPath()); - lqw.eq(StringUtils.isNotBlank(bo.getModelParams()), AihumanConfig::getModelParams, bo.getModelParams()); - lqw.eq(StringUtils.isNotBlank(bo.getAgentParams()), AihumanConfig::getAgentParams, bo.getAgentParams()); - lqw.eq(bo.getCreateTime() != null, AihumanConfig::getCreateTime, bo.getCreateTime()); - lqw.eq(bo.getUpdateTime() != null, AihumanConfig::getUpdateTime, bo.getUpdateTime()); - lqw.eq(bo.getStatus() != null, AihumanConfig::getStatus, bo.getStatus()); - lqw.eq(bo.getPublish() != null, AihumanConfig::getPublish, bo.getPublish()); - return lqw; - } - - /** - * 新增交互数字人配置 - */ - @Override - public Boolean insertByBo(AihumanConfigBo bo) { - AihumanConfig add = MapstructUtils.convert(bo, AihumanConfig.class); - validEntityBeforeSave(add); - boolean flag = baseMapper.insert(add) > 0; - if (flag) { - bo.setId(add.getId()); - } - return flag; - } - - /** - * 修改交互数字人配置 - */ - @Override - public Boolean updateByBo(AihumanConfigBo bo) { - AihumanConfig update = MapstructUtils.convert(bo, AihumanConfig.class); - validEntityBeforeSave(update); - return baseMapper.updateById(update) > 0; - } - - /** - * 保存前的数据校验 - */ - private void validEntityBeforeSave(AihumanConfig entity) { - //TODO 做一些数据校验,如唯一约束 - } - - /** - * 批量删除交互数字人配置 - */ - @Override - public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { - if (isValid) { - //TODO 做一些业务上的校验,判断是否需要校验 - } - return baseMapper.deleteBatchIds(ids) > 0; - } -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanInfoServiceImpl.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanInfoServiceImpl.java deleted file mode 100644 index dd5e8a9c..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanInfoServiceImpl.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.ruoyi.aihuman.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import lombok.RequiredArgsConstructor; -import org.ruoyi.aihuman.domain.AihumanInfo; -import org.ruoyi.aihuman.domain.vo.AihumanInfoVo; -import org.ruoyi.aihuman.mapper.AihumanInfoMapper; -import org.ruoyi.aihuman.service.IAihumanInfoService; -import org.ruoyi.common.core.utils.StringUtils; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.springframework.stereotype.Service; - -import java.util.Collection; -import java.util.List; - -/** - * AI人类交互信息Service业务层处理 - * - * @author QingYunAI - */ -@RequiredArgsConstructor -@Service -public class AihumanInfoServiceImpl implements IAihumanInfoService { - - private final AihumanInfoMapper baseMapper; - - /** - * 查询AI人类交互信息 - */ - @Override - public AihumanInfoVo queryById(Long id) { - return baseMapper.selectVoById(id); - } - - /** - * 查询AI人类交互信息列表 - */ - @Override - public TableDataInfo queryPageList(AihumanInfo record, PageQuery pageQuery) { - LambdaQueryWrapper lqw = buildQueryWrapper(record); - Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); - return TableDataInfo.build(result); - } - - /** - * 查询AI人类交互信息列表 - */ - @Override - public List queryList(AihumanInfo record) { - LambdaQueryWrapper lqw = buildQueryWrapper(record); - return baseMapper.selectVoList(lqw); - } - - /** - * 构建查询条件 - */ - private LambdaQueryWrapper buildQueryWrapper(AihumanInfo record) { - LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.eq(record.getId() != null, AihumanInfo::getId, record.getId()); - lqw.like(StringUtils.isNotBlank(record.getName()), AihumanInfo::getName, record.getName()); - lqw.like(StringUtils.isNotBlank(record.getContent()), AihumanInfo::getContent, record.getContent()); - lqw.orderByDesc(AihumanInfo::getCreateTime); - return lqw; - } - - /** - * 新增AI人类交互信息 - */ - @Override - public int insert(AihumanInfo record) { - return baseMapper.insert(record); - } - - /** - * 修改AI人类交互信息 - */ - @Override - public int update(AihumanInfo record) { - return baseMapper.updateById(record); - } - - /** - * 批量删除AI人类交互信息 - */ - @Override - public int deleteWithValidByIds(Collection ids, Boolean isValid) { - if (isValid) { - // 如果需要逻辑删除,MyBatis-Plus会自动处理 - // 这里的@TableLogic注解已经在实体类中配置 - } - return baseMapper.deleteBatchIds(ids); - } -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanRealConfigServiceImpl.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanRealConfigServiceImpl.java deleted file mode 100644 index 2b162e1c..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanRealConfigServiceImpl.java +++ /dev/null @@ -1,534 +0,0 @@ -package org.ruoyi.aihuman.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.Pointer; -import com.sun.jna.platform.win32.WinNT; -import jakarta.annotation.PreDestroy; -import lombok.RequiredArgsConstructor; -import org.ruoyi.aihuman.domain.AihumanRealConfig; -import org.ruoyi.aihuman.domain.bo.AihumanRealConfigBo; -import org.ruoyi.aihuman.domain.vo.AihumanRealConfigVo; -import org.ruoyi.aihuman.mapper.AihumanRealConfigMapper; -import org.ruoyi.aihuman.service.AihumanRealConfigService; -import org.ruoyi.common.core.utils.MapstructUtils; -import org.ruoyi.common.core.utils.StringUtils; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.ruoyi.common.redis.utils.RedisUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.reflect.Field; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -/** - * 真人交互数字人配置Service业务层处理 - * - * @author ageerle - * @date Tue Oct 21 11:46:52 GMT+08:00 2025 - */ -@RequiredArgsConstructor -@Service -public class AihumanRealConfigServiceImpl implements AihumanRealConfigService { - - private static final Logger log = LoggerFactory.getLogger(AihumanRealConfigServiceImpl.class); - private final AihumanRealConfigMapper baseMapper; - // 存储当前运行的进程,用于停止操作 - private volatile Process runningProcess = null; - - /** - * 查询真人交互数字人配置 - */ - @Override - public AihumanRealConfigVo queryById(Integer id) { - return baseMapper.selectVoById(id); - } - - /** - * 查询真人交互数字人配置列表 - */ - @Override - public TableDataInfo queryPageList(AihumanRealConfigBo bo, PageQuery pageQuery) { - LambdaQueryWrapper lqw = buildQueryWrapper(bo); - Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); - return TableDataInfo.build(result); - } - - /** - * 查询真人交互数字人配置列表 - */ - @Override - public List queryList(AihumanRealConfigBo bo) { - LambdaQueryWrapper lqw = buildQueryWrapper(bo); - return baseMapper.selectVoList(lqw); - } - - private LambdaQueryWrapper buildQueryWrapper(AihumanRealConfigBo bo) { - LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.like(StringUtils.isNotBlank(bo.getName()), AihumanRealConfig::getName, bo.getName()); - lqw.like(StringUtils.isNotBlank(bo.getAvatars()), AihumanRealConfig::getAvatars, bo.getAvatars()); - lqw.like(StringUtils.isNotBlank(bo.getModels()), AihumanRealConfig::getModels, bo.getModels()); - lqw.eq(StringUtils.isNotBlank(bo.getAvatarsParams()), AihumanRealConfig::getAvatarsParams, bo.getAvatarsParams()); - lqw.eq(StringUtils.isNotBlank(bo.getModelsParams()), AihumanRealConfig::getModelsParams, bo.getModelsParams()); - lqw.eq(StringUtils.isNotBlank(bo.getAgentParams()), AihumanRealConfig::getAgentParams, bo.getAgentParams()); - lqw.eq(bo.getCreateTime() != null, AihumanRealConfig::getCreateTime, bo.getCreateTime()); - lqw.eq(bo.getUpdateTime() != null, AihumanRealConfig::getUpdateTime, bo.getUpdateTime()); - lqw.eq(bo.getStatus() != null, AihumanRealConfig::getStatus, bo.getStatus()); - lqw.eq(bo.getPublish() != null, AihumanRealConfig::getPublish, bo.getPublish()); - lqw.eq(StringUtils.isNotBlank(bo.getRunParams()), AihumanRealConfig::getRunParams, bo.getRunParams()); - // 添加runStatus字段的查询条件 - lqw.eq(StringUtils.isNotBlank(bo.getRunStatus()), AihumanRealConfig::getRunStatus, bo.getRunStatus()); - lqw.eq(StringUtils.isNotBlank(bo.getCreateDept()), AihumanRealConfig::getCreateDept, bo.getCreateDept()); - lqw.eq(StringUtils.isNotBlank(bo.getCreateBy()), AihumanRealConfig::getCreateBy, bo.getCreateBy()); - lqw.eq(StringUtils.isNotBlank(bo.getUpdateBy()), AihumanRealConfig::getUpdateBy, bo.getUpdateBy()); - return lqw; - } - - /** - * 新增真人交互数字人配置 - */ - @Override - public Boolean insertByBo(AihumanRealConfigBo bo) { - AihumanRealConfig add = MapstructUtils.convert(bo, AihumanRealConfig.class); - validEntityBeforeSave(add); - boolean flag = baseMapper.insert(add) > 0; - if (flag) { - bo.setId(add.getId()); - } - return flag; - } - - /** - * 修改真人交互数字人配置 - */ - @Override - public Boolean updateByBo(AihumanRealConfigBo bo) { - AihumanRealConfig update = MapstructUtils.convert(bo, AihumanRealConfig.class); - validEntityBeforeSave(update); - return baseMapper.updateById(update) > 0; - } - - /** - * 保存前的数据校验 - */ - private void validEntityBeforeSave(AihumanRealConfig entity) { - //TODO 做一些数据校验,如唯一约束 - } - - /** - * 批量删除真人交互数字人配置 - */ - @Override - public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { - if (isValid) { - //TODO 做一些业务上的校验,判断是否需要校验 - } - return baseMapper.deleteBatchIds(ids) > 0; - } - - /** - * 执行真人交互数字人配置 - * 通过主键获取数据库记录,然后从run_params字段读取命令并执行 - */ - @Override - public Boolean runByBo(AihumanRealConfigBo bo) { - try { - // 1. 通过主键获取数据库记录 - Integer id = bo.getId(); - if (id == null) { - log.error("执行命令失败:主键ID为空"); - throw new RuntimeException("执行命令失败:主键ID为空"); - } - - // 检查是否已经有对应的进程在运行 - String redisKey = "aihuman:process:" + id; - String existingPid = RedisUtils.getCacheObject(redisKey); - if (StringUtils.isNotEmpty(existingPid) && isProcessRunning(existingPid)) { - log.warn("ID为{}的配置已有进程在运行,进程ID: {}", id, existingPid); - // 刷新run_status状态为运行中 - AihumanRealConfig updateStatus = new AihumanRealConfig(); - updateStatus.setId(id); - updateStatus.setRunStatus("1"); // 1表示运行中 - baseMapper.updateById(updateStatus); - return true; - } - - // 查询数据库记录 - AihumanRealConfig config = baseMapper.selectById(id); - if (config == null) { - log.error("执行命令失败:未找到ID为{}的配置记录", id); - throw new RuntimeException("执行命令失败:未找到对应的配置记录"); - } - - // 2. 从记录中获取run_params字段 - String runParams = config.getRunParams(); - if (StringUtils.isBlank(runParams)) { - log.error("执行命令失败:ID为{}的记录中run_params字段为空", id); - throw new RuntimeException("执行命令失败:run_params字段为空"); - } - - // 3. 解析并执行命令 - // 将多行命令合并为一个命令字符串 - String[] commands = runParams.split("\\r?\\n"); - if (commands.length == 0) { - log.error("执行命令失败:runParams中没有有效的命令"); - throw new RuntimeException("执行命令失败:runParams中没有有效的命令"); - } - - // 将所有命令合并到一个命令字符串中,使用&&连接,确保在同一个进程中执行 - StringBuilder mergedCmd = new StringBuilder(); - for (int i = 0; i < commands.length; i++) { - String command = commands[i].trim(); - if (command.isEmpty()) { - continue; - } - - if (mergedCmd.length() > 0) { - mergedCmd.append(" && "); - } - - mergedCmd.append(command); - } - - String cmd = "cmd.exe /c " + mergedCmd.toString(); - log.info("准备执行合并命令:{}", cmd); - - // 更新数据库中的运行状态为运行中 - AihumanRealConfig updateStatus = new AihumanRealConfig(); - updateStatus.setId(id); - updateStatus.setRunStatus("1"); // 1表示运行中 - baseMapper.updateById(updateStatus); - - // 使用线程池执行命令并监听输出 - ExecutorService executor = Executors.newSingleThreadExecutor(); - executor.submit(() -> { - try { - Process process = Runtime.getRuntime().exec(cmd); - // 保存进程引用,用于后续停止操作 - runningProcess = process; - - // 获取进程ID并保存到Redis - String pid = getProcessId(process); - if (!"unknown".equals(pid)) { - RedisUtils.setCacheObject(redisKey, pid); - log.info("保存进程ID到Redis:key={}, pid={}", redisKey, pid); - } - - // 读取标准输出 - new Thread(() -> { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { - String line; - while ((line = reader.readLine()) != null) { - log.info("[LiveTalking] {}", line); - } - } catch (IOException e) { - log.error("读取命令输出失败", e); - } - }).start(); - - // 读取debug输出 - new Thread(() -> { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { - String line; - while ((line = reader.readLine()) != null) { - log.debug("[LiveTalking DEBUG] {}", line); - } - } catch (IOException e) { - log.error("读取命令debug输出失败", e); - } - }).start(); - - // 等待进程结束 - int exitCode = process.waitFor(); - log.info("LiveTalking进程结束,退出码: {}", exitCode); - - // 进程结束后更新数据库状态为已停止 - AihumanRealConfig endStatus = new AihumanRealConfig(); - endStatus.setId(id); - endStatus.setRunStatus("0"); // 0表示已停止 - baseMapper.updateById(endStatus); - - // 进程结束后从Redis中删除进程ID - RedisUtils.deleteObject(redisKey); - log.info("从Redis中删除进程ID:key={}", redisKey); - - // 进程结束后清空引用 - runningProcess = null; - } catch (Exception e) { - log.error("执行命令失败", e); - // 发生异常时更新数据库状态为失败 - try { - AihumanRealConfig errorStatus = new AihumanRealConfig(); - errorStatus.setId(id); - errorStatus.setRunStatus("2"); // 2表示启动失败 - baseMapper.updateById(errorStatus); - } catch (Exception ex) { - log.error("更新状态失败", ex); - } - // 发生异常时从Redis中删除进程ID - RedisUtils.deleteObject(redisKey); - // 发生异常时清空引用 - runningProcess = null; - } - }); - - executor.shutdown(); - return true; - } catch (Exception e) { - log.error("执行命令过程中发生异常", e); - return false; - } - } - - /** - * 检查进程是否正在运行 - * - * @param pid 进程ID - * @return 是否正在运行 - */ - private boolean isProcessRunning(String pid) { - if (StringUtils.isEmpty(pid) || "unknown".equals(pid)) { - return false; - } - - try { - boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win"); - ProcessBuilder processBuilder; - - if (isWindows) { - processBuilder = new ProcessBuilder("tasklist", "/FI", "PID eq " + pid); - } else { - processBuilder = new ProcessBuilder("ps", "-p", pid); - } - - Process process = processBuilder.start(); - int exitCode = process.waitFor(); - - // 在Windows上,tasklist命令如果找不到进程,退出码也是0,但输出中不会包含PID - if (isWindows) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { - String line; - while ((line = reader.readLine()) != null) { - if (line.contains(pid)) { - return true; - } - } - } - return false; - } else { - // 在Linux/Mac上,ps命令如果找不到进程,退出码不为0 - return exitCode == 0; - } - } catch (Exception e) { - log.error("检查进程是否运行失败, pid={}", pid, e); - return false; - } - } - - /** - * 停止正在运行的真人交互数字人配置任务 - */ - @Override - public Boolean stopByBo(AihumanRealConfigBo bo) { - try { - Integer id = bo.getId(); - String redisKey = "aihuman:process:" + id; - - // 首先检查Redis中是否有对应的进程ID - String pid = RedisUtils.getCacheObject(redisKey); - if (StringUtils.isNotEmpty(pid)) { - // 如果Redis中有进程ID,先尝试通过进程ID停止进程 - try { - // 根据操作系统类型,使用不同的命令终止进程树 - if (System.getProperty("os.name").toLowerCase().contains("win")) { - // Windows系统使用taskkill命令终止进程树 - log.info("通过Redis中的PID停止进程: taskkill /F /T /PID {}", pid); - Process killProcess = Runtime.getRuntime().exec("taskkill /F /T /PID " + pid); - // 等待kill命令执行完成 - killProcess.waitFor(5, TimeUnit.SECONDS); - } else { - // Linux/Mac系统使用pkill命令终止进程树 - Runtime.getRuntime().exec("pkill -P " + pid); - } - } catch (Exception e) { - log.error("通过Redis中的PID停止进程失败", e); - } - } - - // 然后检查本地runningProcess引用 - if (runningProcess != null && runningProcess.isAlive()) { - log.info("正在停止LiveTalking进程..."); - // 强制销毁进程树,确保完全停止 - destroyProcessTree(runningProcess); - - // 更新数据库中的运行状态为已停止 - AihumanRealConfig updateStatus = new AihumanRealConfig(); - updateStatus.setId(id); - updateStatus.setRunStatus("0"); // 0表示已停止 - baseMapper.updateById(updateStatus); - - runningProcess = null; - log.info("LiveTalking进程已停止"); - } else { - log.warn("没有正在运行的LiveTalking进程"); - // 确保数据库中的状态也是已停止 - AihumanRealConfig updateStatus = new AihumanRealConfig(); - updateStatus.setId(id); - updateStatus.setRunStatus("0"); // 0表示已停止 - baseMapper.updateById(updateStatus); - } - - // 无论如何都从Redis中删除进程ID - RedisUtils.deleteObject(redisKey); - log.info("从Redis中删除进程ID:key={}", redisKey); - - return true; - } catch (Exception e) { - log.error("停止进程时发生异常", e); - // 发生异常时也尝试从Redis中删除进程ID - try { - RedisUtils.deleteObject("aihuman:process:" + bo.getId()); - } catch (Exception ex) { - log.error("从Redis中删除进程ID失败", ex); - } - return false; - } - } - - /** - * 销毁进程及其子进程(进程树) - * - * @param process 要销毁的进程 - */ - private void destroyProcessTree(Process process) { - try { - if (process.isAlive()) { - // 获取进程ID - String pid = getProcessId(process); - log.info("获取到进程ID: {}", pid); - - // 根据操作系统类型,使用不同的命令终止进程树 - if (System.getProperty("os.name").toLowerCase().contains("win")) { - // Windows系统使用taskkill命令终止进程树 - log.info("执行taskkill命令终止进程树: taskkill /F /T /PID {}", pid); - Process killProcess = Runtime.getRuntime().exec("taskkill /F /T /PID " + pid); - // 等待kill命令执行完成 - killProcess.waitFor(5, TimeUnit.SECONDS); - } else { - // Linux/Mac系统使用pkill命令终止进程树 - Runtime.getRuntime().exec("pkill -P " + pid); - process.destroy(); - } - } - } catch (Exception e) { - log.error("销毁进程树时发生异常", e); - // 如果出现异常,尝试使用普通销毁方法 - process.destroy(); - try { - // 强制销毁 - if (process.isAlive()) { - process.destroyForcibly(); - } - } catch (Exception ex) { - log.error("强制销毁进程失败", ex); - } - } - } - - /** - * 获取进程ID - * - * @param process 进程对象 - * @return 进程ID - */ - private String getProcessId(Process process) { - try { - // 不同JVM实现可能有所不同,这里尝试通过反射获取 - if (process.getClass().getName().equals("java.lang.Win32Process") || - process.getClass().getName().equals("java.lang.ProcessImpl")) { - Field f = process.getClass().getDeclaredField("handle"); - f.setAccessible(true); - long handl = f.getLong(process); - Kernel32 kernel = Kernel32.INSTANCE; - WinNT.HANDLE handle = new WinNT.HANDLE(); - handle.setPointer(Pointer.createConstant(handl)); - return String.valueOf(kernel.GetProcessId(handle)); - } else if (process.getClass().getName().equals("java.lang.UNIXProcess")) { - Field f = process.getClass().getDeclaredField("pid"); - f.setAccessible(true); - return String.valueOf(f.getInt(process)); - } - } catch (Exception e) { - log.error("获取进程ID失败", e); - } - - // 如果反射获取失败,尝试通过wmic命令获取 - try { - // 对于Windows系统,可以尝试使用wmic命令获取进程ID - if (System.getProperty("os.name").toLowerCase().contains("win")) { - ProcessHandle.Info info = process.toHandle().info(); - return String.valueOf(process.toHandle().pid()); - } - } catch (Exception e) { - log.error("通过ProcessHandle获取进程ID失败", e); - } - - return "unknown"; - } - - @PreDestroy - public void onDestroy() { - if (runningProcess != null && runningProcess.isAlive()) { - try { - log.info("应用关闭,正在停止数字人进程"); - destroyProcessTree(runningProcess); - - // 查找所有运行状态为运行中的配置,并更新为已停止 - LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.eq(AihumanRealConfig::getRunStatus, "1"); - List runningConfigs = baseMapper.selectList(lqw); - for (AihumanRealConfig config : runningConfigs) { - config.setRunStatus("0"); - baseMapper.updateById(config); - - // 从Redis中删除对应的进程ID记录 - String redisKey = "aihuman:process:" + config.getId(); - RedisUtils.deleteObject(redisKey); - log.info("应用关闭,从Redis中删除进程ID:key={}", redisKey); - } - } catch (Exception e) { - log.error("停止数字人进程失败", e); - // 即使发生异常,也尝试清理Redis中的进程ID记录 - try { - LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.eq(AihumanRealConfig::getRunStatus, "1"); - List runningConfigs = baseMapper.selectList(lqw); - for (AihumanRealConfig config : runningConfigs) { - RedisUtils.deleteObject("aihuman:process:" + config.getId()); - } - } catch (Exception ex) { - log.error("清理Redis中的进程ID记录失败", ex); - } - } - } - } - - // JNA接口定义,用于Windows系统获取进程ID - interface Kernel32 extends Library { - Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class); - - int GetProcessId(WinNT.HANDLE hProcess); - } -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanVolcengineServiceImpl.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanVolcengineServiceImpl.java deleted file mode 100644 index aa11aabd..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/service/impl/AihumanVolcengineServiceImpl.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.ruoyi.aihuman.service.impl; - -public class AihumanVolcengineServiceImpl { -} diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/volcengine/Bidirection.java b/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/volcengine/Bidirection.java deleted file mode 100644 index dc8ef8c9..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/java/org/ruoyi/aihuman/volcengine/Bidirection.java +++ /dev/null @@ -1,160 +0,0 @@ -package org.ruoyi.aihuman.volcengine; - -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.aihuman.protocol.EventType; -import org.ruoyi.aihuman.protocol.Message; -import org.ruoyi.aihuman.protocol.MsgType; -import org.ruoyi.aihuman.protocol.SpeechWebSocketClient; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.net.URI; -import java.nio.file.Files; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -@Slf4j -public class Bidirection { - private static final String ENDPOINT = "wss://openspeech.bytedance.com/api/v3/tts/bidirection"; - private static final ObjectMapper objectMapper = new ObjectMapper(); - - /** - * Get resource ID based on voice type - * - * @param voice Voice type string - * @return Corresponding resource ID - */ - public static String voiceToResourceId(String voice) { - // Map different voice types to resource IDs based on actual needs - if (voice.startsWith("S_")) { - return "volc.megatts.default"; - } - return "volc.service_type.10029"; - } - - public static void main(String[] args) throws Exception { - // Configure parameters - String appId = System.getProperty("appId", "1055299334"); - String accessToken = System.getProperty("accessToken", "fOHuq4R4dirMYiOruCU3Ek9q75zV0KVW"); - String resourceId = System.getProperty("resourceId", "seed-tts-2.0"); - String voice = System.getProperty("voice", "zh_female_vv_uranus_bigtts"); - String text = System.getProperty("text", "你好呀!我是AI合成的语音,很高兴认识你。"); - String encoding = System.getProperty("encoding", "mp3"); - - if (appId.isEmpty() || accessToken.isEmpty()) { - throw new IllegalArgumentException("Please set appId and accessToken system properties"); - } - - // Set request headers - Map headers = Map.of( - "X-Api-App-Key", appId, - "X-Api-Access-Key", accessToken, - "X-Api-Resource-Id", resourceId.isEmpty() ? voiceToResourceId(voice) : resourceId, - "X-Api-Connect-Id", UUID.randomUUID().toString()); - - // Create WebSocket client - SpeechWebSocketClient client = new SpeechWebSocketClient(new URI(ENDPOINT), headers); - try { - client.connectBlocking(); - Map request = Map.of( - "user", Map.of("uid", UUID.randomUUID().toString()), - "namespace", "BidirectionalTTS", - "req_params", Map.of( - "speaker", voice, - "audio_params", Map.of( - "format", encoding, - "sample_rate", 24000, - "enable_timestamp", true), - // additions requires a JSON string - "additions", objectMapper.writeValueAsString(Map.of( - "disable_markdown_filter", false)))); - - // Start connection - client.sendStartConnection(); - // Wait for connection started - client.waitForMessage(MsgType.FULL_SERVER_RESPONSE, EventType.CONNECTION_STARTED); - - // Process each sentence - String[] sentences = text.split("。"); - boolean audioReceived = false; - for (int i = 0; i < sentences.length; i++) { - if (sentences[i].trim().isEmpty()) { - continue; - } - - String sessionId = UUID.randomUUID().toString(); - ByteArrayOutputStream audioStream = new ByteArrayOutputStream(); - - // Start session - Map startReq = Map.of( - "user", request.get("user"), - "namespace", request.get("namespace"), - "req_params", request.get("req_params"), - "event", EventType.START_SESSION.getValue()); - client.sendStartSession(objectMapper.writeValueAsBytes(startReq), sessionId); - // Wait for session started - client.waitForMessage(MsgType.FULL_SERVER_RESPONSE, EventType.SESSION_STARTED); - - // Send text - for (char c : sentences[i].toCharArray()) { - // Create new req_params with text - @SuppressWarnings("unchecked") - Map currentReqParams = new HashMap<>( - (Map) request.get("req_params")); - currentReqParams.put("text", String.valueOf(c)); - - // Create current request - Map currentRequest = Map.of( - "user", request.get("user"), - "namespace", request.get("namespace"), - "req_params", currentReqParams, - "event", EventType.TASK_REQUEST.getValue()); - - client.sendTaskRequest(objectMapper.writeValueAsBytes(currentRequest), sessionId); - } - - // End session - client.sendFinishSession(sessionId); - - // Receive response - while (true) { - Message msg = client.receiveMessage(); - switch (msg.getType()) { - case FULL_SERVER_RESPONSE: - break; - case AUDIO_ONLY_SERVER: - if (!audioReceived && audioStream.size() > 0) { - audioReceived = true; - } - if (msg.getPayload() != null) { - audioStream.write(msg.getPayload()); - } - break; - default: - throw new RuntimeException("Unexpected message: " + msg); - } - if (msg.getEvent() == EventType.SESSION_FINISHED) { - break; - } - } - - if (audioStream.size() > 0) { - String fileName = String.format("%s_session_%d.%s", voice, i, encoding); - Files.write(new File(fileName).toPath(), audioStream.toByteArray()); - log.info("Audio saved to file: {}", fileName); - } - } - - if (!audioReceived) { - throw new RuntimeException("No audio data received"); - } - - // End connection - client.sendFinishConnection(); - } finally { - client.closeBlocking(); - } - } -} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/resources/mapper/AihumanInfoMapper.xml b/ruoyi-modules/ruoyi-aihuman/src/main/resources/mapper/AihumanInfoMapper.xml deleted file mode 100644 index 22bd47aa..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/resources/mapper/AihumanInfoMapper.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/resources/mapper/aihuman/AihumanConfigMapper.xml b/ruoyi-modules/ruoyi-aihuman/src/main/resources/mapper/aihuman/AihumanConfigMapper.xml deleted file mode 100644 index 0cbe9dcb..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/resources/mapper/aihuman/AihumanConfigMapper.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/ruoyi-modules/ruoyi-aihuman/src/main/resources/mapper/aihuman/AihumanRealConfigMapper.xml b/ruoyi-modules/ruoyi-aihuman/src/main/resources/mapper/aihuman/AihumanRealConfigMapper.xml deleted file mode 100644 index 8c7011ca..00000000 --- a/ruoyi-modules/ruoyi-aihuman/src/main/resources/mapper/aihuman/AihumanRealConfigMapper.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - -