From bd4c3749525f7e9463dbd228f0fe618a65c4448d Mon Sep 17 00:00:00 2001 From: clay Date: Mon, 21 Apr 2025 21:05:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AF=B9=E8=B1=A1=E9=87=8D=E6=96=B0?= =?UTF-8?q?=E6=95=B4=E7=90=86,=20=E6=9E=B6=E6=9E=84=E9=87=8D=E6=96=B0?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1,=20=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E8=8A=82=E7=82=B9=E7=BB=99=E4=BA=88=E5=A4=96=E9=83=A8?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/metis/domain/bo/ProcessBo.java | 4 +- .../metis/facade/ProcessDefinitionFacade.java | 10 ++- .../metis/flow/convert/BaseAppConvert.java | 2 +- .../com/metis/flow/convert/GraphConvert.java | 17 ++++ .../com/metis/flow/domain/bo/BuildApp.java | 1 + .../com/metis/flow/domain/bo/CreateApp.java | 1 + .../java/com/metis/flow/domain/bo/EdgeBO.java | 66 ++++++++++++++++ .../java/com/metis/flow/domain/bo/Graph.java | 29 ------- .../com/metis/flow/domain/bo/GraphBO.java | 43 ++++++++++ .../com/metis/flow/domain/bo/HandleBO.java | 36 +++++++++ .../java/com/metis/flow/domain/bo/NodeBO.java | 60 ++++++++++++++ .../com/metis/flow/domain/bo/NodeDataBO.java | 45 +++++++++++ .../com/metis/flow/domain/bo/PositionBO.java | 20 +++++ .../com/metis/flow/domain/bo/UpdateApp.java | 1 + .../com/metis/flow/domain/bo/ViewportBo.java | 10 +++ .../com/metis/flow/domain/entity/App.java | 2 +- .../metis/flow/domain/entity/GraphDemo.java | 52 ++++++++++++ .../metis/flow/domain/entity/base/Graph.java | 43 ++++++++++ .../metis/flow/domain/entity/base/Node.java | 36 +++++---- .../flow/domain/entity/base/NodeConfig.java | 4 + .../flow/domain/entity/base/NodeData.java | 8 +- .../flow/domain/entity/base/Viewport.java | 10 +++ .../node/DocumentExtractorNodeConfig.java | 5 +- .../entity/config/node/EndNodeConfig.java | 10 +++ .../entity/config/node/StartNodeConfig.java | 6 +- .../impl/AppFlowEngineRunnerServiceImpl.java | 79 ++++++++++++++----- .../java/com/metis/flow/enums/NodeType.java | 11 ++- .../metis/flow/runner/CustomNodeRunner.java | 33 ++++++++ .../com/metis/flow/runner/NodeRunner.java | 11 ++- .../runner/factory/NodeRunnerFactory.java | 64 +++++++++++++++ .../flow/runner/factory/RunnerFactory.java | 36 --------- .../{ => factory}/RunnerInitialize.java | 18 ++++- .../metis/flow/runner/impl/EndNodeRunner.java | 3 +- .../flow/runner/impl/StartNodeRunner.java | 3 +- .../flow/validator/CustomNodeValidator.java | 34 ++++++++ .../metis/flow/validator/NodeValidator.java | 3 +- .../flow/validator/ValidatorInitialize.java | 34 -------- .../factory/NodeValidatorFactory.java | 32 ++++++-- .../factory/ValidatorInitialize.java | 43 ++++++++++ .../validator/impl/ValidatorServiceImpl.java | 36 ++++++--- .../node/DocumentExtractorNodeValidator.java | 2 +- .../validator/impl/node/EndNodeValidator.java | 3 +- .../impl/node/StartNodeValidator.java | 2 +- .../metis/utils/GenericInterfacesUtils.java | 25 ++++++ 44 files changed, 813 insertions(+), 180 deletions(-) create mode 100644 src/main/java/com/metis/flow/convert/GraphConvert.java create mode 100644 src/main/java/com/metis/flow/domain/bo/EdgeBO.java delete mode 100644 src/main/java/com/metis/flow/domain/bo/Graph.java create mode 100644 src/main/java/com/metis/flow/domain/bo/GraphBO.java create mode 100644 src/main/java/com/metis/flow/domain/bo/HandleBO.java create mode 100644 src/main/java/com/metis/flow/domain/bo/NodeBO.java create mode 100644 src/main/java/com/metis/flow/domain/bo/NodeDataBO.java create mode 100644 src/main/java/com/metis/flow/domain/bo/PositionBO.java create mode 100644 src/main/java/com/metis/flow/domain/bo/ViewportBo.java create mode 100644 src/main/java/com/metis/flow/domain/entity/GraphDemo.java create mode 100644 src/main/java/com/metis/flow/domain/entity/base/Graph.java create mode 100644 src/main/java/com/metis/flow/domain/entity/base/NodeConfig.java create mode 100644 src/main/java/com/metis/flow/domain/entity/base/Viewport.java create mode 100644 src/main/java/com/metis/flow/domain/entity/config/node/EndNodeConfig.java create mode 100644 src/main/java/com/metis/flow/runner/CustomNodeRunner.java create mode 100644 src/main/java/com/metis/flow/runner/factory/NodeRunnerFactory.java delete mode 100644 src/main/java/com/metis/flow/runner/factory/RunnerFactory.java rename src/main/java/com/metis/flow/runner/{ => factory}/RunnerInitialize.java (50%) create mode 100644 src/main/java/com/metis/flow/validator/CustomNodeValidator.java delete mode 100644 src/main/java/com/metis/flow/validator/ValidatorInitialize.java create mode 100644 src/main/java/com/metis/flow/validator/factory/ValidatorInitialize.java create mode 100644 src/main/java/com/metis/utils/GenericInterfacesUtils.java diff --git a/src/main/java/com/metis/domain/bo/ProcessBo.java b/src/main/java/com/metis/domain/bo/ProcessBo.java index 325018e..30fd785 100644 --- a/src/main/java/com/metis/domain/bo/ProcessBo.java +++ b/src/main/java/com/metis/domain/bo/ProcessBo.java @@ -1,7 +1,7 @@ package com.metis.domain.bo; import com.metis.enums.YesOrNoEnum; -import com.metis.flow.domain.bo.Graph; +import com.metis.flow.domain.bo.GraphBO; import lombok.Data; @Data @@ -13,7 +13,7 @@ public class ProcessBo { private String description; - private Graph graph; + private GraphBO graph; private YesOrNoEnum defaultUse; diff --git a/src/main/java/com/metis/facade/ProcessDefinitionFacade.java b/src/main/java/com/metis/facade/ProcessDefinitionFacade.java index d1b4958..216102d 100644 --- a/src/main/java/com/metis/facade/ProcessDefinitionFacade.java +++ b/src/main/java/com/metis/facade/ProcessDefinitionFacade.java @@ -1,9 +1,11 @@ package com.metis.facade; import com.metis.domain.bo.ProcessBo; -import com.metis.flow.domain.entity.App; +import com.metis.flow.convert.GraphConvert; import com.metis.flow.domain.bo.CreateApp; import com.metis.flow.domain.bo.UpdateApp; +import com.metis.flow.domain.entity.App; +import com.metis.flow.domain.entity.base.Graph; import com.metis.flow.engine.AppEngineService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -22,9 +24,10 @@ public class ProcessDefinitionFacade { * @param processBo 过程业务对象 */ public Long create(ProcessBo processBo) { + Graph graph = GraphConvert.INSTANCE.toEntity(processBo.getGraph()); CreateApp createApp = CreateApp.builder() .name(processBo.getName()) - .graph(processBo.getGraph()) + .graph(graph) .build(); App app = appEngineService.create(createApp); return app.getWorkflowId(); @@ -35,11 +38,12 @@ public class ProcessDefinitionFacade { } public void update(ProcessBo processBo) { + Graph graph = GraphConvert.INSTANCE.toEntity(processBo.getGraph()); appEngineService.update(UpdateApp.builder() .defaultUse(processBo.getDefaultUse()) .appId(processBo.getAppId()) .name(processBo.getName()) - .graph(processBo.getGraph()) + .graph(graph) .build()); } diff --git a/src/main/java/com/metis/flow/convert/BaseAppConvert.java b/src/main/java/com/metis/flow/convert/BaseAppConvert.java index 5029018..8d13bf1 100644 --- a/src/main/java/com/metis/flow/convert/BaseAppConvert.java +++ b/src/main/java/com/metis/flow/convert/BaseAppConvert.java @@ -32,7 +32,7 @@ public interface BaseAppConvert { * @return {@link App } */ @Mappings({ - @Mapping(target = "graph", expression = "java(com.alibaba.fastjson2.JSON.parseObject(baseApp.getGraphJson(), com.metis.flow.domain.bo.Graph.class))"), + @Mapping(target = "graph", expression = "java(com.alibaba.fastjson2.JSON.parseObject(baseApp.getGraphJson(), com.metis.flow.domain.entity.base.Graph.class))"), @Mapping(target = "workflowId", source = "id") }) App toApp(BaseApp baseApp); diff --git a/src/main/java/com/metis/flow/convert/GraphConvert.java b/src/main/java/com/metis/flow/convert/GraphConvert.java new file mode 100644 index 0000000..930ab9d --- /dev/null +++ b/src/main/java/com/metis/flow/convert/GraphConvert.java @@ -0,0 +1,17 @@ +package com.metis.flow.convert; + + +import com.metis.flow.domain.bo.GraphBO; +import com.metis.flow.domain.entity.base.Graph; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface GraphConvert { + + GraphConvert INSTANCE = Mappers.getMapper(GraphConvert.class); + + + Graph toEntity(GraphBO graph); + +} diff --git a/src/main/java/com/metis/flow/domain/bo/BuildApp.java b/src/main/java/com/metis/flow/domain/bo/BuildApp.java index fd78ff5..1e44ddf 100644 --- a/src/main/java/com/metis/flow/domain/bo/BuildApp.java +++ b/src/main/java/com/metis/flow/domain/bo/BuildApp.java @@ -1,5 +1,6 @@ package com.metis.flow.domain.bo; +import com.metis.flow.domain.entity.base.Graph; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/com/metis/flow/domain/bo/CreateApp.java b/src/main/java/com/metis/flow/domain/bo/CreateApp.java index 42f2a66..e99d0b1 100644 --- a/src/main/java/com/metis/flow/domain/bo/CreateApp.java +++ b/src/main/java/com/metis/flow/domain/bo/CreateApp.java @@ -1,5 +1,6 @@ package com.metis.flow.domain.bo; +import com.metis.flow.domain.entity.base.Graph; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/com/metis/flow/domain/bo/EdgeBO.java b/src/main/java/com/metis/flow/domain/bo/EdgeBO.java new file mode 100644 index 0000000..240aa5c --- /dev/null +++ b/src/main/java/com/metis/flow/domain/bo/EdgeBO.java @@ -0,0 +1,66 @@ +package com.metis.flow.domain.bo; + +import com.metis.flow.enums.EdgeType; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class EdgeBO { + + /** + * 唯一标识符 + */ + @NotNull(message = "唯一标识符不能为空") + private String id; + + /** + * 标签 + */ + private String label; + + /** + * 节点类型 + */ + @NotNull(message = "线类型不能为空") + private EdgeType type; + + /** + * 源节点ID,对应节点id + */ + @NotNull(message = "源节点ID不能为空") + private Long source; + + /** + * 目标节点ID,对应节点id + */ + @NotNull(message = "目标节点ID不能为空") + private Long target; + /** + * 源句柄id + */ + @NotNull(message = "源句柄ID不能为空") + private Long sourceHandle; + + /** + * 目标句柄id + */ + @NotNull(message = "目标句柄ID不能为空") + private Long targetHandle; + + /** + * 边是否动画true/false + */ + private Boolean animated; + + /** + * 开始标志 + */ + private String markerStart; + + /** + * 结束标记 + */ + private String markerEnd; + + +} diff --git a/src/main/java/com/metis/flow/domain/bo/Graph.java b/src/main/java/com/metis/flow/domain/bo/Graph.java deleted file mode 100644 index 626974c..0000000 --- a/src/main/java/com/metis/flow/domain/bo/Graph.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.metis.flow.domain.bo; - -import com.metis.flow.domain.entity.base.Edge; -import com.metis.flow.domain.entity.base.Node; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import lombok.Data; - -import java.util.List; - -@Data -public class Graph { - - /** - * 边缘 - */ - @Valid - @NotNull(message = "连线不能为空") - private List edges; - - /** - * 节点 - */ - @Valid - @NotNull(message = "节点不能为空") - @Size(min = 1, message = "节点不能为空") - private List nodes; -} diff --git a/src/main/java/com/metis/flow/domain/bo/GraphBO.java b/src/main/java/com/metis/flow/domain/bo/GraphBO.java new file mode 100644 index 0000000..7d0c565 --- /dev/null +++ b/src/main/java/com/metis/flow/domain/bo/GraphBO.java @@ -0,0 +1,43 @@ +package com.metis.flow.domain.bo; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +@Data +public class GraphBO { + + /** + * 边缘 + */ + @Valid + @NotEmpty(message = "连线不能为空") + private List edges; + + /** + * 节点 + */ + @Valid + @NotEmpty(message = "节点不能为空") + private List nodes; + + + /** + * 位置 + */ + private List position; + + /** + * 变焦 + */ + private Double zoom; + + /** + * 视窗 + */ + private ViewportBo viewport; + + +} diff --git a/src/main/java/com/metis/flow/domain/bo/HandleBO.java b/src/main/java/com/metis/flow/domain/bo/HandleBO.java new file mode 100644 index 0000000..07e0f67 --- /dev/null +++ b/src/main/java/com/metis/flow/domain/bo/HandleBO.java @@ -0,0 +1,36 @@ +package com.metis.flow.domain.bo; + +import com.metis.flow.enums.HandleType; +import com.metis.flow.enums.PositionType; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 句柄对象 + */ +@Data +public class HandleBO { + /** + * 句柄id + */ + @NotNull(message = "句柄id不能为空") + private Long id; + + /** + * 句柄类型 + */ + @NotNull(message = "句柄类型不能为空") + private HandleType type; + + /** + * 句柄位置 + */ + @NotNull(message = "句柄位置不能为空") + private PositionType position; + + /** + * 是否可以连接 + */ + @NotNull(message = "是否可以连接不能为空") + private Boolean connectable; +} diff --git a/src/main/java/com/metis/flow/domain/bo/NodeBO.java b/src/main/java/com/metis/flow/domain/bo/NodeBO.java new file mode 100644 index 0000000..3fb483b --- /dev/null +++ b/src/main/java/com/metis/flow/domain/bo/NodeBO.java @@ -0,0 +1,60 @@ +package com.metis.flow.domain.bo; + +import com.metis.flow.enums.NodeType; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class NodeBO { + + /** + * id + */ + @NotNull(message = "节点id不能为空") + private Long id; + + /** + * 类型 + */ + @NotNull(message = "节点类型不能为空") + private NodeType type; + + /** + * 自定义类型 + */ + private String customType; + + /** + * 位置 + */ + @Valid + @NotNull(message = "节点位置不能为空") + private PositionBO position; + + /** + * 业务数据 + */ + @Valid + @NotNull(message = "节点业务数据不能为空") + private NodeDataBO data; + + /** + * 宽度 + */ +// @NotNull(message = "节点宽度不能为空") + private Integer width; + + /** + * 高度 + */ +// @NotNull(message = "节点高度不能为空") + private Integer height; + + /** + * 节点是否选中 + */ + private Boolean selected; + + +} diff --git a/src/main/java/com/metis/flow/domain/bo/NodeDataBO.java b/src/main/java/com/metis/flow/domain/bo/NodeDataBO.java new file mode 100644 index 0000000..39b9983 --- /dev/null +++ b/src/main/java/com/metis/flow/domain/bo/NodeDataBO.java @@ -0,0 +1,45 @@ +package com.metis.flow.domain.bo; + +import com.alibaba.fastjson2.JSONObject; +import com.metis.flow.enums.PositionType; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +@Data +public class NodeDataBO { + + /** + * 标签 + */ + @NotBlank(message = "标签不能为空") + private String label; + + /** + * 图标 + */ + private String icon; + + /** + * 工具栏位置 + */ + private PositionType toolbarPosition; + + + /** + * 配置 + */ + private JSONObject config; + + /** + * 句柄列表 + */ + @Valid + @NotEmpty(message = "句柄列表不能为空") + private List handles; + + +} diff --git a/src/main/java/com/metis/flow/domain/bo/PositionBO.java b/src/main/java/com/metis/flow/domain/bo/PositionBO.java new file mode 100644 index 0000000..f91a540 --- /dev/null +++ b/src/main/java/com/metis/flow/domain/bo/PositionBO.java @@ -0,0 +1,20 @@ +package com.metis.flow.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class PositionBO { + /** + * x坐标 + */ + @NotNull(message = "x坐标不能为空") + private Double x; + + /** + * y坐标 + */ + @NotNull(message = "y坐标不能为空") + private Double y; + +} diff --git a/src/main/java/com/metis/flow/domain/bo/UpdateApp.java b/src/main/java/com/metis/flow/domain/bo/UpdateApp.java index a6a0050..8d6d119 100644 --- a/src/main/java/com/metis/flow/domain/bo/UpdateApp.java +++ b/src/main/java/com/metis/flow/domain/bo/UpdateApp.java @@ -1,6 +1,7 @@ package com.metis.flow.domain.bo; import com.metis.enums.YesOrNoEnum; +import com.metis.flow.domain.entity.base.Graph; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/com/metis/flow/domain/bo/ViewportBo.java b/src/main/java/com/metis/flow/domain/bo/ViewportBo.java new file mode 100644 index 0000000..efe7eab --- /dev/null +++ b/src/main/java/com/metis/flow/domain/bo/ViewportBo.java @@ -0,0 +1,10 @@ +package com.metis.flow.domain.bo; + +import lombok.Data; + +@Data +public class ViewportBo { + private Double x; + private Double y; + private Double zoom; +} diff --git a/src/main/java/com/metis/flow/domain/entity/App.java b/src/main/java/com/metis/flow/domain/entity/App.java index 5b97af1..e7546b5 100644 --- a/src/main/java/com/metis/flow/domain/entity/App.java +++ b/src/main/java/com/metis/flow/domain/entity/App.java @@ -2,7 +2,7 @@ package com.metis.flow.domain.entity; import com.metis.enums.YesOrNoEnum; -import com.metis.flow.domain.bo.Graph; +import com.metis.flow.domain.entity.base.Graph; import lombok.Data; import java.time.LocalDateTime; diff --git a/src/main/java/com/metis/flow/domain/entity/GraphDemo.java b/src/main/java/com/metis/flow/domain/entity/GraphDemo.java new file mode 100644 index 0000000..1a2f441 --- /dev/null +++ b/src/main/java/com/metis/flow/domain/entity/GraphDemo.java @@ -0,0 +1,52 @@ +package com.metis.flow.domain.entity; + +import com.metis.flow.domain.entity.base.Edge; +import com.metis.flow.domain.entity.base.Node; + +import java.util.*; + +public class GraphDemo { + private Map nodes = new HashMap<>(); + private Map> adjacencyList = new HashMap<>(); + + public void addNode(Node node) { + nodes.put(node.getId(), node); + adjacencyList.put(node.getId(), new ArrayList<>()); + } + + public void addEdge(Edge edge) { + adjacencyList.get(edge.getSource()) + .add(edge.getTarget()); + } + + public List topologicalSort() { + List sortedNodes = new ArrayList<>(); + Set visited = new HashSet<>(); + Set visiting = new HashSet<>(); + + for (Long nodeId : nodes.keySet()) { + if (!visited.contains(nodeId)) { + dfs(nodeId, visited, visiting, sortedNodes); + } + } + + Collections.reverse(sortedNodes); + return sortedNodes; + } + + private void dfs(Long nodeId, Set visited, Set visiting, List sortedNodes) { + if (visiting.contains(nodeId)) { + throw new IllegalStateException("Cycle detected in the graph"); + } + + if (!visited.contains(nodeId)) { + visiting.add(nodeId); + for (Long neighbor : adjacencyList.get(nodeId)) { + dfs(neighbor, visited, visiting, sortedNodes); + } + visiting.remove(nodeId); + visited.add(nodeId); + sortedNodes.add(nodes.get(nodeId)); + } + } +} diff --git a/src/main/java/com/metis/flow/domain/entity/base/Graph.java b/src/main/java/com/metis/flow/domain/entity/base/Graph.java new file mode 100644 index 0000000..96bd053 --- /dev/null +++ b/src/main/java/com/metis/flow/domain/entity/base/Graph.java @@ -0,0 +1,43 @@ +package com.metis.flow.domain.entity.base; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +@Data +public class Graph { + + /** + * 边缘 + */ + @Valid + @NotEmpty(message = "连线不能为空") + private List edges; + + /** + * 节点 + */ + @Valid + @NotEmpty(message = "节点不能为空") + private List nodes; + + /** + * 位置 + */ + private List position; + + /** + * 变焦 + */ + private Double zoom; + + /** + * 视窗 + */ + private Viewport viewport; + + + +} 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 20dff37..d1def2c 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 @@ -1,16 +1,11 @@ package com.metis.flow.domain.entity.base; -import cn.hutool.core.collection.CollUtil; 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.NotNull; import lombok.Data; -import java.util.Map; -import java.util.stream.Collectors; - @Data public class Node { @@ -26,6 +21,8 @@ public class Node { @NotNull(message = "节点类型不能为空") private NodeType type; + private String customType; + /** * 位置 */ @@ -58,20 +55,31 @@ public class Node { private Boolean selected; - @JsonIgnore - public Map getHandleMap() { - if (CollUtil.isEmpty(data.getHandles())) { - return Map.of(); - } - return data.getHandles().stream().collect(Collectors.toMap(Handle::getId, handle -> handle)); - } + private Class configClass; - @JsonIgnore + + /** + * 获取配置 + * + * @return {@link T } + */ public T getConfig() { if (ObjectUtil.isNull(data.getConfig())) { return null; } - return (T) data.getConfig().to(type.getConfigClass()); + return (T) data.getConfig().to(configClass); + } + + /** + * 设置配置类 + * + * @param configClass 配置类 + */ + public void setConfigClass(Class configClass) { + if (ObjectUtil.isNotNull(this.configClass)) { + return; + } + this.configClass = configClass; } } diff --git a/src/main/java/com/metis/flow/domain/entity/base/NodeConfig.java b/src/main/java/com/metis/flow/domain/entity/base/NodeConfig.java new file mode 100644 index 0000000..e16787d --- /dev/null +++ b/src/main/java/com/metis/flow/domain/entity/base/NodeConfig.java @@ -0,0 +1,4 @@ +package com.metis.flow.domain.entity.base; + +public abstract class NodeConfig { +} diff --git a/src/main/java/com/metis/flow/domain/entity/base/NodeData.java b/src/main/java/com/metis/flow/domain/entity/base/NodeData.java index ed632d2..42d65bb 100644 --- a/src/main/java/com/metis/flow/domain/entity/base/NodeData.java +++ b/src/main/java/com/metis/flow/domain/entity/base/NodeData.java @@ -4,8 +4,7 @@ import com.alibaba.fastjson2.JSONObject; import com.metis.flow.enums.PositionType; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; +import jakarta.validation.constraints.NotEmpty; import lombok.Data; import java.util.List; @@ -30,8 +29,6 @@ public class NodeData { private PositionType toolbarPosition; - - /** * 配置 */ @@ -41,8 +38,7 @@ public class NodeData { * 句柄列表 */ @Valid - @NotNull(message = "句柄列表不能为空") - @Size(min = 1, message = "句柄列表不能为空") + @NotEmpty(message = "句柄列表不能为空") private List handles; diff --git a/src/main/java/com/metis/flow/domain/entity/base/Viewport.java b/src/main/java/com/metis/flow/domain/entity/base/Viewport.java new file mode 100644 index 0000000..5b279b3 --- /dev/null +++ b/src/main/java/com/metis/flow/domain/entity/base/Viewport.java @@ -0,0 +1,10 @@ +package com.metis.flow.domain.entity.base; + +import lombok.Data; + +@Data +public class Viewport { + private Double x; + private Double y; + private Double zoom; +} diff --git a/src/main/java/com/metis/flow/domain/entity/config/node/DocumentExtractorNodeConfig.java b/src/main/java/com/metis/flow/domain/entity/config/node/DocumentExtractorNodeConfig.java index 76d1fc4..1fbb852 100644 --- a/src/main/java/com/metis/flow/domain/entity/config/node/DocumentExtractorNodeConfig.java +++ b/src/main/java/com/metis/flow/domain/entity/config/node/DocumentExtractorNodeConfig.java @@ -1,10 +1,13 @@ package com.metis.flow.domain.entity.config.node; +import com.metis.flow.domain.entity.base.NodeConfig; import jakarta.validation.constraints.NotBlank; import lombok.Data; +import lombok.EqualsAndHashCode; @Data -public class DocumentExtractorNodeConfig { +@EqualsAndHashCode(callSuper = true) +public class DocumentExtractorNodeConfig extends NodeConfig { @NotBlank(message = "文件类型不能为空") private String fileType; diff --git a/src/main/java/com/metis/flow/domain/entity/config/node/EndNodeConfig.java b/src/main/java/com/metis/flow/domain/entity/config/node/EndNodeConfig.java new file mode 100644 index 0000000..8e79214 --- /dev/null +++ b/src/main/java/com/metis/flow/domain/entity/config/node/EndNodeConfig.java @@ -0,0 +1,10 @@ +package com.metis.flow.domain.entity.config.node; + +import com.metis.flow.domain.entity.base.NodeConfig; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class EndNodeConfig extends NodeConfig { +} diff --git a/src/main/java/com/metis/flow/domain/entity/config/node/StartNodeConfig.java b/src/main/java/com/metis/flow/domain/entity/config/node/StartNodeConfig.java index 685c1c4..883dad8 100644 --- a/src/main/java/com/metis/flow/domain/entity/config/node/StartNodeConfig.java +++ b/src/main/java/com/metis/flow/domain/entity/config/node/StartNodeConfig.java @@ -1,14 +1,16 @@ package com.metis.flow.domain.entity.config.node; +import com.metis.flow.domain.entity.base.NodeConfig; import com.metis.flow.domain.entity.base.NodeVariable; import jakarta.validation.Valid; import lombok.Data; +import lombok.EqualsAndHashCode; import java.util.List; @Data -public class StartNodeConfig { - +@EqualsAndHashCode(callSuper = true) +public class StartNodeConfig extends NodeConfig { @Valid private List variables; diff --git a/src/main/java/com/metis/flow/engine/impl/AppFlowEngineRunnerServiceImpl.java b/src/main/java/com/metis/flow/engine/impl/AppFlowEngineRunnerServiceImpl.java index ff00289..5d46a8f 100644 --- a/src/main/java/com/metis/flow/engine/impl/AppFlowEngineRunnerServiceImpl.java +++ b/src/main/java/com/metis/flow/engine/impl/AppFlowEngineRunnerServiceImpl.java @@ -1,14 +1,16 @@ package com.metis.flow.engine.impl; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjectUtil; -import com.metis.flow.domain.bo.Graph; +import cn.hutool.core.util.StrUtil; import com.metis.flow.domain.context.RunningContext; import com.metis.flow.domain.context.RunningResult; import com.metis.flow.domain.context.SysContext; import com.metis.flow.domain.entity.App; import com.metis.flow.domain.entity.base.Edge; +import com.metis.flow.domain.entity.base.Graph; import com.metis.flow.domain.entity.base.Node; import com.metis.flow.engine.AppEngineService; import com.metis.flow.engine.AppFlowEngineRunnerService; @@ -16,13 +18,13 @@ import com.metis.flow.enums.NodeType; import com.metis.flow.runner.FlowRunningContext; import com.metis.flow.runner.NodeRunner; import com.metis.flow.runner.RunnerResult; -import com.metis.flow.runner.factory.RunnerFactory; +import com.metis.flow.runner.factory.NodeRunnerFactory; +import com.metis.utils.GenericInterfacesUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -38,7 +40,6 @@ public class AppFlowEngineRunnerServiceImpl implements AppFlowEngineRunnerServic public RunnerResult running(FlowRunningContext context) { App app = getApp(context); // todo 构建运行实例, 并将运行实例放入上下文 - Long instanceId = IdUtil.getSnowflakeNextId(); // 构建系统上下文信息 SysContext sysContext = SysContext.builder() @@ -55,21 +56,18 @@ public class AppFlowEngineRunnerServiceImpl implements AppFlowEngineRunnerServic .collect(Collectors.toMap(Node::getId, Function.identity())); Map> edgeMap = graph.getEdges().stream() .collect(Collectors.groupingBy(Edge::getSource)); - // 获取到开始节点 - Node runningNode = graph.getNodes().stream().filter(node -> node.getType() == NodeType.START) - .findFirst().orElse(null); - // 开始节点为空,则表示数据存在异常 - Assert.isTrue(ObjectUtil.isNotNull(runningNode), "流程图不存在开始节点"); - while (ObjectUtil.isNotNull(runningNode)) { - NodeRunner nodeRunner = RunnerFactory.get(runningNode.getType()); - // 获取到返回结果 - RunningResult result = nodeRunner.run(runningContext, runningNode, edgeMap.get(runningNode.getId())); - if (ObjectUtil.isNotNull(result.getNodeContext())) { - runningContext.addNodeRunningContext(runningNode.getId(), result.getNodeContext()); - } - runningNode = null; - } + Set readyRunningNode = new HashSet<>(); + // 获取到开始节点 + // 开始节点为空,则表示数据存在异常 + Assert.isTrue(ObjectUtil.isNotNull(readyRunningNode), "流程图不存在开始节点"); + while (CollUtil.isNotEmpty(readyRunningNode)) { + // todo 出现多个节点同时运行, 需要找到他们最终运行的聚合节点, 前期默认只有一条线路运行, 不支持并行流程 + doRunning(readyRunningNode, edgeMap, runningContext); + + + readyRunningNode = null; + } return RunnerResult.builder() .content("你他妈的!") @@ -78,6 +76,49 @@ public class AppFlowEngineRunnerServiceImpl implements AppFlowEngineRunnerServic } + private void doRunning(Set readyRunningNode, Map> edgeMap, RunningContext runningContext) { + Set nextRunningNodeId = new HashSet<>(); + for (Node runningNode : readyRunningNode) { + // 当前节点接下来的连接线信息 + List edges = edgeMap.getOrDefault(runningNode.getId(), new ArrayList<>()); + // 执行 + NodeRunner nodeRunner = getNodeRunner(runningNode); + runningNode.setConfigClass(GenericInterfacesUtils.getClass(nodeRunner)); + // 获取到返回结果 + RunningResult result = nodeRunner.run(runningContext, runningNode, edges); + // 节点执行结果参数放入上下文中 + if (ObjectUtil.isNotNull(result.getNodeContext())) { + runningContext.addNodeRunningContext(runningNode.getId(), result.getNodeContext()); + } + if (CollUtil.isNotEmpty(result.getNextRunNodeId())) { + nextRunningNodeId.addAll(result.getNextRunNodeId()); + } + } + } + + + private RunningResult doRunning(RunningContext runningContext, Node node, List edges) { + NodeRunner nodeRunner = getNodeRunner(node); + node.setConfigClass(GenericInterfacesUtils.getClass(nodeRunner)); + // 获取到返回结果 + return nodeRunner.run(runningContext, node, edges); + } + + + /** + * 获取节点运行程序 + * + * @param node 节点 + * @return {@link NodeRunner } + */ + private NodeRunner getNodeRunner(Node node) { + if (NodeType.CUSTOM_NODE.equals(node.getType())) { + Assert.isTrue(StrUtil.isNotBlank(node.getCustomType()), "自定义节点类型不能为空"); + return NodeRunnerFactory.getCustom(node.getCustomType()); + } + return NodeRunnerFactory.get(node.getType()); + } + /** * 获取到应用程序信息 diff --git a/src/main/java/com/metis/flow/enums/NodeType.java b/src/main/java/com/metis/flow/enums/NodeType.java index a56c7fb..caa5849 100644 --- a/src/main/java/com/metis/flow/enums/NodeType.java +++ b/src/main/java/com/metis/flow/enums/NodeType.java @@ -2,8 +2,6 @@ package com.metis.flow.enums; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -import com.metis.flow.domain.entity.config.node.DocumentExtractorNodeConfig; -import com.metis.flow.domain.entity.config.node.StartNodeConfig; import lombok.AllArgsConstructor; import lombok.Getter; @@ -13,9 +11,10 @@ import java.util.Arrays; @AllArgsConstructor public enum NodeType { - START(1, "start", "开始", StartNodeConfig.class), - END(2, "end", "结束", Object.class), - DOCUMENT_EXTRACTOR(3, "document-extractor", "文档提取器", DocumentExtractorNodeConfig.class); + START(1, "start", "开始"), + END(2, "end", "结束"), + DOCUMENT_EXTRACTOR(3, "document-extractor", "文档提取器"), + CUSTOM_NODE(4, "Custom-Node", "自定义节点"); private final Integer code; @@ -25,7 +24,7 @@ public enum NodeType { private final String name; - private final Class configClass; +// private final Class configClass; /** diff --git a/src/main/java/com/metis/flow/runner/CustomNodeRunner.java b/src/main/java/com/metis/flow/runner/CustomNodeRunner.java new file mode 100644 index 0000000..16887d3 --- /dev/null +++ b/src/main/java/com/metis/flow/runner/CustomNodeRunner.java @@ -0,0 +1,33 @@ +package com.metis.flow.runner; + +import com.metis.flow.domain.entity.base.NodeConfig; +import com.metis.flow.enums.NodeType; + +/** + * 自定义节点运行器 + * + * @author clay + * @date 2025/04/20 + */ +public interface CustomNodeRunner extends NodeRunner { + + + /** + * 获取自定义节点的节点类型 + * + * @return {@link String } + */ + String getCustomNodeType(); + + + /** + * 得到类型 + * + * @return {@link NodeType } + */ + default NodeType getType() { + return NodeType.CUSTOM_NODE; + } + + +} diff --git a/src/main/java/com/metis/flow/runner/NodeRunner.java b/src/main/java/com/metis/flow/runner/NodeRunner.java index 855a7f7..d85fdfa 100644 --- a/src/main/java/com/metis/flow/runner/NodeRunner.java +++ b/src/main/java/com/metis/flow/runner/NodeRunner.java @@ -4,11 +4,18 @@ import com.metis.flow.domain.context.RunningContext; import com.metis.flow.domain.context.RunningResult; import com.metis.flow.domain.entity.base.Edge; import com.metis.flow.domain.entity.base.Node; +import com.metis.flow.domain.entity.base.NodeConfig; import com.metis.flow.enums.NodeType; import java.util.List; -public interface NodeRunner { +/** + * 内置节点运行器 + * + * @author clay + * @date 2025/04/20 + */ +public interface NodeRunner { /** @@ -16,7 +23,7 @@ public interface NodeRunner { * * @param context 上下文 * @param node 节点配置信息 - * @param edges + * @param edges 当前接下来的连线信息, 一些特殊节点需要节点内部判断下一个运行节点 * @return {@link RunningContext } */ RunningResult run(RunningContext context, Node node, List edges); diff --git a/src/main/java/com/metis/flow/runner/factory/NodeRunnerFactory.java b/src/main/java/com/metis/flow/runner/factory/NodeRunnerFactory.java new file mode 100644 index 0000000..f46ad58 --- /dev/null +++ b/src/main/java/com/metis/flow/runner/factory/NodeRunnerFactory.java @@ -0,0 +1,64 @@ +package com.metis.flow.runner.factory; + +import cn.hutool.core.lang.Assert; +import com.metis.flow.enums.NodeType; +import com.metis.flow.runner.CustomNodeRunner; +import com.metis.flow.runner.NodeRunner; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public final class NodeRunnerFactory { + + /** + * 内置节点运行器 + */ + private static final Map NODE_MAP = new ConcurrentHashMap<>(8); + /** + * 自定义节点映射 + */ + private static final Map CUSTOM_NODE_MAP = new ConcurrentHashMap<>(8); + + /** + * 注册 + * + * @param runner 跑步者 + */ + static void register(NodeRunner runner) { + NODE_MAP.put(runner.getType(), runner); + } + + + /** + * 得到 + * + * @param type 类型 + * @return {@link NodeRunner } + */ + public static NodeRunner get(NodeType type) { + return NODE_MAP.get(type); + } + + + /** + * 注册自定义节点 + * + * @param runner 跑步者 + */ + static void registerCustom(CustomNodeRunner runner) { + Assert.isTrue(!CUSTOM_NODE_MAP.containsKey(runner.getCustomNodeType()), "已存在类型:{}, class:{}的运行器", runner.getCustomNodeType(), runner.getClass()); + CUSTOM_NODE_MAP.put(runner.getCustomNodeType(), runner); + } + + /** + * 得到自定义节点运行器 + * + * @param type 类型 + * @return {@link NodeRunner } + */ + public static NodeRunner getCustom(String type) { + return CUSTOM_NODE_MAP.get(type); + } + + +} diff --git a/src/main/java/com/metis/flow/runner/factory/RunnerFactory.java b/src/main/java/com/metis/flow/runner/factory/RunnerFactory.java deleted file mode 100644 index 3a71a82..0000000 --- a/src/main/java/com/metis/flow/runner/factory/RunnerFactory.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.metis.flow.runner.factory; - -import com.metis.flow.enums.NodeType; -import com.metis.flow.runner.NodeRunner; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class RunnerFactory { - - private static final Map MAP = new ConcurrentHashMap<>(8); - - - /** - * 注册 - * - * @param runner 跑步者 - */ - public static void register(NodeRunner runner) { - MAP.put(runner.getType(), runner); - } - - - /** - * 得到 - * - * @param type 类型 - * @return {@link NodeRunner } - */ - public static NodeRunner get(NodeType type) { - return MAP.get(type); - } - - - -} diff --git a/src/main/java/com/metis/flow/runner/RunnerInitialize.java b/src/main/java/com/metis/flow/runner/factory/RunnerInitialize.java similarity index 50% rename from src/main/java/com/metis/flow/runner/RunnerInitialize.java rename to src/main/java/com/metis/flow/runner/factory/RunnerInitialize.java index 094d7fc..940cece 100644 --- a/src/main/java/com/metis/flow/runner/RunnerInitialize.java +++ b/src/main/java/com/metis/flow/runner/factory/RunnerInitialize.java @@ -1,6 +1,9 @@ -package com.metis.flow.runner; +package com.metis.flow.runner.factory; -import com.metis.flow.runner.factory.RunnerFactory; +import cn.hutool.core.lang.Assert; +import com.metis.flow.enums.NodeType; +import com.metis.flow.runner.CustomNodeRunner; +import com.metis.flow.runner.NodeRunner; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -19,9 +22,16 @@ public class RunnerInitialize implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + Map runnerMap = applicationContext.getBeansOfType(NodeRunner.class); - runnerMap.forEach((k, v) -> { - RunnerFactory.register(v); + + runnerMap.forEach((runnerBeanName, runner) -> { + if (NodeType.CUSTOM_NODE.equals(runner.getType())) { + Assert.isTrue(runner instanceof CustomNodeRunner, "自定义节点必须实现CustomNodeRunner接口"); + NodeRunnerFactory.registerCustom((CustomNodeRunner) runner); + } else { + NodeRunnerFactory.register(runner); + } }); } 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 8b252c3..8bd104d 100644 --- a/src/main/java/com/metis/flow/runner/impl/EndNodeRunner.java +++ b/src/main/java/com/metis/flow/runner/impl/EndNodeRunner.java @@ -5,6 +5,7 @@ import com.metis.flow.domain.context.RunningContext; import com.metis.flow.domain.context.RunningResult; import com.metis.flow.domain.entity.base.Edge; import com.metis.flow.domain.entity.base.Node; +import com.metis.flow.domain.entity.config.node.EndNodeConfig; import com.metis.flow.enums.NodeType; import com.metis.flow.runner.NodeRunner; import lombok.extern.slf4j.Slf4j; @@ -14,7 +15,7 @@ import java.util.List; @Slf4j @Service -public class EndNodeRunner implements NodeRunner { +public class EndNodeRunner implements NodeRunner { @Override public RunningResult run(RunningContext context, Node node, List edges) { 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 2975174..d5c2be3 100644 --- a/src/main/java/com/metis/flow/runner/impl/StartNodeRunner.java +++ b/src/main/java/com/metis/flow/runner/impl/StartNodeRunner.java @@ -23,7 +23,7 @@ import java.util.List; */ @Slf4j @Service -public class StartNodeRunner implements NodeRunner { +public class StartNodeRunner implements NodeRunner { @Override public RunningResult run(RunningContext context, Node node, List edges) { @@ -46,6 +46,7 @@ public class StartNodeRunner implements NodeRunner { } + @Override public NodeType getType() { return NodeType.START; diff --git a/src/main/java/com/metis/flow/validator/CustomNodeValidator.java b/src/main/java/com/metis/flow/validator/CustomNodeValidator.java new file mode 100644 index 0000000..ace602a --- /dev/null +++ b/src/main/java/com/metis/flow/validator/CustomNodeValidator.java @@ -0,0 +1,34 @@ +package com.metis.flow.validator; + +import com.metis.flow.domain.entity.base.NodeConfig; +import com.metis.flow.enums.NodeType; + +/** + * 自定义节点验证器 + * + * @author clay + * @date 2025/04/20 + */ +public interface CustomNodeValidator extends NodeValidator { + + + /** + * 获取自定义节点的节点类型 + * + * @return {@link String } + */ + String getCustomNodeType(); + + + /** + * 得到类型 + * + * @return {@link NodeType } + */ + default NodeType getType() { + return NodeType.CUSTOM_NODE; + } + + + +} diff --git a/src/main/java/com/metis/flow/validator/NodeValidator.java b/src/main/java/com/metis/flow/validator/NodeValidator.java index 76f8211..7c6274e 100644 --- a/src/main/java/com/metis/flow/validator/NodeValidator.java +++ b/src/main/java/com/metis/flow/validator/NodeValidator.java @@ -2,11 +2,12 @@ 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.domain.entity.base.NodeConfig; import com.metis.flow.enums.NodeType; import java.util.List; -public interface NodeValidator { +public interface NodeValidator { /** diff --git a/src/main/java/com/metis/flow/validator/ValidatorInitialize.java b/src/main/java/com/metis/flow/validator/ValidatorInitialize.java deleted file mode 100644 index 7201eb7..0000000 --- a/src/main/java/com/metis/flow/validator/ValidatorInitialize.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.metis.flow.validator; - - -import com.metis.flow.validator.factory.EdgeValidatorFactory; -import com.metis.flow.validator.factory.NodeValidatorFactory; -import org.jetbrains.annotations.NotNull; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.stereotype.Service; - -import java.util.Map; - -@Service -public class ValidatorInitialize implements ApplicationContextAware { - - - @Override - public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException { - Map edgeMap = applicationContext.getBeansOfType(EdgeValidator.class); - - edgeMap.forEach((k, v) -> { - EdgeValidatorFactory.register(v); - }); - - - Map nodeMap = applicationContext.getBeansOfType(NodeValidator.class); - nodeMap.forEach((k, v) -> { - NodeValidatorFactory.register(v); - }); - } - - -} diff --git a/src/main/java/com/metis/flow/validator/factory/NodeValidatorFactory.java b/src/main/java/com/metis/flow/validator/factory/NodeValidatorFactory.java index ac00792..92bf27e 100644 --- a/src/main/java/com/metis/flow/validator/factory/NodeValidatorFactory.java +++ b/src/main/java/com/metis/flow/validator/factory/NodeValidatorFactory.java @@ -1,23 +1,33 @@ package com.metis.flow.validator.factory; +import cn.hutool.core.lang.Assert; import com.metis.flow.enums.NodeType; +import com.metis.flow.validator.CustomNodeValidator; import com.metis.flow.validator.NodeValidator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -public class NodeValidatorFactory { +public final class NodeValidatorFactory { + + /** + * 内置节点验证器 + */ + private static final Map NODE_MAP = new ConcurrentHashMap<>(8); + /** + * 自定义节点验证器 + */ + private static final Map CUSTOM_NODE_MAP = new ConcurrentHashMap<>(8); - private static final Map MAP = new ConcurrentHashMap<>(8); /** * 注册 * * @param validator 验证器 */ - public static void register(NodeValidator validator) { - MAP.put(validator.getType(), validator); + static void register(NodeValidator validator) { + NODE_MAP.put(validator.getType(), validator); } /** @@ -27,7 +37,19 @@ public class NodeValidatorFactory { * @return {@link NodeValidator } */ public static NodeValidator get(NodeType type) { - return MAP.get(type); + return NODE_MAP.get(type); + } + + /** + * @param validator 验证器 + */ + static void registerCustom(CustomNodeValidator validator) { + Assert.isTrue(!CUSTOM_NODE_MAP.containsKey(validator.getCustomNodeType()), "已存在类型:{}, class:{}的验证器", validator.getCustomNodeType(), validator.getClass()); + CUSTOM_NODE_MAP.put(validator.getCustomNodeType(), validator); + } + + public static NodeValidator getCustom(String type) { + return CUSTOM_NODE_MAP.get(type); } diff --git a/src/main/java/com/metis/flow/validator/factory/ValidatorInitialize.java b/src/main/java/com/metis/flow/validator/factory/ValidatorInitialize.java new file mode 100644 index 0000000..9b038d6 --- /dev/null +++ b/src/main/java/com/metis/flow/validator/factory/ValidatorInitialize.java @@ -0,0 +1,43 @@ +package com.metis.flow.validator.factory; + + +import cn.hutool.core.lang.Assert; +import com.metis.flow.enums.NodeType; +import com.metis.flow.validator.CustomNodeValidator; +import com.metis.flow.validator.EdgeValidator; +import com.metis.flow.validator.NodeValidator; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Service; + +import java.util.Map; + +@Service +public class ValidatorInitialize implements ApplicationContextAware { + + + @Override + public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException { + + Map edgeMap = applicationContext.getBeansOfType(EdgeValidator.class); + + edgeMap.forEach((edgeValidatorBeanName, edgeValidator) -> { + EdgeValidatorFactory.register(edgeValidator); + }); + + Map nodeMap = applicationContext.getBeansOfType(NodeValidator.class); + + nodeMap.forEach((nodeValidatorBeanName, nodeValidator) -> { + if (NodeType.CUSTOM_NODE.equals(nodeValidator.getType())) { + Assert.isTrue(nodeValidator instanceof CustomNodeValidator, "自定义节点必须实现CustomNodeValidator接口"); + NodeValidatorFactory.registerCustom((CustomNodeValidator) nodeValidator); + } else { + NodeValidatorFactory.register(nodeValidator); + } + }); + } + + +} 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 17b4e8d..a3d9df7 100644 --- a/src/main/java/com/metis/flow/validator/impl/ValidatorServiceImpl.java +++ b/src/main/java/com/metis/flow/validator/impl/ValidatorServiceImpl.java @@ -3,15 +3,17 @@ package com.metis.flow.validator.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import com.metis.flow.domain.bo.BuildApp; -import com.metis.flow.domain.bo.Graph; import com.metis.flow.domain.entity.base.Edge; +import com.metis.flow.domain.entity.base.Graph; import com.metis.flow.domain.entity.base.Node; import com.metis.flow.enums.EdgeType; import com.metis.flow.enums.NodeType; import com.metis.flow.validator.*; import com.metis.flow.validator.factory.EdgeValidatorFactory; import com.metis.flow.validator.factory.NodeValidatorFactory; +import com.metis.utils.GenericInterfacesUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -28,16 +30,16 @@ public class ValidatorServiceImpl implements ValidatorService { private final ValidatorCodeService validatorCodeService; @Override - public void validate(BuildApp graph) { + public void validate(BuildApp buildApp) { // validation 编程式校验 - validatorCodeService.validateThrow(graph); - Graph model = graph.getGraph(); + validatorCodeService.validateThrow(buildApp); + Graph graph = buildApp.getGraph(); // 节点参数校验 - validateNode(model.getNodes()); + validateNode(graph.getNodes()); // 线参数校验 - validateEdge(model.getEdges()); + validateEdge(graph.getEdges()); // 关系验证 - validateRelation(model.getNodes(), model.getEdges()); + validateRelation(graph.getNodes(), graph.getEdges()); } @@ -50,7 +52,8 @@ public class ValidatorServiceImpl implements ValidatorService { List errorMessage = new ArrayList<>(); for (Node node : nodes) { NodeType type = node.getType(); - NodeValidator validator = NodeValidatorFactory.get(type); + NodeValidator validator = getNodeValidator(node); + node.setConfigClass(GenericInterfacesUtils.getClass(validator)); // 节点校验器 Assert.isTrue(ObjectUtil.isNotNull(validator), "无:{}类型的节点校验器", type.getName()); ValidatorResult result = validator.validateValue(node); @@ -237,7 +240,7 @@ public class ValidatorServiceImpl implements ValidatorService { Map> targetMap = edges.stream().collect(Collectors.groupingBy(Edge::getTarget)); for (Node node : nodes) { - NodeValidator validator = NodeValidatorFactory.get(node.getType()); + NodeValidator validator = getNodeValidator(node); // 获取当前节点的 source 和 target List sources = targetMap.getOrDefault(node.getId(), new ArrayList<>()); List targets = sourceMap.getOrDefault(node.getId(), new ArrayList<>()); @@ -247,4 +250,19 @@ public class ValidatorServiceImpl implements ValidatorService { } } + + /** + * 获取节点验证器 + * + * @param node 节点 + * @return {@link NodeValidator } + */ + private NodeValidator getNodeValidator(Node node) { + if (NodeType.CUSTOM_NODE.equals(node.getType())) { + Assert.isTrue(StrUtil.isNotBlank(node.getCustomType()), "自定义节点类型不能为空"); + return NodeValidatorFactory.getCustom(node.getCustomType()); + } + return NodeValidatorFactory.get(node.getType()); + } + } 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 6d82c99..b9187cc 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 @@ -17,7 +17,7 @@ import java.util.List; @Slf4j @Service @RequiredArgsConstructor -public class DocumentExtractorNodeValidator implements NodeValidator { +public class DocumentExtractorNodeValidator implements NodeValidator { private final ValidatorCodeService validatorCodeService; @Override 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 fbe87d0..8ba8281 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 @@ -3,6 +3,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.EndNodeConfig; import com.metis.flow.enums.NodeType; import com.metis.flow.validator.NodeValidator; import com.metis.flow.validator.ValidatorResult; @@ -15,7 +16,7 @@ import java.util.List; @Slf4j @Service @RequiredArgsConstructor -public class EndNodeValidator implements NodeValidator { +public class EndNodeValidator implements NodeValidator { 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 97dfc11..d2fbee7 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 @@ -20,7 +20,7 @@ import java.util.List; @Slf4j @Service @RequiredArgsConstructor -public class StartNodeValidator implements NodeValidator { +public class StartNodeValidator implements NodeValidator { private final ValidatorCodeService validatorCodeService; diff --git a/src/main/java/com/metis/utils/GenericInterfacesUtils.java b/src/main/java/com/metis/utils/GenericInterfacesUtils.java new file mode 100644 index 0000000..c8b14f8 --- /dev/null +++ b/src/main/java/com/metis/utils/GenericInterfacesUtils.java @@ -0,0 +1,25 @@ +package com.metis.utils; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public class GenericInterfacesUtils { + + + public static Class getClass(Object object) { + + Type[] genericInterfaces = object.getClass().getGenericInterfaces(); + for (Type genericInterface : genericInterfaces) { + if (genericInterface instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) genericInterface; + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + if (typeArguments.length > 0) { + return (Class) typeArguments[0]; + } + } + } + throw new IllegalStateException("无法获取泛型类型"); + } + + +}