反序列化问题。

This commit is contained in:
dengqichen 2024-12-17 16:49:03 +08:00
parent d298a8770c
commit 45e6d70ca4
22 changed files with 266 additions and 292 deletions

View File

@ -1052,3 +1052,43 @@ eventSource.addEventListener('STDERR', (event) => {
eventSource.onerror = () => { eventSource.onerror = () => {
eventSource.close(); eventSource.close();
}; };
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;

View File

@ -26,7 +26,7 @@
<jjwt.version>0.12.3</jjwt.version> <jjwt.version>0.12.3</jjwt.version>
<springdoc.version>2.2.0</springdoc.version> <springdoc.version>2.2.0</springdoc.version>
<hutool.version>5.8.23</hutool.version> <hutool.version>5.8.23</hutool.version>
<flowable.version>7.0.0</flowable.version> <flowable.version>7.1.0</flowable.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@ -222,6 +222,11 @@
</dependency> </dependency>
<!-- <dependency>--> <!-- <dependency>-->
<!-- <groupId>org.flowable</groupId>--> <!-- <groupId>org.flowable</groupId>-->
<!-- <artifactId>flowable-bpmn-model</artifactId>-->
<!-- <version>${flowable.version}</version> &lt;!&ndash; 使用您项目中的版本 &ndash;&gt;-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.flowable</groupId>-->
<!-- <artifactId>flowable-spring-boot-starter-rest</artifactId>--> <!-- <artifactId>flowable-spring-boot-starter-rest</artifactId>-->
<!-- <version>${flowable.version}</version>--> <!-- <version>${flowable.version}</version>-->
<!-- </dependency>--> <!-- </dependency>-->

View File

@ -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.WorkflowExecutionDTO;
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceStartRequest; 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.WorkflowDefinition;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import com.qqchen.deploy.backend.workflow.query.WorkflowDefinitionQuery; 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.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.variable.api.history.HistoricVariableInstance; import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -56,6 +58,13 @@ public class WorkflowInstanceApiController extends BaseController<WorkflowInstan
return Response.success(); return Response.success();
} }
@Operation(summary = "查询已发布流程模板及其最近实例")
@GetMapping("/templates-with-instances")
public Response<Page<WorkflowTemplateWithInstancesDTO>> getTemplatesWithInstances(WorkflowDefinitionQuery query) {
return Response.success(workflowInstanceService.findTemplatesWithRecentInstances(query));
}
@Override @Override
protected void exportData(HttpServletResponse response, List<WorkflowInstanceDTO> data) { protected void exportData(HttpServletResponse response, List<WorkflowInstanceDTO> data) {

View File

@ -103,7 +103,7 @@ public class WorkflowNodeInstanceApiController extends BaseController<WorkflowNo
emitter.completeWithError(e); emitter.completeWithError(e);
} }
} else { } else {
log.warn("No emitter found for process: {}", processInstanceId); // log.warn("No emitter found for process: {}", processInstanceId);
} }
} }

View File

@ -10,4 +10,5 @@ public interface WorkFlowConstants {
String SEQUENCE_FLOW_ERROR_PREFIX = "SEQUENCE_FLOW_ERROR_"; String SEQUENCE_FLOW_ERROR_PREFIX = "SEQUENCE_FLOW_ERROR_";
String ASYNC_CONTINUATION = "async-continuation";
} }

View File

