feat: node 和 edge id切换为String类型

This commit is contained in:
2025-05-04 23:04:12 +08:00
parent 7a72358fc7
commit bef6849e45
14 changed files with 75 additions and 70 deletions

View File

@@ -2,6 +2,7 @@ package com.metis.domain.bo;
import com.metis.enums.EdgeType; import com.metis.enums.EdgeType;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
@@ -11,7 +12,7 @@ public class EdgeBO {
/** /**
* 唯一标识符 * 唯一标识符
*/ */
@NotNull(message = "唯一标识符不能为空") @NotBlank(message = "唯一标识符不能为空")
private String id; private String id;
/** /**
@@ -29,24 +30,24 @@ public class EdgeBO {
* 源节点ID,对应节点id * 源节点ID,对应节点id
*/ */
@NotNull(message = "源节点ID不能为空") @NotNull(message = "源节点ID不能为空")
private Long source; private String source;
/** /**
* 目标节点ID,对应节点id * 目标节点ID,对应节点id
*/ */
@NotNull(message = "目标节点ID不能为空") @NotNull(message = "目标节点ID不能为空")
private Long target; private String target;
/** /**
* 源句柄id * 源句柄id
*/ */
@NotNull(message = "源句柄ID不能为空") @NotNull(message = "源句柄ID不能为空")
private Long sourceHandle; private String sourceHandle;
/** /**
* 目标句柄id * 目标句柄id
*/ */
@NotNull(message = "目标句柄ID不能为空") @NotNull(message = "目标句柄ID不能为空")
private Long targetHandle; private String targetHandle;
/** /**
* 边是否动画true/false * 边是否动画true/false

View File

@@ -3,6 +3,7 @@ package com.metis.domain.bo;
import com.metis.enums.HandleType; import com.metis.enums.HandleType;
import com.metis.enums.PositionType; import com.metis.enums.PositionType;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
@@ -14,9 +15,9 @@ public class HandleBO {
/** /**
* 句柄id * 句柄id
*/ */
@NotNull(message = "句柄id不能为空") @NotBlank(message = "句柄id不能为空")
@Schema(description = "句柄id") @Schema(description = "句柄id")
private Long id; private String id;
/** /**
* 句柄类型 * 句柄类型

View File

@@ -3,6 +3,7 @@ package com.metis.domain.bo;
import com.metis.enums.NodeType; import com.metis.enums.NodeType;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
@@ -12,9 +13,9 @@ public class NodeBO {
/** /**
* id * id
*/ */
@NotNull(message = "节点id不能为空") @NotBlank(message = "节点id不能为空")
@Schema(description = "节点id") @Schema(description = "节点id")
private Long id; private String id;
/** /**
* 类型 * 类型

View File

@@ -36,12 +36,12 @@ public class RunningContext {
/** /**
* 节点运行上下文, 需要数据进行传递 * 节点运行上下文, 需要数据进行传递
*/ */
private Map<Long, JSONObject> nodeRunningContext; private Map<String, JSONObject> nodeRunningContext;
/** /**
* 下一个运行节点id集合, 可能是多个, 执行器每一次清空该节点 * 下一个运行节点id集合, 可能是多个, 执行器每一次清空该节点
*/ */
private Set<Long> nextRunNodeId; private Set<String> nextRunNodeId;
/** /**
@@ -50,7 +50,7 @@ public class RunningContext {
* @param nodeId 节点id * @param nodeId 节点id
* @param nodeRunningContext 节点运行背景信息 * @param nodeRunningContext 节点运行背景信息
*/ */
public void addNodeRunningContext(Long nodeId, JSONObject nodeRunningContext) { public void addNodeRunningContext(String nodeId, JSONObject nodeRunningContext) {
this.nodeRunningContext.put(nodeId, nodeRunningContext); this.nodeRunningContext.put(nodeId, nodeRunningContext);
} }
@@ -60,7 +60,7 @@ public class RunningContext {
* @param nodeId 节点id * @param nodeId 节点id
* @return {@link JSONObject } * @return {@link JSONObject }
*/ */
public JSONObject getRunningContext(Long nodeId) { public JSONObject getRunningContext(String nodeId) {
return this.nodeRunningContext.get(nodeId); return this.nodeRunningContext.get(nodeId);
} }
@@ -79,19 +79,19 @@ public class RunningContext {
return parser.parseExpression(key).getValue(context); return parser.parseExpression(key).getValue(context);
} }
try { // try {
// 解析 key 中的数字部分并转换为 Long 类型 // // 解析 key 中的数字部分并转换为 Long 类型
String[] parts = key.split("\\."); // String[] parts = key.split("\\.");
if (parts.length == 2 && StrUtil.isNumeric(parts[0])) { // if (parts.length == 2 && StrUtil.isNumeric(parts[0])) {
Long nodeId = Long.valueOf(parts[0]); // String nodeId = Long.valueOf(parts[0]);
JSONObject runningContext = getRunningContext(nodeId); // JSONObject runningContext = getRunningContext(nodeId);
if (runningContext != null) { // if (runningContext != null) {
return runningContext.get(parts[1]); // return runningContext.get(parts[1]);
} // }
} // }
} catch (Exception e) { // } catch (Exception e) {
log.error("数字类型获取动态参数失败: {}", key); // log.error("数字类型获取动态参数失败: {}", key);
} // }
ExpressionParser parser = new SpelExpressionParser(); ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext(this); StandardEvaluationContext context = new StandardEvaluationContext(this);
return parser.parseExpression(key).getValue(context); return parser.parseExpression(key).getValue(context);
@@ -105,7 +105,7 @@ public class RunningContext {
* @param key 关键 * @param key 关键
* @return {@link Object } * @return {@link Object }
*/ */
public Object getValue(Long nodeId, String key) { public Object getValue(String nodeId, String key) {
if (ObjectUtil.isNull(nodeId)) { if (ObjectUtil.isNull(nodeId)) {
return null; return null;
} }

View File

@@ -18,7 +18,7 @@ public class RunningResult {
/** /**
* 下一个运行节点id, 一些特殊节点需要, 必须条件节点满足后, 才会运行下一个节点 * 下一个运行节点id, 一些特殊节点需要, 必须条件节点满足后, 才会运行下一个节点
*/ */
private Set<Long> nextRunNodeId; private Set<String> nextRunNodeId;
/** /**
@@ -28,7 +28,7 @@ public class RunningResult {
* @param nextRunNodeId 下一个运行节点id * @param nextRunNodeId 下一个运行节点id
* @return {@link RunningResult } * @return {@link RunningResult }
*/ */
public static RunningResult buildResult(JSONObject nodeContext, Set<Long> nextRunNodeId) { public static RunningResult buildResult(JSONObject nodeContext, Set<String> nextRunNodeId) {
return RunningResult.builder() return RunningResult.builder()
.nodeContext(nodeContext) .nodeContext(nodeContext)
.nextRunNodeId(nextRunNodeId) .nextRunNodeId(nextRunNodeId)
@@ -41,7 +41,7 @@ public class RunningResult {
* @param nextRunNodeId 下一个运行节点id * @param nextRunNodeId 下一个运行节点id
* @return {@link RunningResult } * @return {@link RunningResult }
*/ */
public static RunningResult buildResult(Set<Long> nextRunNodeId) { public static RunningResult buildResult(Set<String> nextRunNodeId) {
return RunningResult.builder() return RunningResult.builder()
.nextRunNodeId(nextRunNodeId) .nextRunNodeId(nextRunNodeId)
.build(); .build();

View File

@@ -12,13 +12,13 @@ import java.util.stream.Collectors;
public class GraphDto { public class GraphDto {
private final Map<Long, Node> nodeMap; private final Map<String, Node> nodeMap;
private final Map<Long, Boolean> nodeReadyMap; private final Map<String, Boolean> nodeReadyMap;
private final Map<Long, List<Edge>> edgeMap; private final Map<String, List<Edge>> edgeMap;
private final Map<Long, List<Long>> adjacencyList = new HashMap<>(); private final Map<String, List<String>> adjacencyList = new HashMap<>();
private final List<Node> sortedNodes = new ArrayList<>(); private final List<Node> sortedNodes = new ArrayList<>();
@@ -27,7 +27,7 @@ public class GraphDto {
return new ArrayList<>(sortedNodes); return new ArrayList<>(sortedNodes);
} }
public List<Edge> getEdgeNodeId(Long nodeId) { public List<Edge> getEdgeNodeId(String nodeId) {
return edgeMap.getOrDefault(nodeId, new ArrayList<>()); return edgeMap.getOrDefault(nodeId, new ArrayList<>());
} }
@@ -38,15 +38,15 @@ public class GraphDto {
.orElse(null); .orElse(null);
} }
public Node getNode(Long nodeId) { public Node getNode(String nodeId) {
return nodeMap.get(nodeId); return nodeMap.get(nodeId);
} }
public void updateNodeReadyMap(Long nodeId, Boolean ready) { public void updateNodeReadyMap(String nodeId, Boolean ready) {
nodeReadyMap.put(nodeId, ready); nodeReadyMap.put(nodeId, ready);
} }
public Boolean isNodeReady(Long nodeId) { public Boolean isNodeReady(String nodeId) {
return nodeReadyMap.get(nodeId); return nodeReadyMap.get(nodeId);
} }
@@ -69,7 +69,7 @@ public class GraphDto {
private void initAdjacencyList(List<Edge> edges) { private void initAdjacencyList(List<Edge> edges) {
for (Edge edge : edges) { for (Edge edge : edges) {
List<Long> targetList = adjacencyList.getOrDefault(edge.getSource(), new ArrayList<>()); List<String> targetList = adjacencyList.getOrDefault(edge.getSource(), new ArrayList<>());
targetList.add(edge.getTarget()); targetList.add(edge.getTarget());
adjacencyList.put(edge.getSource(), targetList); adjacencyList.put(edge.getSource(), targetList);
} }
@@ -83,9 +83,9 @@ public class GraphDto {
*/ */
private List<Node> topologicalSort() { private List<Node> topologicalSort() {
List<Node> sortedNodes = new ArrayList<>(); List<Node> sortedNodes = new ArrayList<>();
Set<Long> visited = new HashSet<>(); Set<String> visited = new HashSet<>();
Set<Long> visiting = new HashSet<>(); Set<String> visiting = new HashSet<>();
for (Long nodeId : nodeMap.keySet()) { for (String nodeId : nodeMap.keySet()) {
if (!visited.contains(nodeId)) { if (!visited.contains(nodeId)) {
dfs(nodeId, visited, visiting, sortedNodes); dfs(nodeId, visited, visiting, sortedNodes);
} }
@@ -102,13 +102,13 @@ public class GraphDto {
* @param visiting 参观 * @param visiting 参观
* @param sortedNodes 排序节点 * @param sortedNodes 排序节点
*/ */
private void dfs(Long nodeId, Set<Long> visited, Set<Long> visiting, List<Node> sortedNodes) { private void dfs(String nodeId, Set<String> visited, Set<String> visiting, List<Node> sortedNodes) {
if (visiting.contains(nodeId)) { if (visiting.contains(nodeId)) {
throw new IllegalStateException("Cycle detected in the graph"); throw new IllegalStateException("Cycle detected in the graph");
} }
if (!visited.contains(nodeId)) { if (!visited.contains(nodeId)) {
visiting.add(nodeId); visiting.add(nodeId);
for (Long neighbor : adjacencyList.getOrDefault(nodeId, new ArrayList<>())) { for (String neighbor : adjacencyList.getOrDefault(nodeId, new ArrayList<>())) {
dfs(neighbor, visited, visiting, sortedNodes); dfs(neighbor, visited, visiting, sortedNodes);
} }
visiting.remove(nodeId); visiting.remove(nodeId);

View File

@@ -1,6 +1,7 @@
package com.metis.domain.entity.base; package com.metis.domain.entity.base;
import com.metis.enums.EdgeType; import com.metis.enums.EdgeType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
@@ -10,7 +11,7 @@ public class Edge {
/** /**
* 唯一标识符 * 唯一标识符
*/ */
@NotNull(message = "唯一标识符不能为空") @NotBlank(message = "唯一标识符不能为空")
private String id; private String id;
/** /**
@@ -28,24 +29,24 @@ public class Edge {
* 源节点ID,对应节点id * 源节点ID,对应节点id
*/ */
@NotNull(message = "源节点ID不能为空") @NotNull(message = "源节点ID不能为空")
private Long source; private String source;
/** /**
* 目标节点ID,对应节点id * 目标节点ID,对应节点id
*/ */
@NotNull(message = "目标节点ID不能为空") @NotNull(message = "目标节点ID不能为空")
private Long target; private String target;
/** /**
* 源句柄id * 源句柄id
*/ */
@NotNull(message = "源句柄ID不能为空") @NotNull(message = "源句柄ID不能为空")
private Long sourceHandle; private String sourceHandle;
/** /**
* 目标句柄id * 目标句柄id
*/ */
@NotNull(message = "目标句柄ID不能为空") @NotNull(message = "目标句柄ID不能为空")
private Long targetHandle; private String targetHandle;
/** /**
* 边是否动画true/false * 边是否动画true/false

View File

@@ -2,6 +2,7 @@ package com.metis.domain.entity.base;
import com.metis.enums.HandleType; import com.metis.enums.HandleType;
import com.metis.enums.PositionType; import com.metis.enums.PositionType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
@@ -13,8 +14,8 @@ public class Handle {
/** /**
* 句柄id * 句柄id
*/ */
@NotNull(message = "句柄id不能为空") @NotBlank(message = "句柄id不能为空")
private Long id; private String id;
/** /**
* 句柄类型 * 句柄类型

View File

@@ -13,7 +13,7 @@ public class Node {
* id * id
*/ */
@NotNull(message = "节点id不能为空") @NotNull(message = "节点id不能为空")
private Long id; private String id;
/** /**
* 类型 * 类型

View File

@@ -59,7 +59,7 @@ public class AppFlowEngineRunnerServiceImpl implements AppFlowEngineRunnerServic
// 开始节点为空,则表示数据存在异常 // 开始节点为空,则表示数据存在异常
Assert.isTrue(ObjectUtil.isNotNull(readyRunningNode), "流程图不存在开始节点"); Assert.isTrue(ObjectUtil.isNotNull(readyRunningNode), "流程图不存在开始节点");
for (Node node : graph.getSortedNodes()) { for (Node node : graph.getSortedNodes()) {
Long nodeId = node.getId(); String nodeId = node.getId();
if (!graph.isNodeReady(nodeId)) { if (!graph.isNodeReady(nodeId)) {
continue; continue;
} }
@@ -79,7 +79,7 @@ public class AppFlowEngineRunnerServiceImpl implements AppFlowEngineRunnerServic
} }
// 下一个需要运行的节点id加入到可以运行的节点中 // 下一个需要运行的节点id加入到可以运行的节点中
if (CollUtil.isNotEmpty(result.getNextRunNodeId())) { if (CollUtil.isNotEmpty(result.getNextRunNodeId())) {
for (Long nextNodeId : result.getNextRunNodeId()) { for (String nextNodeId : result.getNextRunNodeId()) {
graph.updateNodeReadyMap(nextNodeId, true); graph.updateNodeReadyMap(nextNodeId, true);
} }
} else { } else {

View File

@@ -46,7 +46,7 @@ public interface NodeRunner<T extends NodeConfig> {
* @param edges 边缘 * @param edges 边缘
* @return {@link Set }<{@link Long }> * @return {@link Set }<{@link Long }>
*/ */
default Set<Long> getNextNodeIds(List<Edge> edges) { default Set<String> getNextNodeIds(List<Edge> edges) {
if (CollUtil.isEmpty(edges)) { if (CollUtil.isEmpty(edges)) {
return Set.of(); return Set.of();
} }

View File

@@ -21,11 +21,11 @@ public class QuestionClassifierRunner implements NodeRunner<QuestionClassifierCo
@Override @Override
public RunningResult run(RunningContext context, Node node, List<Edge> edges) { public RunningResult run(RunningContext context, Node node, List<Edge> edges) {
Set<Long> nextNodeIds = getNextNodeIds(edges); Set<String> nextNodeIds = getNextNodeIds(edges);
// 生成随机索引 // 生成随机索引
Random random = new Random(); Random random = new Random();
int randomIndex = random.nextInt(nextNodeIds.size()); int randomIndex = random.nextInt(nextNodeIds.size());
List<Long> nodeIds = new ArrayList<>(nextNodeIds); List<String> nodeIds = new ArrayList<>(nextNodeIds);
return RunningResult.buildResult(Set.of(nodeIds.get(randomIndex))); return RunningResult.buildResult(Set.of(nodeIds.get(randomIndex)));
} }

View File

@@ -35,14 +35,14 @@ public class RenderContext {
/** /**
* 节点运行上下文, 需要数据进行传递 * 节点运行上下文, 需要数据进行传递
*/ */
private Map<Long, JSONObject> nodeRunningContext; private Map<String, JSONObject> nodeRunningContext;
public Map<String, Object> getContext() { public Map<String, Object> getContext() {
Map<String, Object> context = new HashMap<>(); Map<String, Object> context = new HashMap<>();
context.put("context", this.context); context.put("context", this.context);
context.put("sys", this.sys); context.put("sys", this.sys);
for (Map.Entry<Long, JSONObject> entry : nodeRunningContext.entrySet()) { for (Map.Entry<String, JSONObject> entry : nodeRunningContext.entrySet()) {
context.put(String.valueOf(entry.getKey()), entry.getValue()); context.put(String.valueOf(entry.getKey()), entry.getValue());
} }
return context; return context;

View File

@@ -137,10 +137,10 @@ public class ValidatorServiceImpl implements ValidatorService {
* @param edges 边缘 * @param edges 边缘
*/ */
private void validateEdgeConnections(List<Node> nodes, List<Edge> edges) { private void validateEdgeConnections(List<Node> nodes, List<Edge> edges) {
Map<Long, Node> nodeMap = nodes.stream().collect(Collectors.toMap(Node::getId, Function.identity())); Map<String, Node> nodeMap = nodes.stream().collect(Collectors.toMap(Node::getId, Function.identity()));
for (Edge edge : edges) { for (Edge edge : edges) {
Long source = edge.getSource(); String source = edge.getSource();
Long target = edge.getTarget(); String target = edge.getTarget();
Assert.isTrue(nodeMap.containsKey(source), "边 {} 的源节点 {} 不存在", edge.getLabel(), source); Assert.isTrue(nodeMap.containsKey(source), "边 {} 的源节点 {} 不存在", edge.getLabel(), source);
Assert.isTrue(nodeMap.containsKey(target), "边 {} 的目标节点 {} 不存在", edge.getLabel(), target); Assert.isTrue(nodeMap.containsKey(target), "边 {} 的目标节点 {} 不存在", edge.getLabel(), target);
} }
@@ -153,7 +153,7 @@ public class ValidatorServiceImpl implements ValidatorService {
* @param edges 边缘 * @param edges 边缘
*/ */
private void validateCycle(List<Node> nodes, List<Edge> edges) { private void validateCycle(List<Node> nodes, List<Edge> edges) {
Map<Long, List<Long>> adjacencyList = buildAdjacencyList(edges); Map<String, List<String>> adjacencyList = buildAdjacencyList(edges);
for (Node node : nodes) { for (Node node : nodes) {
if (hasCycle(node.getId(), adjacencyList, new HashSet<>())) { if (hasCycle(node.getId(), adjacencyList, new HashSet<>())) {
throw new IllegalArgumentException("图中存在环结构,起始节点: " + node.getData().getLabel()); throw new IllegalArgumentException("图中存在环结构,起始节点: " + node.getData().getLabel());
@@ -168,7 +168,7 @@ public class ValidatorServiceImpl implements ValidatorService {
* @param edges 边缘 * @param edges 边缘
*/ */
private void validateIsolatedNodes(List<Node> nodes, List<Edge> edges) { private void validateIsolatedNodes(List<Node> nodes, List<Edge> edges) {
Set<Long> connectedNodes = new HashSet<>(); Set<String> connectedNodes = new HashSet<>();
for (Edge edge : edges) { for (Edge edge : edges) {
connectedNodes.add(edge.getSource()); connectedNodes.add(edge.getSource());
connectedNodes.add(edge.getTarget()); connectedNodes.add(edge.getTarget());
@@ -184,8 +184,8 @@ public class ValidatorServiceImpl implements ValidatorService {
* @param edges 边缘 * @param edges 边缘
* @return {@link Map }<{@link Long }, {@link List }<{@link Long }>> * @return {@link Map }<{@link Long }, {@link List }<{@link Long }>>
*/ */
private Map<Long, List<Long>> buildAdjacencyList(List<Edge> edges) { private Map<String, List<String>> buildAdjacencyList(List<Edge> edges) {
Map<Long, List<Long>> adjacencyList = new HashMap<>(); Map<String, List<String>> adjacencyList = new HashMap<>();
for (Edge edge : edges) { for (Edge edge : edges) {
adjacencyList.computeIfAbsent(edge.getSource(), k -> new ArrayList<>()).add(edge.getTarget()); adjacencyList.computeIfAbsent(edge.getSource(), k -> new ArrayList<>()).add(edge.getTarget());
} }
@@ -200,12 +200,12 @@ public class ValidatorServiceImpl implements ValidatorService {
* @param visited 是否已经访问过了 * @param visited 是否已经访问过了
* @return boolean * @return boolean
*/ */
private boolean hasCycle(Long nodeId, Map<Long, List<Long>> adjacencyList, Set<Long> visited) { private boolean hasCycle(String nodeId, Map<String, List<String>> adjacencyList, Set<String> visited) {
if (visited.contains(nodeId)) { if (visited.contains(nodeId)) {
return true; // 发现环 return true; // 发现环
} }
visited.add(nodeId); visited.add(nodeId);
for (Long neighbor : adjacencyList.getOrDefault(nodeId, new ArrayList<>())) { for (String neighbor : adjacencyList.getOrDefault(nodeId, new ArrayList<>())) {
if (hasCycle(neighbor, adjacencyList, visited)) { if (hasCycle(neighbor, adjacencyList, visited)) {
return true; return true;
} }
@@ -236,8 +236,8 @@ public class ValidatorServiceImpl implements ValidatorService {
*/ */
private void validateNodeRelations(List<Node> nodes, List<Edge> edges) { private void validateNodeRelations(List<Node> nodes, List<Edge> edges) {
// 构建 source 和 target 的映射 // 构建 source 和 target 的映射
Map<Long, List<Edge>> sourceMap = edges.stream().collect(Collectors.groupingBy(Edge::getSource)); Map<String, List<Edge>> sourceMap = edges.stream().collect(Collectors.groupingBy(Edge::getSource));
Map<Long, List<Edge>> targetMap = edges.stream().collect(Collectors.groupingBy(Edge::getTarget)); Map<String, List<Edge>> targetMap = edges.stream().collect(Collectors.groupingBy(Edge::getTarget));
for (Node node : nodes) { for (Node node : nodes) {
NodeValidator validator = getNodeValidator(node); NodeValidator validator = getNodeValidator(node);