feat: 2.6.0
1. 优化查询DAG详情
This commit is contained in:
parent
594013f65b
commit
9c99330adb
@ -0,0 +1,29 @@
|
|||||||
|
package com.aizuda.easy.retry.server.common.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author xiaowoniu
|
||||||
|
* @date 2023-12-15 22:42:17
|
||||||
|
* @since 2.6.0
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum WorkflowNodeType {
|
||||||
|
TASK(1, "任务节点"),
|
||||||
|
Condition(1, "条件节点"),
|
||||||
|
callback(1, "回调节点");
|
||||||
|
|
||||||
|
private final Integer nodeType;
|
||||||
|
private final String desc;
|
||||||
|
|
||||||
|
public static WorkflowNodeType get(Integer nodeType) {
|
||||||
|
for (WorkflowNodeType workflowNodeType : WorkflowNodeType.values()) {
|
||||||
|
if (workflowNodeType.nodeType.equals(nodeType)) {
|
||||||
|
return workflowNodeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +1,19 @@
|
|||||||
package com.aizuda.easy.retry.server.web.controller;
|
package com.aizuda.easy.retry.server.web.controller;
|
||||||
|
|
||||||
import cn.hutool.core.util.ReUtil;
|
import com.aizuda.easy.retry.server.web.annotation.LoginRequired;
|
||||||
import com.aizuda.easy.retry.server.common.config.SystemProperties;
|
|
||||||
import com.aizuda.easy.retry.server.web.annotation.LoginUser;
|
import com.aizuda.easy.retry.server.web.annotation.LoginUser;
|
||||||
|
import com.aizuda.easy.retry.server.web.annotation.RoleEnum;
|
||||||
import com.aizuda.easy.retry.server.web.model.base.PageResult;
|
import com.aizuda.easy.retry.server.web.model.base.PageResult;
|
||||||
import com.aizuda.easy.retry.server.web.model.request.GroupConfigQueryVO;
|
import com.aizuda.easy.retry.server.web.model.request.GroupConfigQueryVO;
|
||||||
import com.aizuda.easy.retry.server.web.model.request.GroupConfigRequestVO;
|
import com.aizuda.easy.retry.server.web.model.request.GroupConfigRequestVO;
|
||||||
import com.aizuda.easy.retry.server.web.model.request.UserSessionVO;
|
import com.aizuda.easy.retry.server.web.model.request.UserSessionVO;
|
||||||
import com.aizuda.easy.retry.server.web.model.response.GroupConfigResponseVO;
|
import com.aizuda.easy.retry.server.web.model.response.GroupConfigResponseVO;
|
||||||
import com.aizuda.easy.retry.server.web.annotation.LoginRequired;
|
|
||||||
import com.aizuda.easy.retry.server.web.annotation.RoleEnum;
|
|
||||||
import com.aizuda.easy.retry.server.web.service.GroupConfigService;
|
import com.aizuda.easy.retry.server.web.service.GroupConfigService;
|
||||||
import com.aizuda.easy.retry.template.datasource.enums.DbTypeEnum;
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.zaxxer.hikari.HikariDataSource;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.slf4j.helpers.MessageFormatter;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.text.MessageFormat;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重试组接口
|
* 重试组接口
|
||||||
|
@ -41,8 +41,9 @@ public class WorkflowController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping
|
@PutMapping
|
||||||
public void updateWorkflow() {
|
@LoginRequired(role = RoleEnum.USER)
|
||||||
|
public Boolean updateWorkflow(@RequestBody @Validated WorkflowRequestVO workflowRequestVO) {
|
||||||
|
return workflowService.updateWorkflow(workflowRequestVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("{id}")
|
@GetMapping("{id}")
|
||||||
|
@ -17,6 +17,8 @@ import java.util.List;
|
|||||||
@Data
|
@Data
|
||||||
public class WorkflowRequestVO {
|
public class WorkflowRequestVO {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
@NotBlank(message = "组名称不能为空")
|
@NotBlank(message = "组名称不能为空")
|
||||||
@Pattern(regexp = "^[A-Za-z0-9_]{1,64}$", message = "仅支持长度为1~64字符且类型为数字、字母和下划线")
|
@Pattern(regexp = "^[A-Za-z0-9_]{1,64}$", message = "仅支持长度为1~64字符且类型为数字、字母和下划线")
|
||||||
private String groupName;
|
private String groupName;
|
||||||
|
@ -21,4 +21,6 @@ public interface WorkflowService {
|
|||||||
WorkflowDetailResponseVO getWorkflowDetail(Long id) throws IOException;
|
WorkflowDetailResponseVO getWorkflowDetail(Long id) throws IOException;
|
||||||
|
|
||||||
PageResult<List<WorkflowResponseVO>> listPage(WorkflowQueryVO queryVO);
|
PageResult<List<WorkflowResponseVO>> listPage(WorkflowQueryVO queryVO);
|
||||||
|
|
||||||
|
Boolean updateWorkflow(WorkflowRequestVO workflowRequestVO);
|
||||||
}
|
}
|
||||||
|
@ -11,16 +11,13 @@ import com.aizuda.easy.retry.server.web.model.request.WorkflowQueryVO;
|
|||||||
import com.aizuda.easy.retry.server.web.model.request.WorkflowRequestVO;
|
import com.aizuda.easy.retry.server.web.model.request.WorkflowRequestVO;
|
||||||
import com.aizuda.easy.retry.server.web.model.request.WorkflowRequestVO.NodeConfig;
|
import com.aizuda.easy.retry.server.web.model.request.WorkflowRequestVO.NodeConfig;
|
||||||
import com.aizuda.easy.retry.server.web.model.request.WorkflowRequestVO.NodeInfo;
|
import com.aizuda.easy.retry.server.web.model.request.WorkflowRequestVO.NodeInfo;
|
||||||
import com.aizuda.easy.retry.server.web.model.response.JobResponseVO;
|
|
||||||
import com.aizuda.easy.retry.server.web.model.response.WorkflowDetailResponseVO;
|
import com.aizuda.easy.retry.server.web.model.response.WorkflowDetailResponseVO;
|
||||||
import com.aizuda.easy.retry.server.web.model.response.WorkflowResponseVO;
|
import com.aizuda.easy.retry.server.web.model.response.WorkflowResponseVO;
|
||||||
import com.aizuda.easy.retry.server.web.service.WorkflowService;
|
import com.aizuda.easy.retry.server.web.service.WorkflowService;
|
||||||
import com.aizuda.easy.retry.server.web.service.convert.JobResponseVOConverter;
|
|
||||||
import com.aizuda.easy.retry.server.web.service.convert.WorkflowConverter;
|
import com.aizuda.easy.retry.server.web.service.convert.WorkflowConverter;
|
||||||
import com.aizuda.easy.retry.server.web.util.UserSessionUtils;
|
import com.aizuda.easy.retry.server.web.util.UserSessionUtils;
|
||||||
import com.aizuda.easy.retry.template.datasource.persistence.mapper.WorkflowMapper;
|
import com.aizuda.easy.retry.template.datasource.persistence.mapper.WorkflowMapper;
|
||||||
import com.aizuda.easy.retry.template.datasource.persistence.mapper.WorkflowNodeMapper;
|
import com.aizuda.easy.retry.template.datasource.persistence.mapper.WorkflowNodeMapper;
|
||||||
import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
|
|
||||||
import com.aizuda.easy.retry.template.datasource.persistence.po.Workflow;
|
import com.aizuda.easy.retry.template.datasource.persistence.po.Workflow;
|
||||||
import com.aizuda.easy.retry.template.datasource.persistence.po.WorkflowNode;
|
import com.aizuda.easy.retry.template.datasource.persistence.po.WorkflowNode;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
@ -28,6 +25,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
|
|||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import com.google.common.graph.GraphBuilder;
|
import com.google.common.graph.GraphBuilder;
|
||||||
import com.google.common.graph.MutableGraph;
|
import com.google.common.graph.MutableGraph;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -101,8 +99,9 @@ public class WorkflowServiceImpl implements WorkflowService {
|
|||||||
|
|
||||||
String flowInfo = workflow.getFlowInfo();
|
String flowInfo = workflow.getFlowInfo();
|
||||||
try {
|
try {
|
||||||
|
MutableGraph<Long> graph = deserializeJsonToGraph(flowInfo);
|
||||||
// 反序列化构建图
|
// 反序列化构建图
|
||||||
WorkflowDetailResponseVO.NodeConfig config = deserializeJsonToGraph(flowInfo, workflowNodeMap);
|
WorkflowDetailResponseVO.NodeConfig config = buildNodeConfig(graph, root, new HashMap<>(), workflowNodeMap);
|
||||||
responseVO.setNodeConfig(config);
|
responseVO.setNodeConfig(config);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("反序列化失败. json:[{}]", flowInfo, e);
|
log.error("反序列化失败. json:[{}]", flowInfo, e);
|
||||||
@ -128,50 +127,100 @@ public class WorkflowServiceImpl implements WorkflowService {
|
|||||||
return new PageResult<>(pageDTO, jobResponseList);
|
return new PageResult<>(pageDTO, jobResponseList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean updateWorkflow(WorkflowRequestVO workflowRequestVO) {
|
||||||
|
|
||||||
|
Assert.notNull(workflowRequestVO.getId(), () -> new EasyRetryServerException("工作流ID不能为空"));
|
||||||
|
|
||||||
|
Assert.isTrue(workflowMapper.selectCount(new LambdaQueryWrapper<Workflow>()
|
||||||
|
.eq(Workflow::getId, workflowRequestVO.getId())) > 0,
|
||||||
|
() -> new EasyRetryServerException("工作流不存在"));
|
||||||
|
|
||||||
|
MutableGraph<Long> graph = GraphBuilder.directed().allowsSelfLoops(false).build();
|
||||||
|
// 添加虚拟头节点
|
||||||
|
graph.addNode(root);
|
||||||
|
|
||||||
|
// 获取DAG节点配置
|
||||||
|
NodeConfig nodeConfig = workflowRequestVO.getNodeConfig();
|
||||||
|
|
||||||
|
// 递归构建图
|
||||||
|
buildGraph(Lists.newArrayList(root), workflowRequestVO.getGroupName(), workflowRequestVO.getId(), nodeConfig, graph);
|
||||||
|
|
||||||
|
log.info("图构建完成. graph:[{}]", graph);
|
||||||
|
|
||||||
|
// 保存图信息
|
||||||
|
Workflow workflow = new Workflow();
|
||||||
|
workflow.setId(workflowRequestVO.getId());
|
||||||
|
workflow.setFlowInfo(JsonUtil.toJsonString(convertGraphToAdjacencyList(graph)));
|
||||||
|
Assert.isTrue(workflowMapper.updateById(workflow) > 0, () -> new EasyRetryServerException("更新失败"));
|
||||||
|
|
||||||
|
return Boolean.TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
// 从JSON反序列化为Guava图
|
// 从JSON反序列化为Guava图
|
||||||
private static WorkflowDetailResponseVO.NodeConfig deserializeJsonToGraph(String jsonGraph,
|
private static MutableGraph<Long> deserializeJsonToGraph(String jsonGraph) throws IOException {
|
||||||
Map<Long, WorkflowDetailResponseVO.NodeInfo> workflowNodeMap) throws IOException {
|
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
// 将JSON字符串转换为Map<String, Iterable<String>>
|
// 将JSON字符串转换为Map<Long, Iterable<Long>>
|
||||||
Map<Long, Iterable<Long>> adjacencyList = objectMapper.readValue(
|
Map<Long, Iterable<Long>> adjacencyList = objectMapper.readValue(
|
||||||
jsonGraph, new TypeReference<Map<Long, Iterable<Long>>>() {});
|
jsonGraph, new TypeReference<Map<Long, Iterable<Long>>>() {
|
||||||
|
});
|
||||||
Map<Long, WorkflowDetailResponseVO.NodeConfig> configMap = new HashMap<>();
|
|
||||||
WorkflowDetailResponseVO.NodeConfig rootConfig = new WorkflowDetailResponseVO.NodeConfig();
|
|
||||||
|
|
||||||
// 创建Guava图并添加节点和边
|
// 创建Guava图并添加节点和边
|
||||||
|
MutableGraph<Long> graph = GraphBuilder.directed().build();
|
||||||
for (Map.Entry<Long, Iterable<Long>> entry : adjacencyList.entrySet()) {
|
for (Map.Entry<Long, Iterable<Long>> entry : adjacencyList.entrySet()) {
|
||||||
Long node = entry.getKey();
|
Long node = entry.getKey();
|
||||||
Iterable<Long> successors = entry.getValue();
|
Iterable<Long> successors = entry.getValue();
|
||||||
WorkflowDetailResponseVO.NodeConfig previousConfig = configMap.getOrDefault(node, new WorkflowDetailResponseVO.NodeConfig());
|
|
||||||
|
|
||||||
WorkflowDetailResponseVO.NodeConfig currentConfig = new WorkflowDetailResponseVO.NodeConfig();
|
graph.addNode(node);
|
||||||
for (Long successor : successors) {
|
for (Long successor : successors) {
|
||||||
WorkflowDetailResponseVO.NodeInfo nodeInfo = workflowNodeMap.get(successor);
|
graph.putEdge(node, successor);
|
||||||
// 第一层节点
|
|
||||||
if (node == root) {
|
|
||||||
rootConfig.setNodeType(nodeInfo.getNodeType());
|
|
||||||
List<WorkflowDetailResponseVO.NodeInfo> nodeInfos = Optional.ofNullable(
|
|
||||||
rootConfig.getConditionNodes()).orElse(Lists.newArrayList());
|
|
||||||
nodeInfos.add(nodeInfo);
|
|
||||||
rootConfig.setConditionNodes(nodeInfos);
|
|
||||||
configMap.put(nodeInfo.getId(), rootConfig);
|
|
||||||
} else {
|
|
||||||
currentConfig.setNodeType(nodeInfo.getNodeType());
|
|
||||||
List<WorkflowDetailResponseVO.NodeInfo> nodeInfos = Optional.ofNullable(
|
|
||||||
currentConfig.getConditionNodes()).orElse(Lists.newArrayList());
|
|
||||||
nodeInfos.add(nodeInfo);
|
|
||||||
currentConfig.setConditionNodes(nodeInfos);
|
|
||||||
configMap.put(nodeInfo.getId(), currentConfig);
|
|
||||||
previousConfig.setChildNode(currentConfig);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rootConfig;
|
return graph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private WorkflowDetailResponseVO.NodeConfig buildNodeConfig(MutableGraph<Long> graph,
|
||||||
|
Long parentId,
|
||||||
|
Map<Long, WorkflowDetailResponseVO.NodeConfig> nodeConfigMap,
|
||||||
|
Map<Long, WorkflowDetailResponseVO.NodeInfo> workflowNodeMap) {
|
||||||
|
|
||||||
|
Set<Long> successors = graph.successors(parentId);
|
||||||
|
if (CollectionUtils.isEmpty(successors)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkflowDetailResponseVO.NodeInfo previousNodeInfo = workflowNodeMap.get(parentId);
|
||||||
|
WorkflowDetailResponseVO.NodeConfig currentConfig = new WorkflowDetailResponseVO.NodeConfig();
|
||||||
|
currentConfig.setConditionNodes(Lists.newArrayList());
|
||||||
|
|
||||||
|
boolean mount = false;
|
||||||
|
|
||||||
|
for (Long successor : successors) {
|
||||||
|
Set<Long> predecessors = graph.predecessors(successor);
|
||||||
|
WorkflowDetailResponseVO.NodeInfo nodeInfo = workflowNodeMap.get(successor);
|
||||||
|
currentConfig.setNodeType(nodeInfo.getNodeType());
|
||||||
|
currentConfig.getConditionNodes().add(nodeInfo);
|
||||||
|
nodeConfigMap.put(successor, currentConfig);
|
||||||
|
|
||||||
|
if (predecessors.size() >= 2) {
|
||||||
|
WorkflowDetailResponseVO.NodeConfig parentNodeConfig = nodeConfigMap.get(new ArrayList<>(predecessors).get(0));
|
||||||
|
parentNodeConfig.setChildNode(currentConfig);
|
||||||
|
mount = false;
|
||||||
|
} else {
|
||||||
|
mount = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildNodeConfig(graph, successor, nodeConfigMap, workflowNodeMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentId != root && mount) {
|
||||||
|
previousNodeInfo.setChildNode(currentConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentConfig;
|
||||||
|
}
|
||||||
|
|
||||||
private Map<Long, Iterable<Long>> convertGraphToAdjacencyList(MutableGraph<Long> graph) {
|
private Map<Long, Iterable<Long>> convertGraphToAdjacencyList(MutableGraph<Long> graph) {
|
||||||
Map<Long, Iterable<Long>> adjacencyList = new HashMap<>();
|
Map<Long, Iterable<Long>> adjacencyList = new HashMap<>();
|
||||||
|
Loading…
Reference in New Issue
Block a user