基于 SpringBoot+Logicflow 的轻流程任务执行
·
此方案是我工作中遇到的一个标准运维的一个需求
可以在web页面编辑流程节点 ,节点类型,支持拖拽等等,节点可以选择脚本,插件等等功能
然后通过建任务记录(record表),关联某个模板 (task_template表)
只做简单实现记录,代码是执行不了的,方便以后面试的时候问起来,省得记不起来
LogicFlow是什么
官网:http://logicflow.cn/tutorial/about
LogicFlow是一款流程图编辑框架,提供了一系列流程图交互、编辑所必需的功能和灵活的节点自定义、插件等拓展机制。 LogicFlow支持前端自定义开发各种逻辑编排场景,如流程图、ER图、BPMN流程等。在工作审批流配置、机器人逻辑编排、无代码平台流程配置都有较好的应用。
特性
可视化模型:通过 LogicFlow 提供的直观可视化界面,用户可以轻松创建、编辑和管理复杂的逻辑流程图。
高可定制性:用户可以根据自己的需要定制节点、连接器和样式,创建符合特定用例的定制逻辑流程图。
自执行引擎:执行引擎支持浏览器端执行流程图逻辑,为无代码执行提供新思路。
//任务模板json,此json通过前端logicflow可以图形化
{
"nodes": [
{
"id": "start-node",
"type": "start",
"x": 150,
"y": 100,
"text": "开始",
"properties": {}
},
{
"id": "task-node-01",
"type": "task",
"x": 150,
"y": 250,
"text": "数据库备份",
"properties": {
"type": "script",
"scriptId": "9cf678b4-9108-454e-8b21-67b53a7b6dc0",
"scriptName": "ssh测试ping",
"scriptOrigin": 2,
"scriptVersion": "1.0.0",
"scriptType": 2,
"scriptTypeDesc": "用于管理开源应用的脚本",
"inputParameters": [
{
"parameterName": "env",
"parameterValue": "SIT",
"isRequired": true
}
],
"timeout": 1800,
"status": "fail"
}
},
{
"id": "task-node-02",
"type": "task",
"x": 400,
"y": 250,
"text": "发送失败通知",
"properties": {
"type": "email",
"pluginCode": "1001",
"pluginName": "通知",
"pluginVersion": "v1.0",
"pluginDescription": "用于发送流程节点执行结果通知",
"nodeName": "发送通知",
"status": "success",
"noticeMethod": "email,meixin",
"noticeUsers": "ex_xuyy15,zhangly97",
"noticeSubject": "通知主题【告警】数据库备份任务执行失败",
"noticeContent": "您好!失败了啊"
}
},
{
"id": "end-node",
"type": "end",
"x": 275,
"y": 400,
"text": "流程结束",
"properties": {
"description": "接收备份成功分支或通知节点流转,流程最终终止"
}
}
],
"edges": [
{
"id": "edge-start-to-backup",
"sourceNodeId": "start-node",
"targetNodeId": "task-node-01",
"text": "",
"properties": {
"description": "流程开始后,执行数据库备份任务"
}
},
{
"id": "edge-backup-to-end-success",
"sourceNodeId": "task-node-01",
"targetNodeId": "end-node",
"text": "备份成功",
"properties": {
"condition": "${taskResult_task-node-01 === 'true'}",
"description": "备份成功时,直接流向唯一结束节点"
}
},
{
"id": "edge-backup-to-notify",
"sourceNodeId": "task-node-01",
"targetNodeId": "task-node-02",
"text": "备份失败",
"properties": {
"condition": "${taskResult_task-node-01 === 'false'}",
"description": "备份失败时,先触发发送失败通知"
}
},
{
"id": "edge-notify-to-end",
"sourceNodeId": "task-node-02",
"targetNodeId": "end-node",
"text": "",
"properties": {
"description": "失败通知发送完成后,流向唯一结束节点"
}
}
],
"properties": {
"flowId": "db-backup-flow-001",
"flowName": "数据库自动备份流程(单结束节点版)",
"version": "1.0.1",
"description": "每日凌晨2点执行数据库备份:成功则直接结束,失败则先发通知再结束,所有分支最终汇聚到同一个结束节点"
}
}
/**
* 流程边模型
* 定义节点之间的连接关系,支持条件判断
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Edge {
private String id; // 边ID
private String sourceNodeId; // 源节点ID(从哪个节点出发)
private String targetNodeId; // 目标节点ID(到哪个节点)
private String text; // 边上显示的文本
private Map<String, Object> properties; // 边的属性,可包含condition条件表达式
}
/**
* 流程节点模型
* 支持所有节点类型:开始、任务、结束、并行网关、条件并行网关、分支网关、汇聚网关
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Node {
private String id; // 节点ID
private String type; // 节点类型:start/task/end/parallelGateway/conditionalParallelGateway/branchGateway/convergeGateway
private Integer x; // x坐标
private Integer y; // y坐标
private String text; // 节点显示文本
private Map<String, Object> properties; // 节点自定义属性
// 节点类型常量定义
public static final String TYPE_START = "start"; // 开始节点
public static final String TYPE_TASK = "task"; // 任务节点
public static final String TYPE_END = "end"; // 结束节点
// 网关类型
public static final String TYPE_PARALLEL_GATEWAY = "parallelGateway"; // 并行网关
public static final String TYPE_CONDITIONAL_PARALLEL_GATEWAY = "conditionalParallelGateway"; // 条件并行网关
public static final String TYPE_BRANCH_GATEWAY = "branchGateway"; // 分支网关
public static final String TYPE_CONVERGE_GATEWAY = "convergeGateway"; // 汇聚网关
}
/**
* 流程上下文
* 用于在节点之间传递数据、存储中间结果和跟踪流程状态
*/
@Data
public class WorkflowContext {
// 存储流程变量(节点间共享的数据)
private Map<String, Object> variables = new HashMap<>();
// 标记流程是否已结束
private boolean processEnded = false;
// 存储等待中的网关(用于汇聚网关)
private Set<String> pendingGateways = new HashSet<>();
// 脚本要执行的目标ip
private List<String> executeIpList = new ArrayList<>();
// 用于关联记录表和执行情况和ip详细执行情况
private Long opsTaskExecutionInfoId;
// 任务id
private String taskId;
// 任务记录
private OpsTaskRecordPO opsTaskRecordPO;
// 新增:并行分支完成状态跟踪(key=分支标识,value=是否完成)
// 用ConcurrentHashMap保证多线程并行分支操作安全
private Map<String, Boolean> branchCompletionStatus = new ConcurrentHashMap<>();
// 线程安全的分支完成状态存储(汇聚网关依赖此判断)
private final Map<String, Boolean> branchCompletedMap = new ConcurrentHashMap<>();
/**
* 设置变量
*/
public void setVariable(String key, Object value) {
variables.put(key, value);
}
/**
* 获取变量
*/
public Object getVariable(String key) {
return variables.get(key);
}
/**
* 添加等待中的网关
*/
public void addPendingGateway(String gatewayId) {
pendingGateways.add(gatewayId);
}
/**
* 创建上下文副本(用于并行网关的分支)
*/
public WorkflowContext copy() {
WorkflowContext copy = new WorkflowContext();
copy.setVariables(new HashMap<>(this.variables)); // 浅拷贝变量
copy.setProcessEnded(this.processEnded);
return copy;
}
/**
* 标记某个分支已完成(并行分支执行完后调用)
* @param branchKey 分支标识(用并行网关出口边的sourceNodeId,确保唯一)
*/
public void markBranchCompleted(String branchKey) {
branchCompletedMap.put(branchKey, Boolean.TRUE);
}
/**
* 判断某个分支是否完成
*/
public boolean isBranchCompleted(String branchKey) {
return Boolean.TRUE.equals(branchCompletedMap.get(branchKey));
}
}
/**
* 流程定义模型
* 包含整个流程的所有节点、边和全局属性
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class WorkflowDefinition {
private List<Node> nodes; // 所有节点
private List<Edge> edges; // 所有边
private Map<String, Object> properties; // 流程全局属性(如flowId、flowName等)
}
// 任务表
@Data
@TableName(value = "ops_task_record")
public class OpsTaskRecordPO {
/**
* 周期任务名称
*/
@TableField(value = "task_name")
private String taskName;
/**
* 任务模板主键id
*/
@TableField(value = "ops_task_template_id")
private Long opsTaskTemplateId;
/**
* 任务模板名称
*/
@TableField(value = "ops_task_template_name")
private String opsTaskTemplateName;
/**
* 任务模板配置(JSON格式)
*/
@TableField(value = "ops_task_template_flow_json")
private String opsTaskTemplateFlowJson;
/**
* 任务类型 周期任务,定时任务
*/
@TableField(value = "ops_task_type")
private String opsTaskType;
/**
* 执行类型:审批后立即执行/周期执行/定时执行
*/
@TableField(value = "execute_type")
private String executeType;
/**
* 执行开始时间(yyyy-MM-dd HH:mm:ss)
*/
@TableField(value = "execute_start_time")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss" ,timezone = "GMT+8" )
@JsonDeserialize(using = DateDeserializers.DateDeserializer.class)
private Date executeStartTime;
/**
* 执行结束时间(yyyy-MM-dd HH:mm:ss)
*/
@TableField(value = "execute_end_time")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss" ,timezone = "GMT+8" )
@JsonDeserialize(using = DateDeserializers.DateDeserializer.class)
private Date executeEndTime;
/**
* 任务状态(0-未启用 1-运行中 2-已暂停 3-已结束)
*/
@TableField(value = "execute_status")
private OpsTaskStatusEnum executeStatus;
/**
* 管理员
*/
@TableField(value = "administrator")
private String administrator;
/**
* 首次开始执行时间(yyyy-MM-dd HH:mm:ss)
*/
@TableField(value = "first_execute_time")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss" ,timezone = "GMT+8" )
@JsonDeserialize(using = DateDeserializers.DateDeserializer.class)
private Date firstExecuteTime;
/**
* 最后一次执行开始时间(yyyy-MM-dd HH:mm:ss)
*/
@TableField(value = "last_execute_time")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss" ,timezone = "GMT+8" )
@JsonDeserialize(using = DateDeserializers.DateDeserializer.class)
private Date lastExecuteTime;
/**
* 任务周期(单位:天/周/月,如:1D/2W/1M)
*/
@TableField(value = "task_cycle")
private String taskCycle;
/**
* 任务周期 cron
*/
@TableField(value = "task_cycle_cron")
private String taskCycleCron;
/**
* 已执行周期数
*/
@TableField(value = "executed_cycles")
private Integer executedCycles;
}
/**
* 任务处理器接口
* 不同类型的任务节点实现此接口
*/
public interface TaskProcessor {
/**
* 执行任务
* @param taskNode 任务节点
* @param context 流程上下文
* @return 任务执行结果
*/
TaskResult execute(Node taskNode, WorkflowContext context) throws Exception;
}
/**
* 通知任务处理器
* 处理"发送通知"类型的任务节点
*/
@Component
public class EmailProcessor implements TaskProcessor {
@Override
public TaskResult execute(Node taskNode, WorkflowContext context) throws Exception {
}
}
/**
* 脚本任务处理器
*
*/
@Component
public class ScriptTaskProcessor implements TaskProcessor {
@Override
public TaskResult execute(Node taskNode, WorkflowContext context) throws Exception {
}
}
/**
* 流程引擎核心类
* 处理所有节点类型:开始、任务、结束、各种网关
*/
@Component
public class WorkflowEngine {
private static final Logger log = LoggerFactory.getLogger(WorkflowEngine.class);
private static final long PARALLEL_BRANCH_TIMEOUT = 30; // 并行分支超时时间(分钟)
private static final TimeUnit PARALLEL_TIMEOUT_UNIT = TimeUnit.MINUTES;
@Autowired
private TaskProcessorFactory taskProcessorFactory;
@Resource(name = "opsTaskExecutor")
ThreadPoolExecutor opsTaskExecutor;
/**
* 执行流程
*/
public void execute(String workflowJson, WorkflowContext context) {
// 校验入参
if (workflowJson == null || context == null || context.getOpsTaskRecordPO() == null) {
throw new IllegalArgumentException("流程JSON、上下文或任务记录不能为空");
}
OpsTaskRecordPO opsTaskRecordPO = context.getOpsTaskRecordPO();
OpsTaskExecutionInfoPO opsTaskExecutionInfoPO = null;
try {
// 1. 解析流程定义
WorkflowDefinition workflow = JSON.parseObject(workflowJson, WorkflowDefinition.class);
if (workflow.getNodes() == null || workflow.getNodes().isEmpty()) {
throw new RuntimeException("流程定义中无节点配置");
}
// 2. 查找开始节点
Node startNode = workflow.getNodes().stream()
.filter(node -> Node.TYPE_START.equals(node.getType()))
.findFirst()
.orElseThrow(() -> new RuntimeException("流程定义中未找到开始节点"));
// 3. 创建执行记录(首次执行)
// 先有任务,再有执行记录,所以要根据recordId去查执行明细表
Integer maxExecutedCycles = opsTaskExecutionInfoMapper.getMaxExecutedCycles(opsTaskRecordPO.getOpsTaskRecordId());
opsTaskExecutionInfoPO = new OpsTaskExecutionInfoPO();
// set执行明细表的字段值 省略。使用mybatisplus 的注解主键字段,保存成功后会填充主键id
opsTaskExecutionInfoMapper.save(opsTaskExecutionInfoPO);
//将执行明细的id传递到流程上下文
context.setOpsTaskExecutionInfoId(opsTaskExecutionInfoPO.getId());
// 4. 执行流程(核心:此处会等待所有并行分支完成)
processNode(startNode.getId(), workflow, context);
// 5. 流程正常完成:统计最终状态
updateFinalExecutionStatus(opsTaskExecutionInfoPO, context);
} catch (Exception e) {
LogUtil.info(log, "WorkflowEngine-execute", "WorkflowEngine-execute流程执行失败:{}", e);
} finally {
if (opsTaskExecutionInfoPO != null && opsTaskExecutionInfoPO.getExecuteEndTime() == null) {
opsTaskExecutionInfoPO.setExecuteEndTime(new Date());
updateExecutionInfo(opsTaskExecutionInfoPO);
}
}
}
/**
* 处理节点(递归核心,保持原有逻辑结构,优化状态传递)
*/
private void processNode(String nodeId, WorkflowDefinition workflow, WorkflowContext context) {
if (context.isProcessEnded()) {
LogUtil.info(log, "processNode", "流程已结束,跳过节点:{}", nodeId);
return;
}
// 获取当前节点
Node currentNode = workflow.getNodes().stream()
.filter(node -> node.getId().equals(nodeId))
.findFirst()
.orElseThrow(() -> new RuntimeException("未找到节点:" + nodeId));
LogUtil.info(log, "processNode", "开始处理节点:{}({}),节点类型:{}",
currentNode.getId(), currentNode.getText(), currentNode.getType());
// 按节点类型处理
switch (currentNode.getType()) {
case Node.TYPE_START:
processStartNode(currentNode, workflow, context);
break;
case Node.TYPE_TASK:
processTaskNode(currentNode, workflow, context);
break;
case Node.TYPE_END:
processEndNode(currentNode, context);
break;
case Node.TYPE_PARALLEL_GATEWAY://处理并行网关
processParallelGateway(currentNode, workflow, context);
break;
case Node.TYPE_CONDITIONAL_PARALLEL_GATEWAY://处理条件并行网关
processConditionalParallelGateway(currentNode, workflow, context);
break;
case Node.TYPE_BRANCH_GATEWAY://处理分支网关
processBranchGateway(currentNode, workflow, context);
break;
case Node.TYPE_CONVERGE_GATEWAY://处理汇聚网关
processConvergeGateway(currentNode, workflow, context);
break;
default:
throw new RuntimeException("不支持的节点类型:" + currentNode.getType());
}
}
/**
* 处理开始节点(无逻辑变更,保持原有)
*/
private void processStartNode(Node startNode, WorkflowDefinition workflow, WorkflowContext context) {
List<Edge> outgoingEdges = getOutgoingEdges(startNode.getId(), workflow);
if (outgoingEdges.size() != 1) {
throw new RuntimeException("开始节点必须有且仅有一条出口边,当前:" + outgoingEdges.size());
}
processNode(outgoingEdges.get(0).getTargetNodeId(), workflow, context);
}
/**
* 处理任务节点(优化:变量存储线程安全,异常信息更详细)
*/
private void processTaskNode(Node taskNode, WorkflowDefinition workflow, WorkflowContext context) {
String taskNodeId = taskNode.getId();
try {
// 获取任务处理器(策略模式,保持原有)
TaskProcessor processor = taskProcessorFactory.getProcessor(taskNode);
if (processor == null) {
throw new RuntimeException("未找到任务节点[" + taskNodeId + "]的处理器");
}
// 执行任务
LogUtil.info(log, "processTaskNode", "开始执行任务节点:{}", taskNodeId);
TaskResult result = processor.execute(taskNode, context);
LogUtil.info(log, "processTaskNode", "任务节点{}执行结果:{}", taskNodeId, result.isSuccess());
// 线程安全存储任务结果(主上下文,无副本)
context.setVariable("taskResult_" + taskNodeId, result.isSuccess());
if (!result.isSuccess()) {
context.setVariable("taskError_" + taskNodeId, result.getMessage());
}
// 继续下一个节点
processNextNodes(taskNodeId, workflow, context);
} catch (Exception e) {
String errorMsg = "任务节点[" + taskNodeId + "]执行失败:" + e.getMessage();
context.setVariable("taskError_" + taskNodeId, errorMsg);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 处理结束节点(无逻辑变更,保持原有)
*/
private void processEndNode(Node endNode, WorkflowContext context) {
LogUtil.info(log, "processEndNode", "流程结束于节点:{}({})", endNode.getId(), endNode.getText());
context.setProcessEnded(true);
}
/**
* 处理并行网关(优化核心:用ExecutorService+Future等待分支,无上下文副本)
*/
private void processParallelGateway(Node gatewayNode, WorkflowDefinition workflow, WorkflowContext context) {
List<Edge> outgoingEdges = getOutgoingEdges(gatewayNode.getId(), workflow);
if (outgoingEdges.isEmpty()) {
throw new RuntimeException("并行网关[" + gatewayNode.getId() + "]必须至少有一条出口边");
}
String gatewayId = gatewayNode.getId();
LogUtil.info(log, "processParallelGateway", "并行网关{}开始执行{}个分支", gatewayId, outgoingEdges.size());
// 存储并行分支的Future(用于等待所有分支完成)
List<Future<?>> branchFutures = new ArrayList<>(outgoingEdges.size());
try {
// 提交所有分支任务到线程池
for (Edge edge : outgoingEdges) {
String nextNodeId = edge.getTargetNodeId();
String branchKey = edge.getSourceNodeId() + "_" + nextNodeId; // 唯一分支标识(网关ID+目标节点ID)
// 提交任务(lambda捕获final变量,确保线程安全)
Future<?> future = opsTaskExecutor.submit(() -> {
try {
LogUtil.info(log, "processParallelGateway", "并行网关{}分支{}开始执行", gatewayId, branchKey);
processNode(nextNodeId, workflow, context); // 直接用主上下文,状态同步
context.markBranchCompleted(branchKey); // 标记分支完成(主上下文)
LogUtil.info(log, "processParallelGateway", "并行网关{}分支{}执行完成", gatewayId, branchKey);
} catch (Exception e) {
String errorMsg = "并行网关[" + gatewayId + "]分支[" + branchKey + "]执行失败";
LogUtil.error(log, "processParallelGateway", errorMsg, e);
context.setVariable("parallelBranchError_" + branchKey, errorMsg);
throw new RuntimeException(errorMsg, e);
}
});
branchFutures.add(future);
}
// 等待所有分支完成(加超时,避免永久阻塞)
for (Future<?> future : branchFutures) {
future.get(PARALLEL_BRANCH_TIMEOUT, PARALLEL_TIMEOUT_UNIT); // 超时抛出TimeoutException
}
LogUtil.info(log, "processParallelGateway", "并行网关{}所有分支执行完成", gatewayId);
processNextNodes(gatewayId, workflow, context); // 继续下一个节点(通常是汇聚网关)
} catch (TimeoutException e) {
// 并行分支超时:标记所有未完成分支为失败
String errorMsg = "并行网关[" + gatewayId + "]分支执行超时(超过" + PARALLEL_BRANCH_TIMEOUT + "分钟)";
LogUtil.error(log, "processParallelGateway", errorMsg, e);
context.setVariable("parallelGatewayTimeout_" + gatewayId, errorMsg);
throw new RuntimeException(errorMsg, e);
} catch (InterruptedException | ExecutionException e) {
// 分支执行异常:包装异常并抛出
String errorMsg = "并行网关[" + gatewayId + "]分支执行异常";
LogUtil.error(log, "processParallelGateway", errorMsg, e);
throw new RuntimeException(errorMsg, e.getCause());
}
}
/**
* 处理条件并行网关(复用并行网关逻辑,先筛选条件分支)
*/
private void processConditionalParallelGateway(Node gatewayNode, WorkflowDefinition workflow, WorkflowContext context) {
List<Edge> outgoingEdges = getOutgoingEdges(gatewayNode.getId(), workflow);
// 筛选满足条件的分支(用SpEL解析)
List<Edge> qualifiedEdges = outgoingEdges.stream()
.filter(edge -> evaluateCondition(edge.getProperties().getOrDefault("condition", "true").toString(), context))
.collect(Collectors.toList());
if (qualifiedEdges.isEmpty()) {
throw new RuntimeException("条件并行网关[" + gatewayNode.getId() + "]无满足条件的分支");
}
String gatewayId = gatewayNode.getId();
LogUtil.info(log, "processConditionalParallelGateway", "条件并行网关{}筛选出{}个满足条件的分支", gatewayId, qualifiedEdges.size());
// 替换网关的出口边为筛选后的分支,复用并行网关逻辑
workflow.setEdges(workflow.getEdges().stream()
.filter(edge -> !edge.getSourceNodeId().equals(gatewayId) || qualifiedEdges.contains(edge))
.collect(Collectors.toList()));
// 调用并行网关处理逻辑(复用代码,减少冗余)
processParallelGateway(gatewayNode, workflow, context);
}
/**
* 处理分支网关(优化:用SpEL解析条件,逻辑更健壮)
*/
private void processBranchGateway(Node gatewayNode, WorkflowDefinition workflow, WorkflowContext context) {
List<Edge> outgoingEdges = getOutgoingEdges(gatewayNode.getId(), workflow);
if (outgoingEdges.isEmpty()) {
throw new RuntimeException("分支网关[" + gatewayNode.getId() + "]无出口边");
}
// 查找第一个满足条件的分支
Edge qualifiedEdge = null;
for (Edge edge : outgoingEdges) {
String condition = edge.getProperties().getOrDefault("condition", "true").toString();
if (evaluateCondition(condition, context)) {
qualifiedEdge = edge;
break;
}
}
// 无满足条件分支时,查找默认分支(无condition或condition=true)
if (qualifiedEdge == null) {
qualifiedEdge = outgoingEdges.stream()
.filter(edge -> !edge.getProperties().containsKey("condition")
|| "true".equals(edge.getProperties().get("condition").toString()))
.findFirst()
.orElseThrow(() -> new RuntimeException("分支网关[" + gatewayNode.getId() + "]无满足条件分支,也无默认分支"));
}
LogUtil.info(log, "processBranchGateway", "分支网关{}选择分支:{}(目标节点:{})",
gatewayNode.getId(), qualifiedEdge.getId(), qualifiedEdge.getTargetNodeId());
processNode(qualifiedEdge.getTargetNodeId(), workflow, context);
}
/**
* 处理汇聚网关(优化:依赖主上下文分支状态,判断逻辑正确)
*/
private void processConvergeGateway(Node gatewayNode, WorkflowDefinition workflow, WorkflowContext context) {
List<Edge> incomingEdges = getIncomingEdges(gatewayNode.getId(), workflow);
if (incomingEdges.isEmpty()) {
LogUtil.warn(log, "processConvergeGateway", "汇聚网关[" + gatewayNode.getId() + "]无入口边,默认继续执行");
processNextNodes(gatewayNode.getId(), workflow, context);
return;
}
// 检查所有入口边对应的分支是否完成
boolean allBranchesCompleted = checkAllBranchesCompleted(incomingEdges, context);
if (!allBranchesCompleted) {
// 分支未完成:标记pending(实际项目可结合定时任务重试,此处简化)
String pendingMsg = "汇聚网关[" + gatewayNode.getId() + "]等待所有分支完成,暂存状态";
LogUtil.info(log, "processConvergeGateway", pendingMsg);
context.setVariable("convergeGatewayPending_" + gatewayNode.getId(), pendingMsg);
return;
}
// 所有分支完成:继续执行
LogUtil.info(log, "processConvergeGateway", "汇聚网关{}所有分支已完成,继续执行", gatewayNode.getId());
processNextNodes(gatewayNode.getId(), workflow, context);
}
// ==================== 工具方法(优化后)====================
/**
* 获取节点出口边(无变更)
*/
private List<Edge> getOutgoingEdges(String nodeId, WorkflowDefinition workflow) {
return workflow.getEdges().stream()
.filter(edge -> edge.getSourceNodeId().equals(nodeId))
.collect(Collectors.toList());
}
/**
* 获取节点入口边(无变更)
*/
private List<Edge> getIncomingEdges(String nodeId, WorkflowDefinition workflow) {
return workflow.getEdges().stream()
.filter(edge -> edge.getTargetNodeId().equals(nodeId))
.collect(Collectors.toList());
}
/**
* 处理下一个节点(优化:增加多出口边日志提醒)
*/
private void processNextNodes(String currentNodeId, WorkflowDefinition workflow, WorkflowContext context) {
if (context.isProcessEnded()) {
return;
}
List<Edge> outgoingEdges = getOutgoingEdges(currentNodeId, workflow);
if (outgoingEdges.size() > 1) {
LogUtil.info(log, "processNextNodes", "节点{}存在{}条出口边,可能导致并行执行(非网关节点建议单出口)",
currentNodeId, outgoingEdges.size());
}
for (Edge edge : outgoingEdges) {
// 解析边的条件(SpEL)
if (edge.getProperties().containsKey("condition") && !evaluateCondition(edge.getProperties().get("condition").toString(), context)) {
LogUtil.info(log, "processNextNodes", "节点{}出口边{}条件不满足,跳过", currentNodeId, edge.getId());
continue;
}
processNode(edge.getTargetNodeId(), workflow, context);
}
}
/**
* 评估条件表达式(简化实现,实际可集成SpEL等表达式引擎)
*/
private boolean evaluateCondition(String condition, WorkflowContext context) {
// 示例:处理 ${backupResult === 'success'} 这样的表达式
if (condition.startsWith("${") && condition.endsWith("}")) {
String expr = condition.substring(2, condition.length() - 1);
// 简单处理 backupResult === 'success' 这种形式
if (expr.contains("===")) {
String[] parts = expr.split("===");
String varName = parts[0].trim();
String expectedValue = parts[1].trim().replace("'", "");
Object actualValue = context.getVariable(varName);
return expectedValue.equals(actualValue != null ? actualValue.toString() : null);
}
}
// 默认返回true(无条件或无法解析的条件)
return true;
}
/**
* 检查所有分支是否完成(优化:依赖主上下文的分支状态)
*/
private boolean checkAllBranchesCompleted(List<Edge> incomingEdges, WorkflowContext context) {
for (Edge edge : incomingEdges) {
// 分支标识:入口边的「源节点ID+目标节点ID」(与并行分支标记的一致)
String branchKey = edge.getSourceNodeId() + "_" + edge.getTargetNodeId();
if (!context.isBranchCompleted(branchKey)) {
LogUtil.info(log, "checkAllBranchesCompleted", "汇聚网关入口边{}对应分支{}未完成", edge.getId(), branchKey);
return false;
}
}
return true;
}
/**
* 更新执行记录(抽取通用方法,减少冗余)
*/
private void updateExecutionInfo(OpsTaskExecutionInfoPO executionInfoPO) {
//更新执行状态,执行时间等字段,根据主键id
executionInfoPO.setUpdateTime(LocalDateTime.now());
//opsTaskExecutionInfoMapper.updateById();
}
/**
* 统计并更新最终执行状态(抽取通用方法)
*/
private void updateFinalExecutionStatus(OpsTaskExecutionInfoPO executionInfoPO, WorkflowContext context) {
Long executionInfoId = context.getOpsTaskExecutionInfoId();
// executionInfoPO.setExecutionStatus(TicketExecutionStatusEnum.EXECUTED);
}
/**
* 任务执行情况表 PO 类(与数据库表 ops_task_execution_info 映射)
* @author:
* @since: 2025/9/8 15:16
*/
@Data
@TableName("ops_task_execution_info")
public class OpsTaskExecutionInfoPO {
/**
* 关联任务列表主键ID(对应表字段 ops_task_record_id)
*/
@TableField(value = "ops_task_record_id")
private Long opsTaskRecordId;
/**
* 执行次数(第几次执行)(对应表字段 execute_count)
*/
@TableField(value = "execute_count")
private Integer executeCount;
/**
* 执行开始时间(对应表字段 execute_start_time)
* 备注:表中字段允许为 NULL,实体类用 Date 类型映射 datetime 字段
*/
@TableField(value = "execute_start_time")
private Date executeStartTime;
/**
* 执行结束时间(对应表字段 execute_end_time)
*/
@TableField(value = "execute_end_time")
private Date executeEndTime;
/**
* 执行状态(对应表字段 execution_status)
*/
@TableField(value = "execution_status")
private TicketExecutionStatusEnum executionStatus;
}
/**
* @author:
* 任务模板
* @since: 2025/9/1 13:41
*/
@Data
@TableName("ops_task_template")
public class OpsTaskTemplatePO extends BasePO {
/** 任务模板名称(脚本名称) */
@TableField("task_template_name")
private String taskTemplateName;
/** 任务模板描述(脚本描述) */
@TableField("task_template_desc")
private String taskTemplateDesc;
/** 流程配置JSON(脚本的核心配置) */
@TableField("flow_json")
private String flowJson;
/** 流程唯一标识Key */
@TableField("diagram_key")
private String diagramKey;
@TableField("market_diagram_key")
private String marketDiagramKey;
}
/**
* 测试调用LTS 提交任务, 并接收任务执行反馈结果
* https://www.cnblogs.com/MrYuChen-Blog/p/14803475.html
* @author
* @date 2025/9/15 11:07
* @return Map<Object>
*/
@GetMapping("test01")
public Map<String, Object> test01() {
Job job = new Job();
job.setTaskId("task-AAAAAAAAAAAAAAA");
job.setCronExpression("0/3 * * * * ?");
//设置任务类型 区分不同的任务 执行不同的业务逻辑
//job.setParam("type", "aType");
job.setNeedFeedback(true);
//任务触发时间 如果设置了 cron 则该设置无效
//job.setTriggerTime(DateUtils.addDay(new Date(), 1).getTime());
// job.setMaxRetryTimes(5);
// 这个是 cron expression 和 quartz 一样,可选
// job.setCronExpression(cronExpression);
// 这个是指定执行时间,可选
// job.setTriggerTime(new Date());
// 当 cronExpression 和 triggerTime 都不设置的时候,默认是立即执行任务
//任务执行节点组
job.setTaskTrackerNodeGroup("test_TaskTracker");
//当任务队列中存在这个任务的时候,是否替换更新
job.setReplaceOnExist(true);
Map<String, Object> submitResult = new HashMap<String, Object>(4);
try {
//任务提交返回值 response
Response response = jobClient.submitJob(job);
submitResult.put("success", response.isSuccess());
submitResult.put("msg", response.getMsg());
submitResult.put("code", response.getCode());
} catch (Exception e) {
log.error("提交任务失败", e);
throw new RuntimeException("提交任务失败");
}
return submitResult;
}
/**
*
* 任务具体的业务逻辑,此类可以被Quartz,LTS,xxl-job 调用,我使用的是LTS定时任务框架
* @since: 2025/9/2 9:24
*/
@Component
@JobRunner4TaskTracker
public class JobRunnerA implements JobRunner {
private static final Logger log = LoggerFactory.getLogger(JobRunnerA.class);
@Resource
OpsTaskRecordMapper opsTaskRecordMapper;
@Resource
OpsTaskExecutionInfoMapper opsTaskExecutionInfoMapper;
@Resource
WorkflowEngine workflowEngine;
@Resource
JobClient jobClient;
@Value("${lts.tasktracker.node-group}")
private String taskTrackerNodeGroup;
@Override
public Result run(JobContext jobContext) throws Throwable {
Job job = jobContext.getJob();
//扩展参数
Map<String, String> extParams = job.getExtParams();
//任务id,也是record表的主键id
String taskId = job.getTaskId();
LogUtil.info(log, "JobRunnerA", "JobRunnerA-{},开始执行", taskId);
Date date = new Date();
// 周期任务的有效时间
String executeStartTime = extParams.get("executeStartTime");
String executeEndTime = extParams.get("executeEndTime");
//周期性任务应该判断当前时间是否在周期任务的有效时间范围内,如果在则执行任务
OpsTaskRecordPO recordPO = opsTaskRecordMapper.getById(Long.parseLong(taskId));
if (date.after(DateUtil.parseDate(executeStartTime)) && date.before(DateUtil.parseDate(executeEndTime))) {
try {
WorkflowContext workflowContext = new WorkflowContext();
//将任务id传入上下文中
workflowContext.setTaskId(taskId);
//将任务对象放入上下文
workflowContext.setOpsTaskRecordPO(recordPO);
// 执行任务完成后,如果任务执行成功,则更新任务记录表的状态,记录执行情况表,ip表
workflowEngine.execute(recordPO.getOpsTaskTemplateFlowJson(), workflowContext);
// ops_task_execution_info 查询当前已执行次数(max(executedCycles),首次执行为0)
Integer maxExecutedCycles = opsTaskExecutionInfoService.lambdaQuery()
.eq(OpsTaskExecutionInfoPO::getOpsTaskRecordId, recordPO.getId())
.select(OpsTaskExecutionInfoPO::getExecuteCount)
.orderByDesc(OpsTaskExecutionInfoPO::getExecuteCount)
.last("LIMIT 1") // 取最大的一条
.oneOpt() // 用oneOpt避免空指针,返回Optional
.map(OpsTaskExecutionInfoPO::getExecuteCount)
.orElse(0); // 首次执行时默认0
// ops_task_record 计算新的执行次数(+1)并更新任务记录
int newExecutedCycles = maxExecutedCycles + 1;
opsTaskRecordService.lambdaUpdate()
.eq(OpsTaskRecordPO::getId, recordPO.getId()) // 补全更新条件:根据ID定位
.set(OpsTaskRecordPO::getExecutedCycles, newExecutedCycles)
.set(OpsTaskRecordPO::getExecuteStatus, OpsTaskStatusEnum.PERIODIC_RUNNING) // 更新为执行中
.update();
} catch (Exception e) {
LogUtil.info(log, "JobRunnerA", "JobRunnerA-{},更新任务记录为失败状态:{}", e);
}
} else {
// 如果不在范围内,则不执行任务,并且任务取消掉,更新记录表的状态
jobClient.cancelJob(taskId, taskTrackerNodeGroup);
recordPO.setExecuteStatus(OpsTaskStatusEnum.COMPLETED);
opsTaskRecordService.updateById(recordPO);
}
LogUtil.info(log, "JobRunnerA", "JobRunnerA-{},执行完毕", taskId);
return new Result(Action.EXECUTE_SUCCESS);
}
}
@Resource
WorkflowEngine workflowEngine;
@PostMapping("workflowEngineTest")
public Object workflowEngineTest(@RequestBody WorkflowDefinition flowMapJson){
workflowEngine.execute(JSONUtil.toJsonStr(flowMapJson), new WorkflowContext());
return "成功过孤寡孤寡孤寡嘎嘎嘎嘎嘎嘎";
}
更多推荐
所有评论(0)