@ -65,21 +65,21 @@ public class ShellTaskDelegate implements JavaDelegate {
// 如果流程变量中有值优先使用流程变量 // 如果流程变量中有值优先使用流程变量
if (execution.hasVariable("script")) { if (execution.hasVariable("script")) {
scriptValue = (String) execution.getVariable("script"); scriptValue = (String) execution.getVariable("script");
log.info("从流程变量获取到script={}", scriptValue); // log.info("从流程变量获取到script={}", scriptValue);
} }
if (execution.hasVariable("workDir")) { if (execution.hasVariable("workDir")) {
workDirValue = (String) execution.getVariable("workDir"); workDirValue = (String) execution.getVariable("workDir");
log.info("从流程变量获取到workDir={}", workDirValue); // log.info("从流程变量获取到workDir={}", workDirValue);
} }
if (execution.hasVariable("env")) { if (execution.hasVariable("env")) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, String> envFromVar = (Map<String, String>) execution.getVariable("env"); Map<String, String> envFromVar = (Map<String, String>) execution.getVariable("env");
envValue = envFromVar; envValue = envFromVar;
log.info("从流程变量获取到env={}", envValue); // log.info("从流程变量获取到env={}", envValue);
} }
if (scriptValue == null) { if (scriptValue == null) {
log.error("脚本内容为空,执行失败"); // log.error("脚本内容为空,执行失败");
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Script is required but not provided"); 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) { if (exitCode != 0) {
log.error("Shell脚本执行失败退出码: {}", exitCode); log.error("Shell脚本执行失败退出码: {}", exitCode);
log.error("错误输出: {}", finalError); execution.setVariable("errorDetail", "Shell脚本执行失败退出码: " + exitCode);
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Shell脚本执行失败退出码: " + exitCode); throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Shell脚本执行失败退出码: " + exitCode);
// throw new RuntimeException("Shell脚本执行失败退出码: " + exitCode);
} }
log.info("Shell脚本执行成功"); log.info("Shell脚本执行成功");
log.debug("脚本输出: {}", finalOutput); log.debug("脚本输出: {}", finalOutput);
} catch (Exception e) { } catch (Exception e) {
log.error("Shell脚本执行失败", 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) { synchronized (output) {
output.append(line).append("\n"); output.append(line).append("\n");
} }
log.info("Shell output: {}", line); // log.info("Shell output: {}", line);
} else { } else {
StringBuilder error = errorMap.get(processInstanceId); StringBuilder error = errorMap.get(processInstanceId);
synchronized (error) { synchronized (error) {
error.append(line).append("\n"); error.append(line).append("\n");
} }
log.error("Shell error: {}", line); // log.error("Shell error: {}", line);
} }
} }
} catch (IOException e) { } catch (IOException e) {

View File

@ -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;
}

View File

