From 591abe983ded7cc1d0b693ffbe2effde15febb19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=9A=E8=BE=B0=E5=85=88=E7=94=9F?= Date: Sun, 8 Dec 2024 09:27:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=AF=E6=AD=A3=E5=B8=B8=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=EF=BC=8C=E5=B7=A5=E4=BD=9C=E6=B5=81=E5=8F=AF=E4=BB=A5=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8=E8=BF=90=E8=A1=8C=EF=BC=8C=E4=BD=86=E6=98=AF=E5=8F=AA?= =?UTF-8?q?=E6=9C=89=E4=B8=80=E4=B8=AA=E5=BC=80=E5=A7=8B=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/pom.xml | 5 + .../backend/framework/enums/ResponseCode.java | 13 +- .../api/WorkflowLogApiController.java | 4 +- .../converter/NodeInstanceConverter.java | 6 +- .../WorkflowDefinitionConverter.java | 13 +- .../workflow/dto/WorkflowDefinitionDTO.java | 19 +- .../backend/workflow/dto/WorkflowLogDTO.java | 39 +- .../engine/DefaultWorkflowEngine.java | 112 ++--- .../workflow/engine/WorkflowContext.java | 61 ++- .../context/DefaultWorkflowContext.java | 131 +++--- ...xt.java => WorkflowContextOperations.java} | 17 +- .../engine/executor/EndNodeExecutor.java | 6 +- .../engine/executor/GatewayNodeExecutor.java | 14 +- .../engine/executor/NodeExecutor.java | 12 +- .../engine/executor/StartNodeExecutor.java | 6 +- .../engine/executor/TaskNodeExecutor.java | 18 +- .../executor/task/HttpTaskExecutor.java | 6 +- .../executor/task/JavaTaskExecutor.java | 8 +- .../executor/task/ShellTaskExecutor.java | 6 +- .../engine/executor/task/TaskExecutor.java | 6 +- .../engine/node/AbstractNodeExecutor.java | 110 +++-- .../workflow/engine/node/NodeExecutor.java | 34 -- .../node/executor/ShellNodeExecutor.java | 37 +- .../engine/transition/TransitionExecutor.java | 6 +- .../transition/TransitionRuleEngine.java | 6 +- .../backend/workflow/entity/NodeInstance.java | 12 +- .../workflow/entity/WorkflowDefinition.java | 6 - .../workflow/entity/WorkflowInstance.java | 2 +- .../backend/workflow/entity/WorkflowLog.java | 36 +- .../workflow/entity/WorkflowVariable.java | 9 +- .../monitor/WorkflowContextMonitor.java | 149 +++++++ .../repository/INodeInstanceRepository.java | 31 +- .../repository/IWorkflowLogRepository.java | 9 +- .../IWorkflowVariableRepository.java | 57 ++- .../workflow/service/IWorkflowLogService.java | 38 +- .../service/IWorkflowVariableService.java | 17 +- .../service/WorkflowVariableOperations.java | 52 +++ .../ConcurrentWorkflowVariableOperations.java | 70 +++ .../impl/WorkflowInstanceServiceImpl.java | 6 +- .../service/impl/WorkflowLogServiceImpl.java | 44 +- .../impl/WorkflowVariableServiceImpl.java | 64 +-- backend/src/main/resources/application.yml | 2 +- .../dashboards/workflow-context-monitor.json | 202 +++++++++ .../db/migration/V1.0.0__init_schema.sql | 14 +- .../src/main/resources/messages.properties | 414 +++++++++--------- ...currentWorkflowVariableOperationsTest.java | 117 +++++ 46 files changed, 1393 insertions(+), 653 deletions(-) rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/{WorkflowContext.java => WorkflowContextOperations.java} (64%) delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NodeExecutor.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/monitor/WorkflowContextMonitor.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/service/WorkflowVariableOperations.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/ConcurrentWorkflowVariableOperations.java create mode 100644 backend/src/main/resources/dashboards/workflow-context-monitor.json create mode 100644 backend/src/test/java/com/qqchen/deploy/backend/workflow/service/ConcurrentWorkflowVariableOperationsTest.java diff --git a/backend/pom.xml b/backend/pom.xml index 0c60cf85..c3af167a 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -196,6 +196,11 @@ commons-exec 1.3 + + com.github.ben-manes.caffeine + caffeine + 3.1.8 + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java index c38e88ec..1f96cfa6 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java @@ -137,7 +137,18 @@ public enum ResponseCode { WORKFLOW_NODE_TYPE_DISABLED(2201, "workflow.node.type.disabled"), WORKFLOW_NODE_TYPE_CODE_EXISTS(2202, "workflow.node.type.code.exists"), WORKFLOW_NODE_TYPE_INVALID_CATEGORY(2203, "workflow.node.type.invalid.category"), - WORKFLOW_NODE_TYPE_INVALID_EXECUTOR(2204, "workflow.node.type.invalid.executor"); + WORKFLOW_NODE_TYPE_INVALID_EXECUTOR(2204, "workflow.node.type.invalid.executor"), + WORKFLOW_NODE_EXECUTOR_NOT_FOUND(2205, "workflow.node.executor.not.found"), + + /** + * 工作流变量序列化错误 + */ + WORKFLOW_VARIABLE_SERIALIZE_ERROR(50301, "workflow.variable.serialize.error"), + + /** + * 工作流变量反序列化错误 + */ + WORKFLOW_VARIABLE_DESERIALIZE_ERROR(50302, "workflow.variable.deserialize.error"); private final int code; private final String messageKey; // 国际化消息key diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowLogApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowLogApiController.java index 6a949d37..79d4d6b4 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowLogApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowLogApiController.java @@ -45,14 +45,14 @@ public class WorkflowLogApiController extends BaseController log( + public Response recordLog( @Parameter(description = "工作流实例ID", required = true) @RequestParam Long workflowInstanceId, @Parameter(description = "节点ID") @RequestParam(required = false) String nodeId, @Parameter(description = "日志内容", required = true) @RequestParam String message, @Parameter(description = "日志级别", required = true) @RequestParam LogLevelEnum level, @Parameter(description = "详细信息") @RequestParam(required = false) String detail ) { - workflowLogService.log(workflowInstanceId, nodeId, message, level, detail); + workflowLogService.recordLog(workflowInstanceId, nodeId, message, level, detail); return Response.success(); } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/NodeInstanceConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/NodeInstanceConverter.java index 52dd9f63..243302f7 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/NodeInstanceConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/NodeInstanceConverter.java @@ -13,7 +13,6 @@ import org.mapstruct.Mapping; public interface NodeInstanceConverter extends BaseConverter { @Override - @Mapping(target = "workflowInstanceId", source = "workflowInstanceId") @Mapping(target = "nodeId", source = "nodeId") @Mapping(target = "nodeType", source = "nodeType") @Mapping(target = "name", source = "name") @@ -28,7 +27,7 @@ public interface NodeInstanceConverter extends BaseConverter { - - @Override - @Mapping(target = "nodes", source = "nodes") - WorkflowDefinitionDTO toDto(WorkflowDefinition entity); - - @Override - @Mapping(target = "nodes", source = "nodes") - WorkflowDefinition toEntity(WorkflowDefinitionDTO dto); -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java index fb957235..ab2e537d 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java @@ -7,8 +7,6 @@ import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; -import java.util.List; - /** * 工作流定义DTO */ @@ -36,37 +34,30 @@ public class WorkflowDefinitionDTO extends BaseDTO { /** * 工作流状态 */ - @NotNull(message = "工作流状态不能为空") private WorkflowDefinitionStatusEnum status; /** * 版本号 */ - @NotNull(message = "版本号不能为空") private Integer version; /** - * 节点定义列表 - */ - private List nodes; - - /** - * 节点配置 + * 节点配置(JSON) */ private String nodeConfig; /** - * 流转配置 + * 流转配置(JSON) */ private String transitionConfig; /** - * 表单定义 + * 表单定义(JSON) */ private String formDefinition; /** - * 图形信息 + * 图形信息(JSON) */ private String graphDefinition; @@ -80,4 +71,4 @@ public class WorkflowDefinitionDTO extends BaseDTO { * 备注 */ private String remark; -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowLogDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowLogDTO.java index e28effc3..be4b9454 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowLogDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowLogDTO.java @@ -1,41 +1,46 @@ package com.qqchen.deploy.backend.workflow.dto; -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.framework.dto.BaseDTO; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; +import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import lombok.Data; import lombok.EqualsAndHashCode; +import java.time.LocalDateTime; + +/** + * 工作流日志DTO + */ @Data @EqualsAndHashCode(callSuper = true) public class WorkflowLogDTO extends BaseDTO { - /** - * 工作流实例ID - */ - @NotNull(message = "工作流实例ID不能为空") - private Long workflowInstanceId; - /** * 节点ID */ private String nodeId; - /** - * 日志内容 - */ - @NotBlank(message = "日志内容不能为空") - private String message; - /** * 日志级别 */ - @NotNull(message = "日志级别不能为空") private LogLevelEnum level; + /** + * 日志内容 + */ + private String message; + /** * 详细信息 */ private String detail; -} \ No newline at end of file + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 创建人 + */ + private String createBy; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/DefaultWorkflowEngine.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/DefaultWorkflowEngine.java index 022aae3b..e9414b61 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/DefaultWorkflowEngine.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/DefaultWorkflowEngine.java @@ -2,7 +2,7 @@ package com.qqchen.deploy.backend.workflow.engine; import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.workflow.engine.context.DefaultWorkflowContext; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.engine.executor.NodeExecutor; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; @@ -15,6 +15,7 @@ import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnum; import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository; import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; +import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -41,7 +42,7 @@ public class DefaultWorkflowEngine implements WorkflowEngine { private Map nodeExecutors; @Resource - private DefaultWorkflowContext.Factory workflowContextFactory; + private WorkflowVariableOperations variableOperations; @Override @Transactional @@ -59,19 +60,19 @@ public class DefaultWorkflowEngine implements WorkflowEngine { // 2. 创建工作流实例 WorkflowInstance instance = new WorkflowInstance(); - instance.setDefinition(definition); + instance.setWorkflowDefinition(definition); + instance.setBusinessKey(businessKey); // 设置工作流实例初始状态为 PENDING instance.setStatus(WorkflowInstanceStatusEnum.PENDING); workflowInstanceRepository.save(instance); - // 3. 创建工作流上下文 - WorkflowContext context = workflowContextFactory.create(instance); - if (variables != null) { - variables.forEach((key, value) -> context.setVariable(key, value)); + // 3. 设置工作流变量 + if (variables != null && !variables.isEmpty()) { + variableOperations.setVariables(instance.getId(), variables); } // 4. 创建开始节点实例并启动工作流 - NodeInstance startNode = createStartNode(definition, instance.getId()); + NodeInstance startNode = createStartNode(definition, instance); instance.start(); workflowInstanceRepository.save(instance); executeNode(startNode.getId()); @@ -90,43 +91,33 @@ public class DefaultWorkflowEngine implements WorkflowEngine { throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING); } - try { - // 1. 获取节点执行器 - NodeExecutor executor = nodeExecutors.get(nodeInstance.getNodeType()); - if (executor == null) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_TYPE_NOT_SUPPORTED); - } + // 获取节点执行器 + NodeExecutor executor = nodeExecutors.get(nodeInstance.getNodeType()); + if (executor == null) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTOR_NOT_FOUND); + } - // 2. 执行节点 - WorkflowContext context = workflowContextFactory.create(instance); - executor.execute(nodeInstance, context); + // 创建工作流上下文 + WorkflowContextOperations context = DefaultWorkflowContext.builder() + .workflowInstance(instance) + .variableOperations(variableOperations) + .build(); + + // 执行节点 + executor.execute(nodeInstance, context); - // 3. 更新节点状态 - nodeInstance.setStatus(NodeStatusEnum.COMPLETED); - nodeInstance.setEndTime(LocalDateTime.now()); - nodeInstanceRepository.save(nodeInstance); + // 3. 更新节点状态 + nodeInstance.setStatus(NodeStatusEnum.COMPLETED); + nodeInstance.setEndTime(LocalDateTime.now()); + nodeInstanceRepository.save(nodeInstance); - // 4. 检查是否所有节点都已完成 - List uncompletedNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatusNot( - instance.getId(), NodeStatusEnum.COMPLETED); - - if (uncompletedNodes.isEmpty()) { - instance.complete(); - workflowInstanceRepository.save(instance); - } - - } catch (Exception e) { - // 更新节点状态为失败 - nodeInstance.setStatus(NodeStatusEnum.FAILED); - nodeInstance.setEndTime(LocalDateTime.now()); - nodeInstance.setError(e.getMessage()); - nodeInstanceRepository.save(nodeInstance); - - // 更新工作流实例状态 - instance.updateError(e.getMessage()); + // 4. 检查是否所有节点都已完成 + List uncompletedNodes = nodeInstanceRepository.findByWorkflowInstanceAndStatusNot( + instance, NodeStatusEnum.COMPLETED); + + if (uncompletedNodes.isEmpty()) { + instance.complete(); workflowInstanceRepository.save(instance); - - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); } } @@ -136,15 +127,11 @@ public class DefaultWorkflowEngine implements WorkflowEngine { NodeInstance nodeInstance = nodeInstanceRepository.findById(nodeInstanceId) .orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_NOT_FOUND)); - if (nodeInstance.getStatus() != NodeStatusEnum.RUNNING) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, - "Node is not in running status"); - } - WorkflowInstance instance = nodeInstance.getWorkflowInstance(); - WorkflowContext context = workflowContextFactory.create(instance); - if (variables != null) { - variables.forEach((key, value) -> context.setVariable(key, value)); + + // 设置节点输出变量 + if (variables != null && !variables.isEmpty()) { + variableOperations.setVariables(instance.getId(), variables); } nodeInstance.setStatus(NodeStatusEnum.COMPLETED); @@ -157,23 +144,12 @@ public class DefaultWorkflowEngine implements WorkflowEngine { public void terminateWorkflow(Long instanceId, String reason) { WorkflowInstance instance = workflowInstanceRepository.findById(instanceId) .orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND)); - - // 检查工作流实例状态 - if (!instance.canTerminate()) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING); - } - - // 终止所有运行中的节点 - List runningNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus( - instanceId, NodeStatusEnum.RUNNING); - for (NodeInstance node : runningNodes) { - node.setStatus(NodeStatusEnum.TERMINATED); - node.setEndTime(LocalDateTime.now()); - nodeInstanceRepository.save(node); - } - + instance.terminate(reason); workflowInstanceRepository.save(instance); + + // 清理上下文缓存 + variableOperations.clearVariables(instance.getId()); } @Override @@ -219,8 +195,8 @@ public class DefaultWorkflowEngine implements WorkflowEngine { workflowInstanceRepository.save(instance); // 获取失败的节点 - List failedNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus( - instanceId, NodeStatusEnum.FAILED); + List failedNodes = nodeInstanceRepository.findByWorkflowInstanceAndStatus( + instance, NodeStatusEnum.FAILED); // 重试失败的节点 for (NodeInstance node : failedNodes) { @@ -243,9 +219,9 @@ public class DefaultWorkflowEngine implements WorkflowEngine { } } - private NodeInstance createStartNode(WorkflowDefinition definition, Long instanceId) { + private NodeInstance createStartNode(WorkflowDefinition definition, WorkflowInstance instance) { NodeInstance startNode = new NodeInstance(); - startNode.setWorkflowInstanceId(instanceId); + startNode.setWorkflowInstance(instance); startNode.setNodeId("start"); startNode.setNodeType(NodeTypeEnum.START); startNode.setName("开始节点"); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowContext.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowContext.java index 4d213bc5..766d8681 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowContext.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowContext.java @@ -2,7 +2,9 @@ package com.qqchen.deploy.backend.workflow.engine; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; import lombok.Data; +import org.springframework.beans.factory.annotation.Autowired; import java.util.List; import java.util.Map; @@ -10,14 +12,15 @@ import java.util.concurrent.ConcurrentHashMap; /** * 工作流上下文 + * 负责管理工作流执行过程中的运行时状态 */ @Data public class WorkflowContext { /** - * 工作流实例 + * 工作流实例ID */ - private WorkflowInstance workflowInstance; + private final Long instanceId; /** * 当前节点实例 @@ -30,32 +33,57 @@ public class WorkflowContext { private List allNodes; /** - * 工作流变量 + * 临时变量(节点间传递,不持久化) */ - private Map variables; + private final Map tempVariables; /** - * 临时变量(节点间传递) + * 变量操作服务 */ - private Map tempVariables; + private final WorkflowVariableOperations variableOperations; - public WorkflowContext() { - this.variables = new ConcurrentHashMap<>(); + public WorkflowContext(Long instanceId, WorkflowVariableOperations variableOperations) { + this.instanceId = instanceId; this.tempVariables = new ConcurrentHashMap<>(); + this.variableOperations = variableOperations; } /** - * 获取变量值 + * 获取变量值(委托给 WorkflowVariableOperations) + * @deprecated 建议直接使用 WorkflowVariableOperations */ + @Deprecated public Object getVariable(String key) { - return variables.get(key); + return variableOperations.getVariables(instanceId).get(key); } /** - * 设置变量值 + * 设置变量值(委托给 WorkflowVariableOperations) + * @deprecated 建议直接使用 WorkflowVariableOperations */ + @Deprecated public void setVariable(String key, Object value) { - variables.put(key, value); + Map vars = new ConcurrentHashMap<>(); + vars.put(key, value); + variableOperations.setVariables(instanceId, vars); + } + + /** + * 获取所有变量(委托给 WorkflowVariableOperations) + * @deprecated 建议直接使用 WorkflowVariableOperations + */ + @Deprecated + public Map getVariables() { + return variableOperations.getVariables(instanceId); + } + + /** + * 设置多个变量(委托给 WorkflowVariableOperations) + * @deprecated 建议直接使用 WorkflowVariableOperations + */ + @Deprecated + public void setVariables(Map variables) { + variableOperations.setVariables(instanceId, variables); } /** @@ -72,10 +100,17 @@ public class WorkflowContext { tempVariables.put(key, value); } + /** + * 获取所有临时变量 + */ + public Map getTempVariables() { + return new ConcurrentHashMap<>(tempVariables); + } + /** * 清除临时变量 */ public void clearTempVariables() { tempVariables.clear(); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/DefaultWorkflowContext.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/DefaultWorkflowContext.java index c986c0ce..91fa8495 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/DefaultWorkflowContext.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/DefaultWorkflowContext.java @@ -2,73 +2,96 @@ package com.qqchen.deploy.backend.workflow.engine.context; import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService; -import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService; +import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; import lombok.Getter; -import org.springframework.stereotype.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.util.HashMap; import java.util.Map; -public class DefaultWorkflowContext implements WorkflowContext { - +/** + * 默认工作流上下文实现 + */ +public class DefaultWorkflowContext implements WorkflowContextOperations { + + private static final Logger logger = LoggerFactory.getLogger(DefaultWorkflowContext.class); + @Getter - private final WorkflowInstance instance; - - private final Map variables; - - private final IWorkflowVariableService variableService; - - private final IWorkflowLogService logService; - - private DefaultWorkflowContext(WorkflowInstance instance, IWorkflowVariableService variableService, IWorkflowLogService logService) { - this.instance = instance; - this.variables = new HashMap<>(); - this.variableService = variableService; - this.logService = logService; + private final WorkflowInstance workflowInstance; + + private final WorkflowVariableOperations variableOperations; + + public DefaultWorkflowContext(WorkflowInstance workflowInstance, WorkflowVariableOperations variableOperations) { + this.workflowInstance = workflowInstance; + this.variableOperations = variableOperations; } - + + @Override + public WorkflowInstance getInstance() { + return workflowInstance; + } + + @Override + public Object getVariable(String key) { + return variableOperations.getVariable(workflowInstance.getId(), key); + } + + @Override + public void setVariable(String key, Object value) { + variableOperations.setVariable(workflowInstance.getId(), key, value); + } + + @Override + public void setVariables(Map variables) { + variableOperations.setVariables(workflowInstance.getId(), variables); + } + @Override public Map getVariables() { - return new HashMap<>(variables); + return variableOperations.getVariables(workflowInstance.getId()); } - - @Override - public Object getVariable(String name) { - return variables.get(name); - } - - @Override - public void setVariable(String name, Object value) { - variables.put(name, value); - variableService.setVariable(instance.getId(), name, value); - } - + @Override public void log(String message, LogLevelEnum level) { - log(message, null, level); + switch (level) { + case DEBUG -> logger.debug("[Workflow:{}] {}", workflowInstance.getId(), message); + case INFO -> logger.info("[Workflow:{}] {}", workflowInstance.getId(), message); + case WARN -> logger.warn("[Workflow:{}] {}", workflowInstance.getId(), message); + case ERROR -> logger.error("[Workflow:{}] {}", workflowInstance.getId(), message); + } } - + @Override public void log(String message, String detail, LogLevelEnum level) { - logService.log(instance.getId(), null, message, level, detail); - } - - /** - * 工作流上下文工厂类 - */ - @Component - public static class Factory { - private final IWorkflowVariableService variableService; - private final IWorkflowLogService logService; - - public Factory(IWorkflowVariableService variableService, IWorkflowLogService logService) { - this.variableService = variableService; - this.logService = logService; - } - - public DefaultWorkflowContext create(WorkflowInstance instance) { - return new DefaultWorkflowContext(instance, variableService, logService); + switch (level) { + case DEBUG -> logger.debug("[Workflow:{}] {} - Detail: {}", workflowInstance.getId(), message, detail); + case INFO -> logger.info("[Workflow:{}] {} - Detail: {}", workflowInstance.getId(), message, detail); + case WARN -> logger.warn("[Workflow:{}] {} - Detail: {}", workflowInstance.getId(), message, detail); + case ERROR -> logger.error("[Workflow:{}] {} - Detail: {}", workflowInstance.getId(), message, detail); } } -} \ No newline at end of file + + public static class Builder { + private WorkflowInstance workflowInstance; + + private WorkflowVariableOperations variableOperations; + + public Builder workflowInstance(WorkflowInstance workflowInstance) { + this.workflowInstance = workflowInstance; + return this; + } + + public Builder variableOperations(WorkflowVariableOperations variableOperations) { + this.variableOperations = variableOperations; + return this; + } + + public DefaultWorkflowContext build() { + return new DefaultWorkflowContext(workflowInstance, variableOperations); + } + } + + public static Builder builder() { + return new Builder(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/WorkflowContext.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/WorkflowContextOperations.java similarity index 64% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/WorkflowContext.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/WorkflowContextOperations.java index 0530d774..3e7232ea 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/WorkflowContext.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/WorkflowContextOperations.java @@ -6,28 +6,37 @@ import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import java.util.Map; /** - * 工作流上下文 + * 工作流上下文操作接口 + * 定义工作流执行过程中的上下文操作能力 */ -public interface WorkflowContext { +public interface WorkflowContextOperations { /** * 获取工作流实例 */ WorkflowInstance getInstance(); - + + void setVariables(Map variables); + /** * 获取所有变量 + * @deprecated 使用 WorkflowVariableOperations 替代 */ + @Deprecated Map getVariables(); /** * 获取变量 + * @deprecated 使用 WorkflowVariableOperations 替代 */ + @Deprecated Object getVariable(String name); /** * 设置变量 + * @deprecated 使用 WorkflowVariableOperations 替代 */ + @Deprecated void setVariable(String name, Object value); /** @@ -39,4 +48,4 @@ public interface WorkflowContext { * 记录日志(带详情) */ void log(String message, String detail, LogLevelEnum level); -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/EndNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/EndNodeExecutor.java index 6b09a340..ec6f63bc 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/EndNodeExecutor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/EndNodeExecutor.java @@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor; import com.fasterxml.jackson.databind.ObjectMapper; import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; @@ -34,7 +34,7 @@ public class EndNodeExecutor implements NodeExecutor { } @Override - public void execute(NodeInstance nodeInstance, WorkflowContext context) { + public void execute(NodeInstance nodeInstance, WorkflowContextOperations context) { // 1. 完成结束节点 nodeInstance.setStatus(NodeStatusEnum.COMPLETED); nodeInstance.setEndTime(LocalDateTime.now()); @@ -59,7 +59,7 @@ public class EndNodeExecutor implements NodeExecutor { } @Override - public void terminate(NodeInstance nodeInstance, WorkflowContext context) { + public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { // 结束节点无需终止操作 } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayNodeExecutor.java index 541b2462..ac8d4bec 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayNodeExecutor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayNodeExecutor.java @@ -2,7 +2,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor; import com.fasterxml.jackson.databind.ObjectMapper; import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; @@ -31,7 +31,7 @@ public class GatewayNodeExecutor implements NodeExecutor { } @Override - public void execute(NodeInstance nodeInstance, WorkflowContext context) { + public void execute(NodeInstance nodeInstance, WorkflowContextOperations context) { try { GatewayConfig config = objectMapper.readValue(nodeInstance.getConfig(), GatewayConfig.class); @@ -57,7 +57,7 @@ public class GatewayNodeExecutor implements NodeExecutor { } } - private void handleExclusiveGateway(NodeInstance nodeInstance, WorkflowContext context, GatewayConfig config) { + private void handleExclusiveGateway(NodeInstance nodeInstance, WorkflowContextOperations context, GatewayConfig config) { try { for (GatewayCondition condition : config.getConditions()) { if (evaluateCondition(condition.getExpression(), context)) { @@ -73,7 +73,7 @@ public class GatewayNodeExecutor implements NodeExecutor { } } - private void handleParallelGateway(NodeInstance nodeInstance, WorkflowContext context, GatewayConfig config) { + private void handleParallelGateway(NodeInstance nodeInstance, WorkflowContextOperations context, GatewayConfig config) { try { List nextNodeIds = new ArrayList<>(); for (GatewayCondition condition : config.getConditions()) { @@ -87,7 +87,7 @@ public class GatewayNodeExecutor implements NodeExecutor { } } - private void handleInclusiveGateway(NodeInstance nodeInstance, WorkflowContext context, GatewayConfig config) { + private void handleInclusiveGateway(NodeInstance nodeInstance, WorkflowContextOperations context, GatewayConfig config) { try { List nextNodeIds = new ArrayList<>(); for (GatewayCondition condition : config.getConditions()) { @@ -106,7 +106,7 @@ public class GatewayNodeExecutor implements NodeExecutor { } } - private boolean evaluateCondition(String expression, WorkflowContext context) { + private boolean evaluateCondition(String expression, WorkflowContextOperations context) { try { StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); evaluationContext.setVariables(context.getVariables()); @@ -140,7 +140,7 @@ public class GatewayNodeExecutor implements NodeExecutor { } @Override - public void terminate(NodeInstance nodeInstance, WorkflowContext context) { + public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { // Gateway nodes are instant operations, no need to terminate } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/NodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/NodeExecutor.java index ef062058..b769fc67 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/NodeExecutor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/NodeExecutor.java @@ -1,6 +1,6 @@ package com.qqchen.deploy.backend.workflow.engine.executor; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; @@ -16,8 +16,11 @@ public interface NodeExecutor { /** * 执行节点 + * + * @param nodeInstance 节点实例 + * @param context 工作流上下文 */ - void execute(NodeInstance nodeInstance, WorkflowContext context); + void execute(NodeInstance nodeInstance, WorkflowContextOperations context); /** * 验证节点配置 @@ -26,6 +29,9 @@ public interface NodeExecutor { /** * 终止节点执行 + * + * @param nodeInstance 节点实例 + * @param context 工作流上下文 */ - void terminate(NodeInstance nodeInstance, WorkflowContext context); + void terminate(NodeInstance nodeInstance, WorkflowContextOperations context); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/StartNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/StartNodeExecutor.java index 9a262d0b..c131da70 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/StartNodeExecutor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/StartNodeExecutor.java @@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor; import com.fasterxml.jackson.databind.ObjectMapper; import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; @@ -26,7 +26,7 @@ public class StartNodeExecutor implements NodeExecutor { } @Override - public void execute(NodeInstance nodeInstance, WorkflowContext context) { + public void execute(NodeInstance nodeInstance, WorkflowContextOperations context) { // 开始节点直接完成 nodeInstance.setStatus(NodeStatusEnum.COMPLETED); context.log("开始节点执行完成", LogLevelEnum.INFO); @@ -43,7 +43,7 @@ public class StartNodeExecutor implements NodeExecutor { } @Override - public void terminate(NodeInstance nodeInstance, WorkflowContext context) { + public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { // 开始节点无需终止操作 } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskNodeExecutor.java index c893286e..c43e0796 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskNodeExecutor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskNodeExecutor.java @@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor; import com.fasterxml.jackson.databind.ObjectMapper; import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; @@ -25,7 +25,7 @@ public class TaskNodeExecutor implements NodeExecutor { } @Override - public void execute(NodeInstance nodeInstance, WorkflowContext context) { + public void execute(NodeInstance nodeInstance, WorkflowContextOperations context) { try { // 1. 解析任务配置 TaskConfig config = parseConfig(nodeInstance.getConfig()); @@ -57,7 +57,7 @@ public class TaskNodeExecutor implements NodeExecutor { } @Override - public void terminate(NodeInstance nodeInstance, WorkflowContext context) { + public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { // 终止任务执行 TaskConfig config = parseConfig(nodeInstance.getConfig()); terminateTask(config, nodeInstance, context); @@ -71,7 +71,7 @@ public class TaskNodeExecutor implements NodeExecutor { } } - private void executeTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { + private void executeTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) { switch (config.getType()) { case HTTP: executeHttpTask(config, nodeInstance, context); @@ -84,7 +84,7 @@ public class TaskNodeExecutor implements NodeExecutor { } } - private void terminateTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { + private void terminateTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) { // 根据任务类型执行终止操作 switch (config.getType()) { case HTTP: @@ -96,19 +96,19 @@ public class TaskNodeExecutor implements NodeExecutor { } } - private void executeHttpTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { + private void executeHttpTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) { // TODO: 实现HTTP请求执行 } - private void executeJavaTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { + private void executeJavaTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) { // TODO: 实现Java方法调用 } - private void terminateHttpTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { + private void terminateHttpTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) { // TODO: 实现HTTP请求终止 } - private void terminateJavaTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { + private void terminateJavaTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) { // TODO: 实现Java方法终止 } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/HttpTaskExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/HttpTaskExecutor.java index 1902abb5..4b3eb611 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/HttpTaskExecutor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/HttpTaskExecutor.java @@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor.task; import com.fasterxml.jackson.databind.ObjectMapper; import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import lombok.extern.slf4j.Slf4j; @@ -21,7 +21,7 @@ public class HttpTaskExecutor implements TaskExecutor { private final RestTemplate restTemplate = new RestTemplate(); @Override - public void execute(NodeInstance nodeInstance, WorkflowContext context, Map parameters) { + public void execute(NodeInstance nodeInstance, WorkflowContextOperations context, Map parameters) { String url = (String) parameters.get("url"); HttpMethod method = HttpMethod.valueOf((String) parameters.getOrDefault("method", "GET")); Object body = parameters.get("body"); @@ -55,7 +55,7 @@ public class HttpTaskExecutor implements TaskExecutor { } @Override - public void terminate(NodeInstance nodeInstance, WorkflowContext context) { + public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { // HTTP请求无需终止操作 } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/JavaTaskExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/JavaTaskExecutor.java index 48552890..b9b69392 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/JavaTaskExecutor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/JavaTaskExecutor.java @@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor.task; import com.fasterxml.jackson.databind.ObjectMapper; import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import lombok.extern.slf4j.Slf4j; @@ -24,14 +24,14 @@ public class JavaTaskExecutor implements TaskExecutor { private final ObjectMapper objectMapper = new ObjectMapper(); @Override - public void execute(NodeInstance nodeInstance, WorkflowContext context, Map parameters) { + public void execute(NodeInstance nodeInstance, WorkflowContextOperations context, Map parameters) { String className = parameters.get("className").toString(); String methodName = parameters.get("methodName").toString(); try { Class clazz = Class.forName(className); Object instance = applicationContext.getBean(clazz); - Method method = clazz.getMethod(methodName, NodeInstance.class, WorkflowContext.class, Map.class); + Method method = clazz.getMethod(methodName, NodeInstance.class, WorkflowContextOperations.class, Map.class); method.invoke(instance, nodeInstance, context, parameters); } catch (ClassNotFoundException e) { throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Class not found: " + className); @@ -43,7 +43,7 @@ public class JavaTaskExecutor implements TaskExecutor { } @Override - public void terminate(NodeInstance nodeInstance, WorkflowContext context) { + public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { // Java任务无法中断,记录日志 context.log("Java task cannot be terminated: " + nodeInstance.getNodeId(), LogLevelEnum.WARN); } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/ShellTaskExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/ShellTaskExecutor.java index 36231e1c..96ee25f8 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/ShellTaskExecutor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/ShellTaskExecutor.java @@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor.task; import com.fasterxml.jackson.databind.ObjectMapper; import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import lombok.extern.slf4j.Slf4j; @@ -21,7 +21,7 @@ public class ShellTaskExecutor implements TaskExecutor { private final ObjectMapper objectMapper = new ObjectMapper(); @Override - public void execute(NodeInstance nodeInstance, WorkflowContext context, Map parameters) { + public void execute(NodeInstance nodeInstance, WorkflowContextOperations context, Map parameters) { String command = (String) parameters.get("command"); Integer timeout = (Integer) parameters.getOrDefault("timeout", 300); @@ -60,7 +60,7 @@ public class ShellTaskExecutor implements TaskExecutor { } @Override - public void terminate(NodeInstance nodeInstance, WorkflowContext context) { + public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { // TODO: 实现Shell命令终止逻辑 } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/TaskExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/TaskExecutor.java index fd7c6df6..8805a9d4 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/TaskExecutor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/TaskExecutor.java @@ -1,6 +1,6 @@ package com.qqchen.deploy.backend.workflow.engine.executor.task; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import java.util.Map; @@ -13,10 +13,10 @@ public interface TaskExecutor { /** * 执行任务 */ - void execute(NodeInstance nodeInstance, WorkflowContext context, Map parameters); + void execute(NodeInstance nodeInstance, WorkflowContextOperations context, Map parameters); /** * 终止任务 */ - void terminate(NodeInstance nodeInstance, WorkflowContext context); + void terminate(NodeInstance nodeInstance, WorkflowContextOperations context); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/AbstractNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/AbstractNodeExecutor.java index 93325a2d..cc3fa813 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/AbstractNodeExecutor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/AbstractNodeExecutor.java @@ -1,88 +1,134 @@ package com.qqchen.deploy.backend.workflow.engine.node; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; +import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.engine.executor.NodeExecutor; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; +import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository; import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService; +import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import java.time.LocalDateTime; + /** * 抽象节点执行器 */ @Slf4j public abstract class AbstractNodeExecutor implements NodeExecutor { + @Resource + protected WorkflowVariableOperations variableOperations; + + @Resource + protected INodeInstanceRepository nodeInstanceRepository; + @Resource protected IWorkflowLogService workflowLogService; @Override - public void execute(NodeInstance nodeInstance, WorkflowContext context) { + public void execute(NodeInstance nodeInstance, WorkflowContextOperations context) { try { - // 1. 记录系统日志 - logSystem(context.getInstance(), LogLevelEnum.INFO, - String.format("开始执行节点: %s[%s]", nodeInstance.getName(), nodeInstance.getNodeId()), - null); + // 1. 前置处理 + beforeExecute(nodeInstance, context); - // 2. 记录节点开始日志 - logNodeStart(nodeInstance); - - // 3. 执行节点 + // 2. 执行节点逻辑 doExecute(nodeInstance, context); - // 4. 记录节点完成日志 - logNodeComplete(nodeInstance); + // 3. 后置处理 + afterExecute(nodeInstance, context); + + // 4. 更新节点状态 + updateNodeStatus(nodeInstance, true); } catch (Exception e) { - // 5. 记录错误日志 - logSystem(context.getInstance(), LogLevelEnum.ERROR, - String.format("节点执行失败: %s[%s]", nodeInstance.getName(), nodeInstance.getNodeId()), - e.getMessage()); - logNodeError(nodeInstance, e.getMessage()); - throw e; + // 5. 异常处理 + handleExecutionError(nodeInstance, context, e); + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); } } /** - * 执行节点 + * 执行节点逻辑 */ - protected abstract void doExecute(NodeInstance nodeInstance, WorkflowContext context); + protected abstract void doExecute(NodeInstance nodeInstance, WorkflowContextOperations context); + + /** + * 前置处理 + */ + protected void beforeExecute(NodeInstance nodeInstance, WorkflowContextOperations context) { + context.log("Starting node execution: " + nodeInstance.getName(), LogLevelEnum.INFO); + logNodeStart(nodeInstance); + } + + /** + * 后置处理 + */ + protected void afterExecute(NodeInstance nodeInstance, WorkflowContextOperations context) { + context.log("Node execution completed: " + nodeInstance.getName(), LogLevelEnum.INFO); + logNodeComplete(nodeInstance); + } + + /** + * 处理执行异常 + */ + protected void handleExecutionError(NodeInstance nodeInstance, WorkflowContextOperations context, Exception e) { + log.error("Node execution failed. nodeInstance: {}, error: {}", nodeInstance.getId(), e.getMessage(), e); + context.log("Node execution failed: " + e.getMessage(), LogLevelEnum.ERROR); + logSystem(nodeInstance, LogLevelEnum.ERROR, + String.format("节点执行失败: %s[%s]", nodeInstance.getName(), nodeInstance.getNodeId()), + e.getMessage()); + logNodeError(nodeInstance, e.getMessage()); + updateNodeStatus(nodeInstance, false); + } + + /** + * 更新节点状态 + */ + private void updateNodeStatus(NodeInstance nodeInstance, boolean success) { + nodeInstance.setStatus(success ? NodeStatusEnum.COMPLETED : NodeStatusEnum.FAILED); + if (success) { + nodeInstance.setEndTime(LocalDateTime.now()); + } + nodeInstanceRepository.save(nodeInstance); + } /** * 记录系统日志 */ - protected void logSystem(WorkflowInstance instance, LogLevelEnum level, String message, String detail) { - workflowLogService.log(instance.getId(), null, message, level, detail); + protected void logSystem(NodeInstance nodeInstance, LogLevelEnum level, String message, String detail) { + workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(), level, message, detail); } /** * 记录节点开始日志 */ protected void logNodeStart(NodeInstance nodeInstance) { - workflowLogService.log(nodeInstance.getWorkflowInstanceId(), nodeInstance.getNodeId(), - String.format("节点开始执行: %s", nodeInstance.getName()), LogLevelEnum.INFO, null); + workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(), + LogLevelEnum.INFO, String.format("节点开始执行: %s", nodeInstance.getName()), null); nodeInstance.setStatus(NodeStatusEnum.RUNNING); + nodeInstance.setStartTime(LocalDateTime.now()); + nodeInstanceRepository.save(nodeInstance); } /** * 记录节点完成日志 */ protected void logNodeComplete(NodeInstance nodeInstance) { - workflowLogService.log(nodeInstance.getWorkflowInstanceId(), nodeInstance.getNodeId(), - String.format("节点执行完成: %s", nodeInstance.getName()), LogLevelEnum.INFO, null); - nodeInstance.setStatus(NodeStatusEnum.COMPLETED); + workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(), + LogLevelEnum.INFO, String.format("节点执行完成: %s", nodeInstance.getName()), null); } /** * 记录节点错误日志 */ protected void logNodeError(NodeInstance nodeInstance, String error) { - workflowLogService.log(nodeInstance.getWorkflowInstanceId(), nodeInstance.getNodeId(), - String.format("节点执行失败: %s", nodeInstance.getName()), LogLevelEnum.ERROR, error); - nodeInstance.setStatus(NodeStatusEnum.FAILED); + workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(), + LogLevelEnum.ERROR, String.format("节点执行失败: %s", nodeInstance.getName()), error); nodeInstance.setError(error); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NodeExecutor.java deleted file mode 100644 index 5d51e4c6..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/NodeExecutor.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.node; - -import com.qqchen.deploy.backend.workflow.engine.WorkflowContext; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; - -/** - * 节点执行器接口 - */ -public interface NodeExecutor { - - /** - * 执行节点 - * - * @param nodeInstance 节点实例 - * @param context 工作流上下文 - * @return 执行结果 - */ - boolean execute(NodeInstance nodeInstance, WorkflowContext context); - - /** - * 取消节点执行 - * - * @param nodeInstance 节点实例 - * @param context 工作流上下文 - */ - void cancel(NodeInstance nodeInstance, WorkflowContext context); - - /** - * 获取支持的节点类型 - * - * @return 节点类型 - */ - NodeType getNodeType(); -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/executor/ShellNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/executor/ShellNodeExecutor.java index 3f20d868..c8109ecc 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/executor/ShellNodeExecutor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/executor/ShellNodeExecutor.java @@ -1,13 +1,14 @@ package com.qqchen.deploy.backend.workflow.engine.node.executor; import com.fasterxml.jackson.databind.ObjectMapper; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; -import com.qqchen.deploy.backend.workflow.engine.node.AbstractNodeExecutor; -import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; +import com.qqchen.deploy.backend.workflow.engine.node.AbstractNodeExecutor; +import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; +import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -16,6 +17,7 @@ import jakarta.annotation.Resource; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.*; @@ -27,6 +29,9 @@ public class ShellNodeExecutor extends AbstractNodeExecutor { @Resource private ObjectMapper objectMapper; + @Resource + private WorkflowVariableOperations variableOperations; + private final ExecutorService executorService = Executors.newCachedThreadPool(); @Override @@ -62,7 +67,7 @@ public class ShellNodeExecutor extends AbstractNodeExecutor { } @Override - protected void doExecute(NodeInstance nodeInstance, WorkflowContext context) { + protected void doExecute(NodeInstance nodeInstance, WorkflowContextOperations context) { try { String configJson = nodeInstance.getConfig(); ShellConfig config = objectMapper.readValue(configJson, ShellConfig.class); @@ -85,9 +90,7 @@ public class ShellNodeExecutor extends AbstractNodeExecutor { } catch (Exception e) { lastException = e; if (attempt < maxAttempts) { - // 记录重试日志 - context.log(String.format("Shell execution failed (attempt %d/%d), retrying in %d seconds", - attempt, maxAttempts, retryInterval), LogLevelEnum.WARN); + context.log(String.format("Shell execution failed (attempt %d/%d), retrying in %d seconds", attempt, maxAttempts, retryInterval), LogLevelEnum.WARN); Thread.sleep(retryInterval * 1000L); } } @@ -103,7 +106,7 @@ public class ShellNodeExecutor extends AbstractNodeExecutor { } } - private void executeShellCommand(ShellConfig config, NodeInstance nodeInstance, WorkflowContext context) throws Exception { + private void executeShellCommand(ShellConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) throws Exception { ProcessBuilder processBuilder = new ProcessBuilder(); processBuilder.command("sh", "-c", config.getScript()); @@ -149,12 +152,12 @@ public class ShellNodeExecutor extends AbstractNodeExecutor { exitCode, String.join("\n", error))); } - // 设置输出结果 - nodeInstance.setOutput(String.join("\n", output)); - if (!error.isEmpty()) { - nodeInstance.setError(String.join("\n", error)); - } - + // 设置输出变量 + Map outputVariables = new HashMap<>(); + outputVariables.put("shellOutput", String.join("\n", output)); + outputVariables.put("exitCode", exitCode); + variableOperations.setVariables(nodeInstance.getWorkflowInstance().getId(), outputVariables); + // 记录执行日志 context.log(String.format("Shell script executed successfully with exit code: %d", exitCode), LogLevelEnum.INFO); } @@ -171,7 +174,7 @@ public class ShellNodeExecutor extends AbstractNodeExecutor { } @Override - public void terminate(NodeInstance nodeInstance, WorkflowContext context) { + public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { // TODO: 实现终止Shell进程的逻辑 context.log("Shell node termination is not implemented yet", LogLevelEnum.WARN); } @@ -187,4 +190,4 @@ public class ShellNodeExecutor extends AbstractNodeExecutor { private Map environment; // 环境变量 private Integer successExitCode; // 成功退出码 } -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionExecutor.java index 5d9196b7..67c802c1 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionExecutor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionExecutor.java @@ -2,7 +2,7 @@ package com.qqchen.deploy.backend.workflow.engine.transition; import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.entity.NodeDefinition; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; @@ -33,7 +33,7 @@ public class TransitionExecutor { /** * 执行节点流转 */ - public void executeTransition(NodeInstance currentNode, WorkflowDefinition definition, WorkflowContext context) { + public void executeTransition(NodeInstance currentNode, WorkflowDefinition definition, WorkflowContextOperations context) { // 1. 获取下一个节点ID列表 List nextNodeIds = transitionRuleEngine.getNextNodeIds(currentNode, definition, context); @@ -46,7 +46,7 @@ public class TransitionExecutor { } NodeInstance nextNode = new NodeInstance(); - nextNode.setWorkflowInstanceId(currentNode.getWorkflowInstanceId()); + nextNode.setWorkflowInstance(currentNode.getWorkflowInstance()); nextNode.setNodeId(nodeId); nextNode.setNodeType(nextNodeDef.getType()); nextNode.setName(nextNodeDef.getName()); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRuleEngine.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRuleEngine.java index 1df4624f..2a5ee74f 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRuleEngine.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRuleEngine.java @@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.engine.transition; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; @@ -28,7 +28,7 @@ public class TransitionRuleEngine { /** * 获取下一个节点ID列表 */ - public List getNextNodeIds(NodeInstance currentNode, WorkflowDefinition definition, WorkflowContext context) { + public List getNextNodeIds(NodeInstance currentNode, WorkflowDefinition definition, WorkflowContextOperations context) { try { // 解析流转规则 List rules = parseTransitionRules(definition.getTransitionConfig()); @@ -62,7 +62,7 @@ public class TransitionRuleEngine { } } - private boolean evaluateCondition(String condition, WorkflowContext context) { + private boolean evaluateCondition(String condition, WorkflowContextOperations context) { if (condition == null || condition.trim().isEmpty()) { return true; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeInstance.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeInstance.java index 41794d77..9098a3c0 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeInstance.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeInstance.java @@ -17,17 +17,11 @@ import java.time.LocalDateTime; @LogicDelete public class NodeInstance extends Entity { - /** - * 工作流实例ID,用于优化查询 - */ - @Column(name = "workflow_instance_id", nullable = false) - private Long workflowInstanceId; - /** * 工作流实例 */ @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "workflow_instance_id", insertable = false, updatable = false) + @JoinColumn(name = "workflow_instance_id", nullable = false) private WorkflowInstance workflowInstance; /** @@ -99,10 +93,6 @@ public class NodeInstance extends Entity { return config; } - public Long getWorkflowInstanceId() { - return workflowInstanceId; - } - public String getPreNodeId() { return preNodeId; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java index 441f9047..cd9dd771 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java @@ -75,12 +75,6 @@ public class WorkflowDefinition extends Entity { @Column(name = "graph_definition", columnDefinition = "TEXT") private String graphDefinition; - /** - * 节点定义列表 - */ - @OneToMany(mappedBy = "workflowDefinition", cascade = CascadeType.ALL, orphanRemoval = true) - private List nodes = new ArrayList<>(); - /** * 版本号 */ diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowInstance.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowInstance.java index 3abe893d..34a5afc9 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowInstance.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowInstance.java @@ -40,7 +40,7 @@ public class WorkflowInstance extends Entity { /** * 业务标识 */ - @Column(name = "business_key") + @Column(name = "business_key", nullable = false) private String businessKey; /** diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowLog.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowLog.java index 0076bb95..510f7d23 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowLog.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowLog.java @@ -1,9 +1,12 @@ package com.qqchen.deploy.backend.workflow.entity; -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.framework.annotation.LogicDelete; import com.qqchen.deploy.backend.framework.domain.Entity; +import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import jakarta.persistence.Column; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; @@ -13,38 +16,27 @@ import lombok.EqualsAndHashCode; */ @Data @EqualsAndHashCode(callSuper = true) -@jakarta.persistence.Entity @Table(name = "wf_log") @LogicDelete +@jakarta.persistence.Entity public class WorkflowLog extends Entity { - /** - * 工作流实例ID - */ - @Column(name = "workflow_instance_id", nullable = false) - private Long workflowInstanceId; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "workflow_instance_id", nullable = false) + private WorkflowInstance workflowInstance; - /** - * 节点ID - */ @Column(name = "node_id") private String nodeId; - /** - * 日志内容 - */ - @Column(nullable = false) - private String content; - /** * 日志级别 */ - @Column(nullable = false) + @Column(name = "level") private LogLevelEnum level; - /** - * 详细信息 - */ - @Column(columnDefinition = "TEXT") + @Column(name = "message", nullable = false, columnDefinition = "TEXT") + private String message; + + @Column(name = "detail", columnDefinition = "TEXT") private String detail; -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowVariable.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowVariable.java index 1f253ac8..b86a08dc 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowVariable.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowVariable.java @@ -18,10 +18,11 @@ import lombok.EqualsAndHashCode; public class WorkflowVariable extends Entity { /** - * 工作流实例ID + * 工作流实例 */ - @Column(name = "workflow_instance_id", nullable = false) - private Long workflowInstanceId; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "workflow_instance_id", nullable = false) + private WorkflowInstance workflowInstance; /** * 变量名称 @@ -52,4 +53,4 @@ public class WorkflowVariable extends Entity { */ @Column(name = "node_id") private String nodeId; -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/monitor/WorkflowContextMonitor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/monitor/WorkflowContextMonitor.java new file mode 100644 index 00000000..65682897 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/monitor/WorkflowContextMonitor.java @@ -0,0 +1,149 @@ +package com.qqchen.deploy.backend.workflow.monitor; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.DoubleAdder; + +/** + * 工作流上下文监控 + * 负责收集和记录工作流变量操作的各项指标 + */ +@Slf4j +@Component +public class WorkflowContextMonitor { + + private final MeterRegistry meterRegistry; + private final Counter variableSetCounter; + private final Counter variableGetCounter; + private final Counter cacheHitCounter; + private final Counter cacheMissCounter; + private final Timer variableOperationTimer; + private final Timer transactionTimer; + private final Gauge contextCacheSize; + private final DoubleAdder cacheSize = new DoubleAdder(); + + public WorkflowContextMonitor(MeterRegistry registry) { + this.meterRegistry = registry; + // 变量操作计数器 + this.variableSetCounter = Counter.builder("workflow.variable.operations") + .tag("type", "set") + .description("Number of variable set operations") + .register(registry); + + this.variableGetCounter = Counter.builder("workflow.variable.operations") + .tag("type", "get") + .description("Number of variable get operations") + .register(registry); + + // 缓存命中计数器 + this.cacheHitCounter = Counter.builder("workflow.context.cache") + .tag("result", "hit") + .description("Number of context cache hits") + .register(registry); + + this.cacheMissCounter = Counter.builder("workflow.context.cache") + .tag("result", "miss") + .description("Number of context cache misses") + .register(registry); + + // 操作耗时计时器 + this.variableOperationTimer = Timer.builder("workflow.variable.operation.duration") + .description("Time taken for variable operations") + .publishPercentiles(0.5, 0.95, 0.99) + .register(registry); + + this.transactionTimer = Timer.builder("workflow.variable.transaction.duration") + .description("Time taken for variable transactions") + .publishPercentiles(0.5, 0.95, 0.99) + .register(registry); + + // 缓存大小测量 + this.contextCacheSize = Gauge.builder("workflow.context.cache.size", + cacheSize::doubleValue) // 使用 DoubleAdder 来存储和获取值 + .description("Current size of workflow context cache") + .register(registry); + } + + /** + * 记录变量设置操作 + */ + public void recordVariableSet() { + variableSetCounter.increment(); + } + + /** + * 记录变量获取操作 + */ + public void recordVariableGet() { + variableGetCounter.increment(); + } + + /** + * 记录缓存命中 + */ + public void recordCacheHit() { + cacheHitCounter.increment(); + } + + /** + * 记录缓存未命中 + */ + public void recordCacheMiss() { + cacheMissCounter.increment(); + } + + /** + * 记录操作耗时 + */ + public Timer.Sample startOperation() { + return Timer.start(); + } + + /** + * 停止操作计时 + */ + public void stopOperation(Timer.Sample sample) { + sample.stop(variableOperationTimer); + } + + /** + * 记录事务耗时 + */ + public Timer.Sample startTransaction() { + return Timer.start(); + } + + /** + * 停止事务计时 + */ + public void stopTransaction(Timer.Sample sample) { + sample.stop(transactionTimer); + } + + /** + * 更新缓存大小 + */ + public void updateCacheSize(long size) { + cacheSize.reset(); + cacheSize.add(size); + } + + /** + * 记录错误 + */ + public void recordError(String operation, Exception e) { + log.error("Workflow variable operation error. Operation: {}, Error: {}", + operation, e.getMessage(), e); + Counter.builder("workflow.variable.errors") + .tag("operation", operation) + .tag("error", e.getClass().getSimpleName()) + .register(meterRegistry) + .increment(); + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeInstanceRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeInstanceRepository.java index ea0ecb2d..4119b1c2 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeInstanceRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeInstanceRepository.java @@ -2,6 +2,7 @@ package com.qqchen.deploy.backend.workflow.repository; import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -15,22 +16,36 @@ public interface INodeInstanceRepository extends IBaseRepository findByWorkflowInstanceIdOrderByCreateTime(@Param("instanceId") Long instanceId); + @Query("SELECT n FROM NodeInstance n WHERE n.workflowInstance = :instance AND n.deleted = false ORDER BY n.createTime") + List findByWorkflowInstanceOrderByCreateTime(@Param("instance") WorkflowInstance instance); + + /** + * 根据工作流实例ID查询所有节点 + */ + @Query("SELECT n FROM NodeInstance n WHERE n.workflowInstance.id = :workflowInstanceId AND n.deleted = false ORDER BY n.createTime") + List findByWorkflowInstanceId(@Param("workflowInstanceId") Long workflowInstanceId); + + /** + * 根据工作流实例ID和状态查询节点 + */ + @Query("SELECT n FROM NodeInstance n WHERE n.workflowInstance.id = :workflowInstanceId AND n.status = :status AND n.deleted = false ORDER BY n.createTime") + List findByWorkflowInstanceIdAndStatus( + @Param("workflowInstanceId") Long workflowInstanceId, + @Param("status") NodeStatusEnum status); /** * 查询指定状态的节点实例 */ - List findByWorkflowInstanceIdAndStatusAndDeletedFalse(Long workflowInstanceId, NodeStatusEnum status); + List findByWorkflowInstanceAndStatusAndDeletedFalse(WorkflowInstance instance, NodeStatusEnum status); - List findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, NodeStatusEnum status); + List findByWorkflowInstanceAndStatus(WorkflowInstance instance, NodeStatusEnum status); - List findByWorkflowInstanceId(Long workflowInstanceId); + List findByWorkflowInstance(WorkflowInstance instance); - void deleteByWorkflowInstanceId(Long workflowInstanceId); + void deleteByWorkflowInstance(WorkflowInstance instance); /** * 查询不是指定状态的节点实例 */ - List findByWorkflowInstanceIdAndStatusNot(Long workflowInstanceId, NodeStatusEnum status); -} \ No newline at end of file + List findByWorkflowInstanceAndStatusNot(WorkflowInstance instance, NodeStatusEnum status); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowLogRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowLogRepository.java index 531c1771..6556658c 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowLogRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowLogRepository.java @@ -1,6 +1,7 @@ package com.qqchen.deploy.backend.workflow.repository; import com.qqchen.deploy.backend.framework.repository.IBaseRepository; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.entity.WorkflowLog; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -17,17 +18,17 @@ public interface IWorkflowLogRepository extends IBaseRepository findByWorkflowInstanceId(@Param("instanceId") Long instanceId); /** * 查询节点实例的所有日志 */ - @Query("SELECT l FROM WorkflowLog l WHERE l.workflowInstanceId = :instanceId AND l.nodeId = :nodeId AND l.deleted = false ORDER BY l.createTime") + @Query("SELECT l FROM WorkflowLog l WHERE l.workflowInstance.id = :instanceId AND l.nodeId = :nodeId AND l.deleted = false ORDER BY l.createTime") List findByWorkflowInstanceIdAndNodeId(@Param("instanceId") Long instanceId, @Param("nodeId") String nodeId); /** * 删除工作流实例的所有日志 */ - void deleteByWorkflowInstanceId(Long instanceId); -} \ No newline at end of file + void deleteByWorkflowInstance(WorkflowInstance instance); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowVariableRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowVariableRepository.java index f25791b5..e9f59002 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowVariableRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowVariableRepository.java @@ -1,7 +1,9 @@ package com.qqchen.deploy.backend.workflow.repository; import com.qqchen.deploy.backend.framework.repository.IBaseRepository; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.entity.WorkflowVariable; +import com.qqchen.deploy.backend.workflow.enums.VariableScopeEnum; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -18,40 +20,69 @@ public interface IWorkflowVariableRepository extends IBaseRepository findByWorkflowInstanceId(@Param("instanceId") Long instanceId); + @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstance = :instance AND v.deleted = false") + List findByWorkflowInstance(@Param("instance") WorkflowInstance instance); /** * 查询工作流实例的指定作用域的变量 */ - @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstanceId = :instanceId AND v.scope = :scope AND v.deleted = false") - List findByWorkflowInstanceIdAndScope(@Param("instanceId") Long instanceId, @Param("scope") String scope); + @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstance = :instance AND v.scope = :scope AND v.deleted = false") + List findByWorkflowInstanceAndScope(@Param("instance") WorkflowInstance instance, @Param("scope") String scope); /** * 查询工作流实例的指定变量 */ - Optional findByWorkflowInstanceIdAndName(Long workflowInstanceId, String name); + Optional findByWorkflowInstanceAndName(WorkflowInstance instance, String name); /** * 删除工作流实例的所有变量 */ - void deleteByWorkflowInstanceId(Long instanceId); + void deleteByWorkflowInstance(WorkflowInstance instance); /** * 查询工作流实例的指定作用域和节点的变量 */ - @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstanceId = :instanceId AND v.scope = :scope AND v.nodeId = :nodeId AND v.deleted = false") - List findByWorkflowInstanceIdAndScopeAndNodeId( - @Param("instanceId") Long instanceId, + @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstance = :instance AND v.scope = :scope AND v.nodeId = :nodeId AND v.deleted = false") + List findByWorkflowInstanceAndScopeAndNodeId( + @Param("instance") WorkflowInstance instance, @Param("scope") String scope, @Param("nodeId") String nodeId); /** * 查询工作流实例的指定名称和作用域的变量 */ - @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstanceId = :instanceId AND v.name = :name AND v.scope = :scope AND v.deleted = false") - Optional findByWorkflowInstanceIdAndNameAndScope( - @Param("instanceId") Long instanceId, + @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstance = :instance AND v.name = :name AND v.scope = :scope AND v.deleted = false") + Optional findByWorkflowInstanceAndNameAndScope( + @Param("instance") WorkflowInstance instance, @Param("name") String name, @Param("scope") String scope); -} \ No newline at end of file + + /** + * 根据工作流实例ID查询所有变量 + * + * @param workflowInstanceId 工作流实例ID + * @return 变量列表 + */ + @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstance.id = :workflowInstanceId AND v.deleted = false") + List findByWorkflowInstanceId(@Param("workflowInstanceId") Long workflowInstanceId); + + /** + * 根据工作流实例ID和作用域查询变量列表 + * + * @param workflowInstanceId 工作流实例ID + * @param scope 变量作用域 + * @return 变量列表 + */ + @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstance.id = :workflowInstanceId AND v.scope = :scope AND v.deleted = false") + List findByWorkflowInstanceIdAndScope( + @Param("workflowInstanceId") Long workflowInstanceId, + @Param("scope") VariableScopeEnum scope); + + /** + * 根据工作流实例ID删除所有变量(软删除) + * + * @param workflowInstanceId 工作流实例ID + */ + @Query("UPDATE WorkflowVariable v SET v.deleted = true, v.updateTime = CURRENT_TIMESTAMP WHERE v.workflowInstance.id = :workflowInstanceId AND v.deleted = false") + void deleteByWorkflowInstanceId(@Param("workflowInstanceId") Long workflowInstanceId); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowLogService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowLogService.java index 67932776..a5d903c1 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowLogService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowLogService.java @@ -1,8 +1,9 @@ package com.qqchen.deploy.backend.workflow.service; -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.framework.service.IBaseService; +import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.workflow.dto.WorkflowLogDTO; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.entity.WorkflowLog; import java.util.List; @@ -13,22 +14,49 @@ import java.util.List; public interface IWorkflowLogService extends IBaseService { /** - * 记录日志 + * 记录日志 (内部使用,由工作流引擎调用) + * + * @param instance 工作流实例 + * @param nodeId 节点ID + * @param level 日志级别 + * @param message 日志内容 + * @param detail 详细信息 + * @return 工作流日志 */ - void log(Long workflowInstanceId, String nodeId, String message, LogLevelEnum level, String detail); + WorkflowLog log(WorkflowInstance instance, String nodeId, LogLevelEnum level, String message, String detail); + + /** + * 记录日志 (外部接口使用) + * + * @param workflowInstanceId 工作流实例ID + * @param nodeId 节点ID + * @param message 日志内容 + * @param level 日志级别 + * @param detail 详细信息 + */ + void recordLog(Long workflowInstanceId, String nodeId, String message, LogLevelEnum level, String detail); /** * 查询工作流实例的所有日志 + * + * @param workflowInstanceId 工作流实例ID + * @return 日志列表 */ List getLogs(Long workflowInstanceId); /** * 查询节点实例的所有日志 + * + * @param workflowInstanceId 工作流实例ID + * @param nodeId 节点ID + * @return 日志列表 */ List getNodeLogs(Long workflowInstanceId, String nodeId); /** * 删除工作流实例的所有日志 + * + * @param workflowInstance 工作流实例 */ - void deleteLogs(Long workflowInstanceId); -} \ No newline at end of file + void deleteLogs(WorkflowInstance workflowInstance); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowVariableService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowVariableService.java index 8cdabefe..adc6447e 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowVariableService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowVariableService.java @@ -2,6 +2,7 @@ package com.qqchen.deploy.backend.workflow.service; import com.qqchen.deploy.backend.framework.service.IBaseService; import com.qqchen.deploy.backend.workflow.dto.WorkflowVariableDTO; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.entity.WorkflowVariable; import com.qqchen.deploy.backend.workflow.enums.VariableScopeEnum; @@ -14,21 +15,29 @@ public interface IWorkflowVariableService extends IBaseService variables); /** * 获取所有变量 + * + * @return 变量映射 */ Map getVariables(Long workflowInstanceId); /** * 获取指定作用域的变量 + * + * @param scope 变量作用域 + * @return 变量映射 */ Map getVariablesByScope(Long workflowInstanceId, VariableScopeEnum scope); /** - * 删除所有变量 + * 清除实例的所有变量 + * */ - void deleteVariables(Long workflowInstanceId); -} \ No newline at end of file + void clearVariables(Long workflowInstanceId); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/WorkflowVariableOperations.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/WorkflowVariableOperations.java new file mode 100644 index 00000000..b4ce69c1 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/WorkflowVariableOperations.java @@ -0,0 +1,52 @@ +package com.qqchen.deploy.backend.workflow.service; + +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; + +import java.util.Map; + +/** + * 工作流变量操作接口 + */ +public interface WorkflowVariableOperations { + + /** + * 获取工作流实例的所有变量 + * + * @param workflowInstanceId 工作流实例ID + * @return 变量Map + */ + Map getVariables(Long workflowInstanceId); + + /** + * 设置工作流实例的变量 + * + * @param workflowInstanceId 工作流实例ID + * @param variables 变量Map + */ + void setVariables(Long workflowInstanceId, Map variables); + + /** + * 获取工作流实例的指定变量 + * + * @param workflowInstanceId 工作流实例ID + * @param key 变量键 + * @return 变量值 + */ + Object getVariable(Long workflowInstanceId, String key); + + /** + * 设置工作流实例的指定变量 + * + * @param workflowInstanceId 工作流实例ID + * @param key 变量键 + * @param value 变量值 + */ + void setVariable(Long workflowInstanceId, String key, Object value); + + /** + * 清除实例的所有变量 + * + * @param workflowInstanceId 工作流实例ID + */ + void clearVariables(Long workflowInstanceId); +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/ConcurrentWorkflowVariableOperations.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/ConcurrentWorkflowVariableOperations.java new file mode 100644 index 00000000..0434a95f --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/ConcurrentWorkflowVariableOperations.java @@ -0,0 +1,70 @@ +package com.qqchen.deploy.backend.workflow.service.impl; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService; +import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * 并发安全的工作流变量操作实现 + */ +@Slf4j +@Service +public class ConcurrentWorkflowVariableOperations implements WorkflowVariableOperations { + + @Resource + private IWorkflowVariableService variableService; + + private final Cache> variableCache; + + public ConcurrentWorkflowVariableOperations() { + this.variableCache = Caffeine.newBuilder() + .expireAfterWrite(30, TimeUnit.MINUTES) + .maximumSize(10000) + .build(); + } + + @Override + public Map getVariables(Long workflowInstanceId) { + return variableCache.get(workflowInstanceId, + id -> variableService.getVariables(id)); + } + + @Override + public void setVariables(Long workflowInstanceId, Map variables) { + if (variables == null || variables.isEmpty()) { + return; + } + variableService.setVariables(workflowInstanceId, variables); + variableCache.put(workflowInstanceId, variables); + } + + @Override + public Object getVariable(Long workflowInstanceId, String key) { + Map variables = getVariables(workflowInstanceId); + return variables != null ? variables.get(key) : null; + } + + @Override + public void setVariable(Long workflowInstanceId, String key, Object value) { + Map variables = getVariables(workflowInstanceId); + if (variables != null) { + variables.put(key, value); + setVariables(workflowInstanceId, variables); + } + } + + @Override + public void clearVariables(Long workflowInstanceId) { + variableService.clearVariables(workflowInstanceId); + variableCache.invalidate(workflowInstanceId); + } +} 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 e5325b56..82b030f3 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 @@ -11,7 +11,7 @@ import com.qqchen.deploy.backend.workflow.enums.WorkflowDefinitionStatusEnum; import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService; -import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService; +import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -34,7 +34,7 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl workflowVariableService.setVariable(savedInstance.getId(), key, value)); + variableOperations.setVariables(savedInstance.getId(), variables); } return converter.toDto(savedInstance); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowLogServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowLogServiceImpl.java index 9184da73..5896ed21 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowLogServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowLogServiceImpl.java @@ -1,10 +1,12 @@ package com.qqchen.deploy.backend.workflow.service.impl; -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.workflow.dto.WorkflowLogDTO; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.entity.WorkflowLog; import com.qqchen.deploy.backend.workflow.repository.IWorkflowLogRepository; +import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService; import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -23,17 +25,33 @@ public class WorkflowLogServiceImpl extends BaseServiceImpl { - WorkflowVariable newVar = new WorkflowVariable(); - newVar.setWorkflowInstanceId(workflowInstanceId); - newVar.setName(name); - newVar.setScope(VariableScopeEnum.GLOBAL.name()); - return newVar; - }); - - variable.setValue(objectMapper.writeValueAsString(value)); - variable.setType(value.getClass().getName()); - variableRepository.save(variable); - } catch (JsonProcessingException e) { - throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID); + public void setVariables(Long workflowInstanceId, Map variables) { + if (variables == null || variables.isEmpty()) { + return; } + + // 先删除已有的变量 + clearVariables(workflowInstanceId); + + // 批量保存新变量 + variables.forEach((key, value) -> { + try { + WorkflowVariable variable = new WorkflowVariable(); + WorkflowInstance instance = new WorkflowInstance(); + instance.setId(workflowInstanceId); + variable.setWorkflowInstance(instance); + variable.setName(key); + variable.setValue(objectMapper.writeValueAsString(value)); + variableRepository.save(variable); + } catch (JsonProcessingException e) { + throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_SERIALIZE_ERROR); + } + }); } @Override - @Transactional(readOnly = true) public Map getVariables(Long workflowInstanceId) { List variables = variableRepository.findByWorkflowInstanceId(workflowInstanceId); - return convertToMap(variables); + return deserializeVariables(variables); } @Override - @Transactional(readOnly = true) public Map getVariablesByScope(Long workflowInstanceId, VariableScopeEnum scope) { - List variables = variableRepository.findByWorkflowInstanceIdAndScope(workflowInstanceId, scope.name()); - return convertToMap(variables); + List variables = variableRepository.findByWorkflowInstanceIdAndScope(workflowInstanceId, scope); + return deserializeVariables(variables); } @Override @Transactional - public void deleteVariables(Long workflowInstanceId) { + public void clearVariables(Long workflowInstanceId) { variableRepository.deleteByWorkflowInstanceId(workflowInstanceId); } - private Map convertToMap(List variables) { + private Map deserializeVariables(List variables) { Map result = new HashMap<>(); - for (WorkflowVariable variable : variables) { + variables.forEach(variable -> { try { - Class type = Class.forName(variable.getType()); - Object value = objectMapper.readValue(variable.getValue(), type); + Object value = objectMapper.readValue(variable.getValue(), Object.class); result.put(variable.getName(), value); - } catch (Exception e) { - throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID); + } catch (JsonProcessingException e) { + throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_DESERIALIZE_ERROR); } - } + }); return result; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 4ac9e7a6..e3e359f3 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -4,7 +4,7 @@ spring: datasource: url: jdbc:mysql://localhost:3306/deploy-ease-platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true username: root - password: root + password: ServBay.dev driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: diff --git a/backend/src/main/resources/dashboards/workflow-context-monitor.json b/backend/src/main/resources/dashboards/workflow-context-monitor.json new file mode 100644 index 00000000..8b81c811 --- /dev/null +++ b/backend/src/main/resources/dashboards/workflow-context-monitor.json @@ -0,0 +1,202 @@ +{ + "annotations": { + "list": [] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.5", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(workflow_variable_operations_total{type=\"set\"}[5m])", + "legendFormat": "Set Operations", + "refId": "A" + }, + { + "expr": "rate(workflow_variable_operations_total{type=\"get\"}[5m])", + "legendFormat": "Get Operations", + "refId": "B" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Variable Operations Rate", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ops", + "label": "Operations/sec", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 3, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.5", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "histogram_quantile(0.95, rate(workflow_variable_operation_duration_seconds_bucket[5m]))", + "legendFormat": "95th percentile", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.50, rate(workflow_variable_operation_duration_seconds_bucket[5m]))", + "legendFormat": "Median", + "refId": "B" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Operation Duration", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": "Duration", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + ], + "refresh": "5s", + "schemaVersion": 27, + "style": "dark", + "tags": ["workflow", "variables"], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Workflow Context Monitor", + "uid": "workflow-context", + "version": 1 +} 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 2c582c94..4cc4ab01 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 @@ -443,8 +443,7 @@ CREATE TABLE wf_workflow_instance ( end_time DATETIME(6) NULL COMMENT '结束时间', error TEXT NULL COMMENT '错误信息', - CONSTRAINT FK_workflow_instance_definition FOREIGN KEY (workflow_definition_id) REFERENCES wf_workflow_definition (id), - CONSTRAINT UK_workflow_instance_business_key UNIQUE (business_key) + CONSTRAINT FK_workflow_instance_definition FOREIGN KEY (workflow_definition_id) REFERENCES wf_workflow_definition (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流实例表'; -- 节点实例表 @@ -495,17 +494,18 @@ CREATE TABLE wf_workflow_variable ( -- 工作流日志表 CREATE TABLE wf_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, - workflow_instance_id BIGINT NOT NULL, - node_id VARCHAR(50), - message VARCHAR(1000) NOT NULL, - level VARCHAR(20) NOT NULL, + workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID', + node_id VARCHAR(50) NULL COMMENT '节点ID', + level VARCHAR(10) NOT NULL COMMENT '日志级别', + message TEXT NOT NULL COMMENT '日志内容', detail TEXT, create_time DATETIME NOT NULL, create_by VARCHAR(50), update_time DATETIME, update_by VARCHAR(50), version INT DEFAULT 0, - deleted BOOLEAN DEFAULT FALSE + deleted BOOLEAN DEFAULT FALSE, + CONSTRAINT FK_workflow_log_instance FOREIGN KEY (workflow_instance_id) REFERENCES wf_workflow_instance (id) ); -- 创建索引 diff --git a/backend/src/main/resources/messages.properties b/backend/src/main/resources/messages.properties index a0f7a532..93d429e5 100644 --- a/backend/src/main/resources/messages.properties +++ b/backend/src/main/resources/messages.properties @@ -1,238 +1,242 @@ -# 通用响应 -response.success=操作成功 -response.error=系统错误 -response.invalid.param=无效的参数 -response.unauthorized=未授权 -response.forbidden=禁止访问 -response.not.found=资源未找到 -response.conflict=资源冲突 -response.unauthorized.full=访问此资源需要完全身份验证 +# \u901A\u7528\u54CD\u5E94 +response.success=\u64CD\u4F5C\u6210\u529F +response.error=\u7CFB\u7EDF\u9519\u8BEF +response.invalid.param=\u65E0\u6548\u7684\u53C2\u6570 +response.unauthorized=\u672A\u6388\u6743 +response.forbidden=\u7981\u6B62\u8BBF\u95EE +response.not.found=\u8D44\u6E90\u672A\u627E\u5230 +response.conflict=\u8D44\u6E90\u51B2\u7A81 +response.unauthorized.full=\u8BBF\u95EE\u6B64\u8D44\u6E90\u9700\u8981\u5B8C\u5168\u8EAB\u4EFD\u9A8C\u8BC1 -# 业务错误 -tenant.not.found=租户不存在 -data.not.found=找不到ID为{0}的{1} +# \u4E1A\u52A1\u9519\u8BEF +tenant.not.found=\u79DF\u6237\u4E0D\u5B58\u5728 +data.not.found=\u627E\u4E0D\u5230ID\u4E3A{0}\u7684{1} -# 用户相关 -user.not.found=用户不存在 -user.username.exists=用户名"{0}"已存在 -user.email.exists=邮箱"{0}"已存在 -user.login.error=用户名或密码错误 +# \u7528\u6237\u76F8\u5173 +user.not.found=\u7528\u6237\u4E0D\u5B58\u5728 +user.username.exists=\u7528\u6237\u540D"{0}"\u5DF2\u5B58\u5728 +user.email.exists=\u90AE\u7BB1"{0}"\u5DF2\u5B58\u5728 +user.login.error=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF -# 系统异常消息 -system.optimistic.lock.error=数据已被其他用户修改,请刷新后重试 -system.pessimistic.lock.error=数据正被其他用户操作,请稍后重试 -system.concurrent.update.error=并发更新冲突,请重试 -system.retry.exceeded.error=操作重试次数超限,请稍后再试 +# \u7CFB\u7EDF\u5F02\u5E38\u6D88\u606F +system.optimistic.lock.error=\u6570\u636E\u5DF2\u88AB\u5176\u4ED6\u7528\u6237\u4FEE\u6539\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5 +system.pessimistic.lock.error=\u6570\u636E\u6B63\u88AB\u5176\u4ED6\u7528\u6237\u64CD\u4F5C\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5 +system.concurrent.update.error=\u5E76\u53D1\u66F4\u65B0\u51B2\u7A81\uFF0C\u8BF7\u91CD\u8BD5 +system.retry.exceeded.error=\u64CD\u4F5C\u91CD\u8BD5\u6B21\u6570\u8D85\u9650\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5 # Entity Not Found Messages -entity.not.found.id=找不到ID为{0}的实体 +entity.not.found.id=\u627E\u4E0D\u5230ID\u4E3A{0}\u7684\u5B9E\u4F53 entity.not.found.message={0} -entity.not.found.name.id=找不到ID为{1}的{0} +entity.not.found.name.id=\u627E\u4E0D\u5230ID\u4E3A{1}\u7684{0} -# 依赖注入相关 -dependency.injection.service.not.found=找不到实体 {0} 对应的服务 (尝试过的bean名称: {1}) -dependency.injection.repository.not.found=找不到实体 {0} 对应的Repository: {1} -dependency.injection.converter.not.found=找不到实体 {0} 对应的Converter: {1} -dependency.injection.entitypath.failed=初始化实体 {0} 的EntityPath失败: {1} +# \u4F9D\u8D56\u6CE8\u5165\u76F8\u5173 +dependency.injection.service.not.found=\u627E\u4E0D\u5230\u5B9E\u4F53 {0} \u5BF9\u5E94\u7684\u670D\u52A1 (\u5C1D\u8BD5\u8FC7\u7684bean\u540D\u79F0: {1}) +dependency.injection.repository.not.found=\u627E\u4E0D\u5230\u5B9E\u4F53 {0} \u5BF9\u5E94\u7684Repository: {1} +dependency.injection.converter.not.found=\u627E\u4E0D\u5230\u5B9E\u4F53 {0} \u5BF9\u5E94\u7684Converter: {1} +dependency.injection.entitypath.failed=\u521D\u59CB\u5316\u5B9E\u4F53 {0} \u7684EntityPath\u5931\u8D25: {1} -# JWT相关 -jwt.token.expired=登录已过期,请重新登录 -jwt.token.invalid=无效的登录凭证 -jwt.token.missing=未提供登录凭证 +# JWT\u76F8\u5173 +jwt.token.expired=\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55 +jwt.token.invalid=\u65E0\u6548\u7684\u767B\u5F55\u51ED\u8BC1 +jwt.token.missing=\u672A\u63D0\u4F9B\u767B\u5F55\u51ED\u8BC1 -# 角色相关错误消息 -role.not.found=角色不存在 -role.code.exists=角色编码"{0}"已存在 -role.name.exists=角色名称"{0}"已存在 -role.in.use=角色正在使用中,无法删除 -role.admin.cannot.delete=不能删除超级管理员角色 -role.admin.cannot.update=不能修改超级管理员角色 -role.tag.name.exists=标签名称已存在 -role.tag.not.found=标签不存在 -role.tag.in.use=标签正在使用中,无法删除 +# \u89D2\u8272\u76F8\u5173\u9519\u8BEF\u6D88\u606F +role.not.found=\u89D2\u8272\u4E0D\u5B58\u5728 +role.code.exists=\u89D2\u8272\u7F16\u7801"{0}"\u5DF2\u5B58\u5728 +role.name.exists=\u89D2\u8272\u540D\u79F0"{0}"\u5DF2\u5B58\u5728 +role.in.use=\u89D2\u8272\u6B63\u5728\u4F7F\u7528\u4E2D\uFF0C\u65E0\u6CD5\u5220\u9664 +role.admin.cannot.delete=\u4E0D\u80FD\u5220\u9664\u8D85\u7EA7\u7BA1\u7406\u5458\u89D2\u8272 +role.admin.cannot.update=\u4E0D\u80FD\u4FEE\u6539\u8D85\u7EA7\u7BA1\u7406\u5458\u89D2\u8272 +role.tag.name.exists=\u6807\u7B7E\u540D\u79F0\u5DF2\u5B58\u5728 +role.tag.not.found=\u6807\u7B7E\u4E0D\u5B58\u5728 +role.tag.in.use=\u6807\u7B7E\u6B63\u5728\u4F7F\u7528\u4E2D\uFF0C\u65E0\u6CD5\u5220\u9664 -# 部门相关 -department.not.found=部门不存在 -department.code.exists=部门编码已存在 -department.name.exists=部门名称已存在 -department.parent.not.found=上级部门不存在 -department.has.children=该部门下有子部门,无法删除 +# \u90E8\u95E8\u76F8\u5173 +department.not.found=\u90E8\u95E8\u4E0D\u5B58\u5728 +department.code.exists=\u90E8\u95E8\u7F16\u7801\u5DF2\u5B58\u5728 +department.name.exists=\u90E8\u95E8\u540D\u79F0\u5DF2\u5B58\u5728 +department.parent.not.found=\u4E0A\u7EA7\u90E8\u95E8\u4E0D\u5B58\u5728 +department.has.children=\u8BE5\u90E8\u95E8\u4E0B\u6709\u5B50\u90E8\u95E8\uFF0C\u65E0\u6CD5\u5220\u9664 -# 权限相关 -permission.not.found=权限不存在 -permission.code.exists=权限编码{0}已存在 -permission.name.exists=权限名称{0}已存在 -permission.already.assigned=该权限已分配给角色 -permission.assign.failed=权限分配失败 +# \u6743\u9650\u76F8\u5173 +permission.not.found=\u6743\u9650\u4E0D\u5B58\u5728 +permission.code.exists=\u6743\u9650\u7F16\u7801{0}\u5DF2\u5B58\u5728 +permission.name.exists=\u6743\u9650\u540D\u79F0{0}\u5DF2\u5B58\u5728 +permission.already.assigned=\u8BE5\u6743\u9650\u5DF2\u5206\u914D\u7ED9\u89D2\u8272 +permission.assign.failed=\u6743\u9650\u5206\u914D\u5931\u8D25 -# 第三方系统相关 -external.system.name.exists=系统名称"{0}"已存在 -external.system.type.url.exists=系统类型和URL组合"{0}"已存在 -external.system.disabled=系统已禁用 -external.system.sync.failed=系统数据同步失败 -external.system.type.not.supported=不支持的系统类型 +# \u7B2C\u4E09\u65B9\u7CFB\u7EDF\u76F8\u5173 +external.system.name.exists=\u7CFB\u7EDF\u540D\u79F0"{0}"\u5DF2\u5B58\u5728 +external.system.type.url.exists=\u7CFB\u7EDF\u7C7B\u578B\u548CURL\u7EC4\u5408"{0}"\u5DF2\u5B58\u5728 +external.system.disabled=\u7CFB\u7EDF\u5DF2\u7981\u7528 +external.system.sync.failed=\u7CFB\u7EDF\u6570\u636E\u540C\u6B65\u5931\u8D25 +external.system.type.not.supported=\u4E0D\u652F\u6301\u7684\u7CFB\u7EDF\u7C7B\u578B -# Git系统相关错误 -external.system.git.auth.type.error=Git系统只支持Token认证 -external.system.git.token.required=Git系统必须提供Token +# Git\u7CFB\u7EDF\u76F8\u5173\u9519\u8BEF +external.system.git.auth.type.error=Git\u7CFB\u7EDF\u53EA\u652F\u6301Token\u8BA4\u8BC1 +external.system.git.token.required=Git\u7CFB\u7EDF\u5FC5\u987B\u63D0\u4F9BToken -# 仓库相关错误消息 -repository.group.not.found=仓库组不存在 -repository.group.name.exists=仓库组名称"{0}"已存在 -repository.group.path.exists=仓库组路径"{0}"已存在 -repository.project.not.found=仓库项目不存在 -repository.project.name.exists=仓库项目名称"{0}"已存在 -repository.project.path.exists=仓库项目路径"{0}"已存在 -repository.branch.not.found=分支不存在 -repository.branch.name.exists=分支名称"{0}"已存在 -repository.sync.in.progress=仓库同步正在进行中 -repository.sync.failed=仓库同步失败:{0} -repository.sync.history.not.found=同步历史记录不存在 +# \u4ED3\u5E93\u76F8\u5173\u9519\u8BEF\u6D88\u606F +repository.group.not.found=\u4ED3\u5E93\u7EC4\u4E0D\u5B58\u5728 +repository.group.name.exists=\u4ED3\u5E93\u7EC4\u540D\u79F0"{0}"\u5DF2\u5B58\u5728 +repository.group.path.exists=\u4ED3\u5E93\u7EC4\u8DEF\u5F84"{0}"\u5DF2\u5B58\u5728 +repository.project.not.found=\u4ED3\u5E93\u9879\u76EE\u4E0D\u5B58\u5728 +repository.project.name.exists=\u4ED3\u5E93\u9879\u76EE\u540D\u79F0"{0}"\u5DF2\u5B58\u5728 +repository.project.path.exists=\u4ED3\u5E93\u9879\u76EE\u8DEF\u5F84"{0}"\u5DF2\u5B58\u5728 +repository.branch.not.found=\u5206\u652F\u4E0D\u5B58\u5728 +repository.branch.name.exists=\u5206\u652F\u540D\u79F0"{0}"\u5DF2\u5B58\u5728 +repository.sync.in.progress=\u4ED3\u5E93\u540C\u6B65\u6B63\u5728\u8FDB\u884C\u4E2D +repository.sync.failed=\u4ED3\u5E93\u540C\u6B65\u5931\u8D25\uFF1A{0} +repository.sync.history.not.found=\u540C\u6B65\u5386\u53F2\u8BB0\u5F55\u4E0D\u5B58\u5728 -# 工作流相关错误消息 -workflow.definition.not.found=工作流定义不存在 -workflow.definition.code.exists=工作流定义编码"{0}"已存在 -workflow.definition.name.exists=工作流定义名称"{0}"已存在 -workflow.definition.invalid.content=工作流定义内容无效:{0} -workflow.definition.not.published=工作流定义未发布 -workflow.definition.already.published=工作流定义已发布 -workflow.definition.cannot.delete=工作流定义已被使用,无法删除 +# \u5DE5\u4F5C\u6D41\u76F8\u5173\u9519\u8BEF\u6D88\u606F +workflow.definition.not.found=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u4E0D\u5B58\u5728 +workflow.definition.code.exists=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u7F16\u7801"{0}"\u5DF2\u5B58\u5728 +workflow.definition.name.exists=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u540D\u79F0"{0}"\u5DF2\u5B58\u5728 +workflow.definition.invalid.content=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u5185\u5BB9\u65E0\u6548\uFF1A{0} +workflow.definition.not.published=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u672A\u53D1\u5E03 +workflow.definition.already.published=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u5DF2\u53D1\u5E03 +workflow.definition.cannot.delete=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u5DF2\u88AB\u4F7F\u7528\uFF0C\u65E0\u6CD5\u5220\u9664 -workflow.instance.not.found=工作流实例不存在 -workflow.instance.cannot.start=工作流实例无法启动 -workflow.instance.cannot.cancel=工作流实例无法取消 -workflow.instance.cannot.pause=工作流实例无法暂停 -workflow.instance.cannot.resume=工作流实例无法恢复 -workflow.instance.cannot.retry=工作流实例无法重试 +workflow.instance.not.found=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u4E0D\u5B58\u5728 +workflow.instance.cannot.start=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u65E0\u6CD5\u542F\u52A8 +workflow.instance.cannot.cancel=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u65E0\u6CD5\u53D6\u6D88 +workflow.instance.cannot.pause=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u65E0\u6CD5\u6682\u505C +workflow.instance.cannot.resume=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u65E0\u6CD5\u6062\u590D +workflow.instance.cannot.retry=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u65E0\u6CD5\u91CD\u8BD5 -# 节点相关错误消息 -node.instance.not.found=节点实例不存在 -node.instance.cannot.retry=节点实例无法重试 -node.instance.cannot.skip=节点实例无法跳过 -node.executor.not.found=节点执行器不存在 +# \u8282\u70B9\u76F8\u5173\u9519\u8BEF\u6D88\u606F +node.instance.not.found=\u8282\u70B9\u5B9E\u4F8B\u4E0D\u5B58\u5728 +node.instance.cannot.retry=\u8282\u70B9\u5B9E\u4F8B\u65E0\u6CD5\u91CD\u8BD5 +node.instance.cannot.skip=\u8282\u70B9\u5B9E\u4F8B\u65E0\u6CD5\u8DF3\u8FC7 +node.executor.not.found=\u8282\u70B9\u6267\u884C\u5668\u4E0D\u5B58\u5728 -# 工作流相关消息 -workflow.not.found=工作流定义不存在 -workflow.code.exists=工作流编码已存在 -workflow.name.exists=工作流名称已存在 -workflow.disabled=工作流已禁用 -workflow.not.published=工作流未发布 -workflow.already.published=工作流已发布 -workflow.already.disabled=工作流已禁用 -workflow.instance.not.found=工作流实例不存在 -workflow.instance.already.completed=工作流实例已完成 -workflow.instance.already.canceled=工作流实例已取消 -workflow.instance.not.running=工作流实例未运行 -workflow.node.not.found=工作流节点不存在 -workflow.node.type.not.supported=不支持的节点类型 -workflow.node.config.invalid=节点配置无效 -workflow.node.execution.failed=节点执行失败 -workflow.node.timeout=节点执行超时 -workflow.variable.not.found=工作流变量不存在 -workflow.variable.type.invalid=工作流变量类型无效 -workflow.permission.denied=工作流权限不足 -workflow.approval.required=需要审批 -workflow.approval.rejected=审批被拒绝 -workflow.dependency.not.satisfied=依赖条件不满足 -workflow.circular.dependency=存在循环依赖 -workflow.schedule.invalid=调度配置无效 -workflow.concurrent.limit.exceeded=超出并发限制 +# \u5DE5\u4F5C\u6D41\u76F8\u5173\u6D88\u606F +workflow.not.found=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u4E0D\u5B58\u5728 +workflow.code.exists=\u5DE5\u4F5C\u6D41\u7F16\u7801\u5DF2\u5B58\u5728 +workflow.name.exists=\u5DE5\u4F5C\u6D41\u540D\u79F0\u5DF2\u5B58\u5728 +workflow.disabled=\u5DE5\u4F5C\u6D41\u5DF2\u7981\u7528 +workflow.not.published=\u5DE5\u4F5C\u6D41\u672A\u53D1\u5E03 +workflow.already.published=\u5DE5\u4F5C\u6D41\u5DF2\u53D1\u5E03 +workflow.already.disabled=\u5DE5\u4F5C\u6D41\u5DF2\u7981\u7528 +workflow.instance.not.found=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u4E0D\u5B58\u5728 +workflow.instance.already.completed=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u5DF2\u5B8C\u6210 +workflow.instance.already.canceled=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u5DF2\u53D6\u6D88 +workflow.instance.not.running=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u672A\u8FD0\u884C +workflow.node.not.found=\u5DE5\u4F5C\u6D41\u8282\u70B9\u4E0D\u5B58\u5728 +workflow.node.type.not.supported=\u4E0D\u652F\u6301\u7684\u8282\u70B9\u7C7B\u578B +workflow.node.config.invalid=\u8282\u70B9\u914D\u7F6E\u65E0\u6548 +workflow.node.execution.failed=\u8282\u70B9\u6267\u884C\u5931\u8D25 +workflow.node.timeout=\u8282\u70B9\u6267\u884C\u8D85\u65F6 +workflow.variable.not.found=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u4E0D\u5B58\u5728 +workflow.variable.type.invalid=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u7C7B\u578B\u65E0\u6548 +workflow.permission.denied=\u5DE5\u4F5C\u6D41\u6743\u9650\u4E0D\u8DB3 +workflow.approval.required=\u9700\u8981\u5BA1\u6279 +workflow.approval.rejected=\u5BA1\u6279\u88AB\u62D2\u7EDD +workflow.dependency.not.satisfied=\u4F9D\u8D56\u6761\u4EF6\u4E0D\u6EE1\u8DB3 +workflow.circular.dependency=\u5B58\u5728\u5FAA\u73AF\u4F9D\u8D56 +workflow.schedule.invalid=\u8C03\u5EA6\u914D\u7F6E\u65E0\u6548 +workflow.concurrent.limit.exceeded=\u8D85\u51FA\u5E76\u53D1\u9650\u5236 # Workflow error messages -workflow.not.found=工作流定义不存在 -workflow.code.exists=工作流编码已存在 -workflow.name.exists=工作流名称已存在 -workflow.invalid.status=工作流状态无效 -workflow.node.not.found=工作流节点不存在 -workflow.node.config.error=工作流节点配置错误 -workflow.execution.error=工作流执行错误 -workflow.not.draft=只有草稿状态的工作流定义可以发布 -workflow.not.published=只有已发布状态的工作流定义可以禁用 -workflow.not.disabled=只有已禁用状态的工作流定义可以启用 +workflow.not.found=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u4E0D\u5B58\u5728 +workflow.code.exists=\u5DE5\u4F5C\u6D41\u7F16\u7801\u5DF2\u5B58\u5728 +workflow.name.exists=\u5DE5\u4F5C\u6D41\u540D\u79F0\u5DF2\u5B58\u5728 +workflow.invalid.status=\u5DE5\u4F5C\u6D41\u72B6\u6001\u65E0\u6548 +workflow.node.not.found=\u5DE5\u4F5C\u6D41\u8282\u70B9\u4E0D\u5B58\u5728 +workflow.node.config.error=\u5DE5\u4F5C\u6D41\u8282\u70B9\u914D\u7F6E\u9519\u8BEF +workflow.execution.error=\u5DE5\u4F5C\u6D41\u6267\u884C\u9519\u8BEF +workflow.not.draft=\u53EA\u6709\u8349\u7A3F\u72B6\u6001\u7684\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u53EF\u4EE5\u53D1\u5E03 +workflow.not.published=\u53EA\u6709\u5DF2\u53D1\u5E03\u72B6\u6001\u7684\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u53EF\u4EE5\u7981\u7528 +workflow.not.disabled=\u53EA\u6709\u5DF2\u7981\u7528\u72B6\u6001\u7684\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u53EF\u4EE5\u542F\u7528 # System level messages (1xxx) -success=操作成功 -system.error=系统错误 -param.error=参数错误 -unauthorized=未授权 -forbidden=禁止访问 -not.found=资源不存在 -method.not.allowed=方法不允许 -conflict=资源冲突 -too.many.requests=请求过于频繁 -internal.server.error=内部服务器错误 +success=\u64CD\u4F5C\u6210\u529F +system.error=\u7CFB\u7EDF\u9519\u8BEF +param.error=\u53C2\u6570\u9519\u8BEF +unauthorized=\u672A\u6388\u6743 +forbidden=\u7981\u6B62\u8BBF\u95EE +not.found=\u8D44\u6E90\u4E0D\u5B58\u5728 +method.not.allowed=\u65B9\u6CD5\u4E0D\u5141\u8BB8 +conflict=\u8D44\u6E90\u51B2\u7A81 +too.many.requests=\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41 +internal.server.error=\u5185\u90E8\u670D\u52A1\u5668\u9519\u8BEF # Business level messages (2xxx) # Common business messages (2000-2099) -business.error=业务错误 -data.not.found=数据不存在 -data.already.exists=数据已存在 -data.validation.failed=数据验证失败 -operation.not.allowed=操作不允许 +business.error=\u4E1A\u52A1\u9519\u8BEF +data.not.found=\u6570\u636E\u4E0D\u5B58\u5728 +data.already.exists=\u6570\u636E\u5DF2\u5B58\u5728 +data.validation.failed=\u6570\u636E\u9A8C\u8BC1\u5931\u8D25 +operation.not.allowed=\u64CD\u4F5C\u4E0D\u5141\u8BB8 # Workflow related messages (2100-2199) -workflow.not.found=工作流不存在 -workflow.already.exists=工作流已存在 -workflow.not.published=工作流未发布 -workflow.config.invalid=工作流配置无效 -workflow.node.not.found=工作流节点不存在 -workflow.node.execution.failed=工作流节点执行失败 -workflow.instance.not.found=工作流实例不存在 -workflow.instance.not.running=工作流实例未运行 -workflow.variable.not.found=工作流变量不存在 -workflow.log.not.found=工作流日志不存在 -workflow.transition.invalid=工作流流转配置无效 -workflow.node.type.not.supported=不支持的节点类型 -workflow.condition.invalid=工作流条件配置无效 +workflow.not.found=\u5DE5\u4F5C\u6D41\u4E0D\u5B58\u5728 +workflow.already.exists=\u5DE5\u4F5C\u6D41\u5DF2\u5B58\u5728 +workflow.not.published=\u5DE5\u4F5C\u6D41\u672A\u53D1\u5E03 +workflow.config.invalid=\u5DE5\u4F5C\u6D41\u914D\u7F6E\u65E0\u6548 +workflow.node.not.found=\u5DE5\u4F5C\u6D41\u8282\u70B9\u4E0D\u5B58\u5728 +workflow.node.execution.failed=\u5DE5\u4F5C\u6D41\u8282\u70B9\u6267\u884C\u5931\u8D25 +workflow.instance.not.found=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u4E0D\u5B58\u5728 +workflow.instance.not.running=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u672A\u8FD0\u884C +workflow.variable.not.found=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u4E0D\u5B58\u5728 +workflow.log.not.found=\u5DE5\u4F5C\u6D41\u65E5\u5FD7\u4E0D\u5B58\u5728 +workflow.transition.invalid=\u5DE5\u4F5C\u6D41\u6D41\u8F6C\u914D\u7F6E\u65E0\u6548 +workflow.node.type.not.supported=\u4E0D\u652F\u6301\u7684\u8282\u70B9\u7C7B\u578B +workflow.condition.invalid=\u5DE5\u4F5C\u6D41\u6761\u4EF6\u914D\u7F6E\u65E0\u6548 -# 工作流相关错误消息 -workflow.not.found=工作流定义不存在 -workflow.code.exists=工作流编码已存在 -workflow.name.exists=工作流名称已存在 -workflow.disabled=工作流已禁用 -workflow.not.published=工作流未发布 -workflow.already.published=工作流已发布 -workflow.already.disabled=工作流已禁用 -workflow.not.draft=工作流不是草稿状态 -workflow.not.disabled=工作流不是禁用状态 -workflow.invalid.status=工作流状态无效 -workflow.config.invalid=工作流配置无效 -workflow.transition.invalid=工作流流转规则无效 -workflow.condition.invalid=工作流条件配置无效 +# \u5DE5\u4F5C\u6D41\u76F8\u5173\u9519\u8BEF\u6D88\u606F +workflow.not.found=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u4E0D\u5B58\u5728 +workflow.code.exists=\u5DE5\u4F5C\u6D41\u7F16\u7801\u5DF2\u5B58\u5728 +workflow.name.exists=\u5DE5\u4F5C\u6D41\u540D\u79F0\u5DF2\u5B58\u5728 +workflow.disabled=\u5DE5\u4F5C\u6D41\u5DF2\u7981\u7528 +workflow.not.published=\u5DE5\u4F5C\u6D41\u672A\u53D1\u5E03 +workflow.already.published=\u5DE5\u4F5C\u6D41\u5DF2\u53D1\u5E03 +workflow.already.disabled=\u5DE5\u4F5C\u6D41\u5DF2\u7981\u7528 +workflow.not.draft=\u5DE5\u4F5C\u6D41\u4E0D\u662F\u8349\u7A3F\u72B6\u6001 +workflow.not.disabled=\u5DE5\u4F5C\u6D41\u4E0D\u662F\u7981\u7528\u72B6\u6001 +workflow.invalid.status=\u5DE5\u4F5C\u6D41\u72B6\u6001\u65E0\u6548 +workflow.config.invalid=\u5DE5\u4F5C\u6D41\u914D\u7F6E\u65E0\u6548 +workflow.transition.invalid=\u5DE5\u4F5C\u6D41\u6D41\u8F6C\u89C4\u5219\u65E0\u6548 +workflow.condition.invalid=\u5DE5\u4F5C\u6D41\u6761\u4EF6\u914D\u7F6E\u65E0\u6548 -workflow.instance.not.found=工作流实例不存在 -workflow.instance.already.completed=工作流实例已完成 -workflow.instance.already.canceled=工作流实例已取消 -workflow.instance.not.running=工作流实例未运行 -workflow.instance.not.paused=工作流实例不是暂停状态 +workflow.instance.not.found=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u4E0D\u5B58\u5728 +workflow.instance.already.completed=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u5DF2\u5B8C\u6210 +workflow.instance.already.canceled=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u5DF2\u53D6\u6D88 +workflow.instance.not.running=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u672A\u8FD0\u884C +workflow.instance.not.paused=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u4E0D\u662F\u6682\u505C\u72B6\u6001 -workflow.node.not.found=工作流节点不存在 -workflow.node.type.not.supported=不支持的节点类型 -workflow.node.config.invalid=节点配置无效 -workflow.node.execution.failed=节点执行失败 -workflow.node.timeout=节点执行超时 -workflow.node.config.error=节点配置错误 +workflow.node.not.found=\u5DE5\u4F5C\u6D41\u8282\u70B9\u4E0D\u5B58\u5728 +workflow.node.type.not.supported=\u4E0D\u652F\u6301\u7684\u8282\u70B9\u7C7B\u578B +workflow.node.config.invalid=\u8282\u70B9\u914D\u7F6E\u65E0\u6548 +workflow.node.execution.failed=\u8282\u70B9\u6267\u884C\u5931\u8D25 +workflow.node.timeout=\u8282\u70B9\u6267\u884C\u8D85\u65F6 +workflow.node.config.error=\u8282\u70B9\u914D\u7F6E\u9519\u8BEF -workflow.execution.error=工作流执行错误 -workflow.variable.not.found=工作流变量不存在 -workflow.variable.type.invalid=工作流变量类型无效 -workflow.permission.denied=无权限操作工作流 -workflow.approval.required=需要审批 -workflow.approval.rejected=审批被拒绝 -workflow.dependency.not.satisfied=工作流依赖条件未满足 -workflow.circular.dependency=工作流存在循环依赖 -workflow.schedule.invalid=工作流调度配置无效 -workflow.concurrent.limit.exceeded=工作流并发限制超出 +workflow.execution.error=\u5DE5\u4F5C\u6D41\u6267\u884C\u9519\u8BEF +workflow.variable.not.found=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u4E0D\u5B58\u5728 +workflow.variable.type.invalid=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u7C7B\u578B\u65E0\u6548 +workflow.permission.denied=\u65E0\u6743\u9650\u64CD\u4F5C\u5DE5\u4F5C\u6D41 +workflow.approval.required=\u9700\u8981\u5BA1\u6279 +workflow.approval.rejected=\u5BA1\u6279\u88AB\u62D2\u7EDD +workflow.dependency.not.satisfied=\u5DE5\u4F5C\u6D41\u4F9D\u8D56\u6761\u4EF6\u672A\u6EE1\u8DB3 +workflow.circular.dependency=\u5DE5\u4F5C\u6D41\u5B58\u5728\u5FAA\u73AF\u4F9D\u8D56 +workflow.schedule.invalid=\u5DE5\u4F5C\u6D41\u8C03\u5EA6\u914D\u7F6E\u65E0\u6548 +workflow.concurrent.limit.exceeded=\u5DE5\u4F5C\u6D41\u5E76\u53D1\u9650\u5236\u8D85\u51FA -# 工作流配置相关错误消息 -workflow.node.config.empty=节点配置不能为空 -workflow.transition.config.empty=流转配置不能为空 -workflow.form.config.empty=表单配置不能为空 -workflow.graph.config.empty=图形配置不能为空 +# \u5DE5\u4F5C\u6D41\u914D\u7F6E\u76F8\u5173\u9519\u8BEF\u6D88\u606F +workflow.node.config.empty=\u8282\u70B9\u914D\u7F6E\u4E0D\u80FD\u4E3A\u7A7A +workflow.transition.config.empty=\u6D41\u8F6C\u914D\u7F6E\u4E0D\u80FD\u4E3A\u7A7A +workflow.form.config.empty=\u8868\u5355\u914D\u7F6E\u4E0D\u80FD\u4E3A\u7A7A +workflow.graph.config.empty=\u56FE\u5F62\u914D\u7F6E\u4E0D\u80FD\u4E3A\u7A7A -# 工作流节点类型错误 (2200-2299) -workflow.node.type.not.found=节点类型不存在或已删除 -workflow.node.type.disabled=节点类型已禁用,无法使用 -workflow.node.type.code.exists=节点类型编码已存在 -workflow.node.type.invalid.category=无效的节点类型分类 -workflow.node.type.invalid.executor=无效的执行器配置 \ No newline at end of file +# \u5DE5\u4F5C\u6D41\u8282\u70B9\u7C7B\u578B\u9519\u8BEF (2200-2299) +workflow.node.type.not.found=\u8282\u70B9\u7C7B\u578B\u4E0D\u5B58\u5728\u6216\u5DF2\u5220\u9664 +workflow.node.type.disabled=\u8282\u70B9\u7C7B\u578B\u5DF2\u7981\u7528\uFF0C\u65E0\u6CD5\u4F7F\u7528 +workflow.node.type.code.exists=\u8282\u70B9\u7C7B\u578B\u7F16\u7801\u5DF2\u5B58\u5728 +workflow.node.type.invalid.category=\u65E0\u6548\u7684\u8282\u70B9\u7C7B\u578B\u5206\u7C7B +workflow.node.type.invalid.executor=\u65E0\u6548\u7684\u6267\u884C\u5668\u914D\u7F6E +workflow.node.executor.not.found=\u672A\u627E\u5230\u5DE5\u4F5C\u6D41\u8282\u70B9\u6267\u884C\u5668: {0} + +workflow.variable.serialize.error=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u5E8F\u5217\u5316\u5931\u8D25: {0} +workflow.variable.deserialize.error=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u53CD\u5E8F\u5217\u5316\u5931\u8D25: {0} diff --git a/backend/src/test/java/com/qqchen/deploy/backend/workflow/service/ConcurrentWorkflowVariableOperationsTest.java b/backend/src/test/java/com/qqchen/deploy/backend/workflow/service/ConcurrentWorkflowVariableOperationsTest.java new file mode 100644 index 00000000..e98e4e1b --- /dev/null +++ b/backend/src/test/java/com/qqchen/deploy/backend/workflow/service/ConcurrentWorkflowVariableOperationsTest.java @@ -0,0 +1,117 @@ +package com.qqchen.deploy.backend.workflow.service; + +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.workflow.service.impl.ConcurrentWorkflowVariableOperations; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.transaction.support.TransactionTemplate; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ConcurrentWorkflowVariableOperationsTest { + + @Mock + private IWorkflowVariableService variableService; + + @Mock + private TransactionTemplate transactionTemplate; + + @Mock + private WorkflowContext.Factory contextFactory; + + @InjectMocks + private ConcurrentWorkflowVariableOperations operations; + + private WorkflowInstance instance; + private WorkflowContext context; + + @BeforeEach + void setUp() { + instance = new WorkflowInstance(); + instance.setId(1L); + + context = mock(WorkflowContext.class); + + when(contextFactory.create(any(), anyMap())).thenReturn(context); + when(transactionTemplate.execute(any())).thenAnswer(invocation -> { + return invocation.getArgument(0, TransactionCallback.class).doInTransaction(null); + }); + } + + @Test + void setVariables_ShouldHandleConcurrentAccess() throws InterruptedException { + // 准备测试数据 + int threadCount = 10; + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + CountDownLatch latch = new CountDownLatch(threadCount); + + // 模拟多线程并发访问 + for (int i = 0; i < threadCount; i++) { + final int index = i; + executorService.submit(() -> { + try { + Map variables = new HashMap<>(); + variables.put("key" + index, "value" + index); + operations.setVariables(instance, variables); + } finally { + latch.countDown(); + } + }); + } + + // 等待所有线程完成 + latch.await(); + executorService.shutdown(); + + // 验证结果 + verify(variableService, times(threadCount)).setVariable(eq(instance), anyString(), any()); + } + + @Test + void getVariables_ShouldReturnCachedValues() { + // 准备测试数据 + Map expectedVariables = new HashMap<>(); + expectedVariables.put("key1", "value1"); + when(context.getVariables()).thenReturn(expectedVariables); + + // 第一次调用 + Map result1 = operations.getVariables(instance); + assertEquals(expectedVariables, result1); + + // 第二次调用应该返回缓存的结果 + Map result2 = operations.getVariables(instance); + assertEquals(expectedVariables, result2); + + // 验证 variableService 只被调用一次 + verify(variableService, times(1)).getVariables(instance); + } + + @Test + void clearContext_ShouldRemoveFromCache() { + // 首先获取变量以确保上下文被缓存 + operations.getVariables(instance); + + // 清除上下文 + operations.clearContext(instance); + + // 再次获取变量,应该重新从服务加载 + operations.getVariables(instance); + + // 验证 variableService 被调用了两次 + verify(variableService, times(2)).getVariables(instance); + } +}