From 84ed63b894ba6148e35a6febb11315a56a64f484 Mon Sep 17 00:00:00 2001 From: clay Date: Tue, 8 Apr 2025 00:02:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=85=B3=E7=B3=BB=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=AE=8C=E6=88=90,=20runner=E5=85=A5?= =?UTF-8?q?=E5=8F=82=E7=A1=AE=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metis/facade/ProcessDefinitionFacade.java | 2 +- .../flow/domain/context/RunningContext.java | 32 +++++++ .../metis/flow/domain/context/SysContext.java | 34 ++++++- .../metis/flow/domain/entity/base/Edge.java | 19 ++-- .../metis/flow/domain/entity/base/Handle.java | 7 +- .../metis/flow/domain/entity/base/Node.java | 7 +- .../flow/engine/AppEngineRunnerService.java | 6 -- .../metis/flow/engine/AppEngineService.java | 12 ++- .../engine/AppFlowEngineRunnerService.java | 27 ++++++ .../engine/impl/AppEngineServiceImpl.java | 10 +- .../impl/AppFlowEngineRunnerServiceImpl.java | 57 +++++++++++ .../metis/flow/runner/FlowRunningContext.java | 45 +++++++++ .../com/metis/flow/runner/NodeRunner.java | 6 +- .../metis/flow/runner/RunnerInitialize.java | 6 ++ .../com/metis/flow/runner/RunnerResult.java | 27 ++++++ .../metis/flow/runner/impl/EndNodeRunner.java | 5 +- .../flow/runner/impl/StartNodeRunner.java | 5 +- .../metis/flow/validator/NodeValidator.java | 16 +++- .../validator/impl/ValidatorServiceImpl.java | 96 +++++++++++++++++-- .../node/DocumentExtractorNodeValidator.java | 14 ++- .../validator/impl/node/EndNodeValidator.java | 18 +++- .../impl/node/StartNodeValidator.java | 18 +++- 22 files changed, 422 insertions(+), 47 deletions(-) delete mode 100644 src/main/java/com/metis/flow/engine/AppEngineRunnerService.java create mode 100644 src/main/java/com/metis/flow/engine/AppFlowEngineRunnerService.java create mode 100644 src/main/java/com/metis/flow/engine/impl/AppFlowEngineRunnerServiceImpl.java create mode 100644 src/main/java/com/metis/flow/runner/FlowRunningContext.java create mode 100644 src/main/java/com/metis/flow/runner/RunnerResult.java diff --git a/src/main/java/com/metis/facade/ProcessDefinitionFacade.java b/src/main/java/com/metis/facade/ProcessDefinitionFacade.java index 5272714..e3749ae 100644 --- a/src/main/java/com/metis/facade/ProcessDefinitionFacade.java +++ b/src/main/java/com/metis/facade/ProcessDefinitionFacade.java @@ -30,7 +30,7 @@ public class ProcessDefinitionFacade { } public App getByDeploymentId(Long deploymentId) { - return appEngineService.getByDeploymentId(deploymentId); + return appEngineService.getByWorkflowId(deploymentId); } public void update(ProcessBo processBo) { diff --git a/src/main/java/com/metis/flow/domain/context/RunningContext.java b/src/main/java/com/metis/flow/domain/context/RunningContext.java index 891b196..8dbff6f 100644 --- a/src/main/java/com/metis/flow/domain/context/RunningContext.java +++ b/src/main/java/com/metis/flow/domain/context/RunningContext.java @@ -1,9 +1,16 @@ package com.metis.flow.domain.context; import com.alibaba.fastjson2.JSONObject; +import com.metis.flow.runner.FlowRunningContext; +import lombok.Builder; import lombok.Data; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + @Data +@Builder public class RunningContext { /** @@ -16,4 +23,29 @@ public class RunningContext { */ private JSONObject custom; + /** + * 节点运行上下文, 需要数据进行传递 + */ + private Map nodeRunningContext; + + /** + * 下一个运行节点id集合, 可能是多个 + */ + private Set nextRunNodeId; + + + /** + * 构建上下文 + * + * @param context 上下文 + * @return {@link RunningContext } + */ + public static RunningContext buildContext(SysContext sysContext, FlowRunningContext context) { + return RunningContext.builder() + .sys(sysContext) + .custom(context.getCustom()) + .nodeRunningContext(new HashMap<>()) + .build(); + } + } diff --git a/src/main/java/com/metis/flow/domain/context/SysContext.java b/src/main/java/com/metis/flow/domain/context/SysContext.java index 59be745..54bc818 100644 --- a/src/main/java/com/metis/flow/domain/context/SysContext.java +++ b/src/main/java/com/metis/flow/domain/context/SysContext.java @@ -1,19 +1,51 @@ package com.metis.flow.domain.context; +import lombok.Builder; import lombok.Data; import java.util.List; @Data +@Builder public class SysContext { - private List file; + /** + * 文件列表 + */ + private List files; + /** + * 沟通的内容 + */ + private String query; + + /** + * 对话数 + */ + private Integer dialogueCount; + + /** + * 沟通的id + */ + private String conversationId; + + /** + * 应用程序id + */ private Long appId; + /** + * 用户id + */ private Long userId; + /** + * 工作流id + */ private Long workflowId; + /** + * 实例id + */ private Long instanceId; } diff --git a/src/main/java/com/metis/flow/domain/entity/base/Edge.java b/src/main/java/com/metis/flow/domain/entity/base/Edge.java index ce657d1..96e72d2 100644 --- a/src/main/java/com/metis/flow/domain/entity/base/Edge.java +++ b/src/main/java/com/metis/flow/domain/entity/base/Edge.java @@ -1,7 +1,6 @@ package com.metis.flow.domain.entity.base; import com.metis.flow.enums.EdgeType; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -11,7 +10,7 @@ public class Edge { /** * 唯一标识符 */ - @NotBlank(message = "唯一标识符不能为空") + @NotNull(message = "唯一标识符不能为空") private String id; /** @@ -28,25 +27,25 @@ public class Edge { /** * 源节点ID,对应节点id */ - @NotBlank(message = "源节点ID不能为空") - private String source; + @NotNull(message = "源节点ID不能为空") + private Long source; /** * 目标节点ID,对应节点id */ - @NotBlank(message = "目标节点ID不能为空") - private String target; + @NotNull(message = "目标节点ID不能为空") + private Long target; /** * 源句柄id */ - @NotBlank(message = "源句柄ID不能为空") - private String sourceHandle; + @NotNull(message = "源句柄ID不能为空") + private Long sourceHandle; /** * 目标句柄id */ - @NotBlank(message = "目标句柄ID不能为空") - private String targetHandle; + @NotNull(message = "目标句柄ID不能为空") + private Long targetHandle; /** * 边是否动画true/false diff --git a/src/main/java/com/metis/flow/domain/entity/base/Handle.java b/src/main/java/com/metis/flow/domain/entity/base/Handle.java index f49dda3..acaa9b5 100644 --- a/src/main/java/com/metis/flow/domain/entity/base/Handle.java +++ b/src/main/java/com/metis/flow/domain/entity/base/Handle.java @@ -1,8 +1,7 @@ package com.metis.flow.domain.entity.base; -import com.metis.flow.enums.PositionType; import com.metis.flow.enums.HandleType; -import jakarta.validation.constraints.NotBlank; +import com.metis.flow.enums.PositionType; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -14,8 +13,8 @@ public class Handle { /** * 句柄id */ - @NotBlank(message = "句柄id不能为空") - private String id; + @NotNull(message = "句柄id不能为空") + private Long id; /** * 句柄类型 diff --git a/src/main/java/com/metis/flow/domain/entity/base/Node.java b/src/main/java/com/metis/flow/domain/entity/base/Node.java index c5b0fd9..057f0b7 100644 --- a/src/main/java/com/metis/flow/domain/entity/base/Node.java +++ b/src/main/java/com/metis/flow/domain/entity/base/Node.java @@ -5,7 +5,6 @@ import cn.hutool.core.util.ObjectUtil; import com.fasterxml.jackson.annotation.JsonIgnore; import com.metis.flow.enums.NodeType; import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -18,8 +17,8 @@ public class Node { /** * id */ - @NotBlank(message = "节点id不能为空") - private String id; + @NotNull(message = "节点id不能为空") + private Long id; /** * 类型 @@ -43,7 +42,7 @@ public class Node { @JsonIgnore - public Map getHandleMap() { + public Map getHandleMap() { if (CollUtil.isEmpty(data.getHandles())) { return Map.of(); } diff --git a/src/main/java/com/metis/flow/engine/AppEngineRunnerService.java b/src/main/java/com/metis/flow/engine/AppEngineRunnerService.java deleted file mode 100644 index 8bf6396..0000000 --- a/src/main/java/com/metis/flow/engine/AppEngineRunnerService.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.metis.flow.engine; - -public interface AppEngineRunnerService { - - -} diff --git a/src/main/java/com/metis/flow/engine/AppEngineService.java b/src/main/java/com/metis/flow/engine/AppEngineService.java index d2fe8d9..4ce22c8 100644 --- a/src/main/java/com/metis/flow/engine/AppEngineService.java +++ b/src/main/java/com/metis/flow/engine/AppEngineService.java @@ -26,6 +26,14 @@ public interface AppEngineService { List list(AppQuery query); + /** + * 通过应用id获取 + * + * @param appId 应用程序id + * @return {@link App } + */ + App getByAppId(Long appId); + /** * 按身份证领取 * @@ -37,10 +45,10 @@ public interface AppEngineService { /** * 通过部署id获取 * - * @param deploymentId 部署id + * @param workflowId 部署id * @return {@link App } */ - App getByDeploymentId(Long deploymentId); + App getByWorkflowId(Long workflowId); /** * 按id列表 diff --git a/src/main/java/com/metis/flow/engine/AppFlowEngineRunnerService.java b/src/main/java/com/metis/flow/engine/AppFlowEngineRunnerService.java new file mode 100644 index 0000000..dfb85c3 --- /dev/null +++ b/src/main/java/com/metis/flow/engine/AppFlowEngineRunnerService.java @@ -0,0 +1,27 @@ +package com.metis.flow.engine; + +import com.metis.flow.runner.FlowRunningContext; +import com.metis.flow.runner.RunnerResult; + +/** + * 应用引擎运行器服务 + * + * @author clay + * @date 2025/04/07 + */ +public interface AppFlowEngineRunnerService { + + + /** + * 运行 + * + * @param context 上下文 + * @return {@link RunnerResult } + */ + RunnerResult running(FlowRunningContext context); + + + + + +} diff --git a/src/main/java/com/metis/flow/engine/impl/AppEngineServiceImpl.java b/src/main/java/com/metis/flow/engine/impl/AppEngineServiceImpl.java index 7a5b117..92c5bc1 100644 --- a/src/main/java/com/metis/flow/engine/impl/AppEngineServiceImpl.java +++ b/src/main/java/com/metis/flow/engine/impl/AppEngineServiceImpl.java @@ -42,6 +42,12 @@ public class AppEngineServiceImpl implements AppEngineService { return BaseAppConvert.INSTANCE.toApps(list); } + @Override + public App getByAppId(Long appId) { + BaseApp baseApp = baseAppService.getByAppId(appId); + return BaseAppConvert.INSTANCE.toApp(baseApp); + } + @Override public App getByAppId(Long appId, Integer version) { BaseApp baseApp = baseAppService.getByAppIdAndVersion(appId, version); @@ -49,8 +55,8 @@ public class AppEngineServiceImpl implements AppEngineService { } @Override - public App getByDeploymentId(Long deploymentId) { - BaseApp baseApp = baseAppService.getById(deploymentId); + public App getByWorkflowId(Long workflowId) { + BaseApp baseApp = baseAppService.getById(workflowId); return BaseAppConvert.INSTANCE.toApp(baseApp); } diff --git a/src/main/java/com/metis/flow/engine/impl/AppFlowEngineRunnerServiceImpl.java b/src/main/java/com/metis/flow/engine/impl/AppFlowEngineRunnerServiceImpl.java new file mode 100644 index 0000000..10dd2c0 --- /dev/null +++ b/src/main/java/com/metis/flow/engine/impl/AppFlowEngineRunnerServiceImpl.java @@ -0,0 +1,57 @@ +package com.metis.flow.engine.impl; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import com.metis.flow.domain.context.RunningContext; +import com.metis.flow.domain.context.SysContext; +import com.metis.flow.domain.entity.App; +import com.metis.flow.engine.AppEngineService; +import com.metis.flow.engine.AppFlowEngineRunnerService; +import com.metis.flow.runner.FlowRunningContext; +import com.metis.flow.runner.RunnerResult; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AppFlowEngineRunnerServiceImpl implements AppFlowEngineRunnerService { + + private final AppEngineService appEngineService; + + + @Override + public RunnerResult running(FlowRunningContext context) { + App app = getApp(context); + // todo 构建运行实例, 并将运行实例放入上下文 + + Long instanceId = IdUtil.getSnowflakeNextId(); + // 构建系统上下文信息 + SysContext sysContext = SysContext.builder() + .files(context.getFiles()) + .appId(app.getId()) + .workflowId(app.getWorkflowId()) + .instanceId(instanceId) + .build(); + // 构建运行中上下文 + RunningContext runningContext = RunningContext.buildContext(sysContext, context); + + + return null; + } + + + /** + * 获取到应用程序信息 + * + * @param context 上下文 + * @return {@link App } + */ + private App getApp(FlowRunningContext context) { + if (ObjectUtil.isNull(context.getWorkflowId())) { + return appEngineService.getByWorkflowId(context.getWorkflowId()); + } + return appEngineService.getByAppId(context.getAppId()); + } +} diff --git a/src/main/java/com/metis/flow/runner/FlowRunningContext.java b/src/main/java/com/metis/flow/runner/FlowRunningContext.java new file mode 100644 index 0000000..c477353 --- /dev/null +++ b/src/main/java/com/metis/flow/runner/FlowRunningContext.java @@ -0,0 +1,45 @@ +package com.metis.flow.runner; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +/** + * 运行上下文 + * + * @author clay + * @date 2025/04/07 + */ +@Data +@Builder +public class FlowRunningContext { + + /** + * 文件列表 + */ + private List files; + + /** + * 应用程序id + */ + private Long appId; + + /** + * 用户id + */ + private Long userId; + + /** + * 工作流id + */ + private Long workflowId; + + /** + * 自定义 + */ + private JSONObject custom; + + +} diff --git a/src/main/java/com/metis/flow/runner/NodeRunner.java b/src/main/java/com/metis/flow/runner/NodeRunner.java index c9a8420..c7e5679 100644 --- a/src/main/java/com/metis/flow/runner/NodeRunner.java +++ b/src/main/java/com/metis/flow/runner/NodeRunner.java @@ -1,9 +1,12 @@ package com.metis.flow.runner; import com.metis.flow.domain.context.RunningContext; +import com.metis.flow.domain.entity.base.Edge; import com.metis.flow.domain.entity.base.Node; import com.metis.flow.enums.NodeType; +import java.util.List; + public interface NodeRunner { @@ -12,9 +15,10 @@ public interface NodeRunner { * * @param context 上下文 * @param node 节点配置信息 + * @param edges * @return {@link RunningContext } */ - RunningContext run(RunningContext context, Node node); + RunningContext run(RunningContext context, Node node, List edges); /** diff --git a/src/main/java/com/metis/flow/runner/RunnerInitialize.java b/src/main/java/com/metis/flow/runner/RunnerInitialize.java index b2dafa0..094d7fc 100644 --- a/src/main/java/com/metis/flow/runner/RunnerInitialize.java +++ b/src/main/java/com/metis/flow/runner/RunnerInitialize.java @@ -8,6 +8,12 @@ import org.springframework.stereotype.Service; import java.util.Map; +/** + * runner初始化 + * + * @author clay + * @date 2025/04/07 + */ @Service public class RunnerInitialize implements ApplicationContextAware { diff --git a/src/main/java/com/metis/flow/runner/RunnerResult.java b/src/main/java/com/metis/flow/runner/RunnerResult.java new file mode 100644 index 0000000..17358df --- /dev/null +++ b/src/main/java/com/metis/flow/runner/RunnerResult.java @@ -0,0 +1,27 @@ +package com.metis.flow.runner; + + +import com.metis.flow.domain.context.SysContext; +import lombok.Data; + +/** + * 运行结果 + * + * @author clay + * @date 2025/04/07 + */ +@Data +public class RunnerResult { + + /** + * 运行内容 + */ + private String content; + + /** + * 上下文 + */ + private SysContext context; + + +} diff --git a/src/main/java/com/metis/flow/runner/impl/EndNodeRunner.java b/src/main/java/com/metis/flow/runner/impl/EndNodeRunner.java index 35191e1..430b2db 100644 --- a/src/main/java/com/metis/flow/runner/impl/EndNodeRunner.java +++ b/src/main/java/com/metis/flow/runner/impl/EndNodeRunner.java @@ -2,18 +2,21 @@ package com.metis.flow.runner.impl; import com.metis.flow.domain.context.RunningContext; +import com.metis.flow.domain.entity.base.Edge; import com.metis.flow.domain.entity.base.Node; import com.metis.flow.enums.NodeType; import com.metis.flow.runner.NodeRunner; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.List; + @Slf4j @Service public class EndNodeRunner implements NodeRunner { @Override - public RunningContext run(RunningContext context, Node node) { + public RunningContext run(RunningContext context, Node node, List edges) { return context; } diff --git a/src/main/java/com/metis/flow/runner/impl/StartNodeRunner.java b/src/main/java/com/metis/flow/runner/impl/StartNodeRunner.java index f43a570..3d7332b 100644 --- a/src/main/java/com/metis/flow/runner/impl/StartNodeRunner.java +++ b/src/main/java/com/metis/flow/runner/impl/StartNodeRunner.java @@ -1,18 +1,21 @@ package com.metis.flow.runner.impl; import com.metis.flow.domain.context.RunningContext; +import com.metis.flow.domain.entity.base.Edge; import com.metis.flow.domain.entity.base.Node; import com.metis.flow.enums.NodeType; import com.metis.flow.runner.NodeRunner; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.List; + @Slf4j @Service public class StartNodeRunner implements NodeRunner { @Override - public RunningContext run(RunningContext context, Node node) { + public RunningContext run(RunningContext context, Node node, List edges) { return context; } diff --git a/src/main/java/com/metis/flow/validator/NodeValidator.java b/src/main/java/com/metis/flow/validator/NodeValidator.java index ecaa51f..76f8211 100644 --- a/src/main/java/com/metis/flow/validator/NodeValidator.java +++ b/src/main/java/com/metis/flow/validator/NodeValidator.java @@ -1,8 +1,11 @@ package com.metis.flow.validator; +import com.metis.flow.domain.entity.base.Edge; import com.metis.flow.domain.entity.base.Node; import com.metis.flow.enums.NodeType; +import java.util.List; + public interface NodeValidator { @@ -12,7 +15,18 @@ public interface NodeValidator { * @param node 节点 * @return {@link ValidatorResult } */ - ValidatorResult validate(Node node); + ValidatorResult validateValue(Node node); + + + /** + * 验证关系 + * + * @param node 节点 + * @param sources 来源 + * @param targets 目标 + * @return {@link ValidatorResult } + */ + ValidatorResult validateRelation(Node node, List sources, List targets); /** diff --git a/src/main/java/com/metis/flow/validator/impl/ValidatorServiceImpl.java b/src/main/java/com/metis/flow/validator/impl/ValidatorServiceImpl.java index d6630fe..17b4e8d 100644 --- a/src/main/java/com/metis/flow/validator/impl/ValidatorServiceImpl.java +++ b/src/main/java/com/metis/flow/validator/impl/ValidatorServiceImpl.java @@ -53,7 +53,7 @@ public class ValidatorServiceImpl implements ValidatorService { NodeValidator validator = NodeValidatorFactory.get(type); // 节点校验器 Assert.isTrue(ObjectUtil.isNotNull(validator), "无:{}类型的节点校验器", type.getName()); - ValidatorResult result = validator.validate(node); + ValidatorResult result = validator.validateValue(node); // 返回值检查 Assert.isTrue(ObjectUtil.isNotNull(result), "类型:{} 的校验器无返回值", validator.getType().getName()); if (!result.getValid()) { @@ -94,6 +94,9 @@ public class ValidatorServiceImpl implements ValidatorService { * @param edges 边缘 */ private void validateRelation(List nodes, List edges) { + // 0. 检查是否只有一个起始节点 + validateSingleStartNode(nodes); + // 1. 检查线是否连接有效节点 validateEdgeConnections(nodes, edges); @@ -102,16 +105,39 @@ public class ValidatorServiceImpl implements ValidatorService { // 3. 检查是否存在孤立节点 validateIsolatedNodes(nodes, edges); + + // 4. 检查两个节点间是否有多根连线 + validateMultipleEdgesBetweenNodes(edges); + + // 5. 检查节点自己的关系信息 + validateNodeRelations(nodes, edges); + } + /** + * 检查是否只有一个起始节点 + * + * @param nodes 节点 + */ + private void validateSingleStartNode(List nodes) { + Long startNodeCount = nodes.stream() + .filter(node -> NodeType.START.equals(node.getType())) + .count(); + Assert.isTrue(startNodeCount.equals(1L), "图中必须且只能有一个起始节点,当前起始节点数量: {}", startNodeCount); + } + + /** * 检查线是否连接有效节点 + * + * @param nodes 节点 + * @param edges 边缘 */ private void validateEdgeConnections(List nodes, List edges) { - Map nodeMap = nodes.stream().collect(Collectors.toMap(Node::getId, Function.identity())); + Map nodeMap = nodes.stream().collect(Collectors.toMap(Node::getId, Function.identity())); for (Edge edge : edges) { - String source = edge.getSource(); - String target = edge.getTarget(); + Long source = edge.getSource(); + Long target = edge.getTarget(); Assert.isTrue(nodeMap.containsKey(source), "边 {} 的源节点 {} 不存在", edge.getLabel(), source); Assert.isTrue(nodeMap.containsKey(target), "边 {} 的目标节点 {} 不存在", edge.getLabel(), target); } @@ -119,9 +145,12 @@ public class ValidatorServiceImpl implements ValidatorService { /** * 检查是否存在环结构 + * + * @param nodes 节点 + * @param edges 边缘 */ private void validateCycle(List nodes, List edges) { - Map> adjacencyList = buildAdjacencyList(edges); + Map> adjacencyList = buildAdjacencyList(edges); for (Node node : nodes) { if (hasCycle(node.getId(), adjacencyList, new HashSet<>())) { throw new IllegalArgumentException("图中存在环结构,起始节点: " + node.getData().getLabel()); @@ -131,9 +160,12 @@ public class ValidatorServiceImpl implements ValidatorService { /** * 检查是否存在孤立节点 + * + * @param nodes 节点 + * @param edges 边缘 */ private void validateIsolatedNodes(List nodes, List edges) { - Set connectedNodes = new HashSet<>(); + Set connectedNodes = new HashSet<>(); for (Edge edge : edges) { connectedNodes.add(edge.getSource()); connectedNodes.add(edge.getTarget()); @@ -145,9 +177,12 @@ public class ValidatorServiceImpl implements ValidatorService { /** * 构建邻接表 + * + * @param edges 边缘 + * @return {@link Map }<{@link Long }, {@link List }<{@link Long }>> */ - private Map> buildAdjacencyList(List edges) { - Map> adjacencyList = new HashMap<>(); + private Map> buildAdjacencyList(List edges) { + Map> adjacencyList = new HashMap<>(); for (Edge edge : edges) { adjacencyList.computeIfAbsent(edge.getSource(), k -> new ArrayList<>()).add(edge.getTarget()); } @@ -156,13 +191,18 @@ public class ValidatorServiceImpl implements ValidatorService { /** * 深度优先搜索(DFS)检查环 + * + * @param nodeId 节点id + * @param adjacencyList 邻接表 + * @param visited 是否已经访问过了 + * @return boolean */ - private boolean hasCycle(String nodeId, Map> adjacencyList, Set visited) { + private boolean hasCycle(Long nodeId, Map> adjacencyList, Set visited) { if (visited.contains(nodeId)) { return true; // 发现环 } visited.add(nodeId); - for (String neighbor : adjacencyList.getOrDefault(nodeId, new ArrayList<>())) { + for (Long neighbor : adjacencyList.getOrDefault(nodeId, new ArrayList<>())) { if (hasCycle(neighbor, adjacencyList, visited)) { return true; } @@ -171,4 +211,40 @@ public class ValidatorServiceImpl implements ValidatorService { return false; } + /** + * 检查两个节点间是否有多根连线 + * + * @param edges 边缘 + */ + private void validateMultipleEdgesBetweenNodes(List edges) { + Map edgeCountMap = new HashMap<>(); + for (Edge edge : edges) { + String key = edge.getSource() + "-" + edge.getTarget(); // 使用 source 和 target 作为唯一标识 + edgeCountMap.put(key, edgeCountMap.getOrDefault(key, 0) + 1); + if (edgeCountMap.get(key) > 1) { + throw new IllegalArgumentException("节点 " + edge.getSource() + " 和节点 " + edge.getTarget() + " 之间存在多根连线"); + } + } + } + + + /** + * 检查节点的关系信息 + */ + private void validateNodeRelations(List nodes, List edges) { + // 构建 source 和 target 的映射 + Map> sourceMap = edges.stream().collect(Collectors.groupingBy(Edge::getSource)); + Map> targetMap = edges.stream().collect(Collectors.groupingBy(Edge::getTarget)); + + for (Node node : nodes) { + NodeValidator validator = NodeValidatorFactory.get(node.getType()); + // 获取当前节点的 source 和 target + List sources = targetMap.getOrDefault(node.getId(), new ArrayList<>()); + List targets = sourceMap.getOrDefault(node.getId(), new ArrayList<>()); + // 检查节点关系 + ValidatorResult result = validator.validateRelation(node, sources, targets); + Assert.isTrue(result.getValid(), result.getMessage()); + } + } + } diff --git a/src/main/java/com/metis/flow/validator/impl/node/DocumentExtractorNodeValidator.java b/src/main/java/com/metis/flow/validator/impl/node/DocumentExtractorNodeValidator.java index 042d42f..6d82c99 100644 --- a/src/main/java/com/metis/flow/validator/impl/node/DocumentExtractorNodeValidator.java +++ b/src/main/java/com/metis/flow/validator/impl/node/DocumentExtractorNodeValidator.java @@ -1,5 +1,7 @@ package com.metis.flow.validator.impl.node; +import cn.hutool.core.lang.Assert; +import com.metis.flow.domain.entity.base.Edge; import com.metis.flow.domain.entity.base.Node; import com.metis.flow.domain.entity.config.node.DocumentExtractorNodeConfig; import com.metis.flow.enums.NodeType; @@ -10,6 +12,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.List; + @Slf4j @Service @RequiredArgsConstructor @@ -17,13 +21,21 @@ public class DocumentExtractorNodeValidator implements NodeValidator { private final ValidatorCodeService validatorCodeService; @Override - public ValidatorResult validate(Node node) { + public ValidatorResult validateValue(Node node) { DocumentExtractorNodeConfig config = node.getConfig(); validatorCodeService.validateThrow(config); // 业务检查未通过 return ValidatorResult.invalid("业务报错"); } + @Override + public ValidatorResult validateRelation(Node node, List sources, List targets) { + // 1. 检查 targets 数量是否等于 1(只允许一个出) + Assert.isTrue(targets.size() == 1, "节点 {} 的出连接数必须为 1,当前数量: {}", node.getId(), targets.size()); + + return ValidatorResult.valid(); + } + @Override public NodeType getType() { return NodeType.DOCUMENT_EXTRACTOR; diff --git a/src/main/java/com/metis/flow/validator/impl/node/EndNodeValidator.java b/src/main/java/com/metis/flow/validator/impl/node/EndNodeValidator.java index 5c8eba9..fbe87d0 100644 --- a/src/main/java/com/metis/flow/validator/impl/node/EndNodeValidator.java +++ b/src/main/java/com/metis/flow/validator/impl/node/EndNodeValidator.java @@ -1,5 +1,7 @@ package com.metis.flow.validator.impl.node; +import cn.hutool.core.lang.Assert; +import com.metis.flow.domain.entity.base.Edge; import com.metis.flow.domain.entity.base.Node; import com.metis.flow.enums.NodeType; import com.metis.flow.validator.NodeValidator; @@ -8,6 +10,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.List; + @Slf4j @Service @RequiredArgsConstructor @@ -16,7 +20,19 @@ public class EndNodeValidator implements NodeValidator { @Override - public ValidatorResult validate(Node node) { + public ValidatorResult validateValue(Node node) { + return ValidatorResult.valid(); + } + + @Override + public ValidatorResult validateRelation(Node node, List sources, List targets) { + // 1. 结束节点不允许有目标连接 + Assert.isTrue(targets.isEmpty(), "结束节点 {} 不允许有目标连接", node.getId()); + + // 2. 检查 sources 数量是否小于 handles 数量 + int handleCount = node.getData().getHandles().size(); + Assert.isTrue(sources.size() <= handleCount, "结束节点 {} 的源连接数超过 handles 数量", node.getId()); + return ValidatorResult.valid(); } diff --git a/src/main/java/com/metis/flow/validator/impl/node/StartNodeValidator.java b/src/main/java/com/metis/flow/validator/impl/node/StartNodeValidator.java index f32d657..02be8ac 100644 --- a/src/main/java/com/metis/flow/validator/impl/node/StartNodeValidator.java +++ b/src/main/java/com/metis/flow/validator/impl/node/StartNodeValidator.java @@ -1,5 +1,7 @@ package com.metis.flow.validator.impl.node; +import cn.hutool.core.lang.Assert; +import com.metis.flow.domain.entity.base.Edge; import com.metis.flow.domain.entity.base.Node; import com.metis.flow.enums.NodeType; import com.metis.flow.validator.NodeValidator; @@ -7,12 +9,26 @@ import com.metis.flow.validator.ValidatorResult; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.List; + @Slf4j @Service public class StartNodeValidator implements NodeValidator { @Override - public ValidatorResult validate(Node node) { + public ValidatorResult validateValue(Node node) { + return ValidatorResult.valid(); + } + + @Override + public ValidatorResult validateRelation(Node node, List sources, List targets) { + // 1. 开始节点不允许有源连接 + Assert.isTrue(sources.isEmpty(), "开始节点 {} 不允许有源连接", node.getId()); + + // 2. 检查 targets 数量是否小于 handles 数量 + int handleCount = node.getData().getHandles().size(); + Assert.isTrue(targets.size() <= handleCount, "开始节点 {} 的目标连接数超过 handles 数量", node.getId()); + return ValidatorResult.valid(); }