1. WorkflowSubTaskEntity定义
必须要包含workflowId、taskName、dependsOn字段
public class WorkflowSubTaskEntity extends BaseEntity implements Serializable {
@JsonProperty("workflowId")
private String workflowId;
/**
* 任务ID,用于日志、追踪、重试等
*/
@JsonProperty("taskName")
private String taskName;
/**
* 任务类型,由子类隐式指定(Jackson 自动处理)
*/
@JsonProperty("taskType")
private TaskType taskType;
/**
* 描述信息
*/
@JsonProperty("description")
private String description;
/**
* 依赖
*/
@JsonProperty("dependsOn")
private List<String> dependsOn;
@JsonProperty("config")
private Map<String, Object> config;
}
2. 拓扑排序核心思想(Kahn 算法变种)
我们使用 入度(in-degree)为 0 的节点逐层剥离法(Kahn 算法的扩展):
步骤概览:
- 计算每个任务的 入度(有多少前置任务)
- 找出所有 入度为 0 的任务 → 它们组成 第 0 层
- 移除这些任务,并更新它们的后继任务的入度
- 重复步骤 2--3,得到第 1 层、第 2 层......直到没有任务剩下
逐层详解(以具体例子说明)
假设任务依赖如下(用 → 表示依赖):

任务列表:
- Start
- Validate
- FetchData
- EnrichData
- Process
- End
依赖关系(前置 → 后置): - Start → Validate
- Validate → FetchData
- Validate → EnrichData
- FetchData → Process
- EnrichData → Process
- Process → End
🔹 第 0 层:初始化
- 计算每个任务的入度(in-degree):
This content is only supported in a Feishu Docs - 入度为 0 的任务:只有 Start
→ Level 0 = [Start]
🔹 第 1 层:移除 Level 0,更新入度
- 移除 Start
- Start 的后继是 Validate → Validate 入度减 1(从 1 → 0)
更新后入度:
This content is only supported in a Feishu Docs - 入度为 0 的任务:Validate
→ Level 1 = [Validate]
🔹 第 2 层:移除 Level 1,更新入度
- 移除 Validate
- 它的后继:FetchData 和 EnrichData
- FetchData 入度:1 → 0 ✅
- EnrichData 入度:1 → 0 ✅
更新后入度:
This content is only supported in a Feishu Docs
- 入度为 0 的任务:FetchData, EnrichData
→ Level 2 = [FetchData, EnrichData] ← 可并行!
🔹 第 3 层:移除 Level 2,更新入度
- 移除 FetchData 和 EnrichData
- 它们的共同后继:Process
- Process 原入度 2,减 2 → 0 ✅
更新后入度:
This content is only supported in a Feishu Docs
- Process 原入度 2,减 2 → 0 ✅
- 入度为 0 的任务:Process
→ Level 3 = [Process]
🔹 第 4 层:移除 Level 3
- 移除 Process
- 后继:End → 入度 1 → 0 ✅
→ Level 4 = [End]
✅ 最终分层结果:
java
编辑
List<List> levels = [
"Start"\], \["Validate"\], \["FetchData", "EnrichData"\], // 并行执行 \["Process"\], \["End"
]
环判断
原理:如果图中存在环(cycle),拓扑排序算法就无法处理所有节点,最终输出的节点总数会少于输入的节点总数。
假设有一个环:A → B → C → A
- 初始入度:
- A: 1(来自 C)
- B: 1(来自 A)
- C: 1(来自 B)
- 没有任何节点入度为 0 → 队列一开始就是空的
- 算法直接结束,levels 为空或只包含环外节点
- 最终 levels 中的节点数 < 总节点数
3. topologicalSort算法实现
public static List<List<WorkflowSubTaskEntity>> topologicalSort(List<WorkflowSubTaskEntity> workflowSubTaskEntities) {
if (workflowSubTaskEntities == null || workflowSubTaskEntities.isEmpty()) {
return Collections.emptyList();
}
// 构建 taskId -> Task 映射
Map<String, WorkflowSubTaskEntity> taskMap = new HashMap<>();
for (WorkflowSubTaskEntity t : workflowSubTaskEntities) {
if (taskMap.containsKey(t.getTaskName())) {
throw new IllegalArgumentException("Duplicate task ID: " + t.getTaskName());
}
taskMap.put(t.getTaskName(), t);
}
// 构建入度表和邻接表
Map<String, Integer> inDegree = new HashMap<>();
Map<String, List<String>> graph = new HashMap<>();
for (String id : taskMap.keySet()) {
inDegree.put(id, 0);
graph.put(id, new ArrayList<>());
}
// 填充依赖关系
for (WorkflowSubTaskEntity workflowSubTaskEntity : workflowSubTaskEntities) {
String currentId = workflowSubTaskEntity.getTaskName();
List<String> deps = workflowSubTaskEntity.getDependsOn();
if (deps != null) {
for (String dep : deps) {
if (!taskMap.containsKey(dep)) {
throw new IllegalArgumentException("Dependency not found: " + dep + " for task " + currentId);
}
graph.get(dep).add(currentId); // dep → current
inDegree.put(currentId, inDegree.get(currentId) + 1);
}
}
}
// Kahn 算法拓扑排序
Queue<String> queue = new LinkedList<>();
for (Map.Entry<String, Integer> entry : inDegree.entrySet()) {
if (entry.getValue() == 0) {
queue.offer(entry.getKey());
}
}
List<List<WorkflowSubTaskEntity>> levels = new ArrayList<>();
while (!queue.isEmpty()) {
int size = queue.size();
List<WorkflowSubTaskEntity> currentLevel = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
String id = queue.poll();
currentLevel.add(taskMap.get(id));
// 减少后继节点的入度
for (String neighbor : graph.get(id)) {
inDegree.put(neighbor, inDegree.get(neighbor) - 1);
if (inDegree.get(neighbor) == 0) {
queue.offer(neighbor);
}
}
}
levels.add(currentLevel);
}
// 检查环
if (levels.stream().mapToInt(List::size).sum() != workflowSubTaskEntities.size()) {
throw new IllegalArgumentException("Cycle detected in workflow DAG");
}
return levels;
}