diff --git a/backend/README.md b/backend/README.md index 9d14a804..c161f37f 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1051,4 +1051,44 @@ eventSource.addEventListener('STDERR', (event) => { // 当流程结束时关闭连接 eventSource.onerror = () => { eventSource.close(); -}; \ No newline at end of file +}; + +SELECT + hi.PROC_INST_ID_ as process_instance_id, + hi.PROC_DEF_ID_ as process_definition_id, + hi.START_TIME_ as start_time, + hi.END_TIME_ as end_time, + hi.DELETE_REASON_ as delete_reason, + -- 使用 GROUP_CONCAT 合并错误信息 + GROUP_CONCAT(DISTINCT + CASE + WHEN var.NAME_ = 'errorDetail' THEN var.TEXT_ + WHEN var.NAME_ IN ('errorMessage', 'error', 'exception') THEN var.TEXT_ + END + SEPARATOR '; ' + ) as error_message, + -- 获取最后一个活动节点 + MAX(CASE WHEN act.END_TIME_ IS NULL THEN act.ACT_NAME_ END) as last_activity_name, + MAX(CASE WHEN act.END_TIME_ IS NULL THEN act.ACT_ID_ END) as last_activity_id +FROM + ACT_HI_PROCINST hi +LEFT JOIN + ACT_HI_VARINST var ON hi.PROC_INST_ID_ = var.PROC_INST_ID_ +LEFT JOIN + ACT_HI_ACTINST act ON hi.PROC_INST_ID_ = act.PROC_INST_ID_ +WHERE + hi.PROC_INST_ID_ = '86faae44-bc47-11ef-a4cd-00155dcaffac' + AND ( + var.NAME_ IN ('errorDetail', 'errorMessage', 'error', 'exception') + OR var.TEXT_ LIKE '%error%' + OR var.TEXT_ LIKE '%exception%' + OR var.TEXT_ LIKE '%失败%' + ) +GROUP BY + hi.PROC_INST_ID_, + hi.PROC_DEF_ID_, + hi.START_TIME_, + hi.END_TIME_, + hi.DELETE_REASON_ +ORDER BY + hi.START_TIME_ DESC; \ No newline at end of file diff --git a/backend/pom.xml b/backend/pom.xml index 4bc6af70..16c963ab 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -26,7 +26,7 @@ 0.12.3 2.2.0 5.8.23 - 7.0.0 + 7.1.0 @@ -222,9 +222,14 @@ - - + + + + + + + com.networknt json-schema-validator diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowInstanceApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowInstanceApiController.java index f4cb5729..9ac25321 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowInstanceApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowInstanceApiController.java @@ -7,6 +7,7 @@ import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO; import com.qqchen.deploy.backend.workflow.dto.WorkflowExecutionDTO; import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceStartRequest; +import com.qqchen.deploy.backend.workflow.dto.WorkflowTemplateWithInstancesDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.query.WorkflowDefinitionQuery; @@ -24,6 +25,7 @@ import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.variable.api.history.HistoricVariableInstance; +import org.springframework.data.domain.Page; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -56,6 +58,13 @@ public class WorkflowInstanceApiController extends BaseController> getTemplatesWithInstances(WorkflowDefinitionQuery query) { + return Response.success(workflowInstanceService.findTemplatesWithRecentInstances(query)); + } + + @Override protected void exportData(HttpServletResponse response, List data) { diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowNodeInstanceApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowNodeInstanceApiController.java index 60f8d9cd..71895ffc 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowNodeInstanceApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowNodeInstanceApiController.java @@ -103,7 +103,7 @@ public class WorkflowNodeInstanceApiController extends BaseController envFromVar = (Map) execution.getVariable("env"); envValue = envFromVar; - log.info("从流程变量获取到env={}", envValue); +// log.info("从流程变量获取到env={}", envValue); } if (scriptValue == null) { - log.error("脚本内容为空,执行失败"); +// log.error("脚本内容为空,执行失败"); throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Script is required but not provided"); } @@ -169,16 +169,18 @@ public class ShellTaskDelegate implements JavaDelegate { if (exitCode != 0) { log.error("Shell脚本执行失败,退出码: {}", exitCode); - log.error("错误输出: {}", finalError); + execution.setVariable("errorDetail", "Shell脚本执行失败,退出码: " + exitCode); throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Shell脚本执行失败,退出码: " + exitCode); +// throw new RuntimeException("Shell脚本执行失败,退出码: " + exitCode); } - log.info("Shell脚本执行成功"); log.debug("脚本输出: {}", finalOutput); } catch (Exception e) { log.error("Shell脚本执行失败", e); - throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Shell脚本执行失败"); +// runtimeService.deleteProcessInstance(execution.getProcessInstanceId(), e.getMessage()); +// throw new FlowableException("任务执行失败: " + e.getMessage(), e); + throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, e.getMessage()); } } @@ -195,13 +197,13 @@ public class ShellTaskDelegate implements JavaDelegate { synchronized (output) { output.append(line).append("\n"); } - log.info("Shell output: {}", line); +// log.info("Shell output: {}", line); } else { StringBuilder error = errorMap.get(processInstanceId); synchronized (error) { error.append(line).append("\n"); } - log.error("Shell error: {}", line); +// log.error("Shell error: {}", line); } } } catch (IOException e) { diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowTemplateWithInstancesDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowTemplateWithInstancesDTO.java new file mode 100644 index 00000000..8a408dec --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowTemplateWithInstancesDTO.java @@ -0,0 +1,22 @@ +package com.qqchen.deploy.backend.workflow.dto; + +import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +public class WorkflowTemplateWithInstancesDTO { + + private Long id; + + private String name; + + private String businessKey; + + private LocalDateTime lastExecutionTime; + + private WorkflowInstanceStatusEnums lastExecutionStatus; + +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowInstanceStatusEnums.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowInstanceStatusEnums.java index 4dfccac6..f65264cd 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowInstanceStatusEnums.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowInstanceStatusEnums.java @@ -9,52 +9,54 @@ import lombok.Getter; */ @Getter public enum WorkflowInstanceStatusEnums { - + + NOT_STARTED("NOT_STARTED", "未开始"), + /** * 已创建:流程实例已创建但还未开始运行 */ CREATED("CREATED", "已创建"), - + /** * 运行中:流程实例正在执行 */ RUNNING("RUNNING", "运行中"), - + /** * 已暂停:流程实例被手动暂停 */ SUSPENDED("SUSPENDED", "已暂停"), - + /** * 已完成:流程实例正常完成 */ COMPLETED("COMPLETED", "已完成"), - + /** * 已终止:流程实例被手动终止 */ TERMINATED("TERMINATED", "已终止"), - + /** * 执行失败:流程实例执行过程中发生错误 */ FAILED("FAILED", "执行失败"); - + /** * 状态编码 */ private final String code; - + /** * 状态描述 */ private final String description; - + WorkflowInstanceStatusEnums(String code, String description) { this.code = code; this.description = description; } - + /** * 根据状态编码获取枚举实例 * @@ -69,35 +71,35 @@ public enum WorkflowInstanceStatusEnums { } throw new IllegalArgumentException("Invalid workflow instance status code: " + code); } - + /** * 判断是否为终态(不可再变化的状态) */ public boolean isFinalState() { return this == COMPLETED || this == TERMINATED || this == FAILED; } - + /** * 判断是否可以暂停 */ public boolean canBeSuspended() { return this == RUNNING; } - + /** * 判断是否可以恢复 */ public boolean canBeResumed() { return this == SUSPENDED; } - + /** * 判断是否可以终止 */ public boolean canBeTerminated() { return this == RUNNING || this == SUSPENDED; } - + @Override public String toString() { return this.code; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/FlowableEventDispatcher.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/FlowableEventDispatcher.java index 6885e1d1..3b4a4752 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/FlowableEventDispatcher.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/FlowableEventDispatcher.java @@ -2,49 +2,57 @@ package com.qqchen.deploy.backend.workflow.listener; import com.qqchen.deploy.backend.workflow.listener.handler.IFlowableEventHandler; import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEvent; import org.flowable.common.engine.api.delegate.event.FlowableEventListener; +import org.flowable.engine.RuntimeService; +import org.flowable.job.service.impl.persistence.entity.JobEntityImpl; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import jakarta.annotation.Resource; + import java.util.List; +import static com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants.ASYNC_CONTINUATION; + @Slf4j @Component public class FlowableEventDispatcher implements FlowableEventListener { - + @Resource @Lazy private List eventHandlers; - + + @Resource + @Lazy + private RuntimeService runtimeService; + @Override public void onEvent(FlowableEvent event) { String eventType = event.getType().name(); -// log.info("Dispatching Flowable event: {}, event class: {}", eventType, event.getClass().getName()); - + log.info("Dispatching Flowable event: {}, event class: {}", eventType, event.getClass().getName()); for (IFlowableEventHandler handler : eventHandlers) { if (handler.canHandle(eventType)) { try { handler.handle(event); } catch (Exception e) { - log.error("Error handling event {} by handler {}: {}", - eventType, handler.getClass().getSimpleName(), e.getMessage(), e); + log.error("Error handling event {} by handler {}: {}", eventType, handler.getClass().getSimpleName(), e.getMessage(), e); } } } } - + @Override public boolean isFailOnException() { return false; } - + @Override public boolean isFireOnTransactionLifecycleEvent() { return true; } - + @Override public String getOnTransaction() { return "committed"; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/FlowableJobEventListener.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/FlowableJobEventListener.java deleted file mode 100644 index 58432aa2..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/FlowableJobEventListener.java +++ /dev/null @@ -1,179 +0,0 @@ -package com.qqchen.deploy.backend.workflow.listener; - -import cn.hutool.core.date.DateUtil; -import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums; -import com.qqchen.deploy.backend.workflow.enums.WorkflowNodeInstanceStatusEnums; -import com.qqchen.deploy.backend.workflow.event.WorkflowInstanceStatusChangeEvent; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.flowable.common.engine.api.delegate.event.FlowableEvent; -import org.flowable.common.engine.api.delegate.event.FlowableEventListener; -import org.flowable.common.engine.api.delegate.event.FlowableEventType; -import org.flowable.engine.HistoryService; -import org.flowable.engine.delegate.event.FlowableProcessEngineEvent; -import org.flowable.engine.history.HistoricProcessInstance; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Component; - - -//@Component -@Slf4j -public class FlowableJobEventListener implements FlowableEventListener { - - @Resource - private ApplicationEventPublisher publisher; - - @Resource - @Lazy - private HistoryService historyService; - - @Override - public void onEvent(FlowableEvent event) { - FlowableEventType eventType = event.getType(); - log.info("Received Flowable event: {}, event class: {}", eventType, event.getClass().getName()); - // 只处理实体事件 -// if (!(event instanceof FlowableEngineEntityEvent entity)) { -// return; -// } - -// String processInstanceId = entity.getProcessInstanceId(); - - if (isProcessLevelEvent(eventType.name())) { - FlowableProcessEngineEvent flowableEntity = (FlowableProcessEngineEvent) event; - WorkflowInstanceStatusEnums status = convertProcessLevelEventToStatus(eventType.name()); - log.info("Process level event received: {} -> {}, processInstanceId: {}", eventType, status, null); - HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() - .processInstanceId(flowableEntity.getProcessInstanceId()) - .singleResult(); - publisher.publishEvent(WorkflowInstanceStatusChangeEvent.builder() - .processInstanceId(flowableEntity.getProcessInstanceId()) - .status(status) - .endTime(DateUtil.toLocalDateTime(historicProcessInstance.getEndTime())) - .build() - ); - } - - if (isTaskInstanceEvent(eventType.name())) { -// WorkflowNodeInstanceStatusEnums status = convertJobEventToStatus(eventType.name()); -// // 获取Job信息 -// String executionId = entity.getExecutionId(); -// org.flowable.job.api.Job job = (org.flowable.job.service.impl.persistence.entity.JobEntityImpl) entity.getEntity(); -// String activityId = job.getElementId(); -// String activityName = job.getElementName(); -// String activityType = job.getJobHandlerType(); -// -// LocalDateTime startTime = job.getCreateTime() != null ? DateUtil.toLocalDateTime(job.getCreateTime()) : null; -// LocalDateTime endTime = null; -// if (status == WorkflowNodeInstanceStatusEnums.COMPLETED || -// status == WorkflowNodeInstanceStatusEnums.FAILED) { -// endTime = LocalDateTime.now(); // 对于完成或失败状态,使用当前时间作为结束时间 -// } -// -// publisher.publishEvent(WorkflowNodeInstanceStatusChangeEvent.builder() -// .processInstanceId(processInstanceId) -// .executionId(executionId) -// .nodeId(activityId) -// .nodeName(activityName) -// .nodeType(activityType) -// .status(status) -// .startTime(startTime) -// .endTime(endTime) -// .build() -// ); -// -// log.info("Job event received: {} -> {}, processInstanceId: {}, nodeId: {}, nodeName: {}", -// eventType, status, processInstanceId, activityId, activityName); - } - } - - @Override - public boolean isFailOnException() { - return false; - } - - @Override - public boolean isFireOnTransactionLifecycleEvent() { - return true; - } - - @Override - public String getOnTransaction() { - return "committed"; - } - - private boolean isProcessLevelEvent(String eventType) { - return eventType.startsWith("PROCESS_"); - } - - private boolean isTaskInstanceEvent(String eventType) { - return eventType.startsWith("JOB_EXECUTION_") || - eventType.startsWith("ACTIVITY_"); // 添加对 ACTIVITY_ 事件的监听 - } - - /** - * 将Flowable流程级别事件类型转换为工作流实例状态 - * 只处理流程实例级别的事件,不处理活动节点级别的事件 - * - * @param eventType Flowable事件类型 - * @return 工作流实例状态枚举,如果不是流程级别事件则返回null - */ - private WorkflowInstanceStatusEnums convertProcessLevelEventToStatus(String eventType) { - switch (eventType) { - // 创建相关 - case "PROCESS_CREATED": - return WorkflowInstanceStatusEnums.CREATED; - - // 运行相关 - case "PROCESS_STARTED": - return WorkflowInstanceStatusEnums.RUNNING; - - // 暂停相关 - case "PROCESS_SUSPENDED": - return WorkflowInstanceStatusEnums.SUSPENDED; - - // 恢复相关 - case "PROCESS_RESUMED": - return WorkflowInstanceStatusEnums.RUNNING; - - // 完成相关 - case "PROCESS_COMPLETED": - return WorkflowInstanceStatusEnums.COMPLETED; - - // 终止相关 - case "PROCESS_CANCELLED": - return WorkflowInstanceStatusEnums.TERMINATED; - - // 失败相关 - case "PROCESS_COMPLETED_WITH_ERROR_END_EVENT": - return WorkflowInstanceStatusEnums.FAILED; - - default: - // 不是流程级别的事件,返回null - return null; - } - } - - /** - * 将Flowable活动节点事件类型转换为工作流节点实例状态 - * - * @param eventType Flowable事件类型 - * @return 工作流节点实例状态枚举,如果不是节点级别事件则返回null - */ - private WorkflowNodeInstanceStatusEnums convertJobEventToStatus(String eventType) { - switch (eventType) { - case "JOB_EXECUTION_SUCCESS": - return WorkflowNodeInstanceStatusEnums.COMPLETED; - case "JOB_EXECUTION_START": - return WorkflowNodeInstanceStatusEnums.RUNNING; - case "JOB_EXECUTION_FAILURE": - return WorkflowNodeInstanceStatusEnums.FAILED; - case "JOB_EXECUTION_REJECTED": // 当作业被拒绝执行时 - return WorkflowNodeInstanceStatusEnums.FAILED; - case "JOB_EXECUTION_NOJOB_FOUND": // 当找不到要执行的作业时 - return WorkflowNodeInstanceStatusEnums.FAILED; - default: - return null; - } - } -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/ActivityEventHandler.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/ActivityEventHandler.java index 6bcde15a..39e62d83 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/ActivityEventHandler.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/ActivityEventHandler.java @@ -3,52 +3,66 @@ package com.qqchen.deploy.backend.workflow.listener.handler; import com.qqchen.deploy.backend.workflow.enums.WorkflowNodeInstanceStatusEnums; import com.qqchen.deploy.backend.workflow.event.WorkflowNodeInstanceStatusChangeEvent; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.flowable.common.engine.api.delegate.event.FlowableEvent; import org.flowable.engine.delegate.event.FlowableActivityEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import jakarta.annotation.Resource; + import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants.BOUNDARY_EVENT_ERROR_PREFIX; +import static com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants.END_EVENT_ERROR_PREFIX; @Slf4j @Component public class ActivityEventHandler implements IFlowableEventHandler { - + @Resource private ApplicationEventPublisher publisher; - + + @Override public boolean canHandle(String eventType) { return eventType.startsWith("ACTIVITY_"); } - + @Override public void handle(FlowableEvent event) { String eventType = event.getType().name(); -// log.info("Processing activity event: {}", eventType); - if (!(event instanceof FlowableActivityEvent activityEvent)) { return; } - + log.info("Processing Flowable event: {}, id: {}, name:{}", eventType, activityEvent.getActivityId(), activityEvent.getActivityName()); + + List ignoredList = Arrays.asList(BOUNDARY_EVENT_ERROR_PREFIX, END_EVENT_ERROR_PREFIX); + + boolean ignored = ignoredList.stream().anyMatch(v -> activityEvent.getActivityId().contains(v)); + if (ignored) { + return; + } WorkflowNodeInstanceStatusEnums status = convertToNodeStatus(eventType); - + if (status != null) { -// publisher.publishEvent(WorkflowNodeInstanceStatusChangeEvent.builder() -// .processInstanceId(activityEvent.getProcessInstanceId()) -// .executionId(activityEvent.getExecutionId()) -// .nodeId(activityEvent.getActivityId()) -// .nodeName(activityEvent.getActivityName()) -// .nodeType(activityEvent.getActivityType()) -// .status(status) -// .startTime(status == WorkflowNodeInstanceStatusEnums.RUNNING ? LocalDateTime.now() : null) -// .endTime(status.isFinalState() ? LocalDateTime.now() : null) -// .build() -// ); + publisher.publishEvent(WorkflowNodeInstanceStatusChangeEvent.builder() + .processInstanceId(activityEvent.getProcessInstanceId()) + .executionId(activityEvent.getExecutionId()) + .nodeId(activityEvent.getActivityId()) + .nodeName(StringUtils.isEmpty(activityEvent.getActivityName()) ? activityEvent.getActivityId() : activityEvent.getActivityName()) + .nodeType(activityEvent.getActivityType()) + .status(status) + .startTime(status == WorkflowNodeInstanceStatusEnums.RUNNING ? LocalDateTime.now() : null) + .endTime(status.isFinalState() ? LocalDateTime.now() : null) + .build() + ); } } - + private WorkflowNodeInstanceStatusEnums convertToNodeStatus(String eventType) { return switch (eventType) { case "ACTIVITY_STARTED" -> WorkflowNodeInstanceStatusEnums.RUNNING; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/IFlowableEventHandler.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/IFlowableEventHandler.java index 0a400bc0..c5ea01b8 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/IFlowableEventHandler.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/IFlowableEventHandler.java @@ -2,11 +2,14 @@ package com.qqchen.deploy.backend.workflow.listener.handler; import org.flowable.common.engine.api.delegate.event.FlowableEvent; +import java.util.List; + /** * Flowable事件处理器接口 */ public interface IFlowableEventHandler { - + + /** * 判断是否可以处理该事件 * @@ -14,7 +17,7 @@ public interface IFlowableEventHandler { * @return 是否可以处理 */ boolean canHandle(String eventType); - + /** * 处理事件 * diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/JobEventHandler.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/JobEventHandler.java index c7bdb3e0..4a87132e 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/JobEventHandler.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/JobEventHandler.java @@ -11,34 +11,32 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import jakarta.annotation.Resource; + import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; @Slf4j @Component public class JobEventHandler implements IFlowableEventHandler { - + @Resource private ApplicationEventPublisher publisher; - + @Override public boolean canHandle(String eventType) { return eventType.startsWith("JOB_EXECUTION_"); } - + @Override public void handle(FlowableEvent event) { String eventType = event.getType().name(); -// log.info("Processing job event: {}", eventType); - if (!(event instanceof FlowableEngineEntityEvent entityEvent)) { return; } - // log.info("Processing job event: {}, jobType: {}", eventType, entityEvent.getType()); - JobEntityImpl job = (JobEntityImpl) entityEvent.getEntity(); WorkflowNodeInstanceStatusEnums status = convertToNodeStatus(eventType); - if (status != null) { // publisher.publishEvent(WorkflowNodeInstanceStatusChangeEvent.builder() // .processInstanceId(job.getProcessInstanceId()) @@ -53,7 +51,7 @@ public class JobEventHandler implements IFlowableEventHandler { // ); } } - + private WorkflowNodeInstanceStatusEnums convertToNodeStatus(String eventType) { return switch (eventType) { case "JOB_EXECUTION_SUCCESS" -> WorkflowNodeInstanceStatusEnums.COMPLETED; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/ProcessEventHandler.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/ProcessEventHandler.java index 2641284c..f22c6798 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/ProcessEventHandler.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/listener/handler/ProcessEventHandler.java @@ -14,6 +14,9 @@ import org.springframework.stereotype.Component; import jakarta.annotation.Resource; +import java.util.Collections; +import java.util.List; + @Slf4j @Component public class ProcessEventHandler implements IFlowableEventHandler { @@ -33,8 +36,6 @@ public class ProcessEventHandler implements IFlowableEventHandler { @Override public void handle(FlowableEvent event) { String eventType = event.getType().name(); - log.info("Processing event: {}", eventType); - FlowableProcessEngineEvent processEvent = (FlowableProcessEngineEvent) event; WorkflowInstanceStatusEnums status = convertToWorkflowStatus(eventType); @@ -59,12 +60,12 @@ public class ProcessEventHandler implements IFlowableEventHandler { case "PROCESS_CREATED" -> WorkflowInstanceStatusEnums.CREATED; case "PROCESS_STARTED" -> WorkflowInstanceStatusEnums.RUNNING; case "PROCESS_COMPLETED" -> WorkflowInstanceStatusEnums.COMPLETED; - case "PROCESS_COMPLETED_WITH_ERROR_END_EVENT" -> WorkflowInstanceStatusEnums.FAILED; - case "PROCESS_CANCELLED" -> WorkflowInstanceStatusEnums.TERMINATED; + case "PROCESS_CANCELLED" -> WorkflowInstanceStatusEnums.FAILED; case "PROCESS_SUSPENDED" -> WorkflowInstanceStatusEnums.SUSPENDED; - case "PROCESS_RESUMED" -> WorkflowInstanceStatusEnums.RUNNING; +// case "PROCESS_RESUMED" -> WorkflowInstanceStatusEnums.RUNNING; case "PROCESS_COMPLETED_WITH_TERMINATE_END_EVENT" -> WorkflowInstanceStatusEnums.TERMINATED; default -> null; }; } -} \ No newline at end of file + +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowDefinitionRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowDefinitionRepository.java index 503b1d08..1d3aaa1d 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowDefinitionRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowDefinitionRepository.java @@ -2,8 +2,12 @@ package com.qqchen.deploy.backend.workflow.repository; import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnums; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; /** @@ -22,4 +26,6 @@ public interface IWorkflowDefinitionRepository extends IBaseRepository findByKey(String businessKey); + + Page findByStatus(WorkflowStatusEnums workflowStatusEnums, Pageable pageable); } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowInstanceRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowInstanceRepository.java index ab9ba52e..daa0451b 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowInstanceRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowInstanceRepository.java @@ -21,4 +21,5 @@ public interface IWorkflowInstanceRepository extends IBaseRepository findByBusinessKey(String businessKey); + List findTop1ByWorkflowDefinitionIdOrderByCreateTimeDesc(Long workflowDefinitionId); } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowNodeInstanceRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowNodeInstanceRepository.java index 5c4c7f97..9ad33356 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowNodeInstanceRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowNodeInstanceRepository.java @@ -14,4 +14,6 @@ public interface IWorkflowNodeInstanceRepository extends IBaseRepository findByProcessInstanceId(String processInstanceId); WorkflowNodeInstance findByNodeId(String nodeId); + + WorkflowNodeInstance findByProcessInstanceIdAndNodeId(String processInstanceId, String nodeId); } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowInstanceService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowInstanceService.java index 999e7d66..611044a5 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowInstanceService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowInstanceService.java @@ -1,13 +1,14 @@ package com.qqchen.deploy.backend.workflow.service; import com.qqchen.deploy.backend.framework.service.IBaseService; -import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO; import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceStartRequest; -import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; +import com.qqchen.deploy.backend.workflow.dto.WorkflowTemplateWithInstancesDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums; +import com.qqchen.deploy.backend.workflow.query.WorkflowDefinitionQuery; import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.data.domain.Page; import java.time.LocalDateTime; import java.util.Date; @@ -58,4 +59,7 @@ public interface IWorkflowInstanceService extends IBaseService variables); WorkflowInstanceDTO startWorkflow(WorkflowInstanceStartRequest request); + + Page findTemplatesWithRecentInstances(WorkflowDefinitionQuery query); + } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowInstanceServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowInstanceServiceImpl.java index 82c710fa..999c9ecf 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowInstanceServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowInstanceServiceImpl.java @@ -7,9 +7,12 @@ import com.qqchen.deploy.backend.workflow.converter.WorkflowInstanceConverter; import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO; import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceStartRequest; +import com.qqchen.deploy.backend.workflow.dto.WorkflowTemplateWithInstancesDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnums; +import com.qqchen.deploy.backend.workflow.query.WorkflowDefinitionQuery; import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService; @@ -20,7 +23,12 @@ import lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; @@ -30,6 +38,8 @@ import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; +import static java.util.stream.Collectors.toList; + @Slf4j @Service public class WorkflowInstanceServiceImpl extends BaseServiceImpl implements IWorkflowInstanceService { @@ -59,7 +69,7 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl findByBusinessKey(String businessKey) { return workflowInstanceRepository.findByBusinessKey(businessKey).stream() .map(instance -> getInstanceDetails(instance.getProcessInstanceId())) - .collect(Collectors.toList()); + .collect(toList()); } @Override @@ -164,6 +174,26 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl findTemplatesWithRecentInstances(WorkflowDefinitionQuery query) { + Pageable pageable = PageRequest.of(query.getPageNum() - 1, query.getPageSize()); + Page definitionPage = workflowDefinitionRepository.findByStatus(WorkflowStatusEnums.PUBLISHED, pageable); + List result = definitionPage.getContent().stream().map(definition -> { + List workflowInstances = workflowInstanceRepository.findTop1ByWorkflowDefinitionIdOrderByCreateTimeDesc(definition.getId()); + WorkflowTemplateWithInstancesDTO workflowTemplateWithInstancesDTO = new WorkflowTemplateWithInstancesDTO(); + Optional optional = workflowInstances.stream().findFirst(); + if (optional.isPresent()) { + workflowTemplateWithInstancesDTO.setLastExecutionStatus(optional.get().getStatus()); + workflowTemplateWithInstancesDTO.setLastExecutionTime(optional.get().getCreateTime()); + } + workflowTemplateWithInstancesDTO.setId(definition.getId()); + workflowTemplateWithInstancesDTO.setName(definition.getName()); + workflowTemplateWithInstancesDTO.setBusinessKey(definition.getKey()); + return workflowTemplateWithInstancesDTO; + }).collect(toList()); + return new PageImpl<>(result, definitionPage.getPageable(), definitionPage.getTotalElements()); + } + // private WorkflowInstanceDTO.ActivityInstance convertToActivityInstance(HistoricActivityInstance hai) { // WorkflowInstanceDTO.ActivityInstance ai = new WorkflowInstanceDTO.ActivityInstance(); // ai.setId(hai.getId()); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowNodeInstanceServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowNodeInstanceServiceImpl.java index cbad113d..cece0b09 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowNodeInstanceServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowNodeInstanceServiceImpl.java @@ -67,7 +67,7 @@ public class WorkflowNodeInstanceServiceImpl extends BaseServiceImpl new RuntimeException("Node instance not found for processInstanceId: " + nodeInstance.getProcessInstanceId())); - WorkflowNodeInstance workflowNodeInstance = workflowNodeInstanceRepository.findByNodeId(event.getNodeId()); + WorkflowNodeInstance workflowNodeInstance = workflowNodeInstanceRepository.findByProcessInstanceIdAndNodeId(workflowInstance.getProcessInstanceId(), event.getNodeId()); if (workflowNodeInstance == null) { nodeInstance.setWorkflowInstanceId(workflowInstance.getId()); nodeInstance.setWorkflowDefinitionId(workflowInstance.getWorkflowDefinitionId()); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java index 013a4d29..d34a787d 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java @@ -18,17 +18,18 @@ import java.util.Map; /** * BPMN 模型转换工具 + * * @author cascade * @date 2024-12-11 */ @Slf4j @Component public class BpmnConverter { - + /** * 将工作流定义图转换为Flowable XML * - * @param graph 工作流定义图 + * @param graph 工作流定义图 * @param processKey 流程定义的唯一标识 * @return Flowable XML字符串 * @throws RuntimeException 当转换失败时抛出 @@ -36,11 +37,12 @@ public class BpmnConverter { public String convertToXml(WorkflowDefinitionGraph graph, String processKey) { try { log.debug("开始转换工作流定义为XML, processKey: {}", processKey); - + // 创建BPMN模型 BpmnModel bpmnModel = new BpmnModel(); bpmnModel.setTargetNamespace("http://www.flowable.org/test"); - + bpmnModel.addError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, WorkFlowConstants.WORKFLOW_EXEC_ERROR); + Process process = new Process(); // 确保processKey符合NCName规则 String validProcessKey = processKey.replaceAll("[^a-zA-Z0-9-_.]", "_"); @@ -51,36 +53,36 @@ public class BpmnConverter { // 创建节点ID到规范化ID的映射 Map idMapping = new HashMap<>(); - + // 转换节点 for (WorkflowDefinitionNode node : graph.getNodes()) { log.debug("转换节点: {}, 类型: {}", node.getName(), node.getCode()); - + // 通过NodeTypeEnums获取对应的BpmnTypeEnums中定义的实例类型 @SuppressWarnings("unchecked") Class instanceClass = (Class) NodeTypeEnums.valueOf(node.getCode()) - .getBpmnType() - .getInstance(); - + .getBpmnType() + .getInstance(); + // 创建节点实例 FlowElement element = instanceClass.getDeclaredConstructor().newInstance(); String validId = sanitizeId(node.getId()); idMapping.put(node.getId(), validId); element.setId(validId); element.setName(node.getName()); - + // 设置节点特定属性 configureFlowElement(element, node, process); - + process.addFlowElement(element); } // 转换连线 for (WorkflowDefinitionEdge edge : graph.getEdges()) { log.debug("转换连线: from {} to {}", edge.getFrom(), edge.getTo()); - + SequenceFlow flow = new SequenceFlow(); - flow.setId("flow_" + edge.getId().replaceAll("[^a-zA-Z0-9-_.]", "_")); + flow.setId("FLOW_" + edge.getId().replaceAll("[^a-zA-Z0-9-_.]", "_")); flow.setName(edge.getName()); flow.setSourceRef(idMapping.get(edge.getFrom())); flow.setTargetRef(idMapping.get(edge.getTo())); @@ -94,10 +96,10 @@ public class BpmnConverter { BpmnXMLConverter converter = new BpmnXMLConverter(); byte[] bytes = converter.convertToXML(bpmnModel); String xml = new String(bytes, StandardCharsets.UTF_8); - + log.debug("工作流定义转换完成"); return xml; - + } catch (Exception e) { log.error("转换工作流定义为XML失败", e); throw new RuntimeException("转换工作流定义为XML失败: " + e.getMessage(), e); @@ -105,13 +107,14 @@ public class BpmnConverter { } private String sanitizeId(String id) { - return "node_" + id.replaceAll("[^a-zA-Z0-9-_.]", "_"); + return "NODE_" + id.replaceAll("[^a-zA-Z0-9-_.]", "_"); } /** * 配置流程节点的特定属性 + * * @param element 流程节点元素 - * @param node 工作流节点定义 + * @param node 工作流节点定义 * @param process 当前流程 */ private void configureFlowElement(FlowElement element, WorkflowDefinitionNode node, Process process) { @@ -122,19 +125,19 @@ public class BpmnConverter { serviceTask.setImplementationType("delegateExpression"); serviceTask.setImplementation(delegate); serviceTask.setAsynchronous(true); // 设置为异步执行 - + // 设置失败时不重试 ExtensionElement failedJobRetryTimeCycle = new ExtensionElement(); failedJobRetryTimeCycle.setName("failedJobRetryTimeCycle"); failedJobRetryTimeCycle.setNamespace("http://flowable.org/bpmn"); failedJobRetryTimeCycle.setNamespacePrefix("flowable"); failedJobRetryTimeCycle.setElementText("R0/PT1H"); // 设置为0次重试 - + Map> extensionElements = new HashMap<>(); List retryElements = new ArrayList<>(); retryElements.add(failedJobRetryTimeCycle); extensionElements.put("failedJobRetryTimeCycle", retryElements); - + // 添加字段注入 List fieldExtensions = new ArrayList<>(); node.getConfig().forEach((key, value) -> { @@ -147,7 +150,7 @@ public class BpmnConverter { }); serviceTask.setFieldExtensions(fieldExtensions); serviceTask.setExtensionElements(extensionElements); - + // 添加错误边界事件 addErrorBoundaryEventHandler(process, serviceTask); } @@ -157,14 +160,14 @@ public class BpmnConverter { * 为服务任务添加错误边界事件和错误结束事件 * 当服务任务执行失败时,会触发错误边界事件,并流转到错误结束事件 * - * @param process BPMN流程定义 + * @param process BPMN流程定义 * @param serviceTask 需要添加错误处理的服务任务 */ private void addErrorBoundaryEventHandler(Process process, ServiceTask serviceTask) { BoundaryEvent boundaryEvent = createErrorBoundaryEvent(serviceTask); EndEvent errorEndEvent = createErrorEndEvent(serviceTask); SequenceFlow errorFlow = createErrorSequenceFlow(boundaryEvent, errorEndEvent); - + // 将错误处理相关的元素添加到流程中 process.addFlowElement(boundaryEvent); process.addFlowElement(errorEndEvent); @@ -180,15 +183,16 @@ public class BpmnConverter { private BoundaryEvent createErrorBoundaryEvent(ServiceTask serviceTask) { BoundaryEvent boundaryEvent = new BoundaryEvent(); boundaryEvent.setId(WorkFlowConstants.BOUNDARY_EVENT_ERROR_PREFIX + serviceTask.getId()); + boundaryEvent.setName("边界事件异常"); boundaryEvent.setAttachedToRef(serviceTask); boundaryEvent.setAttachedToRefId(serviceTask.getId()); boundaryEvent.setCancelActivity(true); // 确保取消原有活动 - + // 配置错误事件定义 ErrorEventDefinition errorEventDefinition = new ErrorEventDefinition(); errorEventDefinition.setErrorCode(WorkFlowConstants.WORKFLOW_EXEC_ERROR); boundaryEvent.addEventDefinition(errorEventDefinition); - + return boundaryEvent; } @@ -201,15 +205,15 @@ public class BpmnConverter { private EndEvent createErrorEndEvent(ServiceTask serviceTask) { EndEvent errorEndEvent = new EndEvent(); errorEndEvent.setId(WorkFlowConstants.END_EVENT_ERROR_PREFIX + serviceTask.getId()); - + errorEndEvent.setName("结束事件异常"); // 添加终止定义 -// TerminateEventDefinition terminateEventDefinition = new TerminateEventDefinition(); -// errorEndEvent.addEventDefinition(terminateEventDefinition); + TerminateEventDefinition terminateEventDefinition = new TerminateEventDefinition(); + errorEndEvent.addEventDefinition(terminateEventDefinition); + +// ErrorEventDefinition errorEventDefinition = new ErrorEventDefinition(); +// errorEventDefinition.setErrorCode(WorkFlowConstants.WORKFLOW_EXEC_ERROR); +// errorEndEvent.addEventDefinition(errorEventDefinition); - ErrorEventDefinition errorEventDefinition = new ErrorEventDefinition(); - errorEventDefinition.setErrorCode(WorkFlowConstants.WORKFLOW_EXEC_ERROR); - errorEndEvent.addEventDefinition(errorEventDefinition); - return errorEndEvent; } @@ -223,9 +227,10 @@ public class BpmnConverter { private SequenceFlow createErrorSequenceFlow(BoundaryEvent boundaryEvent, EndEvent errorEndEvent) { SequenceFlow errorFlow = new SequenceFlow(); errorFlow.setId(WorkFlowConstants.SEQUENCE_FLOW_ERROR_PREFIX + boundaryEvent.getAttachedToRefId()); + errorFlow.setName("连接线异常"); errorFlow.setSourceRef(boundaryEvent.getId()); errorFlow.setTargetRef(errorEndEvent.getId()); - + return errorFlow; } } diff --git a/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql b/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql index a0fb6b0e..7f61d246 100644 --- a/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql +++ b/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql @@ -473,7 +473,7 @@ CREATE TABLE workflow_instance process_definition_id VARCHAR(64) NOT NULL COMMENT '流程定义ID', workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID', business_key VARCHAR(64) NULL COMMENT '业务标识', - status VARCHAR(32) NOT NULL COMMENT '实例状态', + status VARCHAR(100) NOT NULL COMMENT '实例状态', variables TEXT NULL COMMENT '流程变量(JSON)', start_time DATETIME(6) NULL COMMENT '开始时间', end_time DATETIME(6) NULL COMMENT '结束时间'