diff --git a/pom.xml b/pom.xml
index 851893c2..38482dea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -265,13 +265,6 @@
${lock4j.version}
-
-
- com.xuxueli
- xxl-job-core
- ${xxl-job.version}
-
-
com.alibaba
transmittable-thread-local
@@ -341,6 +334,19 @@
ruoyi-generator
${revision}
+
+
+ org.ruoyi
+ ruoyi-workflow
+ ${revision}
+
+
+
+ org.ruoyi
+ ruoyi-workflow-api
+ ${revision}
+
+
diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index 6f84d8b5..b702ba52 100644
--- a/ruoyi-admin/pom.xml
+++ b/ruoyi-admin/pom.xml
@@ -57,6 +57,11 @@
ruoyi-generator
+
+ org.ruoyi
+ ruoyi-workflow
+
+
diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml
index 1fd51a25..b45a7257 100644
--- a/ruoyi-admin/src/main/resources/application-dev.yml
+++ b/ruoyi-admin/src/main/resources/application-dev.yml
@@ -16,9 +16,9 @@ spring:
master:
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://127.0.0.1:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
- username: root
- password: root
+ url: jdbc:mysql://47.112.190.27:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
+ username: ruoyi-ai
+ password: 5YEAWhSFZXKaMGxi
hikari:
# 最大连接池数量
@@ -37,6 +37,8 @@ spring:
connectionTestQuery: SELECT 1
# 多久检查一次连接的活性
keepaliveTime: 30000
+ mail:
+ username: xx
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring.data:
@@ -102,5 +104,13 @@ pdf:
#百炼模型配置
dashscope:
key: sk-xxxx
- model: qvq-max
+
+local:
+ images: xx
+
+
+
+ files: xx
+
+
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index 6d5e6d2f..c979de51 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -156,6 +156,8 @@ security:
# actuator 监控配置
- /actuator
- /actuator/**
+ - /workflow/**
+ - /admin/workflow/**
# 多租户配置
tenant:
# 是否开启
diff --git a/ruoyi-modules-api/pom.xml b/ruoyi-modules-api/pom.xml
index a3f0ef00..2cf63600 100644
--- a/ruoyi-modules-api/pom.xml
+++ b/ruoyi-modules-api/pom.xml
@@ -17,6 +17,7 @@
ruoyi-chat-api
ruoyi-knowledge-api
ruoyi-system-api
+ ruoyi-workflow-api
diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/vo/ChatConfigVo.java b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/vo/ChatConfigVo.java
index 148d2dc8..410e4a2d 100644
--- a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/vo/ChatConfigVo.java
+++ b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/vo/ChatConfigVo.java
@@ -18,7 +18,7 @@ import java.io.Serializable;
*/
@Data
@ExcelIgnoreUnannotated
-@AutoMapper(target = ChatConfig.class)
+ @AutoMapper(target = ChatConfig.class)
public class ChatConfigVo implements Serializable {
@Serial
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/pom.xml b/ruoyi-modules-api/ruoyi-workflow-api/pom.xml
new file mode 100644
index 00000000..7b1cafac
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/pom.xml
@@ -0,0 +1,128 @@
+
+
+ 4.0.0
+
+ org.ruoyi
+ ruoyi-modules-api
+ ${revision}
+ ../pom.xml
+
+
+ ruoyi-workflow-api
+
+
+ 工作流API模块
+
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+
+ org.springframework
+ spring-web
+
+
+
+ org.ruoyi
+ ruoyi-system-api
+
+
+
+ org.ruoyi
+ ruoyi-common-mail
+
+
+
+ org.ruoyi
+ ruoyi-chat
+
+
+
+ dev.langchain4j
+ langchain4j-core
+ 1.2.0
+
+
+
+ cn.hutool
+ hutool-all
+ 5.8.12
+ compile
+
+
+
+ org.bsc.langgraph4j
+ langgraph4j-core
+ 1.5.3
+
+
+
+ org.bsc.langgraph4j
+ langgraph4j-langchain4j
+ 1.5.3
+
+
+
+ io.swagger.core.v3
+ swagger-annotations
+ 2.2.8
+
+
+
+ dev.langchain4j
+ langchain4j-open-ai
+ 1.2.0
+ compile
+
+
+
+ dev.langchain4j
+ langchain4j-community-dashscope
+ 1.2.0-beta8
+
+
+
+ com.baomidou
+ mybatis-plus-generator
+ 3.5.3.1
+
+
+
+ dev.langchain4j
+ langchain4j-http-client-jdk
+ 1.2.0
+
+
+
+ dev.langchain4j
+ langchain4j-document-parser-apache-poi
+ 1.2.0-beta8
+
+
+
+
+ com.google.api-client
+ google-api-client
+ 2.6.0
+
+
+
+
+
+
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/CodeGenerator.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/CodeGenerator.java
new file mode 100644
index 00000000..6b77b030
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/CodeGenerator.java
@@ -0,0 +1,43 @@
+package org.ruoyi.workflow;
+
+import com.baomidou.mybatisplus.generator.FastAutoGenerator;
+import com.baomidou.mybatisplus.generator.config.OutputFile;
+import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
+
+import java.sql.Types;
+import java.util.Collections;
+
+public class CodeGenerator {
+ public static void main(String[] args) {
+ FastAutoGenerator.create("jdbc:postgres://172.17.30.40:5432/aideepin?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&tinyInt1isBit=false&allowMultiQueries=true", "postgres", "postgres")
+ .globalConfig(builder -> {
+ builder.author("moyz") // 设置作者
+ .enableSwagger() // 开启 swagger 模式
+ .fileOverride() // 覆盖已生成文件
+ .outputDir("D://"); // 指定输出目录
+ })
+ .dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
+ int typeCode = metaInfo.getJdbcType().TYPE_CODE;
+ if (typeCode == Types.SMALLINT) {
+ // 自定义类型转换
+ return DbColumnType.INTEGER;
+ }
+ return typeRegistry.getColumnType(metaInfo);
+
+ }))
+ .packageConfig(builder -> {
+ builder.mapper("com.adi.common.mapper")
+ .parent("")
+ .moduleName("")
+ .entity("po")
+ .serviceImpl("service.impl")
+ .pathInfo(Collections.singletonMap(OutputFile.xml, "D://mybatisplus-generatorcode")); // 设置mapperXml生成路径
+ })
+ .strategyConfig(builder -> {
+ builder.addInclude("adi_knowledge_base_qa_record") // 设置需要生成的表名
+ .addTablePrefix("adi_");
+ builder.mapperBuilder().enableBaseResultMap().enableMapperAnnotation().build();
+ })
+ .execute();
+ }
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/base/BaseResponse.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/base/BaseResponse.java
new file mode 100644
index 00000000..d6220abd
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/base/BaseResponse.java
@@ -0,0 +1,51 @@
+package org.ruoyi.workflow.base;
+
+import lombok.Data;
+import org.ruoyi.workflow.enums.ErrorEnum;
+
+import java.io.Serializable;
+
+@Data
+public class BaseResponse implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ /**
+ * 是否成功
+ */
+ private boolean success;
+ /**
+ * 状态码
+ */
+ private String code;
+ /**
+ * 提示
+ */
+ private String message;
+ /**
+ * 数据
+ */
+ private T data;
+
+ public BaseResponse() {
+ }
+
+ public BaseResponse(boolean success) {
+ this.success = success;
+ }
+
+ public BaseResponse(boolean success, T data) {
+ this.data = data;
+ this.success = success;
+ }
+
+ public BaseResponse(String code, String message, T data) {
+ this.code = code;
+ this.success = false;
+ this.message = message;
+ this.data = data;
+ }
+
+ public static BaseResponse success(String message) {
+ return new BaseResponse(ErrorEnum.SUCCESS.getCode(), message, "");
+ }
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/base/NodeInputConfigTypeHandler.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/base/NodeInputConfigTypeHandler.java
new file mode 100644
index 00000000..4bd44fbb
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/base/NodeInputConfigTypeHandler.java
@@ -0,0 +1,118 @@
+package org.ruoyi.workflow.base;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+import org.ruoyi.workflow.enums.WfIODataTypeEnum;
+import org.ruoyi.workflow.util.JsonUtil;
+import org.ruoyi.workflow.workflow.WfNodeInputConfig;
+import org.ruoyi.workflow.workflow.def.WfNodeIO;
+import org.ruoyi.workflow.workflow.def.WfNodeParamRef;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.ruoyi.workflow.workflow.WfNodeIODataUtil.INPUT_TYPE_TO_NODE_IO_DEF;
+
+@Slf4j
+@MappedJdbcTypes({JdbcType.JAVA_OBJECT})
+@MappedTypes({WfNodeInputConfig.class})
+public class NodeInputConfigTypeHandler extends BaseTypeHandler {
+
+ public static WfNodeInputConfig fillNodeInputConfig(String jsonSource) {
+ ObjectNode jsonNode = (ObjectNode) JsonUtil.toJsonNode(jsonSource);
+ return createNodeInputConfig(jsonNode);
+ }
+
+ public static WfNodeInputConfig createNodeInputConfig(ObjectNode jsonNode) {
+ List userInputs = new ArrayList<>();
+ WfNodeInputConfig result = new WfNodeInputConfig();
+ result.setUserInputs(userInputs);
+ result.setRefInputs(new ArrayList<>());
+ if (null == jsonNode) {
+ return result;
+ }
+ ArrayNode userInputsJson = jsonNode.withArray("user_inputs");
+ ArrayNode refInputs = jsonNode.withArray("ref_inputs");
+ if (!userInputsJson.isEmpty()) {
+ for (JsonNode userInput : userInputsJson) {
+ if (userInput instanceof ObjectNode objectNode) {
+ int type = objectNode.get("type").asInt();
+ Class extends WfNodeIO> nodeIOClass = INPUT_TYPE_TO_NODE_IO_DEF.get(WfIODataTypeEnum.getByValue(type));
+ WfNodeIO wfNodeIO = JsonUtil.fromJson(objectNode, nodeIOClass);
+ if (null != wfNodeIO) {
+ userInputs.add(wfNodeIO);
+ } else {
+ log.warn("用户输入格式不正确:{}", userInput);
+ }
+ }
+ }
+ }
+ if (!refInputs.isEmpty()) {
+ List list = JsonUtil.fromArrayNode(refInputs, WfNodeParamRef.class);
+ if (CollectionUtils.isNotEmpty(list)) {
+ result.setRefInputs(list);
+ } else {
+ log.warn("引用输入格式不正确:{}", refInputs);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public void setNonNullParameter(PreparedStatement ps, int i, WfNodeInputConfig parameter, JdbcType jdbcType) {
+// PGobject jsonObject = new PGobject();
+// jsonObject.setType("jsonb");
+// try {
+// jsonObject.setValue(JsonUtil.toJson(parameter));
+// ps.setObject(i, jsonObject);
+// } catch (Exception e) {
+// throw new RuntimeException(e);
+// }
+ }
+
+ @Override
+ public WfNodeInputConfig getNullableResult(ResultSet rs, String columnName) throws SQLException {
+ String jsonSource = rs.getString(columnName);
+ if (jsonSource != null) {
+ try {
+ return fillNodeInputConfig(jsonSource);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public WfNodeInputConfig getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+ String jsonSource = rs.getString(columnIndex);
+ if (jsonSource != null) {
+ return fillNodeInputConfig(jsonSource);
+ }
+ return null;
+ }
+
+ @Override
+ public WfNodeInputConfig getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+ String jsonSource = cs.getString(columnIndex);
+ if (jsonSource != null) {
+ try {
+ return fillNodeInputConfig(jsonSource);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/base/ThreadContext.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/base/ThreadContext.java
new file mode 100644
index 00000000..5b841d9a
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/base/ThreadContext.java
@@ -0,0 +1,67 @@
+package org.ruoyi.workflow.base;
+
+import io.micrometer.common.util.StringUtils;
+import org.ruoyi.workflow.entity.User;
+import org.ruoyi.workflow.enums.UserStatusEnum;
+import org.ruoyi.workflow.exception.WorkflowBaseException;
+
+import static org.ruoyi.workflow.enums.ErrorEnum.A_USER_NOT_FOUND;
+
+public class ThreadContext {
+ private static final ThreadLocal currentUser = new ThreadLocal<>();
+ private static final ThreadLocal currentToken = new ThreadLocal<>();
+
+ private ThreadContext() {
+ }
+
+ public static User getCurrentUser() {
+ User user = new User();
+ user.setName("admin");
+ user.setEmail("12345@qq.com");
+ user.setUuid("123456789");
+ user.setUnderstandContextMsgPairNum(1);
+ user.setQuotaByTokenDaily(1);
+ user.setQuotaByTokenMonthly(1);
+ user.setQuotaByRequestDaily(1);
+ user.setQuotaByRequestMonthly(1);
+ user.setQuotaByImageDaily(1);
+ user.setQuotaByImageMonthly(1);
+ user.setUserStatus(UserStatusEnum.NORMAL);
+ user.setIsAdmin(true);
+ user.setId(1L);
+ return user;
+ }
+
+ public static void setCurrentUser(User user) {
+ currentUser.set(user);
+ }
+
+ public static Long getCurrentUserId() {
+ return 1L;
+ }
+
+ public static String getToken() {
+ return currentToken.get();
+ }
+
+ public static void setToken(String token) {
+ currentToken.set(token);
+ }
+
+ public static boolean isLogin() {
+ return StringUtils.isNotBlank(currentToken.get());
+ }
+
+ public static User getExistCurrentUser() {
+ User user = ThreadContext.getCurrentUser();
+ if (null == user) {
+ throw new WorkflowBaseException(A_USER_NOT_FOUND);
+ }
+ return user;
+ }
+
+ public void unload() {
+ currentUser.remove();
+ currentToken.remove();
+ }
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/config/BeanConfig.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/config/BeanConfig.java
new file mode 100644
index 00000000..5623208b
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/config/BeanConfig.java
@@ -0,0 +1,76 @@
+package org.ruoyi.workflow.config;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import lombok.extern.slf4j.Slf4j;
+import org.ruoyi.workflow.util.LocalDateTimeUtil;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.http.client.BufferingClientHttpRequestFactory;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+import org.springframework.web.client.RestTemplate;
+
+@Slf4j
+@Configuration
+public class BeanConfig {
+
+ @Bean
+ public RestTemplate restTemplate() {
+ log.info("Configuration:create restTemplate");
+ SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
+ // 设置建立连接超时时间 毫秒
+ requestFactory.setConnectTimeout(60000);
+ // 设置读取数据超时时间 毫秒
+ requestFactory.setReadTimeout(60000);
+ RestTemplate restTemplate = new RestTemplate();
+ // 注册LOG拦截器
+// restTemplate.setInterceptors(Lists.newArrayList(new LogClientHttpRequestInterceptor()));
+ restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(requestFactory));
+
+ return restTemplate;
+ }
+
+ @Bean
+ @Primary
+ public ObjectMapper objectMapper() {
+ log.info("Configuration:create objectMapper");
+ ObjectMapper objectMapper = new Jackson2ObjectMapperBuilder().createXmlMapper(false).build();
+ objectMapper.registerModules(LocalDateTimeUtil.getSimpleModule(), new JavaTimeModule(), new Jdk8Module());
+ //设置null值不参与序列化(字段不被显示)
+ objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ return objectMapper;
+ }
+
+ @Bean(name = "mainExecutor")
+ @Primary
+ public AsyncTaskExecutor mainExecutor() {
+ int processorsNum = Runtime.getRuntime().availableProcessors();
+ log.info("mainExecutor,processorsNum:{}", processorsNum);
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ executor.setCorePoolSize(processorsNum * 2);
+ executor.setMaxPoolSize(100);
+ return executor;
+ }
+
+ @Bean(name = "imagesExecutor")
+ public AsyncTaskExecutor imagesExecutor() {
+ int processorsNum = Runtime.getRuntime().availableProcessors();
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ log.info("imagesExecutor corePoolSize:{},maxPoolSize:{}", processorsNum, processorsNum * 2);
+ executor.setCorePoolSize(processorsNum);
+ executor.setMaxPoolSize(processorsNum * 2);
+ return executor;
+ }
+
+ @Bean(name = "beanValidator")
+ public LocalValidatorFactoryBean validator() {
+ return new LocalValidatorFactoryBean();
+ }
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/cosntant/AdiConstant.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/cosntant/AdiConstant.java
new file mode 100644
index 00000000..d9b4bac7
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/cosntant/AdiConstant.java
@@ -0,0 +1,407 @@
+package org.ruoyi.workflow.cosntant;
+
+import dev.langchain4j.model.input.PromptTemplate;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+public class AdiConstant {
+ public static final int DEFAULT_PAGE_SIZE = 10;
+ /**
+ * 验证码id过期时间:1小时
+ */
+ public static final int AUTH_CAPTCHA_ID_EXPIRE = 1;
+ /**
+ * 验证码过期时间,5分钟
+ */
+ public static final int AUTH_CAPTCHA_EXPIRE = 5;
+ /**
+ * 注册激活码有效时长,8小时
+ */
+ public static final int AUTH_ACTIVE_CODE_EXPIRE = 8;
+ /**
+ * token存活时间(8小时)
+ */
+ public static final int USER_TOKEN_EXPIRE = 8;
+ public static final String DEFAULT_PASSWORD = "123456";
+ public static final int LOGIN_MAX_FAIL_TIMES = 3;
+ public static final String[] WEB_RESOURCES = {
+ "/swagger-ui/index.html",
+ "/swagger-ui",
+ "/swagger-resources",
+ "/v3/api-docs",
+ "/favicon.ico",
+ ".css",
+ ".js",
+ "/doc.html"
+ };
+ public static final int SECRET_KEY_TYPE_SYSTEM = 1;
+ public static final int SECRET_KEY_TYPE_CUSTOM = 2;
+ public static final String OPENAI_MESSAGE_DONE_FLAG = "[DONE]";
+ public static final String DEFAULT_MODEL = "gpt-3.5-turbo";
+ public static final String CREATE_IMAGE_RESP_FORMATS_B64JSON = "b64_json";
+ public static final String OPENAI_CREATE_IMAGE_RESP_FORMATS_URL = "url";
+ public static final List DALLE2_CREATE_IMAGE_SIZES = List.of("256x256", "512x512", "1024x1024");
+ public static final List DALLE3_CREATE_IMAGE_SIZES = List.of("1024x1024", "1024x1792", "1792x1024");
+ public static final PromptTemplate PROMPT_EXTRA_TEMPLATE = PromptTemplate.from("""
+ ## 要求
+ 尽可能准确地回答用户的问题
+
+ ## 用户的问题
+ {{question}}
+
+ ## 注意
+ {{extraInfo}}
+ """);
+ public static final PromptTemplate PROMPT_INFO_TEMPLATE = PromptTemplate.from("""
+ ## 要求
+ 根据已知信息,尽可能准确地回答用户的问题
+
+ ## 用户的问题
+ {{question}}
+
+ ## 已知信息
+ {{information}}
+
+ ## 注意
+ 回答的内容不能让用户感知到已知信息的存在
+ """);
+ /**
+ * 可能的 extraInfo 如适用转音频的要求: 2. 回答的内容要尽量口语化,以方便将内容转成语音
+ */
+ public static final PromptTemplate PROMPT_INFO_EXTRA_TEMPLATE = PromptTemplate.from("""
+ ## 要求
+ 根据已知信息,尽可能准确地回答用户的问题
+
+ ## 用户的问题
+ {{question}}
+
+ ## 已知信息
+ {{information}}
+
+ ## 注意
+ 1. 回答的内容不能让用户感知到已知信息的存在
+ {{extraInfo}}
+ """);
+ public static final String PROMPT_EXTRA_AUDIO = "2. 回答的内容要尽量口语化,以方便将内容转成语音";
+ public static final Double LLM_TEMPERATURE_DEFAULT = 0.7D;
+ public static final Double RAG_RETRIEVE_MIN_SCORE_DEFAULT = 0.6D;
+ public static final int tts_ = 1;
+ public static final String[] POI_DOC_TYPES = {"doc", "docx", "ppt", "pptx", "xls", "xlsx"};
+ public static final long SSE_TIMEOUT = (2 * 60 + 30) * 1000L; // 2.5分钟
+ public static final int RAG_TYPE_KB = 1;
+ public static final int RAG_TYPE_SEARCH = 2;
+ /**
+ * 每块文档长度(按token算)
+ */
+ public static final int RAG_MAX_SEGMENT_SIZE_IN_TOKENS = 1000;
+ /**
+ * 文档召回默认数量
+ */
+ public static final int RAG_RETRIEVE_NUMBER_DEFAULT = 3;
+ /**
+ * 文档召回最大数量
+ */
+ public static final int RAG_RETRIEVE_NUMBER_MAX = 5;
+ /**
+ * 向量搜索时命中所需的最低分数
+ */
+ public static final double RAG_MIN_SCORE = 0.6;
+ /**
+ * 默认的最大输入token数
+ */
+ public static final int LLM_MAX_INPUT_TOKENS_DEFAULT = 4096;
+ public static final String LLM_INPUT_TYPE_TEXT = "text";
+ public static final String LLM_INPUT_TYPE_IMAGE = "image";
+ public static final String LLM_INPUT_TYPE_AUDIO = "audio";
+ public static final String LLM_INPUT_TYPE_VIDEO = "video";
+ public static final String[] GRAPH_ENTITY_EXTRACTION_ENTITY_TYPES = {"organization", "person", "geo", "event"};
+ public static final String GRAPH_TUPLE_DELIMITER = "<|>";
+ public static final String GRAPH_RECORD_DELIMITER = "##";
+ public static final String GRAPH_COMPLETION_DELIMITER = "<|COMPLETE|>";
+ public static final List GRAPH_STORE_MAIN_FIELDS = List.of("name", "label", "text_segment_id", "description");
+ /**
+ * 唯一标识字段,如果该字段有指定,则根据该配置判断Vertex或Edge是否唯一,如知识库中根据 name、metadata->>kb_uuid 来做判断
+ */
+ public static final String GRAPH_METADATA_IDENTIFY_COLUMNS = "graph_metadata_identify_columns";
+ /**
+ * 内容追加字段
+ * 更新数据时,如遇到该标识中的字段,追加内容而不是替换
+ */
+ public static final String GRAPH_METADATA_APPEND_COLUMNS = "graph_metadata_append_columns_if_exist";
+ public static final int AI_IMAGE_TYPE_REGULAR = 1;
+ public static final int AI_IMAGE_TYPE_THUMBNAIL = 2;
+ public static final int AI_IMAGE_TYPE_REGULAR_MARK = 3;
+ public static final int AI_IMAGE_TYPE_THUMBNAIL_MARK = 4;
+ public static final String DOC_INDEX_TYPE_EMBEDDING = "embedding";
+ public static final String DOC_INDEX_TYPE_GRAPHICAL = "graphical";
+ public static final String DRAW_TYPE_PUBLIC = "public";
+ public static final String DRAW_TYPE_STARRED = "starred";
+ public static final String DRAW_TYPE_MINE = "mine";
+ public static final String MP_LIMIT_1 = "limit 1";
+ /**
+ * 文件存储在本地
+ */
+ public static final int STORAGE_LOCATION_LOCAL = 1;
+ /**
+ * 文件存储到阿里云OSS
+ */
+ public static final int STORAGE_LOCATION_ALI_OSS = 2;
+ public static final String URL_PREFIX_FILE = "/file/";
+ public static final String URL_PREFIX_IMAGE = "/image/";
+ public static final String URL_PREFIX_MY_IMAGE = "/my-image/";
+ public static final String URL_PREFIX_MY_THUMBNAIL = "/my-thumbnail/";
+ public static final List IMAGE_EXTENSIONS = List.of("jpg", "jpeg", "png", "gif", "bmp", "webp");
+ public static final String W_FAILED = "FAILED";
+ public static final String COLUMN_NAME_IS_DELETE = "is_deleted";
+ public static final String COLUMN_NAME_USER_ID = "user_id";
+ public static final String COLUMN_NAME_ID = "id";
+ public static final String COLUMN_NAME_UUID = "uuid";
+ public static final String FORM_DATA_BOUNDARY_PRE = "----WebKitFormBoundary";
+
+ private AdiConstant() {
+ }
+
+ public static class ConversationConstant {
+ public static final String DEFAULT_NAME = "通用智能助手";
+ public static final int ANSWER_CONTENT_TYPE_AUTO = 1;
+ public static final int ANSWER_CONTENT_TYPE_TEXT = 2;
+ public static final int ANSWER_CONTENT_TYPE_AUDIO = 3;
+ public static final String AUDIO_CONFIG_FIELD_ANSWER_VOICE = "answer_voice";
+ public static final String AUDIO_CONFIG_FIELD_VOICE_PLATFORM = "platform";
+
+ private ConversationConstant() {
+ }
+ }
+
+ public static class GenerateImage {
+ public static final int INTERACTING_METHOD_GENERATE_IMAGE = 1;
+ public static final int INTERACTING_METHOD_EDIT_IMAGE = 2;
+ public static final int INTERACTING_METHOD_VARIATION = 3;
+ public static final int INTERACTING_METHOD_BACKGROUND_GENERATION = 4;
+ public static final int STATUS_DOING = 1;
+ public static final int STATUS_FAIL = 2;
+ public static final int STATUS_SUCCESS = 3;
+
+ private GenerateImage() {
+ }
+ }
+
+ public static class MetadataKey {
+ public static final String KB_UUID = "kb_uuid";
+ public static final String KB_ITEM_UUID = "kb_item_uuid";
+ public static final String ENGINE_NAME = "engine_name";
+ public static final String SEARCH_UUID = "search_uuid";
+
+ private MetadataKey() {
+ }
+ }
+
+ public static class SysConfigKey {
+ public static final String DEEPSEEK_SETTING = "deepseek_setting";
+ public static final String OPENAI_SETTING = "openai_setting";
+ public static final String DASHSCOPE_SETTING = "dashscope_setting";
+ public static final String QIANFAN_SETTING = "qianfan_setting";
+ public static final String OLLAMA_SETTING = "ollama_setting";
+ public static final String SILICONFLOW_SETTING = "siliconflow_setting";
+ public static final String GOOGLE_SETTING = "google_setting";
+ public static final String BING_SETTING = "bing_setting";
+ public static final String BAIDU_SETTING = "baidu_setting";
+ public static final String REQUEST_TEXT_RATE_LIMIT = "request_text_rate_limit";
+ public static final String REQUEST_IMAGE_RATE_LIMIT = "request_image_rate_limit";
+ public static final String CONVERSATION_MAX_NUM = "conversation_max_num";
+ public static final String QUOTA_BY_TOKEN_DAILY = "quota_by_token_daily";
+ public static final String QUOTA_BY_TOKEN_MONTHLY = "quota_by_token_monthly";
+ public static final String QUOTA_BY_REQUEST_DAILY = "quota_by_request_daily";
+ public static final String QUOTA_BY_REQUEST_MONTHLY = "quota_by_request_monthly";
+ public static final String QUOTA_BY_IMAGE_DAILY = "quota_by_image_daily";
+ public static final String QUOTA_BY_IMAGE_MONTHLY = "quota_by_image_monthly";
+ public static final String QUOTA_BY_QA_ASK_DAILY = "quota_by_qa_ask_daily";
+ public static final String STORAGE_LOCATION = "storage_location";
+ public static final String STORAGE_LOCATION_ALI_OSS = "storage_location_ali_oss";
+ public static final String ASR_SETTING = "asr_setting";
+ public static final String TTS_SETTING = "tts_setting";
+
+ private SysConfigKey() {
+ }
+ }
+
+ public static class ModelPlatform {
+ public static final String DEEPSEEK = "deepseek";
+ public static final String OPENAI = "openai";
+ public static final String DASHSCOPE = "dashscope";
+ public static final String QIANFAN = "qianfan";
+ public static final String OLLAMA = "ollama";
+ public static final String SILICONFLOW = "siliconflow";
+
+ private ModelPlatform() {
+ }
+
+ // 获取所有公共静态常量(String类型的值)的列表
+ public static List getModelConstants() {
+ List list = new ArrayList<>();
+ Class clazz = ModelPlatform.class;
+ for (Field field : clazz.getDeclaredFields()) {
+ try {
+ String value = (String) field.get(null);
+ list.add(value);
+ } catch (ReflectiveOperationException e) {
+ log.error("error", e);
+ }
+
+ }
+ return list;
+ }
+ }
+
+ public static class ModelType {
+ public static final String TEXT = "text";
+ public static final String IMAGE = "image";
+ public static final String EMBEDDING = "embedding";
+ public static final String RERANK = "rerank";
+ public static final String ASR = "asr";
+ public static final String TTS = "tts";
+
+ private ModelType() {
+ }
+
+ public static List getModelType() {
+ List list = new ArrayList<>();
+ Class clazz = ModelType.class;
+ for (Field field : clazz.getDeclaredFields()) {
+ try {
+ String value = (String) field.get(null);
+ list.add(value);
+ } catch (ReflectiveOperationException e) {
+ log.error("error", e);
+ }
+
+ }
+ return list;
+ }
+ }
+
+ public static class SearchEngineName {
+ public static final String GOOGLE = "google";
+ public static final String BING = "bing";
+ public static final String BAIDU = "baidu";
+ public static final String[] GOOGLE_COUNTRIES = {"cn", "af", "al", "dz", "as", "ad", "ao", "ai", "aq", "ag", "ar", "am", "aw", "au", "at", "az", "bs", "bh", "bd", "bb", "by", "be", "bz", "bj", "bm", "bt", "bo", "ba", "bw", "bv", "br", "io", "bn", "bg", "bf", "bi", "kh", "cm", "ca", "cv", "ky", "cf", "td", "cl", "cx", "cc", "co", "km", "cg", "cd", "ck", "cr", "ci", "hr", "cu", "cy", "cz", "dk", "dj", "dm", "do", "ec", "eg", "sv", "gq", "er", "ee", "et", "fk", "fo", "fj", "fi", "fr", "gf", "pf", "tf", "ga", "gm", "ge", "de", "gh", "gi", "gr", "gl", "gd", "gp", "gu", "gt", "gn", "gw", "gy", "ht", "hm", "va", "hn", "hk", "hu", "is", "in", "id", "ir", "iq", "ie", "il", "it", "jm", "jp", "jo", "kz", "ke", "ki", "kp", "kr", "kw", "kg", "la", "lv", "lb", "ls", "lr", "ly", "li", "lt", "lu", "mo", "mk", "mg", "mw", "my", "mv", "ml", "mt", "mh", "mq", "mr", "mu", "yt", "mx", "fm", "md", "mc", "mn", "ms", "ma", "mz", "mm", "na", "nr", "np", "nl", "an", "nc", "nz", "ni", "ne", "ng", "nu", "nf", "mp", "no", "om", "pk", "pw", "ps", "pa", "pg", "py", "pe", "ph", "pn", "pl", "pt", "pr", "qa", "re", "ro", "ru", "rw", "sh", "kn", "lc", "pm", "vc", "ws", "sm", "st", "sa", "sn", "rs", "sc", "sl", "sg", "sk", "si", "sb", "so", "za", "gs", "es", "lk", "sd", "sr", "sj", "sz", "se", "ch", "sy", "tw", "tj", "tz", "th", "tl", "tg", "tk", "to", "tt", "tn", "tr", "tm", "tc", "tv", "ug", "ua", "ae", "uk", "gb", "us", "um", "uy", "uz", "vu", "ve", "vn", "vg", "vi", "wf", "eh", "ye", "zm", "zw"};
+ public static final String[] GOOGLE_LANGUAGES = {"zh-cn", "zh-tw", "af", "ak", "sq", "ws", "am", "ar", "hy", "az", "eu", "be", "bem", "bn", "bh", "xx-bork", "bs", "br", "bg", "bt", "km", "ca", "chr", "ny", "co", "hr", "cs", "da", "nl", "xx-elmer", "en", "eo", "et", "ee", "fo", "tl", "fi", "fr", "fy", "gaa", "gl", "ka", "de", "el", "kl", "gn", "gu", "xx-hacker", "ht", "ha", "haw", "iw", "hi", "hu", "is", "ig", "id", "ia", "ga", "it", "ja", "jw", "kn", "kk", "rw", "rn", "xx-klingon", "kg", "ko", "kri", "ku", "ckb", "ky", "lo", "la", "lv", "ln", "lt", "loz", "lg", "ach", "mk", "mg", "ms", "ml", "mt", "mv", "mi", "mr", "mfe", "mo", "mn", "sr-me", "my", "ne", "pcm", "nso", "no", "nn", "oc", "or", "om", "ps", "fa", "xx-pirate", "pl", "pt", "pt-br", "pt-pt", "pa", "qu", "ro", "rm", "nyn", "ru", "gd", "sr", "sh", "st", "tn", "crs", "sn", "sd", "si", "sk", "sl", "so", "es", "es-419", "su", "sw", "sv", "tg", "ta", "tt", "te", "th", "ti", "to", "lua", "tum", "tr", "tk", "tw", "ug", "uk", "ur", "uz", "vu", "vi", "cy", "wo", "xh", "yi", "yo", "zu"};
+
+ private SearchEngineName() {
+ }
+ }
+
+ public static class SSEEventName {
+ public static final String START = "[START]";
+ public static final String DONE = "[DONE]";
+ public static final String ERROR = "[ERROR]";
+ public static final String META = "[META]";
+ public static final String AUDIO = "[AUDIO]";
+ public static final String THINKING = "[THINKING]";
+ public static final String AI_SEARCH_SOURCE_LINKS = "[SOURCE_LINKS]";
+ public static final String WF_NODE_CHUNK = "[WF_NODE_CHUNK]";
+ public static final String WF_NODE_OUTPUT = "[WF_NODE_OUTPUT]";
+ public static final String STATE_CHANGED = "[STATE_CHANGED]";
+
+ private SSEEventName() {
+ }
+ }
+
+ public static class SSEEventData {
+
+ /**
+ * 状态:问题分析中
+ * 如敏感词校验等
+ */
+ public static final String STATE_QUESTION_ANALYSING = """
+ {"state":"question_analysing","remark":"问题分析中"}
+ """;
+
+ public static final String STATE_KNOWLEDGE_SEARCHING = """
+ {"state":"knowledge_searching","remark":"知识库搜索中"}
+ """;
+ //使用 THINKING 事件代替
+ public static final String STATE_THINKING = """
+ {"state":"thinking","remark":"推理中"}
+ """;
+ public static final String STATE_RESPONDING = """
+ {"state":"responding","remark":"回答中"}
+ """;
+ }
+
+ public static class WorkflowConstant {
+ public static final String DEFAULT_INPUT_PARAM_NAME = "input";
+ public static final String DEFAULT_OUTPUT_PARAM_NAME = "output";
+ public static final String DEFAULT_ERROR_OUTPUT_PARAM_NAME = "error_msg";
+ public static final String HUMAN_FEEDBACK_KEY = "human_feedback";
+ public static final int NODE_PROCESS_STATUS_READY = 1;
+ public static final int NODE_PROCESS_STATUS_DOING = 2;
+ public static final int NODE_PROCESS_STATUS_SUCCESS = 3;
+ public static final int NODE_PROCESS_STATUS_FAIL = 4;
+
+ public static final int WORKFLOW_PROCESS_STATUS_READY = 1;
+ public static final int WORKFLOW_PROCESS_STATUS_DOING = 2;
+ public static final int WORKFLOW_PROCESS_STATUS_SUCCESS = 3;
+ public static final int WORKFLOW_PROCESS_STATUS_FAIL = 4;
+ public static final int WORKFLOW_PROCESS_STATUS_WAITING_INPUT = 5;
+
+ public static final int WORKFLOW_NODE_PROCESS_TYPE_NORMAL = 1;
+ public static final int WORKFLOW_NODE_PROCESS_TYPE_CONDITIONAL = 2;
+ public static final int WORKFLOW_NODE_PROCESS_TYPE_PARALLEL = 3;
+
+ public static final int MAIL_SENDER_TYPE_SYS = 1;
+ public static final int MAIL_SENDER_TYPE_CUSTOM = 2;
+ }
+
+ public static class TokenEstimator {
+ public static String OPENAI = "openai";
+ public static String HUGGING_FACE = "huggingface";
+ public static String QWEN = "qwen";
+
+ public static List ALL = List.of(OPENAI, HUGGING_FACE, QWEN);
+ }
+
+ public static class EmbeddingModel {
+ public static String ALL_MINILM_L6 = "local:all-minilm-l6-v2";
+ }
+
+ public static class McpConstant {
+ public static final String TRANSPORT_TYPE_SSE = "sse";
+ public static final String TRANSPORT_TYPE_STDIO = "stdio";
+ public static final String INSTALL_TYPE_REMOTE = "remote";
+ public static final String INSTALL_TYPE_WASM = "wasm";
+ public static final String INSTALL_TYPE_LOCAL = "local";
+ public static final String INSTALL_TYPE_DOCKER = "docker";
+ }
+
+ public static class TtsConstant {
+
+ /**
+ * 语音合成器位置-客户端
+ */
+ public static final String SYNTHESIZER_CLIENT = "client";
+ /**
+ * 语音合成器位置-服务端
+ */
+ public static final String SYNTHESIZER_SERVER = "server";
+
+ /**
+ * 通义默认语音音色-龙应严(义正严辞女声)
+ */
+ public static final String DASHSCOPE_DEFAULT_VOICE = "longyingyan";
+ }
+
+ public static class CustomChatRequestParameterKeys {
+ /**
+ * 是否开启思考模式,默认不开启
+ */
+ public static final String ENABLE_THINKING = "enable_thinking";
+
+ private CustomChatRequestParameterKeys() {
+ }
+ }
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/cosntant/RedisKeyConstant.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/cosntant/RedisKeyConstant.java
new file mode 100644
index 00000000..77e86997
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/cosntant/RedisKeyConstant.java
@@ -0,0 +1,116 @@
+package org.ruoyi.workflow.cosntant;
+
+public class RedisKeyConstant {
+
+ /**
+ * 账号激活码的key
+ */
+ public static final String AUTH_ACTIVE_CODE = "auth:activeCode:{0}";
+ /**
+ * 注册时使用的验证码
+ * 参数:验证码id
+ * 值:验证码
+ */
+ public static final String AUTH_REGISTER_CAPTCHA_ID = "auth:register:captcha:{0}";
+ /**
+ * 登录时使用的验证码id缓存
+ * 参数:验证码id
+ * 值:验证码
+ */
+ public static final String AUTH_LOGIN_CAPTCHA_ID = "auth:login:captcha:{0}";
+ /**
+ * 注册验证码缓存
+ * 参数:验证码
+ * 值:1
+ */
+ public static final String AUTH_CAPTCHA = "auth:register:captcha:{0}";
+ /**
+ * 登录token
+ * {0}:用户token
+ * 值:json.format(user)
+ */
+ public static final String USER_TOKEN = "user:token:{0}";
+ /**
+ * 参数:游客的uuid
+ * 值:json.format(guest)
+ */
+ public static final String GUEST_UUID = "guest:uuid:{0}";
+ /**
+ * 登录失败次数
+ * 参数:用户邮箱
+ * 值: 失效次数
+ */
+ public static final String LOGIN_FAIL_COUNT = "user:login:fail:{0}";
+ /**
+ * 用户是否请求ai中
+ * 参数:用户id
+ * 值: 1或者0
+ */
+ public static final String USER_ASKING = "user:asking:{0}";
+ /**
+ * 用户是否画画中
+ * 参数:用户id
+ * 值: 1或者0
+ */
+ public static final String USER_DRAWING = "user:drawing:{0}";
+ /**
+ * 用户提问限流计数
+ * 参数:用户id
+ * 值: 当前时间窗口访问量
+ */
+ public static final String USER_REQUEST_TEXT_TIMES = "user:request-text:times:{0}";
+ public static final String USER_REQUEST_IMAGE_TIMES = "user:request-image:times:{0}";
+ /**
+ * 用户信息缓存
+ * 参数:用户id
+ * 值: user object
+ */
+ public static final String USER_INFO = "user:info:";
+ /**
+ * 找回密码的请求绑在
+ * 参数:随机数
+ * 值: 用户id,用于校验后续流程中的重置密码使用
+ */
+ public static final String FIND_MY_PASSWORD = "user:find:password:{0}";
+ /**
+ * qa提问次数(每天)
+ * 参数:用户id:日期yyyyMMdd
+ * 值:提问数量
+ */
+ public static final String AQ_ASK_TIMES = "qa:ask:limit:{0}:{1}";
+ /**
+ * 知识库知识点生成数量
+ * 值: 用户id
+ */
+ public static final String QA_ITEM_CREATE_LIMIT = "aq:item:create:{0}";
+ /**
+ * 信号(重新生成知识库统计数据)
+ * 值:知识库uuid
+ */
+ public static final String KB_STATISTIC_RECALCULATE_SIGNAL = "kb:statistic:recalculate:signal";
+ public static final String STATISTIC = "statistic";
+ public static final String STATISTIC_USER = "user";
+ public static final String STATISTIC_KNOWLEDGE_BASE = "kb";
+ public static final String STATISTIC_TOKEN_COST = "token-cost";
+ public static final String STATISTIC_CONVERSATION = "conversation";
+ public static final String STATISTIC_IMAGE_COST = "image-cost";
+ public static final String TOKEN_USAGE_KEY = "token:usage:{0}";
+ /**
+ * 用户正在对文档进行索引
+ * 值:用户id
+ */
+ public static final String USER_INDEXING = "user:indexing:{0}";
+ /**
+ * 用户评论并发限制
+ * 值:用户id
+ */
+ public static final String DRAW_COMMENT_LIMIT_KEY = "user:draw:comment-submitting:{0}";
+ public static final String WORKFLOW_KEY = "workflow";
+ public static final String WORKFLOW_COMPONENTS = "workflow:components";
+ public static final String WORKFLOW_COMPONENT_START_KEY = "workflow:component:start";
+ public static final String WORKFLOW_COMPONENT_KEY = "workflow:component";
+ public static final String WORKFLOW_COPY_DOING = "workflow:copy:doing:{0}";
+
+ private RedisKeyConstant() {
+ }
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfAddReq.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfAddReq.java
new file mode 100644
index 00000000..f201919d
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfAddReq.java
@@ -0,0 +1,17 @@
+package org.ruoyi.workflow.dto.workflow;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+
+@Data
+@Validated
+public class WfAddReq {
+
+ @NotBlank
+ private String title;
+
+ private String remark;
+
+ private Boolean isPublic;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfBaseInfoUpdateReq.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfBaseInfoUpdateReq.java
new file mode 100644
index 00000000..7b12335b
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfBaseInfoUpdateReq.java
@@ -0,0 +1,15 @@
+package org.ruoyi.workflow.dto.workflow;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+
+@Validated
+@Data
+public class WfBaseInfoUpdateReq {
+ @NotBlank
+ private String uuid;
+ private String title;
+ private String remark;
+ private Boolean isPublic;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfComponentReq.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfComponentReq.java
new file mode 100644
index 00000000..84da556f
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfComponentReq.java
@@ -0,0 +1,18 @@
+package org.ruoyi.workflow.dto.workflow;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+
+@Data
+@Validated
+public class WfComponentReq {
+ private String uuid;
+ @NotBlank(message = "标题不能为空")
+ private String name;
+ @NotBlank(message = "标题不能为空")
+ private String title;
+ private String remark;
+ private Boolean isEnable;
+ private Integer displayOrder;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfComponentSearchReq.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfComponentSearchReq.java
new file mode 100644
index 00000000..5ebc9dff
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfComponentSearchReq.java
@@ -0,0 +1,9 @@
+package org.ruoyi.workflow.dto.workflow;
+
+import lombok.Data;
+
+@Data
+public class WfComponentSearchReq {
+ private String title;
+ private Boolean isEnable;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfEdgeReq.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfEdgeReq.java
new file mode 100644
index 00000000..40eb9a19
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfEdgeReq.java
@@ -0,0 +1,25 @@
+package org.ruoyi.workflow.dto.workflow;
+
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+
+@Validated
+@Data
+public class WfEdgeReq {
+ private Long id;
+ @NotBlank
+ private String uuid;
+ @Min(1)
+ private Long workflowId;
+ @NotBlank
+ private String sourceNodeUuid;
+ private String sourceHandle;
+ @NotBlank
+ private String targetNodeUuid;
+ /**
+ * 是否新增
+ */
+ private Boolean isNew;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfNodeDto.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfNodeDto.java
new file mode 100644
index 00000000..b5c5baa5
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfNodeDto.java
@@ -0,0 +1,32 @@
+package org.ruoyi.workflow.dto.workflow;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+
+@Validated
+@Data
+public class WfNodeDto {
+ private Long id;
+ @NotBlank
+ @Size(min = 32, max = 32)
+ private String uuid;
+ private Long workflowId;
+ @Min(1)
+ private Long workflowComponentId;
+ @NotBlank
+ private String title;
+ private String remark;
+ @NotNull
+ private ObjectNode inputConfig;
+ @NotNull
+ private ObjectNode nodeConfig;
+ @NotNull
+ private Double positionX;
+ @NotNull
+ private Double positionY;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfRuntimeNodeDto.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfRuntimeNodeDto.java
new file mode 100644
index 00000000..4ca93525
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfRuntimeNodeDto.java
@@ -0,0 +1,17 @@
+package org.ruoyi.workflow.dto.workflow;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+
+@Validated
+@Data
+public class WfRuntimeNodeDto {
+ private Long id;
+ private String uuid;
+ private Long workflowRuntimeId;
+ private Long nodeId;
+ private ObjectNode input;
+ private ObjectNode output;
+ private Integer status;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfRuntimeResp.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfRuntimeResp.java
new file mode 100644
index 00000000..b5f5fd8a
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfRuntimeResp.java
@@ -0,0 +1,22 @@
+package org.ruoyi.workflow.dto.workflow;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class WfRuntimeResp {
+ private Long id;
+ private String uuid;
+ private Long workflowId;
+ private ObjectNode input;
+ private ObjectNode output;
+ private Integer status;
+ private String statusRemark;
+
+ private String workflowUuid;
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime createTime;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfSearchReq.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfSearchReq.java
new file mode 100644
index 00000000..939c3970
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WfSearchReq.java
@@ -0,0 +1,10 @@
+package org.ruoyi.workflow.dto.workflow;
+
+import lombok.Data;
+
+@Data
+public class WfSearchReq {
+ private String title;
+ private Boolean isEnable;
+ private Boolean isPublic;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowResp.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowResp.java
new file mode 100644
index 00000000..1e14b116
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowResp.java
@@ -0,0 +1,22 @@
+package org.ruoyi.workflow.dto.workflow;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+public class WorkflowResp {
+ private Long id;
+ private String uuid;
+ private String title;
+ private String remark;
+ private Boolean isPublic;
+ private Long userId;
+ private String userUuid;
+ private String userName;
+ private List nodes;
+ private List edges;
+ private LocalDateTime createTime;
+ private LocalDateTime updateTime;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowResumeReq.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowResumeReq.java
new file mode 100644
index 00000000..27680932
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowResumeReq.java
@@ -0,0 +1,8 @@
+package org.ruoyi.workflow.dto.workflow;
+
+import lombok.Data;
+
+@Data
+public class WorkflowResumeReq {
+ private String feedbackContent;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowRunReq.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowRunReq.java
new file mode 100644
index 00000000..54a32a03
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowRunReq.java
@@ -0,0 +1,14 @@
+package org.ruoyi.workflow.dto.workflow;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class WorkflowRunReq {
+ private List inputs;
+ private String uuid;
+
+
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowUpdateReq.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowUpdateReq.java
new file mode 100644
index 00000000..3e26c490
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/dto/workflow/WorkflowUpdateReq.java
@@ -0,0 +1,24 @@
+package org.ruoyi.workflow.dto.workflow;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+@Validated
+@Data
+public class WorkflowUpdateReq {
+ @NotBlank
+ private String uuid;
+ @Size(min = 1)
+ private List nodes;
+ @NotNull
+ private List edges;
+
+ private List deleteNodes;
+
+ private List deleteEdges;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/BaseEntity.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/BaseEntity.java
new file mode 100644
index 00000000..b657eb16
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/BaseEntity.java
@@ -0,0 +1,29 @@
+package org.ruoyi.workflow.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+public class BaseEntity implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
+ @TableField(value = "create_time")
+ private LocalDateTime createTime;
+
+ @TableField(value = "update_time")
+ private LocalDateTime updateTime;
+
+ @Schema(title = "是否删除(0:未删除,1:已删除)")
+ @TableField(value = "is_deleted")
+ private Boolean isDeleted;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/User.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/User.java
new file mode 100644
index 00000000..20ac4dc0
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/User.java
@@ -0,0 +1,66 @@
+package org.ruoyi.workflow.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 java.time.LocalDateTime;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+@TableName("adi_user")
+@Schema(title = "User对象")
+public class User extends BaseEntity {
+
+ @Schema(name = "用户名称")
+ @TableField("name")
+ private String name;
+
+ @TableField("email")
+ private String email;
+
+ @TableField("password")
+ private String password;
+
+ @TableField("uuid")
+ private String uuid;
+
+ @Schema(name = "上下文理解中需要携带的消息对数量(提示词及回复)")
+ @TableField("understand_context_msg_pair_num")
+ private Integer understandContextMsgPairNum;
+
+ @Schema(name = "token quota in one day")
+ @TableField("quota_by_token_daily")
+ private Integer quotaByTokenDaily;
+
+ @Schema(name = "token quota in one month")
+ @TableField("quota_by_token_monthly")
+ private Integer quotaByTokenMonthly;
+
+ @Schema(name = "request quota in one day")
+ @TableField("quota_by_request_daily")
+ private Integer quotaByRequestDaily;
+
+ @Schema(name = "request quota in one month")
+ @TableField("quota_by_request_monthly")
+ private Integer quotaByRequestMonthly;
+
+ @TableField("quota_by_image_daily")
+ private Integer quotaByImageDaily;
+
+ @TableField("quota_by_image_monthly")
+ private Integer quotaByImageMonthly;
+
+ @TableField("user_status")
+ private UserStatusEnum userStatus;
+
+ @TableField("active_time")
+ private LocalDateTime activeTime;
+
+ @Schema(title = "是否管理员(0:否,1:是)")
+ @TableField(value = "is_admin")
+ private Boolean isAdmin;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/Workflow.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/Workflow.java
new file mode 100644
index 00000000..4334b069
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/Workflow.java
@@ -0,0 +1,37 @@
+package org.ruoyi.workflow.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 java.io.Serial;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("t_workflow")
+@Schema(title = "工作流定义 | workflow definition")
+public class Workflow extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @TableField("uuid")
+ private String uuid;
+
+ @TableField("title")
+ private String title;
+
+ @TableField("remark")
+ private String remark;
+
+ @TableField("user_id")
+ private Long userId;
+
+ @TableField("is_public")
+ private Boolean isPublic;
+
+ @TableField("is_enable")
+ private Boolean isEnable;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowComponent.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowComponent.java
new file mode 100644
index 00000000..d75e92c8
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowComponent.java
@@ -0,0 +1,37 @@
+package org.ruoyi.workflow.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 java.io.Serial;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName(value = "t_workflow_component", autoResultMap = true)
+@Schema(title = "工作流组件")
+public class WorkflowComponent extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @TableField("uuid")
+ private String uuid;
+
+ @TableField("name")
+ private String name;
+
+ @TableField("title")
+ private String title;
+
+ @TableField("remark")
+ private String remark;
+
+ @TableField("display_order")
+ private Integer displayOrder;
+
+ @TableField("is_enable")
+ private Boolean isEnable;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowEdge.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowEdge.java
new file mode 100644
index 00000000..0a9bffda
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowEdge.java
@@ -0,0 +1,34 @@
+package org.ruoyi.workflow.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 java.io.Serial;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("t_workflow_edge")
+@Schema(title = "工作流定义-边 | workflow definition edge")
+public class WorkflowEdge extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @TableField("uuid")
+ private String uuid;
+
+ @TableField("workflow_id")
+ private Long workflowId;
+
+ @TableField("source_node_uuid")
+ private String sourceNodeUuid;
+
+ @TableField("source_handle")
+ private String sourceHandle;
+
+ @TableField("target_node_uuid")
+ private String targetNodeUuid;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowNode.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowNode.java
new file mode 100644
index 00000000..cc15504a
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowNode.java
@@ -0,0 +1,46 @@
+package org.ruoyi.workflow.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 java.io.Serial;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName(value = "t_workflow_node", autoResultMap = true)
+@Schema(title = "工作流定义-节点 | workflow definition node")
+public class WorkflowNode extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @TableField("uuid")
+ private String uuid;
+
+ @TableField("workflow_id")
+ private Long workflowId;
+
+ @TableField("workflow_component_id")
+ private Long workflowComponentId;
+
+ @TableField("title")
+ private String title;
+
+ @TableField("remark")
+ private String remark;
+
+ @TableField(value = "input_config")
+ private String inputConfig;
+
+ @TableField(value = "node_config")
+ private String nodeConfig;
+
+ @TableField("position_x")
+ private Double positionX;
+
+ @TableField("position_y")
+ private Double positionY;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntime.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntime.java
new file mode 100644
index 00000000..557de3f4
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntime.java
@@ -0,0 +1,40 @@
+package org.ruoyi.workflow.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 java.io.Serial;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName(value = "t_workflow_runtime", autoResultMap = true)
+@Schema(title = "工作流运行时 | Workflow runtime")
+public class WorkflowRuntime extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @TableField("uuid")
+ private String uuid;
+
+ @TableField("user_id")
+ private Long userId;
+
+ @TableField("workflow_id")
+ private Long workflowId;
+
+ @TableField(value = "input")
+ private String input;
+
+ @TableField(value = "output")
+ private String output;
+
+ @TableField("status")
+ private Integer status;
+
+ @TableField("status_remark")
+ private String statusRemark;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntimeNode.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntimeNode.java
new file mode 100644
index 00000000..271d9eb7
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntimeNode.java
@@ -0,0 +1,42 @@
+package org.ruoyi.workflow.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 java.io.Serial;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName(value = "t_workflow_runtime_node", autoResultMap = true)
+@Schema(title = "工作流实例-节点 | Workflow runtime - node")
+public class WorkflowRuntimeNode extends BaseEntity {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @TableField("uuid")
+ private String uuid;
+
+ @TableField("user_id")
+ private Long userId;
+
+ @TableField("workflow_runtime_id")
+ private Long workflowRuntimeId;
+
+ @TableField("node_id")
+ private Long nodeId;
+
+ @TableField(value = "input")
+ private String input;
+
+ @TableField(value = "output")
+ private String output;
+
+ @TableField("status")
+ private Integer status;
+
+ @TableField("status_remark")
+ private String statusRemark;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/AiModelStatus.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/AiModelStatus.java
new file mode 100644
index 00000000..0dec4a94
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/AiModelStatus.java
@@ -0,0 +1,14 @@
+package org.ruoyi.workflow.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum AiModelStatus implements BaseEnum {
+ ACTIVE(1, "启用"),
+ INACTIVE(2, "停用");
+
+ private final Integer value;
+ private final String desc;
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/BaseEnum.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/BaseEnum.java
new file mode 100644
index 00000000..66a2fb63
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/BaseEnum.java
@@ -0,0 +1,12 @@
+package org.ruoyi.workflow.enums;
+
+import com.baomidou.mybatisplus.annotation.IEnum;
+
+public interface BaseEnum extends IEnum {
+ /**
+ * 获取对应名称
+ *
+ * @return String
+ */
+ String getDesc();
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/ErrorEnum.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/ErrorEnum.java
new file mode 100644
index 00000000..f9de801e
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/ErrorEnum.java
@@ -0,0 +1,127 @@
+package org.ruoyi.workflow.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum ErrorEnum {
+ SUCCESS("00000", "成功"),
+ A_URL_NOT_FOUND("A0001", "地址不存在"),
+ A_PARAMS_ERROR("A0002", "参数校验不通过"),
+ A_REQUEST_TOO_MUCH("A0003", "访问次数太多"),
+ A_LOGIN_ERROR("A0004", "登陆失败,账号或密码错误"),
+ A_LOGIN_ERROR_MAX("A0005", "失败次数太多,请输入验证码重试"),
+ A_LOGIN_CAPTCHA_ERROR("A0006", "验证码不正确"),
+ A_USER_NOT_EXIST("A0007", "用户不存在"),
+ A_CONVERSATION_NOT_EXIST("A0008", "对话不存在"),
+ A_IMAGE_NUMBER_ERROR("A0009", "图片数量不对"),
+ A_IMAGE_SIZE_ERROR("A0010", "图片尺寸不对"),
+ A_FILE_NOT_EXIST("A0011", "文件不存在"),
+ A_DRAWING("A0012", "作图还未完成"),
+ A_USER_EXIST("A0013", "账号已经存在,请使用账号密码登录"),
+ A_FIND_PASSWORD_CODE_ERROR("A0014", "重置码已过期或不存在"),
+ A_USER_WAIT_CONFIRM("A0015", "用户未激活"),
+ A_USER_NOT_AUTH("A0016", "用户无权限"),
+ A_DATA_NOT_FOUND("A0017", "数据不存在"),
+ A_UPLOAD_FAIL("A0018", "上传失败"),
+ A_QA_ASK_LIMIT("A0019", "请求次数太多"),
+ A_QA_ITEM_LIMIT("A0020", "知识点生成已超额度"),
+ A_CONVERSATION_EXIST("A0021", "会话(角色)已存在"),
+ A_MODEL_NOT_FOUND("A0022", "模型不存在"),
+ A_MODEL_ALREADY_EXIST("A0023", "模型已存在"),
+ A_CONVERSATION_NOT_FOUND("A0024", "会话(角色)找不到"),
+ A_AI_IMAGE_NOT_FOUND("A0024", "图片找不到"),
+ A_ENABLE_MODEL_NOT_FOUND("A0025", "没有可用的模型"),
+ A_DOC_INDEX_DOING("A0026", "文档索引正在进行中,请稍后重试"),
+ A_PRESET_CONVERSATION_NOT_EXIST("A0027", "预设会话或角色不存在"),
+ A_CONVERSATION_TITLE_EXIST("A0028", "会话(角色)标题已存在"),
+ A_AI_IMAGE_NO_AUTH("A0029", "无权限查看该图片"),
+ A_USER_NOT_FOUND("A0030", "用户不存在"),
+ A_ACTIVE_CODE_INVALID("A0031", "激活码已失效"),
+ A_OLD_PASSWORD_INVALID("A0032", "原密码不正确"),
+ A_OPT_TOO_FREQUENTLY("A0032", "操作太频繁"),
+ A_DRAW_NOT_FOUND("A00033", "绘图记录找不到"),
+ A_WF_NOT_FOUND("A00034", "工作流找不到"),
+ A_WF_DISABLED("A0035", "工作流已停用"),
+ A_WF_NODE_NOT_FOUND("A0036", "工作流节点找不到"),
+ A_WF_NODE_CONFIG_NOT_FOUND("A0037", "工作流节点配置找不到"),
+ A_WF_NODE_CONFIG_ERROR("A0038", "工作流节点配置异常"),
+ A_WF_INPUT_INVALID("A0039", "工作流输入参数错误"),
+ A_WF_INPUT_MISSING("A0040", "工作流输入缺少参数"),
+ A_WF_MULTIPLE_START_NODE("A0041", "多个开始节点"),
+ A_WF_START_NODE_NOT_FOUND("A0042", "没有开始节点"),
+ A_WF_END_NODE_NOT_FOUND("A0043", "没有结束节点"),
+ A_WF_EDGE_NOT_FOUND("A0044", "工作流的边找不到"),
+ A_WF_RUNTIME_NOT_FOUND("A00045", "工作流运行时数据找不到"),
+ A_SEARCH_QUERY_IS_EMPTY("A00046", "搜索内容不能为空"),
+ A_WF_COMPONENT_NOT_FOUND("A00047", "工作流基础组件找不到"),
+ A_WF_RESUME_FAIL("A00048", "工作流恢复执行时失败"),
+ A_MAIL_SENDER_EMPTY("A00049", "邮件发送人不能为空"),
+ A_MAIL_SENDER_CONFIG_ERROR("A00050", "邮件发送人配置错误"),
+ A_MAIL_RECEIVER_EMPTY("A00051", "邮件接收人不能为空"),
+ A_MCP_SERVER_NOT_FOUND("A00052", "MCP服务找不到"),
+ A_USER_MCP_SERVER_NOT_FOUND("A00053", "用户的MCP服务找不到"),
+ A_PARAMS_INVALID_BY_("A00054", "参数校验异常:{0}"),
+ A_AI_MESSAGE_NOT_FOUND("A00055", "找不到AI的消息"),
+ A_USER_QUESTION_NOT_FOUND("A00056", "用户问题不存在"),
+ A_PLATFORM_NOT_MATCH("A0057", "平台不匹配"),
+ B_UNCAUGHT_ERROR("B0001", "未捕捉异常"),
+ B_COMMON_ERROR("B0002", "业务出错"),
+ B_GLOBAL_ERROR("B0003", "全局异常"),
+ B_SAVE_IMAGE_ERROR("B0004", "保存图片异常"),
+ B_FIND_IMAGE_404("B0005", "无法找到图片"),
+ B_DAILY_QUOTA_USED("B0006", "今天额度已经用完"),
+ B_MONTHLY_QUOTA_USED("B0007", "当月额度已经用完"),
+ B_LLM_NOT_SUPPORT("B0008", "LLM不支持该功能"),
+ B_LLM_SECRET_KEY_NOT_SET("B0009", "LLM的secret key没设置"),
+ B_MESSAGE_NOT_FOUND("B0008", "消息不存在"),
+ B_LLM_SERVICE_DISABLED("B0009", "LLM服务不可用"),
+ B_KNOWLEDGE_BASE_IS_EMPTY("B0010", "知识库内容为空"),
+ B_NO_ANSWER("B0011", "[无答案]"),
+ B_SAVE_FILE_ERROR("B0012", "保存文件异常"),
+ B_BREAK_SEARCH("B0013", "中断搜索"),
+ B_GRAPH_FILTER_NOT_FOUND("B0014", "图过滤器未定义"),
+ B_DB_ERROR("B0015", "数据库查询异常"),
+ B_ACTIVE_USER_ERROR("B0016", "激活用户失败"),
+ B_RESET_PASSWORD_ERROR("B0017", "重置密码失败"),
+ B_IMAGE_LOAD_ERROR("B0018", "加载图片失败"),
+ B_IO_EXCEPTION("B0019", "IO异常"),
+ B_SERVER_EXCEPTION("B0020", "服务端异常"),
+ B_DELETE_FILE_ERROR("B0021", "删除文件异常"),
+ B_WF_RUN_ERROR("B0022", "工作流运行异常"),
+ B_WF_NODE_DEFINITION_NOT_FOUND("B0023", "工作流节点定义找不到"),
+ B_DIR_CREATE_FAIL("B0024", "创建目录失败"),
+ B_LLM_TEMPERATURE_ERROR("B0025", "采样温度应该在 0.1-1之间"),
+ B_ASR_SETTING_NOT_FOUND("B0026", "语音识别设置未找到"),
+ B_URL_INVALID("B0027", "不是有效的网络地址"),
+ B_ASR_MODEL_NOT_FOUND("B0028", "语音识别模型未找到"),
+ B_TTS_SETTING_NOT_FOUND("B0029", "语音合成设置未找到"),
+ B_TTS_MODEL_NOT_FOUND("B0030", "语音合成模型未找到"),
+ B_VOICE_NOT_FOUND("B0031", "声音不存在"),
+ C_DRAW_FAIL("C0001", "大模型生成图片失败,原因:{0}"),
+ C_ALI_OSS_CONFIG_ERROR("C0002", "阿里云OSS初始化失败,原因:{0}"),
+ C_LLM_RESPONSE_INVALID("C0003", "大模型生成结果内容无效"),
+ C_WF_COMPONENT_DELETED_FAIL_BY_USED("C0004", "工作流组件已经被使用,无法被删除,可先停用");
+
+ private final String code;
+ private final String info;
+
+ ErrorEnum(String code, String info) {
+ this.code = code;
+ this.info = info;
+ }
+
+ public static ErrorEnum getErrorEnum(String code) {
+ ErrorEnum result = null;
+ for (ErrorEnum c : ErrorEnum.values()) {
+ if (c.getCode().equals(code)) {
+ result = c;
+ break;
+ }
+ }
+ if (null == result) {
+ result = B_COMMON_ERROR;
+ }
+ return result;
+ }
+
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/UserStatusEnum.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/UserStatusEnum.java
new file mode 100644
index 00000000..2be9a950
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/UserStatusEnum.java
@@ -0,0 +1,23 @@
+package org.ruoyi.workflow.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+@Getter
+@AllArgsConstructor
+public enum UserStatusEnum implements BaseEnum {
+
+ WAIT_CONFIRM(1, "待验证"),
+ NORMAL(2, "正常"),
+ FREEZE(3, "冻结");
+
+ private final Integer value;
+ private final String desc;
+
+ public static UserStatusEnum getByValue(Integer val) {
+ return Arrays.stream(UserStatusEnum.values()).filter(item -> item.value.equals(val)).findFirst().orElse(null);
+ }
+
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/WfIODataTypeEnum.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/WfIODataTypeEnum.java
new file mode 100644
index 00000000..8f8989c6
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/enums/WfIODataTypeEnum.java
@@ -0,0 +1,25 @@
+package org.ruoyi.workflow.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+@Getter
+@AllArgsConstructor
+public enum WfIODataTypeEnum implements BaseEnum {
+ TEXT(1, "文本"),
+ NUMBER(2, "数字"),
+ OPTIONS(3, "下拉选项"),
+ FILES(4, "文件列表"),
+ BOOL(5, "布尔值"),
+ REF_INPUT(6, "引用节点的输入参数"),
+ REF_OUTPUT(7, "引用节点的输出参数");
+
+ private final Integer value;
+ private final String desc;
+
+ public static WfIODataTypeEnum getByValue(Integer val) {
+ return Arrays.stream(WfIODataTypeEnum.values()).filter(item -> item.value.equals(val)).findFirst().orElse(null);
+ }
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/exception/WorkflowBaseException.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/exception/WorkflowBaseException.java
new file mode 100644
index 00000000..198539ee
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/exception/WorkflowBaseException.java
@@ -0,0 +1,49 @@
+package org.ruoyi.workflow.exception;
+
+
+import org.ruoyi.workflow.enums.ErrorEnum;
+
+import java.text.MessageFormat;
+
+public class WorkflowBaseException extends RuntimeException {
+ private final String code;
+ private final String info;
+
+ private Object data;
+
+ public WorkflowBaseException(String code, String info) {
+ super(code + ":" + info);
+ this.code = code;
+ this.info = info;
+ }
+
+ public WorkflowBaseException(ErrorEnum errorEnum, String... infoValues) {
+ super(errorEnum.getCode() + ":" + MessageFormat.format(errorEnum.getInfo(), infoValues));
+ this.code = errorEnum.getCode();
+ if (infoValues.length > 0) {
+ this.info = MessageFormat.format(errorEnum.getInfo(), infoValues);
+ } else {
+ this.info = errorEnum.getInfo();
+ }
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getInfo() {
+ return info;
+ }
+
+ public Object getData() {
+ if (null != data) {
+ return data;
+ }
+ return getMessage();
+ }
+
+ public WorkflowBaseException setData(Object data) {
+ this.data = data;
+ return this;
+ }
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/helper/SSEEmitterHelper.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/helper/SSEEmitterHelper.java
new file mode 100644
index 00000000..74c51ad8
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/helper/SSEEmitterHelper.java
@@ -0,0 +1,143 @@
+package org.ruoyi.workflow.helper;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+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;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Service
+public class SSEEmitterHelper {
+
+ private static final Cache COMPLETED_SSE = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build();
+
+ @Resource
+ private StringRedisTemplate stringRedisTemplate;
+
+ public static void parseAndSendPartialMsg(SseEmitter sseEmitter, String name, String content) {
+ if (Boolean.TRUE.equals(COMPLETED_SSE.getIfPresent(sseEmitter))) {
+ log.warn("sseEmitter already completed,name:{}", name);
+ return;
+ }
+ String[] lines = content.split("[\\r\\n]", -1);
+ if (lines.length > 1) {
+ sendPartial(sseEmitter, name, " " + lines[0]);
+ for (int i = 1; i < lines.length; i++) {
+ sendPartial(sseEmitter, name, "-_wrap_-");
+ sendPartial(sseEmitter, name, " " + lines[i]);
+ }
+ } else {
+ sendPartial(sseEmitter, name, " " + content);
+ }
+// content = content.replaceAll("[\\r\\n]", "\ndata:");
+// sendPartial(sseEmitter, name, " " + content);
+ }
+
+ public static void sendPartial(SseEmitter sseEmitter, String name, String msg) {
+ if (Boolean.TRUE.equals(COMPLETED_SSE.getIfPresent(sseEmitter))) {
+ log.warn("sseEmitter already completed,name:{}", name);
+ return;
+ }
+ try {
+ if (StringUtils.isNotBlank(name)) {
+ sseEmitter.send(SseEmitter.event().name(name).data(msg));
+ } else {
+ sseEmitter.send(msg);
+ }
+ } catch (IOException ioException) {
+ log.error("stream onNext error", ioException);
+ }
+ }
+
+
+ public boolean checkOrComplete(User user, SseEmitter sseEmitter) {
+ //Check: rate limit
+ String requestTimesKey = MessageFormat.format(RedisKeyConstant.USER_REQUEST_TEXT_TIMES, user.getId());
+// if (!rateLimitHelper.checkRequestTimes(requestTimesKey, LocalCache.TEXT_RATE_LIMIT_CONFIG)) {
+// sendErrorAndComplete(user.getId(), sseEmitter, "访问太过频繁");
+// return false;
+// }
+
+ //Check: If still waiting response
+ String askingKey = MessageFormat.format(RedisKeyConstant.USER_ASKING, user.getId());
+ String askingVal = stringRedisTemplate.opsForValue().get(askingKey);
+ if (StringUtils.isNotBlank(askingVal)) {
+ sendErrorAndComplete(user.getId(), sseEmitter, "正在回复中...");
+ return false;
+ }
+ return true;
+ }
+
+
+ public void startSse(User user, SseEmitter sseEmitter, String data) {
+
+ String askingKey = MessageFormat.format(RedisKeyConstant.USER_ASKING, user.getId());
+ stringRedisTemplate.opsForValue().set(askingKey, "1", 15, TimeUnit.SECONDS);
+
+ try {
+ SseEmitter.SseEventBuilder builder = SseEmitter.event().name(AdiConstant.SSEEventName.START);
+ if (StringUtils.isNotBlank(data)) {
+ builder.data(data);
+ }
+ sseEmitter.send(builder);
+ } catch (IOException e) {
+ log.error("startSse error", e);
+ sseEmitter.completeWithError(e);
+ COMPLETED_SSE.put(sseEmitter, Boolean.TRUE);
+ stringRedisTemplate.delete(askingKey);
+ }
+ }
+
+ public void sendComplete(long userId, SseEmitter sseEmitter, String msg) {
+ if (Boolean.TRUE.equals(COMPLETED_SSE.getIfPresent(sseEmitter))) {
+ log.warn("sseEmitter already completed,userId:{}", userId);
+ delSseRequesting(userId);
+ return;
+ }
+ try {
+ sseEmitter.send(SseEmitter.event().name(AdiConstant.SSEEventName.DONE).data(msg));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ COMPLETED_SSE.put(sseEmitter, Boolean.TRUE);
+ delSseRequesting(userId);
+ sseEmitter.complete();
+ }
+ }
+
+
+ public void sendErrorAndComplete(long userId, SseEmitter sseEmitter, String errorMsg) {
+ if (Boolean.TRUE.equals(COMPLETED_SSE.getIfPresent(sseEmitter))) {
+ log.warn("sseEmitter already completed,ignore error:{}", errorMsg);
+ delSseRequesting(userId);
+ return;
+ }
+ try {
+ sseEmitter.send(SseEmitter.event().name(AdiConstant.SSEEventName.ERROR).data(Objects.toString(errorMsg, "")));
+ } catch (IOException e) {
+ log.warn("sendErrorAndComplete userId:{},errorMsg:{}", userId, errorMsg);
+ throw new RuntimeException(e);
+ } finally {
+ COMPLETED_SSE.put(sseEmitter, Boolean.TRUE);
+ delSseRequesting(userId);
+ sseEmitter.complete();
+ }
+ }
+
+ private void delSseRequesting(long userId) {
+ String askingKey = MessageFormat.format(RedisKeyConstant.USER_ASKING, userId);
+ stringRedisTemplate.delete(askingKey);
+ }
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowComponentMapper.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowComponentMapper.java
new file mode 100644
index 00000000..0935eb58
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowComponentMapper.java
@@ -0,0 +1,11 @@
+package org.ruoyi.workflow.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.ruoyi.workflow.entity.WorkflowComponent;
+
+@Mapper
+public interface WorkflowComponentMapper extends BaseMapper {
+ Integer countRefNodes(@Param("uuid") String uuid);
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowEdgeMapper.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowEdgeMapper.java
new file mode 100644
index 00000000..28d21dec
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowEdgeMapper.java
@@ -0,0 +1,9 @@
+package org.ruoyi.workflow.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.ruoyi.workflow.entity.WorkflowEdge;
+
+@Mapper
+public interface WorkflowEdgeMapper extends BaseMapper {
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowMapper.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowMapper.java
new file mode 100644
index 00000000..b8bb1988
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowMapper.java
@@ -0,0 +1,9 @@
+package org.ruoyi.workflow.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.ruoyi.workflow.entity.Workflow;
+
+@Mapper
+public interface WorkflowMapper extends BaseMapper {
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowNodeMapper.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowNodeMapper.java
new file mode 100644
index 00000000..380eec9d
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowNodeMapper.java
@@ -0,0 +1,10 @@
+package org.ruoyi.workflow.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.ruoyi.workflow.entity.WorkflowNode;
+
+@Mapper
+public interface WorkflowNodeMapper extends BaseMapper {
+ WorkflowNode getStartNode(long workflowId);
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowRunMapper.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowRunMapper.java
new file mode 100644
index 00000000..3e73dd6a
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowRunMapper.java
@@ -0,0 +1,13 @@
+package org.ruoyi.workflow.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.ruoyi.workflow.entity.WorkflowRuntime;
+
+@Mapper
+public interface WorkflowRunMapper extends BaseMapper {
+
+ Page pageByWfUuid(Page page, @Param("wfUuid") String wfUuid, @Param("userId") Long userId);
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowRuntimeNodeMapper.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowRuntimeNodeMapper.java
new file mode 100644
index 00000000..91345f1c
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/mapper/WorkflowRuntimeNodeMapper.java
@@ -0,0 +1,9 @@
+package org.ruoyi.workflow.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.ruoyi.workflow.entity.WorkflowRuntimeNode;
+
+@Mapper
+public interface WorkflowRuntimeNodeMapper extends BaseMapper {
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/service/WorkflowComponentService.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/service/WorkflowComponentService.java
new file mode 100644
index 00000000..18afff42
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/service/WorkflowComponentService.java
@@ -0,0 +1,119 @@
+package org.ruoyi.workflow.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+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.apache.commons.lang3.StringUtils;
+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.exception.WorkflowBaseException;
+import org.ruoyi.workflow.mapper.WorkflowComponentMapper;
+import org.ruoyi.workflow.util.PrivilegeUtil;
+import org.ruoyi.workflow.util.UuidUtil;
+import org.ruoyi.workflow.workflow.WfComponentNameEnum;
+import org.springframework.beans.BeanUtils;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+
+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;
+
+@Slf4j
+@Service
+public class WorkflowComponentService extends ServiceImpl {
+
+ @Lazy
+ @Resource
+ private WorkflowComponentService self;
+
+ @CacheEvict(cacheNames = {WORKFLOW_COMPONENTS, WORKFLOW_COMPONENT_START_KEY})
+ public WorkflowComponent addOrUpdate(WfComponentReq req) {
+ WorkflowComponent wfComponent;
+ if (StringUtils.isNotBlank(req.getUuid())) {
+ wfComponent = PrivilegeUtil.checkAndGetByUuid(req.getUuid(), this.query(), ErrorEnum.A_WF_COMPONENT_NOT_FOUND);
+
+ WorkflowComponent update = new WorkflowComponent();
+ BeanUtils.copyProperties(req, update, "id", "uuid");
+ update.setId(wfComponent.getId());
+ update.setName(req.getName());
+ update.setTitle(req.getTitle());
+ update.setRemark(req.getRemark());
+ update.setIsEnable(req.getIsEnable());
+ update.setDisplayOrder(req.getDisplayOrder());
+ this.baseMapper.updateById(update);
+
+ return update;
+ } else {
+ wfComponent = new WorkflowComponent();
+ BeanUtils.copyProperties(req, wfComponent, "id", "uuid");
+ wfComponent.setUuid(UuidUtil.createShort());
+ this.baseMapper.insert(wfComponent);
+
+ return wfComponent;
+ }
+ }
+
+ @CacheEvict(cacheNames = {WORKFLOW_COMPONENTS, WORKFLOW_COMPONENT_START_KEY})
+ public void enable(String uuid, Boolean isEnable) {
+ WorkflowComponent wfComponent = PrivilegeUtil.checkAndGetByUuid(uuid, this.query(), ErrorEnum.A_WF_COMPONENT_NOT_FOUND);
+ WorkflowComponent update = new WorkflowComponent();
+ update.setIsEnable(isEnable);
+ update.setId(wfComponent.getId());
+ this.baseMapper.updateById(update);
+ }
+
+ @CacheEvict(cacheNames = {WORKFLOW_COMPONENTS, WORKFLOW_COMPONENT_START_KEY})
+ public void deleteByUuid(String uuid) {
+ Integer refNodeCount = baseMapper.countRefNodes(uuid);
+ if (refNodeCount > 0) {
+ throw new WorkflowBaseException(C_WF_COMPONENT_DELETED_FAIL_BY_USED);
+ } else {
+// PrivilegeUtil.checkAndDelete(uuid, this.query(), ChainWrappers.updateChain(baseMapper), ErrorEnum.A_WF_COMPONENT_NOT_FOUND);
+ }
+ }
+
+ public Page search(WfComponentSearchReq searchReq, Integer currentPage, Integer pageSize) {
+ LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
+ wrapper.eq(WorkflowComponent::getIsDeleted, false);
+ wrapper.eq(null != searchReq.getIsEnable(), WorkflowComponent::getIsEnable, searchReq.getIsEnable());
+ wrapper.like(StringUtils.isNotBlank(searchReq.getTitle()), WorkflowComponent::getTitle, searchReq.getTitle());
+ wrapper.orderByAsc(List.of(WorkflowComponent::getDisplayOrder, WorkflowComponent::getId));
+ return baseMapper.selectPage(new Page<>(currentPage, pageSize), wrapper);
+ }
+
+ @Cacheable(cacheNames = WORKFLOW_COMPONENTS)
+ public List getAllEnable() {
+ return ChainWrappers.lambdaQueryChain(baseMapper)
+ .eq(WorkflowComponent::getIsEnable, true)
+ .eq(WorkflowComponent::getIsDeleted, false)
+ .orderByAsc(List.of(WorkflowComponent::getDisplayOrder, WorkflowComponent::getId))
+ .list();
+ }
+
+ @Cacheable(cacheNames = WORKFLOW_COMPONENT_START_KEY)
+ public WorkflowComponent getStartComponent() {
+ List components = self.getAllEnable();
+ return components.stream()
+ .filter(component -> WfComponentNameEnum.START.getName().equals(component.getName()))
+ .findFirst()
+ .orElseThrow(() -> new WorkflowBaseException(ErrorEnum.B_WF_NODE_DEFINITION_NOT_FOUND));
+ }
+
+ public WorkflowComponent getComponent(Long id) {
+ List components = self.getAllEnable();
+ return components.stream()
+ .filter(component -> component.getId().equals(id))
+ .findFirst()
+ .orElseThrow(() -> new WorkflowBaseException(ErrorEnum.B_WF_NODE_DEFINITION_NOT_FOUND));
+ }
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/service/WorkflowEdgeService.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/service/WorkflowEdgeService.java
new file mode 100644
index 00000000..e9bc873a
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/service/WorkflowEdgeService.java
@@ -0,0 +1,118 @@
+package org.ruoyi.workflow.service;
+
+import cn.hutool.core.collection.CollUtil;
+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.apache.commons.collections4.CollectionUtils;
+import org.ruoyi.workflow.dto.workflow.WfEdgeReq;
+import org.ruoyi.workflow.entity.WorkflowEdge;
+import org.ruoyi.workflow.enums.ErrorEnum;
+import org.ruoyi.workflow.exception.WorkflowBaseException;
+import org.ruoyi.workflow.mapper.WorkflowEdgeMapper;
+import org.ruoyi.workflow.util.MPPageUtil;
+import org.ruoyi.workflow.util.UuidUtil;
+import org.springframework.beans.BeanUtils;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+@Service
+public class WorkflowEdgeService extends ServiceImpl {
+
+ @Lazy
+ @Resource
+ private WorkflowEdgeService self;
+
+ public List listDtoByWfId(long workflowId) {
+ List edges = ChainWrappers.lambdaQueryChain(baseMapper)
+ .eq(WorkflowEdge::getWorkflowId, workflowId)
+ .eq(WorkflowEdge::getIsDeleted, false)
+ .list();
+ return MPPageUtil.convertToList(edges, WfEdgeReq.class);
+ }
+
+ @Transactional
+ public void createOrUpdateEdges(Long workflowId, List edges) {
+ List uuidList = new ArrayList<>();
+ for (WfEdgeReq edge : edges) {
+ WorkflowEdge newOne = new WorkflowEdge();
+ BeanUtils.copyProperties(edge, newOne);
+ newOne.setWorkflowId(workflowId);
+
+ WorkflowEdge old = self.getByUuid(edge.getUuid());
+ if (null != old) {
+ log.info("更新边,id:{},uuid:{},source:{},sourceHandle:{},target:{}",
+ edge.getId(), edge.getUuid(), edge.getSourceNodeUuid(), edge.getSourceHandle(), edge.getTargetNodeUuid());
+ newOne.setId(old.getId());
+ } else {
+ newOne.setId(null);
+ log.info("新增边,uuid:{},source:{},sourceHandle:{},target:{}",
+ edge.getUuid(), edge.getSourceNodeUuid(), edge.getSourceHandle(), edge.getTargetNodeUuid());
+ }
+ uuidList.add(edge.getUuid());
+ self.saveOrUpdate(newOne);
+ }
+ ChainWrappers.lambdaUpdateChain(baseMapper)
+ .eq(WorkflowEdge::getWorkflowId, workflowId)
+ .notIn(CollUtil.isNotEmpty(uuidList), WorkflowEdge::getUuid, uuidList)
+ .set(WorkflowEdge::getIsDeleted, true)
+ .update();
+ }
+
+ public List listByWorkflowId(Long workflowId) {
+ return ChainWrappers.lambdaQueryChain(baseMapper)
+ .eq(WorkflowEdge::getWorkflowId, workflowId)
+ .eq(WorkflowEdge::getIsDeleted, false)
+ .list();
+ }
+
+ public List copyByWorkflowId(long workflowId, long targetWorkflow) {
+ List result = new ArrayList<>();
+ self.listByWorkflowId(workflowId).forEach(edge -> {
+ result.add(self.copyEdge(targetWorkflow, edge));
+ });
+ return result;
+ }
+
+ public WorkflowEdge copyEdge(long targetWorkflow, WorkflowEdge sourceEdge) {
+ WorkflowEdge newEdge = new WorkflowEdge();
+ BeanUtils.copyProperties(sourceEdge, newEdge, "id", "uuid", "createTime", "updateTime");
+ newEdge.setUuid(UuidUtil.createShort());
+ newEdge.setWorkflowId(targetWorkflow);
+ baseMapper.insert(newEdge);
+ return getById(newEdge.getId());
+ }
+
+ @Transactional
+ public void deleteEdges(Long workflowId, List uuids) {
+ if (CollectionUtils.isEmpty(uuids)) {
+ return;
+ }
+ for (String uuid : uuids) {
+ WorkflowEdge old = self.getByUuid(uuid);
+ if (null != old && !old.getWorkflowId().equals(workflowId)) {
+ log.error("该边不属于指定的工作流,删除失败,workflowId:{},node workflowId:{}", workflowId, workflowId);
+ throw new WorkflowBaseException(ErrorEnum.A_PARAMS_ERROR);
+ }
+ ChainWrappers.lambdaUpdateChain(baseMapper)
+ .eq(WorkflowEdge::getWorkflowId, workflowId)
+ .eq(WorkflowEdge::getUuid, uuid)
+ .set(WorkflowEdge::getIsDeleted, true)
+ .update();
+ }
+ }
+
+ public WorkflowEdge getByUuid(String uuid) {
+ return ChainWrappers.lambdaQueryChain(baseMapper)
+ .eq(WorkflowEdge::getUuid, uuid)
+ .eq(WorkflowEdge::getIsDeleted, false)
+ .last("limit 1")
+ .one();
+ }
+}
diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/service/WorkflowNodeService.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/service/WorkflowNodeService.java
new file mode 100644
index 00000000..59ae5e67
--- /dev/null
+++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/service/WorkflowNodeService.java
@@ -0,0 +1,245 @@
+package org.ruoyi.workflow.service;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+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.apache.commons.collections4.CollectionUtils;
+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.exception.WorkflowBaseException;
+import org.ruoyi.workflow.mapper.WorkflowNodeMapper;
+import org.ruoyi.workflow.util.JsonUtil;
+import org.ruoyi.workflow.util.MPPageUtil;
+import org.ruoyi.workflow.util.UuidUtil;
+import org.ruoyi.workflow.workflow.WfComponentNameEnum;
+import org.ruoyi.workflow.workflow.WfNodeInputConfig;
+import org.ruoyi.workflow.workflow.def.WfNodeIOText;
+import org.springframework.beans.BeanUtils;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+@Service
+public class WorkflowNodeService extends ServiceImpl {
+
+ @Lazy
+ @Resource
+ private WorkflowNodeService self;
+
+ @Resource
+ private WorkflowComponentService workflowComponentService;
+
+ public WorkflowNode getStartNode(long workflowId) {
+ return baseMapper.getStartNode(workflowId);
+ }
+
+ public List listDtoByWfId(long workflowId) {
+ List workflowNodeList = ChainWrappers.lambdaQueryChain(baseMapper)
+ .eq(WorkflowNode::getWorkflowId, workflowId)
+ .eq(WorkflowNode::getIsDeleted, false)
+ .list();
+ workflowNodeList.forEach(this::checkAndDecrypt);
+ return MPPageUtil.convertToList(workflowNodeList, WfNodeDto.class, (source, target) -> {
+ target.setInputConfig(JsonUtil.toBean(source.getInputConfig(), ObjectNode.class));
+ target.setNodeConfig(JsonUtil.toBean(source.getNodeConfig(), ObjectNode.class));
+ return target;
+ });
+ }
+
+ public WorkflowNode getByUuid(long workflowId, String uuid) {
+ WorkflowNode node = ChainWrappers.lambdaQueryChain(baseMapper)
+ .eq(WorkflowNode::getWorkflowId, workflowId)
+ .eq(WorkflowNode::getUuid, uuid)
+ .eq(WorkflowNode::getIsDeleted, false)
+ .last("limit 1")
+ .one();
+ checkAndDecrypt(node);
+ return node;
+ }
+
+ public List listByWorkflowId(Long workflowId) {
+ List list = ChainWrappers.lambdaQueryChain(baseMapper)
+ .eq(WorkflowNode::getWorkflowId, workflowId)
+ .eq(WorkflowNode::getIsDeleted, false)
+ .list();
+ list.forEach(this::checkAndDecrypt);
+ return list;
+ }
+
+ public List copyByWorkflowId(long workflowId, long targetWorkflowId) {
+ List result = new ArrayList<>();
+ self.listByWorkflowId(workflowId).forEach(node -> {
+ result.add(self.copyNode(targetWorkflowId, node));
+ });
+ return result;
+ }
+
+ public WorkflowNode copyNode(Long targetWorkflowId, WorkflowNode sourceNode) {
+ WorkflowNode newNode = new WorkflowNode();
+ BeanUtils.copyProperties(sourceNode, newNode, "id", "createTime", "updateTime");
+ newNode.setWorkflowId(targetWorkflowId);
+ baseMapper.insert(newNode);
+
+ return ChainWrappers.lambdaQueryChain(baseMapper)
+ .eq(WorkflowNode::getWorkflowId, targetWorkflowId)
+ .eq(WorkflowNode::getUuid, newNode.getUuid())
+ .eq(WorkflowNode::getIsDeleted, false)
+ .last("limit 1")
+ .one();
+ }
+
+ @Transactional
+ public void createOrUpdateNodes(Long workflowId, List nodes) {
+ List