@ -10,6 +10,8 @@ import lombok.Getter;
@Getter @Getter
public enum WorkflowInstanceStatusEnums { public enum WorkflowInstanceStatusEnums {
NOT_STARTED("NOT_STARTED", "未开始"),
/** /**
* 已创建流程实例已创建但还未开始运行 * 已创建流程实例已创建但还未开始运行
*/ */

View File

@ -2,14 +2,20 @@ package com.qqchen.deploy.backend.workflow.listener;
import com.qqchen.deploy.backend.workflow.listener.handler.IFlowableEventHandler; import com.qqchen.deploy.backend.workflow.listener.handler.IFlowableEventHandler;
import lombok.extern.slf4j.Slf4j; 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.FlowableEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener; 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.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import java.util.List; import java.util.List;
import static com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants.ASYNC_CONTINUATION;
@Slf4j @Slf4j
@Component @Component
public class FlowableEventDispatcher implements FlowableEventListener { public class FlowableEventDispatcher implements FlowableEventListener {
@ -18,18 +24,20 @@ public class FlowableEventDispatcher implements FlowableEventListener {
@Lazy @Lazy
private List<IFlowableEventHandler> eventHandlers; private List<IFlowableEventHandler> eventHandlers;
@Resource
@Lazy
private RuntimeService runtimeService;
@Override @Override
public void onEvent(FlowableEvent event) { public void onEvent(FlowableEvent event) {
String eventType = event.getType().name(); 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) { for (IFlowableEventHandler handler : eventHandlers) {
if (handler.canHandle(eventType)) { if (handler.canHandle(eventType)) {
try { try {
handler.handle(event); handler.handle(event);
} catch (Exception e) { } catch (Exception e) {
log.error("Error handling event {} by handler {}: {}", log.error("Error handling event {} by handler {}: {}", eventType, handler.getClass().getSimpleName(), e.getMessage(), e);
eventType, handler.getClass().getSimpleName(), e.getMessage(), e);
} }
} }
} }

View File

@ -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;
}
}
}

View File

@ -3,13 +3,21 @@ package com.qqchen.deploy.backend.workflow.listener.handler;
import com.qqchen.deploy.backend.workflow.enums.WorkflowNodeInstanceStatusEnums; import com.qqchen.deploy.backend.workflow.enums.WorkflowNodeInstanceStatusEnums;
import com.qqchen.deploy.backend.workflow.event.WorkflowNodeInstanceStatusChangeEvent; import com.qqchen.deploy.backend.workflow.event.WorkflowNodeInstanceStatusChangeEvent;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.common.engine.api.delegate.event.FlowableEvent; import org.flowable.common.engine.api.delegate.event.FlowableEvent;
import org.flowable.engine.delegate.event.FlowableActivityEvent; import org.flowable.engine.delegate.event.FlowableActivityEvent;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import java.time.LocalDateTime; 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 @Slf4j
@Component @Component
@ -18,6 +26,7 @@ public class ActivityEventHandler implements IFlowableEventHandler {
@Resource @Resource
private ApplicationEventPublisher publisher; private ApplicationEventPublisher publisher;
@Override @Override
public boolean canHandle(String eventType) { public boolean canHandle(String eventType) {
return eventType.startsWith("ACTIVITY_"); return eventType.startsWith("ACTIVITY_");
@ -26,26 +35,31 @@ public class ActivityEventHandler implements IFlowableEventHandler {
@Override @Override
public void handle(FlowableEvent event) { public void handle(FlowableEvent event) {
String eventType = event.getType().name(); String eventType = event.getType().name();
// log.info("Processing activity event: {}", eventType);
if (!(event instanceof FlowableActivityEvent activityEvent)) { if (!(event instanceof FlowableActivityEvent activityEvent)) {
return; return;
} }
log.info("Processing Flowable event: {}, id: {}, name:{}", eventType, activityEvent.getActivityId(), activityEvent.getActivityName());
List<String> 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); WorkflowNodeInstanceStatusEnums status = convertToNodeStatus(eventType);
if (status != null) { if (status != null) {
// publisher.publishEvent(WorkflowNodeInstanceStatusChangeEvent.builder() publisher.publishEvent(WorkflowNodeInstanceStatusChangeEvent.builder()
// .processInstanceId(activityEvent.getProcessInstanceId()) .processInstanceId(activityEvent.getProcessInstanceId())
// .executionId(activityEvent.getExecutionId()) .executionId(activityEvent.getExecutionId())
// .nodeId(activityEvent.getActivityId()) .nodeId(activityEvent.getActivityId())
// .nodeName(activityEvent.getActivityName()) .nodeName(StringUtils.isEmpty(activityEvent.getActivityName()) ? activityEvent.getActivityId() : activityEvent.getActivityName())
// .nodeType(activityEvent.getActivityType()) .nodeType(activityEvent.getActivityType())
// .status(status) .status(status)
// .startTime(status == WorkflowNodeInstanceStatusEnums.RUNNING ? LocalDateTime.now() : null) .startTime(status == WorkflowNodeInstanceStatusEnums.RUNNING ? LocalDateTime.now() : null)
// .endTime(status.isFinalState() ? LocalDateTime.now() : null) .endTime(status.isFinalState() ? LocalDateTime.now() : null)
// .build() .build()
// ); );
} }
} }

View File

@ -2,11 +2,14 @@ package com.qqchen.deploy.backend.workflow.listener.handler;
import org.flowable.common.engine.api.delegate.event.FlowableEvent; import org.flowable.common.engine.api.delegate.event.FlowableEvent;
import java.util.List;
/** /**
* Flowable事件处理器接口 * Flowable事件处理器接口
*/ */
public interface IFlowableEventHandler { public interface IFlowableEventHandler {
/** /**
* 判断是否可以处理该事件 * 判断是否可以处理该事件
* *

View File

@ -11,7 +11,10 @@ import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
@Slf4j @Slf4j
@Component @Component
@ -28,17 +31,12 @@ public class JobEventHandler implements IFlowableEventHandler {
@Override @Override
public void handle(FlowableEvent event) { public void handle(FlowableEvent event) {
String eventType = event.getType().name(); String eventType = event.getType().name();
// log.info("Processing job event: {}", eventType);
if (!(event instanceof FlowableEngineEntityEvent entityEvent)) { if (!(event instanceof FlowableEngineEntityEvent entityEvent)) {
return; return;
} }
// log.info("Processing job event: {}, jobType: {}", eventType, entityEvent.getType()); // log.info("Processing job event: {}, jobType: {}", eventType, entityEvent.getType());
JobEntityImpl job = (JobEntityImpl) entityEvent.getEntity(); JobEntityImpl job = (JobEntityImpl) entityEvent.getEntity();
WorkflowNodeInstanceStatusEnums status = convertToNodeStatus(eventType); WorkflowNodeInstanceStatusEnums status = convertToNodeStatus(eventType);
if (status != null) { if (status != null) {
// publisher.publishEvent(WorkflowNodeInstanceStatusChangeEvent.builder() // publisher.publishEvent(WorkflowNodeInstanceStatusChangeEvent.builder()
// .processInstanceId(job.getProcessInstanceId()) // .processInstanceId(job.getProcessInstanceId())

View File

@ -14,6 +14,9 @@ import org.springframework.stereotype.Component;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import java.util.Collections;
import java.util.List;
@Slf4j @Slf4j
@Component @Component
public class ProcessEventHandler implements IFlowableEventHandler { public class ProcessEventHandler implements IFlowableEventHandler {
@ -33,8 +36,6 @@ public class ProcessEventHandler implements IFlowableEventHandler {
@Override @Override
public void handle(FlowableEvent event) { public void handle(FlowableEvent event) {
String eventType = event.getType().name(); String eventType = event.getType().name();
log.info("Processing event: {}", eventType);
FlowableProcessEngineEvent processEvent = (FlowableProcessEngineEvent) event; FlowableProcessEngineEvent processEvent = (FlowableProcessEngineEvent) event;
WorkflowInstanceStatusEnums status = convertToWorkflowStatus(eventType); WorkflowInstanceStatusEnums status = convertToWorkflowStatus(eventType);
@ -59,12 +60,12 @@ public class ProcessEventHandler implements IFlowableEventHandler {
case "PROCESS_CREATED" -> WorkflowInstanceStatusEnums.CREATED; case "PROCESS_CREATED" -> WorkflowInstanceStatusEnums.CREATED;
case "PROCESS_STARTED" -> WorkflowInstanceStatusEnums.RUNNING; case "PROCESS_STARTED" -> WorkflowInstanceStatusEnums.RUNNING;
case "PROCESS_COMPLETED" -> WorkflowInstanceStatusEnums.COMPLETED; case "PROCESS_COMPLETED" -> WorkflowInstanceStatusEnums.COMPLETED;
case "PROCESS_COMPLETED_WITH_ERROR_END_EVENT" -> WorkflowInstanceStatusEnums.FAILED; case "PROCESS_CANCELLED" -> WorkflowInstanceStatusEnums.FAILED;
case "PROCESS_CANCELLED" -> WorkflowInstanceStatusEnums.TERMINATED;
case "PROCESS_SUSPENDED" -> WorkflowInstanceStatusEnums.SUSPENDED; case "PROCESS_SUSPENDED" -> WorkflowInstanceStatusEnums.SUSPENDED;
case "PROCESS_RESUMED" -> WorkflowInstanceStatusEnums.RUNNING; // case "PROCESS_RESUMED" -> WorkflowInstanceStatusEnums.RUNNING;
case "PROCESS_COMPLETED_WITH_TERMINATE_END_EVENT" -> WorkflowInstanceStatusEnums.TERMINATED; case "PROCESS_COMPLETED_WITH_TERMINATE_END_EVENT" -> WorkflowInstanceStatusEnums.TERMINATED;
default -> null; default -> null;
}; };
} }
} }

View File

@ -2,8 +2,12 @@ package com.qqchen.deploy.backend.workflow.repository;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; 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 org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional; import java.util.Optional;
/** /**
@ -22,4 +26,6 @@ public interface IWorkflowDefinitionRepository extends IBaseRepository<WorkflowD
boolean existsByKeyAndDeletedFalse(String key); boolean existsByKeyAndDeletedFalse(String key);
Optional<WorkflowDefinition> findByKey(String businessKey); Optional<WorkflowDefinition> findByKey(String businessKey);
Page<WorkflowDefinition> findByStatus(WorkflowStatusEnums workflowStatusEnums, Pageable pageable);
} }

View File

@ -21,4 +21,5 @@ public interface IWorkflowInstanceRepository extends IBaseRepository<WorkflowIns
*/ */
List<WorkflowInstance> findByBusinessKey(String businessKey); List<WorkflowInstance> findByBusinessKey(String businessKey);
List<WorkflowInstance> findTop1ByWorkflowDefinitionIdOrderByCreateTimeDesc(Long workflowDefinitionId);
} }

View File

@ -14,4 +14,6 @@ public interface IWorkflowNodeInstanceRepository extends IBaseRepository<Workflo
List<WorkflowNodeInstance> findByProcessInstanceId(String processInstanceId); List<WorkflowNodeInstance> findByProcessInstanceId(String processInstanceId);
WorkflowNodeInstance findByNodeId(String nodeId); WorkflowNodeInstance findByNodeId(String nodeId);
WorkflowNodeInstance findByProcessInstanceIdAndNodeId(String processInstanceId, String nodeId);
} }

View File

@ -1,13 +1,14 @@
package com.qqchen.deploy.backend.workflow.service; package com.qqchen.deploy.backend.workflow.service;
import com.qqchen.deploy.backend.framework.service.IBaseService; 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.WorkflowInstanceDTO;
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceStartRequest; 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.entity.WorkflowInstance;
import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums; import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums;
import com.qqchen.deploy.backend.workflow.query.WorkflowDefinitionQuery;
import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.data.domain.Page;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Date; import java.util.Date;
@ -58,4 +59,7 @@ public interface IWorkflowInstanceService extends IBaseService<WorkflowInstance,
void completeInstance(String processInstanceId, Map<String, Object> variables); void completeInstance(String processInstanceId, Map<String, Object> variables);
WorkflowInstanceDTO startWorkflow(WorkflowInstanceStartRequest request); WorkflowInstanceDTO startWorkflow(WorkflowInstanceStartRequest request);
Page<WorkflowTemplateWithInstancesDTO> findTemplatesWithRecentInstances(WorkflowDefinitionQuery query);
} }

View File

@ -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.WorkflowDefinitionDTO;
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceStartRequest; 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.WorkflowDefinition;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums; 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.IWorkflowDefinitionRepository;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService; 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.HistoryService;
import org.flowable.engine.RuntimeService; import org.flowable.engine.RuntimeService;
import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance; 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.Backoff;
import org.springframework.retry.annotation.Retryable; import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -30,6 +38,8 @@ import java.time.LocalDateTime;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
@Slf4j @Slf4j
@Service @Service
public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstance, WorkflowInstanceDTO, Long> implements IWorkflowInstanceService { public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstance, WorkflowInstanceDTO, Long> implements IWorkflowInstanceService {
@ -59,7 +69,7 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
workflowInstance.setProcessDefinitionId(processInstance.getProcessDefinitionId()); workflowInstance.setProcessDefinitionId(processInstance.getProcessDefinitionId());
workflowInstance.setWorkflowDefinitionId(workflowDefinitionId); workflowInstance.setWorkflowDefinitionId(workflowDefinitionId);
workflowInstance.setBusinessKey(businessKey); workflowInstance.setBusinessKey(businessKey);
workflowInstance.setStatus(WorkflowInstanceStatusEnums.CREATED); workflowInstance.setStatus(WorkflowInstanceStatusEnums.NOT_STARTED);
workflowInstance.setStartTime(LocalDateTime.now()); workflowInstance.setStartTime(LocalDateTime.now());
workflowInstanceRepository.save(workflowInstance); workflowInstanceRepository.save(workflowInstance);
// 3. 返回创建结果 // 3. 返回创建结果
@ -123,7 +133,7 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
public List<WorkflowInstanceDTO> findByBusinessKey(String businessKey) { public List<WorkflowInstanceDTO> findByBusinessKey(String businessKey) {
return workflowInstanceRepository.findByBusinessKey(businessKey).stream() return workflowInstanceRepository.findByBusinessKey(businessKey).stream()
.map(instance -> getInstanceDetails(instance.getProcessInstanceId())) .map(instance -> getInstanceDetails(instance.getProcessInstanceId()))
.collect(Collectors.toList()); .collect(toList());
} }
@Override @Override
@ -164,6 +174,26 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
} }
} }
@Override
public Page<WorkflowTemplateWithInstancesDTO> findTemplatesWithRecentInstances(WorkflowDefinitionQuery query) {
Pageable pageable = PageRequest.of(query.getPageNum() - 1, query.getPageSize());
Page<WorkflowDefinition> definitionPage = workflowDefinitionRepository.findByStatus(WorkflowStatusEnums.PUBLISHED, pageable);
List<WorkflowTemplateWithInstancesDTO> result = definitionPage.getContent().stream().map(definition -> {
List<WorkflowInstance> workflowInstances = workflowInstanceRepository.findTop1ByWorkflowDefinitionIdOrderByCreateTimeDesc(definition.getId());
WorkflowTemplateWithInstancesDTO workflowTemplateWithInstancesDTO = new WorkflowTemplateWithInstancesDTO();
Optional<WorkflowInstance> 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) { // private WorkflowInstanceDTO.ActivityInstance convertToActivityInstance(HistoricActivityInstance hai) {
// WorkflowInstanceDTO.ActivityInstance ai = new WorkflowInstanceDTO.ActivityInstance(); // WorkflowInstanceDTO.ActivityInstance ai = new WorkflowInstanceDTO.ActivityInstance();
// ai.setId(hai.getId()); // ai.setId(hai.getId());

View File

@ -67,7 +67,7 @@ public class WorkflowNodeInstanceServiceImpl extends BaseServiceImpl<WorkflowNod
WorkflowNodeInstance nodeInstance = workflowNodeInstanceConverter.eventToEntity(event); WorkflowNodeInstance nodeInstance = workflowNodeInstanceConverter.eventToEntity(event);
WorkflowInstance workflowInstance = workflowInstanceRepository.findByProcessInstanceId(nodeInstance.getProcessInstanceId()) WorkflowInstance workflowInstance = workflowInstanceRepository.findByProcessInstanceId(nodeInstance.getProcessInstanceId())
.orElseThrow(() -> new RuntimeException("Node instance not found for processInstanceId: " + nodeInstance.getProcessInstanceId())); .orElseThrow(() -> 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) { if (workflowNodeInstance == null) {
nodeInstance.setWorkflowInstanceId(workflowInstance.getId()); nodeInstance.setWorkflowInstanceId(workflowInstance.getId());
nodeInstance.setWorkflowDefinitionId(workflowInstance.getWorkflowDefinitionId()); nodeInstance.setWorkflowDefinitionId(workflowInstance.getWorkflowDefinitionId());

View File

@ -18,6 +18,7 @@ import java.util.Map;
/** /**
* BPMN 模型转换工具 * BPMN 模型转换工具
*
* @author cascade * @author cascade
* @date 2024-12-11 * @date 2024-12-11
*/ */
@ -40,6 +41,7 @@ public class BpmnConverter {
// 创建BPMN模型 // 创建BPMN模型
BpmnModel bpmnModel = new BpmnModel(); BpmnModel bpmnModel = new BpmnModel();
bpmnModel.setTargetNamespace("http://www.flowable.org/test"); bpmnModel.setTargetNamespace("http://www.flowable.org/test");
bpmnModel.addError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, WorkFlowConstants.WORKFLOW_EXEC_ERROR);
Process process = new Process(); Process process = new Process();
// 确保processKey符合NCName规则 // 确保processKey符合NCName规则
@ -80,7 +82,7 @@ public class BpmnConverter {
log.debug("转换连线: from {} to {}", edge.getFrom(), edge.getTo()); log.debug("转换连线: from {} to {}", edge.getFrom(), edge.getTo());
SequenceFlow flow = new SequenceFlow(); 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.setName(edge.getName());
flow.setSourceRef(idMapping.get(edge.getFrom())); flow.setSourceRef(idMapping.get(edge.getFrom()));
flow.setTargetRef(idMapping.get(edge.getTo())); flow.setTargetRef(idMapping.get(edge.getTo()));
@ -105,11 +107,12 @@ public class BpmnConverter {
} }
private String sanitizeId(String id) { private String sanitizeId(String id) {
return "node_" + id.replaceAll("[^a-zA-Z0-9-_.]", "_"); return "NODE_" + id.replaceAll("[^a-zA-Z0-9-_.]", "_");
} }
/** /**
* 配置流程节点的特定属性 * 配置流程节点的特定属性
*
* @param element 流程节点元素 * @param element 流程节点元素
* @param node 工作流节点定义 * @param node 工作流节点定义
* @param process 当前流程 * @param process 当前流程
@ -180,6 +183,7 @@ public class BpmnConverter {
private BoundaryEvent createErrorBoundaryEvent(ServiceTask serviceTask) { private BoundaryEvent createErrorBoundaryEvent(ServiceTask serviceTask) {
BoundaryEvent boundaryEvent = new BoundaryEvent(); BoundaryEvent boundaryEvent = new BoundaryEvent();
boundaryEvent.setId(WorkFlowConstants.BOUNDARY_EVENT_ERROR_PREFIX + serviceTask.getId()); boundaryEvent.setId(WorkFlowConstants.BOUNDARY_EVENT_ERROR_PREFIX + serviceTask.getId());
boundaryEvent.setName("边界事件异常");
boundaryEvent.setAttachedToRef(serviceTask); boundaryEvent.setAttachedToRef(serviceTask);
boundaryEvent.setAttachedToRefId(serviceTask.getId()); boundaryEvent.setAttachedToRefId(serviceTask.getId());
boundaryEvent.setCancelActivity(true); // 确保取消原有活动 boundaryEvent.setCancelActivity(true); // 确保取消原有活动
@ -201,14 +205,14 @@ public class BpmnConverter {
private EndEvent createErrorEndEvent(ServiceTask serviceTask) { private EndEvent createErrorEndEvent(ServiceTask serviceTask) {
EndEvent errorEndEvent = new EndEvent(); EndEvent errorEndEvent = new EndEvent();
errorEndEvent.setId(WorkFlowConstants.END_EVENT_ERROR_PREFIX + serviceTask.getId()); errorEndEvent.setId(WorkFlowConstants.END_EVENT_ERROR_PREFIX + serviceTask.getId());
errorEndEvent.setName("结束事件异常");
// 添加终止定义 // 添加终止定义
// TerminateEventDefinition terminateEventDefinition = new TerminateEventDefinition(); TerminateEventDefinition terminateEventDefinition = new TerminateEventDefinition();
// errorEndEvent.addEventDefinition(terminateEventDefinition); errorEndEvent.addEventDefinition(terminateEventDefinition);
ErrorEventDefinition errorEventDefinition = new ErrorEventDefinition(); // ErrorEventDefinition errorEventDefinition = new ErrorEventDefinition();
errorEventDefinition.setErrorCode(WorkFlowConstants.WORKFLOW_EXEC_ERROR); // errorEventDefinition.setErrorCode(WorkFlowConstants.WORKFLOW_EXEC_ERROR);
errorEndEvent.addEventDefinition(errorEventDefinition); // errorEndEvent.addEventDefinition(errorEventDefinition);
return errorEndEvent; return errorEndEvent;
} }
@ -223,6 +227,7 @@ public class BpmnConverter {
private SequenceFlow createErrorSequenceFlow(BoundaryEvent boundaryEvent, EndEvent errorEndEvent) { private SequenceFlow createErrorSequenceFlow(BoundaryEvent boundaryEvent, EndEvent errorEndEvent) {
SequenceFlow errorFlow = new SequenceFlow(); SequenceFlow errorFlow = new SequenceFlow();
errorFlow.setId(WorkFlowConstants.SEQUENCE_FLOW_ERROR_PREFIX + boundaryEvent.getAttachedToRefId()); errorFlow.setId(WorkFlowConstants.SEQUENCE_FLOW_ERROR_PREFIX + boundaryEvent.getAttachedToRefId());
errorFlow.setName("连接线异常");
errorFlow.setSourceRef(boundaryEvent.getId()); errorFlow.setSourceRef(boundaryEvent.getId());
errorFlow.setTargetRef(errorEndEvent.getId()); errorFlow.setTargetRef(errorEndEvent.getId());

View File

@ -473,7 +473,7 @@ CREATE TABLE workflow_instance
process_definition_id VARCHAR(64) NOT NULL COMMENT '流程定义ID', process_definition_id VARCHAR(64) NOT NULL COMMENT '流程定义ID',
workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID', workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
business_key VARCHAR(64) NULL COMMENT '业务标识', business_key VARCHAR(64) NULL COMMENT '业务标识',
status VARCHAR(32) NOT NULL COMMENT '实例状态', status VARCHAR(100) NOT NULL COMMENT '实例状态',
variables TEXT NULL COMMENT '流程变量(JSON)', variables TEXT NULL COMMENT '流程变量(JSON)',
start_time DATETIME(6) NULL COMMENT '开始时间', start_time DATETIME(6) NULL COMMENT '开始时间',
end_time DATETIME(6) NULL COMMENT '结束时间' end_time DATETIME(6) NULL COMMENT '结束时间'