可正常启动,工作流可以正常运行,但是只有一个开始节点。

This commit is contained in:
戚辰先生 2024-12-08 09:27:14 +08:00
parent 7bbdeefa9b
commit 591abe983d
46 changed files with 1393 additions and 653 deletions

View File

@ -196,6 +196,11 @@
<artifactId>commons-exec</artifactId> <artifactId>commons-exec</artifactId>
<version>1.3</version> <version>1.3</version>
</dependency> </dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -137,7 +137,18 @@ public enum ResponseCode {
WORKFLOW_NODE_TYPE_DISABLED(2201, "workflow.node.type.disabled"), WORKFLOW_NODE_TYPE_DISABLED(2201, "workflow.node.type.disabled"),
WORKFLOW_NODE_TYPE_CODE_EXISTS(2202, "workflow.node.type.code.exists"), 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_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 int code;
private final String messageKey; // 国际化消息key private final String messageKey; // 国际化消息key

View File

@ -45,14 +45,14 @@ public class WorkflowLogApiController extends BaseController<WorkflowLog, Workfl
@Operation(summary = "记录日志") @Operation(summary = "记录日志")
@PostMapping("/record") @PostMapping("/record")
public Response<Void> log( public Response<Void> recordLog(
@Parameter(description = "工作流实例ID", required = true) @RequestParam Long workflowInstanceId, @Parameter(description = "工作流实例ID", required = true) @RequestParam Long workflowInstanceId,
@Parameter(description = "节点ID") @RequestParam(required = false) String nodeId, @Parameter(description = "节点ID") @RequestParam(required = false) String nodeId,
@Parameter(description = "日志内容", required = true) @RequestParam String message, @Parameter(description = "日志内容", required = true) @RequestParam String message,
@Parameter(description = "日志级别", required = true) @RequestParam LogLevelEnum level, @Parameter(description = "日志级别", required = true) @RequestParam LogLevelEnum level,
@Parameter(description = "详细信息") @RequestParam(required = false) String detail @Parameter(description = "详细信息") @RequestParam(required = false) String detail
) { ) {
workflowLogService.log(workflowInstanceId, nodeId, message, level, detail); workflowLogService.recordLog(workflowInstanceId, nodeId, message, level, detail);
return Response.success(); return Response.success();
} }

View File

@ -13,7 +13,6 @@ import org.mapstruct.Mapping;
public interface NodeInstanceConverter extends BaseConverter<NodeInstance, NodeInstanceDTO> { public interface NodeInstanceConverter extends BaseConverter<NodeInstance, NodeInstanceDTO> {
@Override @Override
@Mapping(target = "workflowInstanceId", source = "workflowInstanceId")
@Mapping(target = "nodeId", source = "nodeId") @Mapping(target = "nodeId", source = "nodeId")
@Mapping(target = "nodeType", source = "nodeType") @Mapping(target = "nodeType", source = "nodeType")
@Mapping(target = "name", source = "name") @Mapping(target = "name", source = "name")
@ -28,7 +27,7 @@ public interface NodeInstanceConverter extends BaseConverter<NodeInstance, NodeI
NodeInstanceDTO toDto(NodeInstance entity); NodeInstanceDTO toDto(NodeInstance entity);
@Override @Override
@Mapping(target = "workflowInstanceId", source = "workflowInstanceId") @Mapping(target = "workflowInstance", ignore = true)
@Mapping(target = "nodeId", source = "nodeId") @Mapping(target = "nodeId", source = "nodeId")
@Mapping(target = "nodeType", source = "nodeType") @Mapping(target = "nodeType", source = "nodeType")
@Mapping(target = "name", source = "name") @Mapping(target = "name", source = "name")
@ -40,6 +39,5 @@ public interface NodeInstanceConverter extends BaseConverter<NodeInstance, NodeI
@Mapping(target = "output", source = "output") @Mapping(target = "output", source = "output")
@Mapping(target = "error", source = "error") @Mapping(target = "error", source = "error")
@Mapping(target = "preNodeId", source = "preNodeId") @Mapping(target = "preNodeId", source = "preNodeId")
@Mapping(target = "workflowInstance", ignore = true)
NodeInstance toEntity(NodeInstanceDTO dto); NodeInstance toEntity(NodeInstanceDTO dto);
} }

View File

@ -4,19 +4,10 @@ import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO; import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
/** /**
* 工作流定义转换器 * 工作流定义转换器
*/ */
@Mapper(config = BaseConverter.class, uses = {NodeDefinitionConverter.class}) @Mapper(componentModel = "spring")
public interface WorkflowDefinitionConverter extends BaseConverter<WorkflowDefinition, WorkflowDefinitionDTO> { public interface WorkflowDefinitionConverter extends BaseConverter<WorkflowDefinition, WorkflowDefinitionDTO> {
@Override
@Mapping(target = "nodes", source = "nodes")
WorkflowDefinitionDTO toDto(WorkflowDefinition entity);
@Override
@Mapping(target = "nodes", source = "nodes")
WorkflowDefinition toEntity(WorkflowDefinitionDTO dto);
} }

View File

@ -7,8 +7,6 @@ import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.util.List;
/** /**
* 工作流定义DTO * 工作流定义DTO
*/ */
@ -36,37 +34,30 @@ public class WorkflowDefinitionDTO extends BaseDTO {
/** /**
* 工作流状态 * 工作流状态
*/ */
@NotNull(message = "工作流状态不能为空")
private WorkflowDefinitionStatusEnum status; private WorkflowDefinitionStatusEnum status;
/** /**
* 版本号 * 版本号
*/ */
@NotNull(message = "版本号不能为空")
private Integer version; private Integer version;
/** /**
* 节点定义列表 * 节点配置(JSON)
*/
private List<NodeDefinitionDTO> nodes;
/**
* 节点配置
*/ */
private String nodeConfig; private String nodeConfig;
/** /**
* 流转配置 * 流转配置(JSON)
*/ */
private String transitionConfig; private String transitionConfig;
/** /**
* 表单定义 * 表单定义(JSON)
*/ */
private String formDefinition; private String formDefinition;
/** /**
* 图形信息 * 图形信息(JSON)
*/ */
private String graphDefinition; private String graphDefinition;

View File

@ -1,41 +1,46 @@
package com.qqchen.deploy.backend.workflow.dto; package com.qqchen.deploy.backend.workflow.dto;
import com.qqchen.deploy.backend.system.enums.LogLevelEnum;
import com.qqchen.deploy.backend.framework.dto.BaseDTO; import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import jakarta.validation.constraints.NotBlank; import com.qqchen.deploy.backend.system.enums.LogLevelEnum;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* 工作流日志DTO
*/
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class WorkflowLogDTO extends BaseDTO { public class WorkflowLogDTO extends BaseDTO {
/**
* 工作流实例ID
*/
@NotNull(message = "工作流实例ID不能为空")
private Long workflowInstanceId;
/** /**
* 节点ID * 节点ID
*/ */
private String nodeId; private String nodeId;
/**
* 日志内容
*/
@NotBlank(message = "日志内容不能为空")
private String message;
/** /**
* 日志级别 * 日志级别
*/ */
@NotNull(message = "日志级别不能为空")
private LogLevelEnum level; private LogLevelEnum level;
/**
* 日志内容
*/
private String message;
/** /**
* 详细信息 * 详细信息
*/ */
private String detail; private String detail;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 创建人
*/
private String createBy;
} }

View File

@ -2,7 +2,7 @@ package com.qqchen.deploy.backend.workflow.engine;
import com.qqchen.deploy.backend.framework.enums.ResponseCode; 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.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.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.engine.executor.NodeExecutor; import com.qqchen.deploy.backend.workflow.engine.executor.NodeExecutor;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance; 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.INodeInstanceRepository;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -41,7 +42,7 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
private Map<NodeTypeEnum, NodeExecutor> nodeExecutors; private Map<NodeTypeEnum, NodeExecutor> nodeExecutors;
@Resource @Resource
private DefaultWorkflowContext.Factory workflowContextFactory; private WorkflowVariableOperations variableOperations;
@Override @Override
@Transactional @Transactional
@ -59,19 +60,19 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
// 2. 创建工作流实例 // 2. 创建工作流实例
WorkflowInstance instance = new WorkflowInstance(); WorkflowInstance instance = new WorkflowInstance();
instance.setDefinition(definition); instance.setWorkflowDefinition(definition);
instance.setBusinessKey(businessKey);
// 设置工作流实例初始状态为 PENDING // 设置工作流实例初始状态为 PENDING
instance.setStatus(WorkflowInstanceStatusEnum.PENDING); instance.setStatus(WorkflowInstanceStatusEnum.PENDING);
workflowInstanceRepository.save(instance); workflowInstanceRepository.save(instance);
// 3. 创建工作流上下文 // 3. 设置工作流变量
WorkflowContext context = workflowContextFactory.create(instance); if (variables != null && !variables.isEmpty()) {
if (variables != null) { variableOperations.setVariables(instance.getId(), variables);
variables.forEach((key, value) -> context.setVariable(key, value));
} }
// 4. 创建开始节点实例并启动工作流 // 4. 创建开始节点实例并启动工作流
NodeInstance startNode = createStartNode(definition, instance.getId()); NodeInstance startNode = createStartNode(definition, instance);
instance.start(); instance.start();
workflowInstanceRepository.save(instance); workflowInstanceRepository.save(instance);
executeNode(startNode.getId()); executeNode(startNode.getId());
@ -90,43 +91,33 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING); throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING);
} }
try { // 获取节点执行器
// 1. 获取节点执行器 NodeExecutor executor = nodeExecutors.get(nodeInstance.getNodeType());
NodeExecutor executor = nodeExecutors.get(nodeInstance.getNodeType()); if (executor == null) {
if (executor == null) { throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTOR_NOT_FOUND);
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_TYPE_NOT_SUPPORTED); }
}
// 2. 执行节点 // 创建工作流上下文
WorkflowContext context = workflowContextFactory.create(instance); WorkflowContextOperations context = DefaultWorkflowContext.builder()
executor.execute(nodeInstance, context); .workflowInstance(instance)
.variableOperations(variableOperations)
.build();
// 3. 更新节点状态 // 执行节点
nodeInstance.setStatus(NodeStatusEnum.COMPLETED); executor.execute(nodeInstance, context);
nodeInstance.setEndTime(LocalDateTime.now());
nodeInstanceRepository.save(nodeInstance);
// 4. 检查是否所有节点都已完成 // 3. 更新节点状态
List<NodeInstance> uncompletedNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatusNot( nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
instance.getId(), NodeStatusEnum.COMPLETED); nodeInstance.setEndTime(LocalDateTime.now());
nodeInstanceRepository.save(nodeInstance);
if (uncompletedNodes.isEmpty()) { // 4. 检查是否所有节点都已完成
instance.complete(); List<NodeInstance> uncompletedNodes = nodeInstanceRepository.findByWorkflowInstanceAndStatusNot(
workflowInstanceRepository.save(instance); instance, NodeStatusEnum.COMPLETED);
}
} catch (Exception e) { if (uncompletedNodes.isEmpty()) {
// 更新节点状态为失败 instance.complete();
nodeInstance.setStatus(NodeStatusEnum.FAILED);
nodeInstance.setEndTime(LocalDateTime.now());
nodeInstance.setError(e.getMessage());
nodeInstanceRepository.save(nodeInstance);
// 更新工作流实例状态
instance.updateError(e.getMessage());
workflowInstanceRepository.save(instance); 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) NodeInstance nodeInstance = nodeInstanceRepository.findById(nodeInstanceId)
.orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_NOT_FOUND)); .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(); 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); nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
@ -158,22 +145,11 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
WorkflowInstance instance = workflowInstanceRepository.findById(instanceId) WorkflowInstance instance = workflowInstanceRepository.findById(instanceId)
.orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND)); .orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND));
// 检查工作流实例状态
if (!instance.canTerminate()) {
throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING);
}
// 终止所有运行中的节点
List<NodeInstance> runningNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(
instanceId, NodeStatusEnum.RUNNING);
for (NodeInstance node : runningNodes) {
node.setStatus(NodeStatusEnum.TERMINATED);
node.setEndTime(LocalDateTime.now());
nodeInstanceRepository.save(node);
}
instance.terminate(reason); instance.terminate(reason);
workflowInstanceRepository.save(instance); workflowInstanceRepository.save(instance);
// 清理上下文缓存
variableOperations.clearVariables(instance.getId());
} }
@Override @Override
@ -219,8 +195,8 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
workflowInstanceRepository.save(instance); workflowInstanceRepository.save(instance);
// 获取失败的节点 // 获取失败的节点
List<NodeInstance> failedNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus( List<NodeInstance> failedNodes = nodeInstanceRepository.findByWorkflowInstanceAndStatus(
instanceId, NodeStatusEnum.FAILED); instance, NodeStatusEnum.FAILED);
// 重试失败的节点 // 重试失败的节点
for (NodeInstance node : failedNodes) { 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(); NodeInstance startNode = new NodeInstance();
startNode.setWorkflowInstanceId(instanceId); startNode.setWorkflowInstance(instance);
startNode.setNodeId("start"); startNode.setNodeId("start");
startNode.setNodeType(NodeTypeEnum.START); startNode.setNodeType(NodeTypeEnum.START);
startNode.setName("开始节点"); startNode.setName("开始节点");

View File

@ -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.NodeInstance;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations;
import lombok.Data; import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -10,14 +12,15 @@ import java.util.concurrent.ConcurrentHashMap;
/** /**
* 工作流上下文 * 工作流上下文
* 负责管理工作流执行过程中的运行时状态
*/ */
@Data @Data
public class WorkflowContext { public class WorkflowContext {
/** /**
* 工作流实例 * 工作流实例ID
*/ */
private WorkflowInstance workflowInstance; private final Long instanceId;
/** /**
* 当前节点实例 * 当前节点实例
@ -30,32 +33,57 @@ public class WorkflowContext {
private List<NodeInstance> allNodes; private List<NodeInstance> allNodes;
/** /**
* 工作流变量 * 临时变量节点间传递不持久化
*/ */
private Map<String, Object> variables; private final Map<String, Object> tempVariables;
/** /**
* 临时变量节点间传递 * 变量操作服务
*/ */
private Map<String, Object> tempVariables; private final WorkflowVariableOperations variableOperations;
public WorkflowContext() { public WorkflowContext(Long instanceId, WorkflowVariableOperations variableOperations) {
this.variables = new ConcurrentHashMap<>(); this.instanceId = instanceId;
this.tempVariables = new ConcurrentHashMap<>(); this.tempVariables = new ConcurrentHashMap<>();
this.variableOperations = variableOperations;
} }
/** /**
* 获取变量值 * 获取变量值委托给 WorkflowVariableOperations
* @deprecated 建议直接使用 WorkflowVariableOperations
*/ */
@Deprecated
public Object getVariable(String key) { 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) { public void setVariable(String key, Object value) {
variables.put(key, value); Map<String, Object> vars = new ConcurrentHashMap<>();
vars.put(key, value);
variableOperations.setVariables(instanceId, vars);
}
/**
* 获取所有变量委托给 WorkflowVariableOperations
* @deprecated 建议直接使用 WorkflowVariableOperations
*/
@Deprecated
public Map<String, Object> getVariables() {
return variableOperations.getVariables(instanceId);
}
/**
* 设置多个变量委托给 WorkflowVariableOperations
* @deprecated 建议直接使用 WorkflowVariableOperations
*/
@Deprecated
public void setVariables(Map<String, Object> variables) {
variableOperations.setVariables(instanceId, variables);
} }
/** /**
@ -72,6 +100,13 @@ public class WorkflowContext {
tempVariables.put(key, value); tempVariables.put(key, value);
} }
/**
* 获取所有临时变量
*/
public Map<String, Object> getTempVariables() {
return new ConcurrentHashMap<>(tempVariables);
}
/** /**
* 清除临时变量 * 清除临时变量
*/ */

View File

@ -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.system.enums.LogLevelEnum;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService; import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations;
import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService;
import lombok.Getter; import lombok.Getter;
import org.springframework.stereotype.Component; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class DefaultWorkflowContext implements WorkflowContext { /**
* 默认工作流上下文实现
*/
public class DefaultWorkflowContext implements WorkflowContextOperations {
private static final Logger logger = LoggerFactory.getLogger(DefaultWorkflowContext.class);
@Getter @Getter
private final WorkflowInstance instance; private final WorkflowInstance workflowInstance;
private final Map<String, Object> variables; private final WorkflowVariableOperations variableOperations;
private final IWorkflowVariableService variableService; public DefaultWorkflowContext(WorkflowInstance workflowInstance, WorkflowVariableOperations variableOperations) {
this.workflowInstance = workflowInstance;
this.variableOperations = variableOperations;
}
private final IWorkflowLogService logService; @Override
public WorkflowInstance getInstance() {
return workflowInstance;
}
private DefaultWorkflowContext(WorkflowInstance instance, IWorkflowVariableService variableService, IWorkflowLogService logService) { @Override
this.instance = instance; public Object getVariable(String key) {
this.variables = new HashMap<>(); return variableOperations.getVariable(workflowInstance.getId(), key);
this.variableService = variableService; }
this.logService = logService;
@Override
public void setVariable(String key, Object value) {
variableOperations.setVariable(workflowInstance.getId(), key, value);
}
@Override
public void setVariables(Map<String, Object> variables) {
variableOperations.setVariables(workflowInstance.getId(), variables);
} }
@Override @Override
public Map<String, Object> getVariables() { public Map<String, Object> 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 @Override
public void log(String message, LogLevelEnum level) { 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 @Override
public void log(String message, String detail, LogLevelEnum level) { public void log(String message, String detail, LogLevelEnum level) {
logService.log(instance.getId(), null, message, level, detail); 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);
}
} }
/** public static class Builder {
* 工作流上下文工厂类 private WorkflowInstance workflowInstance;
*/
@Component
public static class Factory {
private final IWorkflowVariableService variableService;
private final IWorkflowLogService logService;
public Factory(IWorkflowVariableService variableService, IWorkflowLogService logService) { private WorkflowVariableOperations variableOperations;
this.variableService = variableService;
this.logService = logService; public Builder workflowInstance(WorkflowInstance workflowInstance) {
this.workflowInstance = workflowInstance;
return this;
} }
public DefaultWorkflowContext create(WorkflowInstance instance) { public Builder variableOperations(WorkflowVariableOperations variableOperations) {
return new DefaultWorkflowContext(instance, variableService, logService); this.variableOperations = variableOperations;
return this;
} }
public DefaultWorkflowContext build() {
return new DefaultWorkflowContext(workflowInstance, variableOperations);
}
}
public static Builder builder() {
return new Builder();
} }
} }

View File

@ -6,28 +6,37 @@ import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import java.util.Map; import java.util.Map;
/** /**
* 工作流上下文 * 工作流上下文操作接口
* 定义工作流执行过程中的上下文操作能力
*/ */
public interface WorkflowContext { public interface WorkflowContextOperations {
/** /**
* 获取工作流实例 * 获取工作流实例
*/ */
WorkflowInstance getInstance(); WorkflowInstance getInstance();
void setVariables(Map<String, Object> variables);
/** /**
* 获取所有变量 * 获取所有变量
* @deprecated 使用 WorkflowVariableOperations 替代
*/ */
@Deprecated
Map<String, Object> getVariables(); Map<String, Object> getVariables();
/** /**
* 获取变量 * 获取变量
* @deprecated 使用 WorkflowVariableOperations 替代
*/ */
@Deprecated
Object getVariable(String name); Object getVariable(String name);
/** /**
* 设置变量 * 设置变量
* @deprecated 使用 WorkflowVariableOperations 替代
*/ */
@Deprecated
void setVariable(String name, Object value); void setVariable(String name, Object value);
/** /**

View File

@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.system.enums.LogLevelEnum; 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.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
@ -34,7 +34,7 @@ public class EndNodeExecutor implements NodeExecutor {
} }
@Override @Override
public void execute(NodeInstance nodeInstance, WorkflowContext context) { public void execute(NodeInstance nodeInstance, WorkflowContextOperations context) {
// 1. 完成结束节点 // 1. 完成结束节点
nodeInstance.setStatus(NodeStatusEnum.COMPLETED); nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
nodeInstance.setEndTime(LocalDateTime.now()); nodeInstance.setEndTime(LocalDateTime.now());
@ -59,7 +59,7 @@ public class EndNodeExecutor implements NodeExecutor {
} }
@Override @Override
public void terminate(NodeInstance nodeInstance, WorkflowContext context) { public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) {
// 结束节点无需终止操作 // 结束节点无需终止操作
} }

View File

@ -2,7 +2,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.framework.enums.ResponseCode; 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.engine.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
@ -31,7 +31,7 @@ public class GatewayNodeExecutor implements NodeExecutor {
} }
@Override @Override
public void execute(NodeInstance nodeInstance, WorkflowContext context) { public void execute(NodeInstance nodeInstance, WorkflowContextOperations context) {
try { try {
GatewayConfig config = objectMapper.readValue(nodeInstance.getConfig(), GatewayConfig.class); 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 { try {
for (GatewayCondition condition : config.getConditions()) { for (GatewayCondition condition : config.getConditions()) {
if (evaluateCondition(condition.getExpression(), context)) { 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 { try {
List<String> nextNodeIds = new ArrayList<>(); List<String> nextNodeIds = new ArrayList<>();
for (GatewayCondition condition : config.getConditions()) { 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 { try {
List<String> nextNodeIds = new ArrayList<>(); List<String> nextNodeIds = new ArrayList<>();
for (GatewayCondition condition : config.getConditions()) { 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 { try {
StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
evaluationContext.setVariables(context.getVariables()); evaluationContext.setVariables(context.getVariables());
@ -140,7 +140,7 @@ public class GatewayNodeExecutor implements NodeExecutor {
} }
@Override @Override
public void terminate(NodeInstance nodeInstance, WorkflowContext context) { public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) {
// Gateway nodes are instant operations, no need to terminate // Gateway nodes are instant operations, no need to terminate
} }

View File

@ -1,6 +1,6 @@
package com.qqchen.deploy.backend.workflow.engine.executor; 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.entity.NodeInstance;
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; 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);
} }

View File

@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.system.enums.LogLevelEnum; 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.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
@ -26,7 +26,7 @@ public class StartNodeExecutor implements NodeExecutor {
} }
@Override @Override
public void execute(NodeInstance nodeInstance, WorkflowContext context) { public void execute(NodeInstance nodeInstance, WorkflowContextOperations context) {
// 开始节点直接完成 // 开始节点直接完成
nodeInstance.setStatus(NodeStatusEnum.COMPLETED); nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
context.log("开始节点执行完成", LogLevelEnum.INFO); context.log("开始节点执行完成", LogLevelEnum.INFO);
@ -43,7 +43,7 @@ public class StartNodeExecutor implements NodeExecutor {
} }
@Override @Override
public void terminate(NodeInstance nodeInstance, WorkflowContext context) { public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) {
// 开始节点无需终止操作 // 开始节点无需终止操作
} }

View File

@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.system.enums.LogLevelEnum;
import com.qqchen.deploy.backend.framework.enums.ResponseCode; 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.engine.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
@ -25,7 +25,7 @@ public class TaskNodeExecutor implements NodeExecutor {
} }
@Override @Override
public void execute(NodeInstance nodeInstance, WorkflowContext context) { public void execute(NodeInstance nodeInstance, WorkflowContextOperations context) {
try { try {
// 1. 解析任务配置 // 1. 解析任务配置
TaskConfig config = parseConfig(nodeInstance.getConfig()); TaskConfig config = parseConfig(nodeInstance.getConfig());
@ -57,7 +57,7 @@ public class TaskNodeExecutor implements NodeExecutor {
} }
@Override @Override
public void terminate(NodeInstance nodeInstance, WorkflowContext context) { public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) {
// 终止任务执行 // 终止任务执行
TaskConfig config = parseConfig(nodeInstance.getConfig()); TaskConfig config = parseConfig(nodeInstance.getConfig());
terminateTask(config, nodeInstance, context); 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()) { switch (config.getType()) {
case HTTP: case HTTP:
executeHttpTask(config, nodeInstance, context); 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()) { switch (config.getType()) {
case HTTP: 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请求执行 // TODO: 实现HTTP请求执行
} }
private void executeJavaTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { private void executeJavaTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) {
// TODO: 实现Java方法调用 // TODO: 实现Java方法调用
} }
private void terminateHttpTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { private void terminateHttpTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) {
// TODO: 实现HTTP请求终止 // TODO: 实现HTTP请求终止
} }
private void terminateJavaTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { private void terminateJavaTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) {
// TODO: 实现Java方法终止 // TODO: 实现Java方法终止
} }
} }

View File

@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor.task;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.system.enums.LogLevelEnum;
import com.qqchen.deploy.backend.framework.enums.ResponseCode; 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.engine.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -21,7 +21,7 @@ public class HttpTaskExecutor implements TaskExecutor {
private final RestTemplate restTemplate = new RestTemplate(); private final RestTemplate restTemplate = new RestTemplate();
@Override @Override
public void execute(NodeInstance nodeInstance, WorkflowContext context, Map<String, Object> parameters) { public void execute(NodeInstance nodeInstance, WorkflowContextOperations context, Map<String, Object> parameters) {
String url = (String) parameters.get("url"); String url = (String) parameters.get("url");
HttpMethod method = HttpMethod.valueOf((String) parameters.getOrDefault("method", "GET")); HttpMethod method = HttpMethod.valueOf((String) parameters.getOrDefault("method", "GET"));
Object body = parameters.get("body"); Object body = parameters.get("body");
@ -55,7 +55,7 @@ public class HttpTaskExecutor implements TaskExecutor {
} }
@Override @Override
public void terminate(NodeInstance nodeInstance, WorkflowContext context) { public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) {
// HTTP请求无需终止操作 // HTTP请求无需终止操作
} }
} }

View File

@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor.task;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.system.enums.LogLevelEnum;
import com.qqchen.deploy.backend.framework.enums.ResponseCode; 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.engine.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -24,14 +24,14 @@ public class JavaTaskExecutor implements TaskExecutor {
private final ObjectMapper objectMapper = new ObjectMapper(); private final ObjectMapper objectMapper = new ObjectMapper();
@Override @Override
public void execute(NodeInstance nodeInstance, WorkflowContext context, Map<String, Object> parameters) { public void execute(NodeInstance nodeInstance, WorkflowContextOperations context, Map<String, Object> parameters) {
String className = parameters.get("className").toString(); String className = parameters.get("className").toString();
String methodName = parameters.get("methodName").toString(); String methodName = parameters.get("methodName").toString();
try { try {
Class<?> clazz = Class.forName(className); Class<?> clazz = Class.forName(className);
Object instance = applicationContext.getBean(clazz); 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); method.invoke(instance, nodeInstance, context, parameters);
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Class not found: " + className); throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Class not found: " + className);
@ -43,7 +43,7 @@ public class JavaTaskExecutor implements TaskExecutor {
} }
@Override @Override
public void terminate(NodeInstance nodeInstance, WorkflowContext context) { public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) {
// Java任务无法中断记录日志 // Java任务无法中断记录日志
context.log("Java task cannot be terminated: " + nodeInstance.getNodeId(), LogLevelEnum.WARN); context.log("Java task cannot be terminated: " + nodeInstance.getNodeId(), LogLevelEnum.WARN);
} }

View File

@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor.task;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.system.enums.LogLevelEnum; import com.qqchen.deploy.backend.system.enums.LogLevelEnum;
import com.qqchen.deploy.backend.framework.enums.ResponseCode; 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.engine.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -21,7 +21,7 @@ public class ShellTaskExecutor implements TaskExecutor {
private final ObjectMapper objectMapper = new ObjectMapper(); private final ObjectMapper objectMapper = new ObjectMapper();
@Override @Override
public void execute(NodeInstance nodeInstance, WorkflowContext context, Map<String, Object> parameters) { public void execute(NodeInstance nodeInstance, WorkflowContextOperations context, Map<String, Object> parameters) {
String command = (String) parameters.get("command"); String command = (String) parameters.get("command");
Integer timeout = (Integer) parameters.getOrDefault("timeout", 300); Integer timeout = (Integer) parameters.getOrDefault("timeout", 300);
@ -60,7 +60,7 @@ public class ShellTaskExecutor implements TaskExecutor {
} }
@Override @Override
public void terminate(NodeInstance nodeInstance, WorkflowContext context) { public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) {
// TODO: 实现Shell命令终止逻辑 // TODO: 实现Shell命令终止逻辑
} }
} }

View File

@ -1,6 +1,6 @@
package com.qqchen.deploy.backend.workflow.engine.executor.task; 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 com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import java.util.Map; import java.util.Map;
@ -13,10 +13,10 @@ public interface TaskExecutor {
/** /**
* 执行任务 * 执行任务
*/ */
void execute(NodeInstance nodeInstance, WorkflowContext context, Map<String, Object> parameters); void execute(NodeInstance nodeInstance, WorkflowContextOperations context, Map<String, Object> parameters);
/** /**
* 终止任务 * 终止任务
*/ */
void terminate(NodeInstance nodeInstance, WorkflowContext context); void terminate(NodeInstance nodeInstance, WorkflowContextOperations context);
} }

View File

@ -1,88 +1,134 @@
package com.qqchen.deploy.backend.workflow.engine.node; 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.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.engine.executor.NodeExecutor;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance; 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.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.IWorkflowLogService;
import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
/** /**
* 抽象节点执行器 * 抽象节点执行器
*/ */
@Slf4j @Slf4j
public abstract class AbstractNodeExecutor implements NodeExecutor { public abstract class AbstractNodeExecutor implements NodeExecutor {
@Resource
protected WorkflowVariableOperations variableOperations;
@Resource
protected INodeInstanceRepository nodeInstanceRepository;
@Resource @Resource
protected IWorkflowLogService workflowLogService; protected IWorkflowLogService workflowLogService;
@Override @Override
public void execute(NodeInstance nodeInstance, WorkflowContext context) { public void execute(NodeInstance nodeInstance, WorkflowContextOperations context) {
try { try {
// 1. 记录系统日志 // 1. 前置处理
logSystem(context.getInstance(), LogLevelEnum.INFO, beforeExecute(nodeInstance, context);
String.format("开始执行节点: %s[%s]", nodeInstance.getName(), nodeInstance.getNodeId()),
null);
// 2. 记录节点开始日志 // 2. 执行节点逻辑
logNodeStart(nodeInstance);
// 3. 执行节点
doExecute(nodeInstance, context); doExecute(nodeInstance, context);
// 4. 记录节点完成日志 // 3. 后置处理
logNodeComplete(nodeInstance); afterExecute(nodeInstance, context);
// 4. 更新节点状态
updateNodeStatus(nodeInstance, true);
} catch (Exception e) { } catch (Exception e) {
// 5. 记录错误日志 // 5. 异常处理
logSystem(context.getInstance(), LogLevelEnum.ERROR, handleExecutionError(nodeInstance, context, e);
String.format("节点执行失败: %s[%s]", nodeInstance.getName(), nodeInstance.getNodeId()), throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
e.getMessage());
logNodeError(nodeInstance, e.getMessage());
throw 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) { protected void logSystem(NodeInstance nodeInstance, LogLevelEnum level, String message, String detail) {
workflowLogService.log(instance.getId(), null, message, level, detail); workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(), level, message, detail);
} }
/** /**
* 记录节点开始日志 * 记录节点开始日志
*/ */
protected void logNodeStart(NodeInstance nodeInstance) { protected void logNodeStart(NodeInstance nodeInstance) {
workflowLogService.log(nodeInstance.getWorkflowInstanceId(), nodeInstance.getNodeId(), workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(),
String.format("节点开始执行: %s", nodeInstance.getName()), LogLevelEnum.INFO, null); LogLevelEnum.INFO, String.format("节点开始执行: %s", nodeInstance.getName()), null);
nodeInstance.setStatus(NodeStatusEnum.RUNNING); nodeInstance.setStatus(NodeStatusEnum.RUNNING);
nodeInstance.setStartTime(LocalDateTime.now());
nodeInstanceRepository.save(nodeInstance);
} }
/** /**
* 记录节点完成日志 * 记录节点完成日志
*/ */
protected void logNodeComplete(NodeInstance nodeInstance) { protected void logNodeComplete(NodeInstance nodeInstance) {
workflowLogService.log(nodeInstance.getWorkflowInstanceId(), nodeInstance.getNodeId(), workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(),
String.format("节点执行完成: %s", nodeInstance.getName()), LogLevelEnum.INFO, null); LogLevelEnum.INFO, String.format("节点执行完成: %s", nodeInstance.getName()), null);
nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
} }
/** /**
* 记录节点错误日志 * 记录节点错误日志
*/ */
protected void logNodeError(NodeInstance nodeInstance, String error) { protected void logNodeError(NodeInstance nodeInstance, String error) {
workflowLogService.log(nodeInstance.getWorkflowInstanceId(), nodeInstance.getNodeId(), workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(),
String.format("节点执行失败: %s", nodeInstance.getName()), LogLevelEnum.ERROR, error); LogLevelEnum.ERROR, String.format("节点执行失败: %s", nodeInstance.getName()), error);
nodeInstance.setStatus(NodeStatusEnum.FAILED);
nodeInstance.setError(error); nodeInstance.setError(error);
} }
} }

View File

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

View File

@ -1,13 +1,14 @@
package com.qqchen.deploy.backend.workflow.engine.node.executor; package com.qqchen.deploy.backend.workflow.engine.node.executor;
import com.fasterxml.jackson.databind.ObjectMapper; 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.system.enums.LogLevelEnum;
import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; 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.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -16,6 +17,7 @@ import jakarta.annotation.Resource;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.*; import java.util.concurrent.*;
@ -27,6 +29,9 @@ public class ShellNodeExecutor extends AbstractNodeExecutor {
@Resource @Resource
private ObjectMapper objectMapper; private ObjectMapper objectMapper;
@Resource
private WorkflowVariableOperations variableOperations;
private final ExecutorService executorService = Executors.newCachedThreadPool(); private final ExecutorService executorService = Executors.newCachedThreadPool();
@Override @Override
@ -62,7 +67,7 @@ public class ShellNodeExecutor extends AbstractNodeExecutor {
} }
@Override @Override
protected void doExecute(NodeInstance nodeInstance, WorkflowContext context) { protected void doExecute(NodeInstance nodeInstance, WorkflowContextOperations context) {
try { try {
String configJson = nodeInstance.getConfig(); String configJson = nodeInstance.getConfig();
ShellConfig config = objectMapper.readValue(configJson, ShellConfig.class); ShellConfig config = objectMapper.readValue(configJson, ShellConfig.class);
@ -85,9 +90,7 @@ public class ShellNodeExecutor extends AbstractNodeExecutor {
} catch (Exception e) { } catch (Exception e) {
lastException = e; lastException = e;
if (attempt < maxAttempts) { 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); 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 processBuilder = new ProcessBuilder();
processBuilder.command("sh", "-c", config.getScript()); processBuilder.command("sh", "-c", config.getScript());
@ -149,11 +152,11 @@ public class ShellNodeExecutor extends AbstractNodeExecutor {
exitCode, String.join("\n", error))); exitCode, String.join("\n", error)));
} }
// 设置输出结果 // 设置输出变量
nodeInstance.setOutput(String.join("\n", output)); Map<String, Object> outputVariables = new HashMap<>();
if (!error.isEmpty()) { outputVariables.put("shellOutput", String.join("\n", output));
nodeInstance.setError(String.join("\n", error)); 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); 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 @Override
public void terminate(NodeInstance nodeInstance, WorkflowContext context) { public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) {
// TODO: 实现终止Shell进程的逻辑 // TODO: 实现终止Shell进程的逻辑
context.log("Shell node termination is not implemented yet", LogLevelEnum.WARN); context.log("Shell node termination is not implemented yet", LogLevelEnum.WARN);
} }

View File

@ -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.system.enums.LogLevelEnum;
import com.qqchen.deploy.backend.framework.enums.ResponseCode; 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.engine.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.entity.NodeDefinition; import com.qqchen.deploy.backend.workflow.entity.NodeDefinition;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance; 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列表 // 1. 获取下一个节点ID列表
List<String> nextNodeIds = transitionRuleEngine.getNextNodeIds(currentNode, definition, context); List<String> nextNodeIds = transitionRuleEngine.getNextNodeIds(currentNode, definition, context);
@ -46,7 +46,7 @@ public class TransitionExecutor {
} }
NodeInstance nextNode = new NodeInstance(); NodeInstance nextNode = new NodeInstance();
nextNode.setWorkflowInstanceId(currentNode.getWorkflowInstanceId()); nextNode.setWorkflowInstance(currentNode.getWorkflowInstance());
nextNode.setNodeId(nodeId); nextNode.setNodeId(nodeId);
nextNode.setNodeType(nextNodeDef.getType()); nextNode.setNodeType(nextNodeDef.getType());
nextNode.setName(nextNodeDef.getName()); nextNode.setName(nextNodeDef.getName());

View File

@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.engine.transition;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.framework.enums.ResponseCode; 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.engine.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
@ -28,7 +28,7 @@ public class TransitionRuleEngine {
/** /**
* 获取下一个节点ID列表 * 获取下一个节点ID列表
*/ */
public List<String> getNextNodeIds(NodeInstance currentNode, WorkflowDefinition definition, WorkflowContext context) { public List<String> getNextNodeIds(NodeInstance currentNode, WorkflowDefinition definition, WorkflowContextOperations context) {
try { try {
// 解析流转规则 // 解析流转规则
List<TransitionRule> rules = parseTransitionRules(definition.getTransitionConfig()); List<TransitionRule> 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()) { if (condition == null || condition.trim().isEmpty()) {
return true; return true;
} }

View File

@ -17,17 +17,11 @@ import java.time.LocalDateTime;
@LogicDelete @LogicDelete
public class NodeInstance extends Entity<Long> { public class NodeInstance extends Entity<Long> {
/**
* 工作流实例ID用于优化查询
*/
@Column(name = "workflow_instance_id", nullable = false)
private Long workflowInstanceId;
/** /**
* 工作流实例 * 工作流实例
*/ */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "workflow_instance_id", insertable = false, updatable = false) @JoinColumn(name = "workflow_instance_id", nullable = false)
private WorkflowInstance workflowInstance; private WorkflowInstance workflowInstance;
/** /**
@ -99,10 +93,6 @@ public class NodeInstance extends Entity<Long> {
return config; return config;
} }
public Long getWorkflowInstanceId() {
return workflowInstanceId;
}
public String getPreNodeId() { public String getPreNodeId() {
return preNodeId; return preNodeId;
} }

View File

@ -75,12 +75,6 @@ public class WorkflowDefinition extends Entity<Long> {
@Column(name = "graph_definition", columnDefinition = "TEXT") @Column(name = "graph_definition", columnDefinition = "TEXT")
private String graphDefinition; private String graphDefinition;
/**
* 节点定义列表
*/
@OneToMany(mappedBy = "workflowDefinition", cascade = CascadeType.ALL, orphanRemoval = true)
private List<NodeDefinition> nodes = new ArrayList<>();
/** /**
* 版本号 * 版本号
*/ */

View File

@ -40,7 +40,7 @@ public class WorkflowInstance extends Entity<Long> {
/** /**
* 业务标识 * 业务标识
*/ */
@Column(name = "business_key") @Column(name = "business_key", nullable = false)
private String businessKey; private String businessKey;
/** /**

View File

@ -1,9 +1,12 @@
package com.qqchen.deploy.backend.workflow.entity; 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.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.domain.Entity; import com.qqchen.deploy.backend.framework.domain.Entity;
import com.qqchen.deploy.backend.system.enums.LogLevelEnum;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -13,38 +16,27 @@ import lombok.EqualsAndHashCode;
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "wf_log") @Table(name = "wf_log")
@LogicDelete @LogicDelete
@jakarta.persistence.Entity
public class WorkflowLog extends Entity<Long> { public class WorkflowLog extends Entity<Long> {
/** @ManyToOne(fetch = FetchType.LAZY)
* 工作流实例ID @JoinColumn(name = "workflow_instance_id", nullable = false)
*/ private WorkflowInstance workflowInstance;
@Column(name = "workflow_instance_id", nullable = false)
private Long workflowInstanceId;
/**
* 节点ID
*/
@Column(name = "node_id") @Column(name = "node_id")
private String nodeId; private String nodeId;
/**
* 日志内容
*/
@Column(nullable = false)
private String content;
/** /**
* 日志级别 * 日志级别
*/ */
@Column(nullable = false) @Column(name = "level")
private LogLevelEnum level; private LogLevelEnum level;
/** @Column(name = "message", nullable = false, columnDefinition = "TEXT")
* 详细信息 private String message;
*/
@Column(columnDefinition = "TEXT") @Column(name = "detail", columnDefinition = "TEXT")
private String detail; private String detail;
} }

View File

@ -18,10 +18,11 @@ import lombok.EqualsAndHashCode;
public class WorkflowVariable extends Entity<Long> { public class WorkflowVariable extends Entity<Long> {
/** /**
* 工作流实例ID * 工作流实例
*/ */
@Column(name = "workflow_instance_id", nullable = false) @ManyToOne(fetch = FetchType.LAZY)
private Long workflowInstanceId; @JoinColumn(name = "workflow_instance_id", nullable = false)
private WorkflowInstance workflowInstance;
/** /**
* 变量名称 * 变量名称

View File

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

View File

@ -2,6 +2,7 @@ package com.qqchen.deploy.backend.workflow.repository;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance; 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.enums.NodeStatusEnum;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
@ -15,22 +16,36 @@ public interface INodeInstanceRepository extends IBaseRepository<NodeInstance, L
/** /**
* 查询工作流实例的所有节点 * 查询工作流实例的所有节点
*/ */
@Query("SELECT n FROM NodeInstance n WHERE n.workflowInstanceId = :instanceId AND n.deleted = false ORDER BY n.createTime") @Query("SELECT n FROM NodeInstance n WHERE n.workflowInstance = :instance AND n.deleted = false ORDER BY n.createTime")
List<NodeInstance> findByWorkflowInstanceIdOrderByCreateTime(@Param("instanceId") Long instanceId); List<NodeInstance> 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<NodeInstance> 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<NodeInstance> findByWorkflowInstanceIdAndStatus(
@Param("workflowInstanceId") Long workflowInstanceId,
@Param("status") NodeStatusEnum status);
/** /**
* 查询指定状态的节点实例 * 查询指定状态的节点实例
*/ */
List<NodeInstance> findByWorkflowInstanceIdAndStatusAndDeletedFalse(Long workflowInstanceId, NodeStatusEnum status); List<NodeInstance> findByWorkflowInstanceAndStatusAndDeletedFalse(WorkflowInstance instance, NodeStatusEnum status);
List<NodeInstance> findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, NodeStatusEnum status); List<NodeInstance> findByWorkflowInstanceAndStatus(WorkflowInstance instance, NodeStatusEnum status);
List<NodeInstance> findByWorkflowInstanceId(Long workflowInstanceId); List<NodeInstance> findByWorkflowInstance(WorkflowInstance instance);
void deleteByWorkflowInstanceId(Long workflowInstanceId); void deleteByWorkflowInstance(WorkflowInstance instance);
/** /**
* 查询不是指定状态的节点实例 * 查询不是指定状态的节点实例
*/ */
List<NodeInstance> findByWorkflowInstanceIdAndStatusNot(Long workflowInstanceId, NodeStatusEnum status); List<NodeInstance> findByWorkflowInstanceAndStatusNot(WorkflowInstance instance, NodeStatusEnum status);
} }

View File

@ -1,6 +1,7 @@
package com.qqchen.deploy.backend.workflow.repository; package com.qqchen.deploy.backend.workflow.repository;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog; import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
@ -17,17 +18,17 @@ public interface IWorkflowLogRepository extends IBaseRepository<WorkflowLog, Lon
/** /**
* 查询工作流实例的所有日志 * 查询工作流实例的所有日志
*/ */
@Query("SELECT l FROM WorkflowLog l WHERE l.workflowInstanceId = :instanceId AND l.deleted = false ORDER BY l.createTime") @Query("SELECT l FROM WorkflowLog l WHERE l.workflowInstance.id = :instanceId AND l.deleted = false ORDER BY l.createTime")
List<WorkflowLog> findByWorkflowInstanceId(@Param("instanceId") Long instanceId); List<WorkflowLog> 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<WorkflowLog> findByWorkflowInstanceIdAndNodeId(@Param("instanceId") Long instanceId, @Param("nodeId") String nodeId); List<WorkflowLog> findByWorkflowInstanceIdAndNodeId(@Param("instanceId") Long instanceId, @Param("nodeId") String nodeId);
/** /**
* 删除工作流实例的所有日志 * 删除工作流实例的所有日志
*/ */
void deleteByWorkflowInstanceId(Long instanceId); void deleteByWorkflowInstance(WorkflowInstance instance);
} }

View File

@ -1,7 +1,9 @@
package com.qqchen.deploy.backend.workflow.repository; package com.qqchen.deploy.backend.workflow.repository;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import com.qqchen.deploy.backend.workflow.entity.WorkflowVariable; 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.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@ -18,40 +20,69 @@ public interface IWorkflowVariableRepository extends IBaseRepository<WorkflowVar
/** /**
* 查询工作流实例的所有变量 * 查询工作流实例的所有变量
*/ */
@Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstanceId = :instanceId AND v.deleted = false") @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstance = :instance AND v.deleted = false")
List<WorkflowVariable> findByWorkflowInstanceId(@Param("instanceId") Long instanceId); List<WorkflowVariable> findByWorkflowInstance(@Param("instance") WorkflowInstance instance);
/** /**
* 查询工作流实例的指定作用域的变量 * 查询工作流实例的指定作用域的变量
*/ */
@Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstanceId = :instanceId AND v.scope = :scope AND v.deleted = false") @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstance = :instance AND v.scope = :scope AND v.deleted = false")
List<WorkflowVariable> findByWorkflowInstanceIdAndScope(@Param("instanceId") Long instanceId, @Param("scope") String scope); List<WorkflowVariable> findByWorkflowInstanceAndScope(@Param("instance") WorkflowInstance instance, @Param("scope") String scope);
/** /**
* 查询工作流实例的指定变量 * 查询工作流实例的指定变量
*/ */
Optional<WorkflowVariable> findByWorkflowInstanceIdAndName(Long workflowInstanceId, String name); Optional<WorkflowVariable> 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") @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstance = :instance AND v.scope = :scope AND v.nodeId = :nodeId AND v.deleted = false")
List<WorkflowVariable> findByWorkflowInstanceIdAndScopeAndNodeId( List<WorkflowVariable> findByWorkflowInstanceAndScopeAndNodeId(
@Param("instanceId") Long instanceId, @Param("instance") WorkflowInstance instance,
@Param("scope") String scope, @Param("scope") String scope,
@Param("nodeId") String nodeId); @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") @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstance = :instance AND v.name = :name AND v.scope = :scope AND v.deleted = false")
Optional<WorkflowVariable> findByWorkflowInstanceIdAndNameAndScope( Optional<WorkflowVariable> findByWorkflowInstanceAndNameAndScope(
@Param("instanceId") Long instanceId, @Param("instance") WorkflowInstance instance,
@Param("name") String name, @Param("name") String name,
@Param("scope") String scope); @Param("scope") String scope);
/**
* 根据工作流实例ID查询所有变量
*
* @param workflowInstanceId 工作流实例ID
* @return 变量列表
*/
@Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstance.id = :workflowInstanceId AND v.deleted = false")
List<WorkflowVariable> 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<WorkflowVariable> 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);
} }

View File

@ -1,8 +1,9 @@
package com.qqchen.deploy.backend.workflow.service; 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.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.dto.WorkflowLogDTO;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog; import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
import java.util.List; import java.util.List;
@ -13,22 +14,49 @@ import java.util.List;
public interface IWorkflowLogService extends IBaseService<WorkflowLog, WorkflowLogDTO, Long> { public interface IWorkflowLogService extends IBaseService<WorkflowLog, WorkflowLogDTO, Long> {
/** /**
* 记录日志 * 记录日志 (内部使用由工作流引擎调用)
*
* @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<WorkflowLogDTO> getLogs(Long workflowInstanceId); List<WorkflowLogDTO> getLogs(Long workflowInstanceId);
/** /**
* 查询节点实例的所有日志 * 查询节点实例的所有日志
*
* @param workflowInstanceId 工作流实例ID
* @param nodeId 节点ID
* @return 日志列表
*/ */
List<WorkflowLogDTO> getNodeLogs(Long workflowInstanceId, String nodeId); List<WorkflowLogDTO> getNodeLogs(Long workflowInstanceId, String nodeId);
/** /**
* 删除工作流实例的所有日志 * 删除工作流实例的所有日志
*
* @param workflowInstance 工作流实例
*/ */
void deleteLogs(Long workflowInstanceId); void deleteLogs(WorkflowInstance workflowInstance);
} }

View File

@ -2,6 +2,7 @@ package com.qqchen.deploy.backend.workflow.service;
import com.qqchen.deploy.backend.framework.service.IBaseService; import com.qqchen.deploy.backend.framework.service.IBaseService;
import com.qqchen.deploy.backend.workflow.dto.WorkflowVariableDTO; 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.entity.WorkflowVariable;
import com.qqchen.deploy.backend.workflow.enums.VariableScopeEnum; import com.qqchen.deploy.backend.workflow.enums.VariableScopeEnum;
@ -14,21 +15,29 @@ public interface IWorkflowVariableService extends IBaseService<WorkflowVariable,
/** /**
* 设置变量 * 设置变量
*
* @param variables 变量映射
*/ */
void setVariable(Long workflowInstanceId, String name, Object value); void setVariables(Long workflowInstanceId, Map<String, Object> variables);
/** /**
* 获取所有变量 * 获取所有变量
*
* @return 变量映射
*/ */
Map<String, Object> getVariables(Long workflowInstanceId); Map<String, Object> getVariables(Long workflowInstanceId);
/** /**
* 获取指定作用域的变量 * 获取指定作用域的变量
*
* @param scope 变量作用域
* @return 变量映射
*/ */
Map<String, Object> getVariablesByScope(Long workflowInstanceId, VariableScopeEnum scope); Map<String, Object> getVariablesByScope(Long workflowInstanceId, VariableScopeEnum scope);
/** /**
* 删除所有变量 * 清除实例的所有变量
*
*/ */
void deleteVariables(Long workflowInstanceId); void clearVariables(Long workflowInstanceId);
} }

View File

@ -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<String, Object> getVariables(Long workflowInstanceId);
/**
* 设置工作流实例的变量
*
* @param workflowInstanceId 工作流实例ID
* @param variables 变量Map
*/
void setVariables(Long workflowInstanceId, Map<String, Object> 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);
}

View File

@ -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<Long, Map<String, Object>> variableCache;
public ConcurrentWorkflowVariableOperations() {
this.variableCache = Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.MINUTES)
.maximumSize(10000)
.build();
}
@Override
public Map<String, Object> getVariables(Long workflowInstanceId) {
return variableCache.get(workflowInstanceId,
id -> variableService.getVariables(id));
}
@Override
public void setVariables(Long workflowInstanceId, Map<String, Object> variables) {
if (variables == null || variables.isEmpty()) {
return;
}
variableService.setVariables(workflowInstanceId, variables);
variableCache.put(workflowInstanceId, variables);
}
@Override
public Object getVariable(Long workflowInstanceId, String key) {
Map<String, Object> variables = getVariables(workflowInstanceId);
return variables != null ? variables.get(key) : null;
}
@Override
public void setVariable(Long workflowInstanceId, String key, Object value) {
Map<String, Object> 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);
}
}

View File

@ -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.IWorkflowDefinitionRepository;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService; 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 jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -34,7 +34,7 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
private IWorkflowInstanceRepository workflowInstanceRepository; private IWorkflowInstanceRepository workflowInstanceRepository;
@Resource @Resource
private IWorkflowVariableService workflowVariableService; private WorkflowVariableOperations variableOperations;
@Override @Override
@Transactional @Transactional
@ -60,7 +60,7 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
// 4. 设置初始变量 // 4. 设置初始变量
if (variables != null && !variables.isEmpty()) { if (variables != null && !variables.isEmpty()) {
variables.forEach((key, value) -> workflowVariableService.setVariable(savedInstance.getId(), key, value)); variableOperations.setVariables(savedInstance.getId(), variables);
} }
return converter.toDto(savedInstance); return converter.toDto(savedInstance);

View File

@ -1,10 +1,12 @@
package com.qqchen.deploy.backend.workflow.service.impl; 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.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.dto.WorkflowLogDTO;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog; import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowLogRepository; 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 com.qqchen.deploy.backend.workflow.service.IWorkflowLogService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -24,16 +26,32 @@ public class WorkflowLogServiceImpl extends BaseServiceImpl<WorkflowLog, Workflo
@Resource @Resource
private IWorkflowLogRepository logRepository; private IWorkflowLogRepository logRepository;
@Resource
private IWorkflowInstanceService workflowInstanceService;
@Override @Override
@Transactional @Transactional
public void log(Long workflowInstanceId, String nodeId, String message, LogLevelEnum level, String detail) { public WorkflowLog log(WorkflowInstance instance, String nodeId, LogLevelEnum level, String message, String detail) {
WorkflowLog log = new WorkflowLog(); WorkflowLog workflowLog = new WorkflowLog();
log.setWorkflowInstanceId(workflowInstanceId); workflowLog.setWorkflowInstance(instance);
log.setNodeId(nodeId); workflowLog.setNodeId(nodeId);
log.setContent(message); workflowLog.setLevel(level);
log.setLevel(level); workflowLog.setMessage(message);
log.setDetail(detail); workflowLog.setDetail(detail);
repository.save(log); return logRepository.save(workflowLog);
}
@Override
@Transactional
public void recordLog(Long workflowInstanceId, String nodeId, String message, LogLevelEnum level, String detail) {
// 先获取工作流实例
WorkflowInstance instance = workflowInstanceService.findEntityById(workflowInstanceId);
if (instance == null) {
throw new IllegalArgumentException("工作流实例不存在: " + workflowInstanceId);
}
// 记录日志
log(instance, nodeId, level, message, detail);
} }
@Override @Override
@ -52,8 +70,8 @@ public class WorkflowLogServiceImpl extends BaseServiceImpl<WorkflowLog, Workflo
@Override @Override
@Transactional @Transactional
public void deleteLogs(Long workflowInstanceId) { public void deleteLogs(WorkflowInstance workflowInstance) {
logRepository.deleteByWorkflowInstanceId(workflowInstanceId); logRepository.deleteByWorkflowInstance(workflowInstance);
log.debug("删除工作流日志成功: instanceId={}", workflowInstanceId); log.debug("删除工作流日志成功: instanceId={}", workflowInstance.getId());
} }
} }

View File

@ -6,6 +6,7 @@ import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException; import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import com.qqchen.deploy.backend.workflow.dto.WorkflowVariableDTO; 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.entity.WorkflowVariable;
import com.qqchen.deploy.backend.workflow.enums.VariableScopeEnum; import com.qqchen.deploy.backend.workflow.enums.VariableScopeEnum;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowVariableRepository; import com.qqchen.deploy.backend.workflow.repository.IWorkflowVariableRepository;
@ -29,57 +30,58 @@ public class WorkflowVariableServiceImpl extends BaseServiceImpl<WorkflowVariabl
@Override @Override
@Transactional @Transactional
public void setVariable(Long workflowInstanceId, String name, Object value) { public void setVariables(Long workflowInstanceId, Map<String, Object> variables) {
try { if (variables == null || variables.isEmpty()) {
WorkflowVariable variable = variableRepository.findByWorkflowInstanceIdAndNameAndScope( return;
workflowInstanceId, name, VariableScopeEnum.GLOBAL.name())
.orElseGet(() -> {
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);
} }
// 先删除已有的变量
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 @Override
@Transactional(readOnly = true)
public Map<String, Object> getVariables(Long workflowInstanceId) { public Map<String, Object> getVariables(Long workflowInstanceId) {
List<WorkflowVariable> variables = variableRepository.findByWorkflowInstanceId(workflowInstanceId); List<WorkflowVariable> variables = variableRepository.findByWorkflowInstanceId(workflowInstanceId);
return convertToMap(variables); return deserializeVariables(variables);
} }
@Override @Override
@Transactional(readOnly = true)
public Map<String, Object> getVariablesByScope(Long workflowInstanceId, VariableScopeEnum scope) { public Map<String, Object> getVariablesByScope(Long workflowInstanceId, VariableScopeEnum scope) {
List<WorkflowVariable> variables = variableRepository.findByWorkflowInstanceIdAndScope(workflowInstanceId, scope.name()); List<WorkflowVariable> variables = variableRepository.findByWorkflowInstanceIdAndScope(workflowInstanceId, scope);
return convertToMap(variables); return deserializeVariables(variables);
} }
@Override @Override
@Transactional @Transactional
public void deleteVariables(Long workflowInstanceId) { public void clearVariables(Long workflowInstanceId) {
variableRepository.deleteByWorkflowInstanceId(workflowInstanceId); variableRepository.deleteByWorkflowInstanceId(workflowInstanceId);
} }
private Map<String, Object> convertToMap(List<WorkflowVariable> variables) { private Map<String, Object> deserializeVariables(List<WorkflowVariable> variables) {
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
for (WorkflowVariable variable : variables) { variables.forEach(variable -> {
try { try {
Class<?> type = Class.forName(variable.getType()); Object value = objectMapper.readValue(variable.getValue(), Object.class);
Object value = objectMapper.readValue(variable.getValue(), type);
result.put(variable.getName(), value); result.put(variable.getName(), value);
} catch (Exception e) { } catch (JsonProcessingException e) {
throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID); throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_DESERIALIZE_ERROR);
} }
} });
return result; return result;
} }
} }

View File

@ -4,7 +4,7 @@ spring:
datasource: datasource:
url: jdbc:mysql://localhost:3306/deploy-ease-platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true url: jdbc:mysql://localhost:3306/deploy-ease-platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: root username: root
password: root password: ServBay.dev
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
jpa: jpa:
hibernate: hibernate:

View File

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

View File

@ -443,8 +443,7 @@ CREATE TABLE wf_workflow_instance (
end_time DATETIME(6) NULL COMMENT '结束时间', end_time DATETIME(6) NULL COMMENT '结束时间',
error TEXT NULL COMMENT '错误信息', error TEXT NULL COMMENT '错误信息',
CONSTRAINT FK_workflow_instance_definition FOREIGN KEY (workflow_definition_id) REFERENCES wf_workflow_definition (id), CONSTRAINT FK_workflow_instance_definition FOREIGN KEY (workflow_definition_id) REFERENCES wf_workflow_definition (id)
CONSTRAINT UK_workflow_instance_business_key UNIQUE (business_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流实例表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流实例表';
-- 节点实例表 -- 节点实例表
@ -495,17 +494,18 @@ CREATE TABLE wf_workflow_variable (
-- 工作流日志表 -- 工作流日志表
CREATE TABLE wf_log ( CREATE TABLE wf_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT, id BIGINT PRIMARY KEY AUTO_INCREMENT,
workflow_instance_id BIGINT NOT NULL, workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
node_id VARCHAR(50), node_id VARCHAR(50) NULL COMMENT '节点ID',
message VARCHAR(1000) NOT NULL, level VARCHAR(10) NOT NULL COMMENT '日志级别',
level VARCHAR(20) NOT NULL, message TEXT NOT NULL COMMENT '日志内容',
detail TEXT, detail TEXT,
create_time DATETIME NOT NULL, create_time DATETIME NOT NULL,
create_by VARCHAR(50), create_by VARCHAR(50),
update_time DATETIME, update_time DATETIME,
update_by VARCHAR(50), update_by VARCHAR(50),
version INT DEFAULT 0, 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)
); );
-- 创建索引 -- 创建索引

View File

@ -1,238 +1,242 @@
# 通用响应 # \u901A\u7528\u54CD\u5E94
response.success=操作成功 response.success=\u64CD\u4F5C\u6210\u529F
response.error=系统错误 response.error=\u7CFB\u7EDF\u9519\u8BEF
response.invalid.param=无效的参数 response.invalid.param=\u65E0\u6548\u7684\u53C2\u6570
response.unauthorized=未授权 response.unauthorized=\u672A\u6388\u6743
response.forbidden=禁止访问 response.forbidden=\u7981\u6B62\u8BBF\u95EE
response.not.found=资源未找到 response.not.found=\u8D44\u6E90\u672A\u627E\u5230
response.conflict=资源冲突 response.conflict=\u8D44\u6E90\u51B2\u7A81
response.unauthorized.full=访问此资源需要完全身份验证 response.unauthorized.full=\u8BBF\u95EE\u6B64\u8D44\u6E90\u9700\u8981\u5B8C\u5168\u8EAB\u4EFD\u9A8C\u8BC1
# 业务错误 # \u4E1A\u52A1\u9519\u8BEF
tenant.not.found=租户不存在 tenant.not.found=\u79DF\u6237\u4E0D\u5B58\u5728
data.not.found=找不到ID为{0}的{1} data.not.found=\u627E\u4E0D\u5230ID\u4E3A{0}\u7684{1}
# 用户相关 # \u7528\u6237\u76F8\u5173
user.not.found=用户不存在 user.not.found=\u7528\u6237\u4E0D\u5B58\u5728
user.username.exists=用户名"{0}"已存在 user.username.exists=\u7528\u6237\u540D"{0}"\u5DF2\u5B58\u5728
user.email.exists=邮箱"{0}"已存在 user.email.exists=\u90AE\u7BB1"{0}"\u5DF2\u5B58\u5728
user.login.error=用户名或密码错误 user.login.error=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF
# 系统异常消息 # \u7CFB\u7EDF\u5F02\u5E38\u6D88\u606F
system.optimistic.lock.error=数据已被其他用户修改,请刷新后重试 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=数据正被其他用户操作,请稍后重试 system.pessimistic.lock.error=\u6570\u636E\u6B63\u88AB\u5176\u4ED6\u7528\u6237\u64CD\u4F5C\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5
system.concurrent.update.error=并发更新冲突,请重试 system.concurrent.update.error=\u5E76\u53D1\u66F4\u65B0\u51B2\u7A81\uFF0C\u8BF7\u91CD\u8BD5
system.retry.exceeded.error=操作重试次数超限,请稍后再试 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 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.message={0}
entity.not.found.name.id=找不到ID为{1}的{0} entity.not.found.name.id=\u627E\u4E0D\u5230ID\u4E3A{1}\u7684{0}
# 依赖注入相关 # \u4F9D\u8D56\u6CE8\u5165\u76F8\u5173
dependency.injection.service.not.found=找不到实体 {0} 对应的服务 (尝试过的bean名称: {1}) 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=找不到实体 {0} 对应的Repository: {1} dependency.injection.repository.not.found=\u627E\u4E0D\u5230\u5B9E\u4F53 {0} \u5BF9\u5E94\u7684Repository: {1}
dependency.injection.converter.not.found=找不到实体 {0} 对应的Converter: {1} dependency.injection.converter.not.found=\u627E\u4E0D\u5230\u5B9E\u4F53 {0} \u5BF9\u5E94\u7684Converter: {1}
dependency.injection.entitypath.failed=初始化实体 {0} 的EntityPath失败: {1} dependency.injection.entitypath.failed=\u521D\u59CB\u5316\u5B9E\u4F53 {0} \u7684EntityPath\u5931\u8D25: {1}
# JWT相关 # JWT\u76F8\u5173
jwt.token.expired=登录已过期,请重新登录 jwt.token.expired=\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55
jwt.token.invalid=无效的登录凭证 jwt.token.invalid=\u65E0\u6548\u7684\u767B\u5F55\u51ED\u8BC1
jwt.token.missing=未提供登录凭证 jwt.token.missing=\u672A\u63D0\u4F9B\u767B\u5F55\u51ED\u8BC1
# 角色相关错误消息 # \u89D2\u8272\u76F8\u5173\u9519\u8BEF\u6D88\u606F
role.not.found=角色不存在 role.not.found=\u89D2\u8272\u4E0D\u5B58\u5728
role.code.exists=角色编码"{0}"已存在 role.code.exists=\u89D2\u8272\u7F16\u7801"{0}"\u5DF2\u5B58\u5728
role.name.exists=角色名称"{0}"已存在 role.name.exists=\u89D2\u8272\u540D\u79F0"{0}"\u5DF2\u5B58\u5728
role.in.use=角色正在使用中,无法删除 role.in.use=\u89D2\u8272\u6B63\u5728\u4F7F\u7528\u4E2D\uFF0C\u65E0\u6CD5\u5220\u9664
role.admin.cannot.delete=不能删除超级管理员角色 role.admin.cannot.delete=\u4E0D\u80FD\u5220\u9664\u8D85\u7EA7\u7BA1\u7406\u5458\u89D2\u8272
role.admin.cannot.update=不能修改超级管理员角色 role.admin.cannot.update=\u4E0D\u80FD\u4FEE\u6539\u8D85\u7EA7\u7BA1\u7406\u5458\u89D2\u8272
role.tag.name.exists=标签名称已存在 role.tag.name.exists=\u6807\u7B7E\u540D\u79F0\u5DF2\u5B58\u5728
role.tag.not.found=标签不存在 role.tag.not.found=\u6807\u7B7E\u4E0D\u5B58\u5728
role.tag.in.use=标签正在使用中,无法删除 role.tag.in.use=\u6807\u7B7E\u6B63\u5728\u4F7F\u7528\u4E2D\uFF0C\u65E0\u6CD5\u5220\u9664
# 部门相关 # \u90E8\u95E8\u76F8\u5173
department.not.found=部门不存在 department.not.found=\u90E8\u95E8\u4E0D\u5B58\u5728
department.code.exists=部门编码已存在 department.code.exists=\u90E8\u95E8\u7F16\u7801\u5DF2\u5B58\u5728
department.name.exists=部门名称已存在 department.name.exists=\u90E8\u95E8\u540D\u79F0\u5DF2\u5B58\u5728
department.parent.not.found=上级部门不存在 department.parent.not.found=\u4E0A\u7EA7\u90E8\u95E8\u4E0D\u5B58\u5728
department.has.children=该部门下有子部门,无法删除 department.has.children=\u8BE5\u90E8\u95E8\u4E0B\u6709\u5B50\u90E8\u95E8\uFF0C\u65E0\u6CD5\u5220\u9664
# 权限相关 # \u6743\u9650\u76F8\u5173
permission.not.found=权限不存在 permission.not.found=\u6743\u9650\u4E0D\u5B58\u5728
permission.code.exists=权限编码{0}已存在 permission.code.exists=\u6743\u9650\u7F16\u7801{0}\u5DF2\u5B58\u5728
permission.name.exists=权限名称{0}已存在 permission.name.exists=\u6743\u9650\u540D\u79F0{0}\u5DF2\u5B58\u5728
permission.already.assigned=该权限已分配给角色 permission.already.assigned=\u8BE5\u6743\u9650\u5DF2\u5206\u914D\u7ED9\u89D2\u8272
permission.assign.failed=权限分配失败 permission.assign.failed=\u6743\u9650\u5206\u914D\u5931\u8D25
# 第三方系统相关 # \u7B2C\u4E09\u65B9\u7CFB\u7EDF\u76F8\u5173
external.system.name.exists=系统名称"{0}"已存在 external.system.name.exists=\u7CFB\u7EDF\u540D\u79F0"{0}"\u5DF2\u5B58\u5728
external.system.type.url.exists=系统类型和URL组合"{0}"已存在 external.system.type.url.exists=\u7CFB\u7EDF\u7C7B\u578B\u548CURL\u7EC4\u5408"{0}"\u5DF2\u5B58\u5728
external.system.disabled=系统已禁用 external.system.disabled=\u7CFB\u7EDF\u5DF2\u7981\u7528
external.system.sync.failed=系统数据同步失败 external.system.sync.failed=\u7CFB\u7EDF\u6570\u636E\u540C\u6B65\u5931\u8D25
external.system.type.not.supported=不支持的系统类型 external.system.type.not.supported=\u4E0D\u652F\u6301\u7684\u7CFB\u7EDF\u7C7B\u578B
# Git系统相关错误 # Git\u7CFB\u7EDF\u76F8\u5173\u9519\u8BEF
external.system.git.auth.type.error=Git系统只支持Token认证 external.system.git.auth.type.error=Git\u7CFB\u7EDF\u53EA\u652F\u6301Token\u8BA4\u8BC1
external.system.git.token.required=Git系统必须提供Token external.system.git.token.required=Git\u7CFB\u7EDF\u5FC5\u987B\u63D0\u4F9BToken
# 仓库相关错误消息 # \u4ED3\u5E93\u76F8\u5173\u9519\u8BEF\u6D88\u606F
repository.group.not.found=仓库组不存在 repository.group.not.found=\u4ED3\u5E93\u7EC4\u4E0D\u5B58\u5728
repository.group.name.exists=仓库组名称"{0}"已存在 repository.group.name.exists=\u4ED3\u5E93\u7EC4\u540D\u79F0"{0}"\u5DF2\u5B58\u5728
repository.group.path.exists=仓库组路径"{0}"已存在 repository.group.path.exists=\u4ED3\u5E93\u7EC4\u8DEF\u5F84"{0}"\u5DF2\u5B58\u5728
repository.project.not.found=仓库项目不存在 repository.project.not.found=\u4ED3\u5E93\u9879\u76EE\u4E0D\u5B58\u5728
repository.project.name.exists=仓库项目名称"{0}"已存在 repository.project.name.exists=\u4ED3\u5E93\u9879\u76EE\u540D\u79F0"{0}"\u5DF2\u5B58\u5728
repository.project.path.exists=仓库项目路径"{0}"已存在 repository.project.path.exists=\u4ED3\u5E93\u9879\u76EE\u8DEF\u5F84"{0}"\u5DF2\u5B58\u5728
repository.branch.not.found=分支不存在 repository.branch.not.found=\u5206\u652F\u4E0D\u5B58\u5728
repository.branch.name.exists=分支名称"{0}"已存在 repository.branch.name.exists=\u5206\u652F\u540D\u79F0"{0}"\u5DF2\u5B58\u5728
repository.sync.in.progress=仓库同步正在进行中 repository.sync.in.progress=\u4ED3\u5E93\u540C\u6B65\u6B63\u5728\u8FDB\u884C\u4E2D
repository.sync.failed=仓库同步失败:{0} repository.sync.failed=\u4ED3\u5E93\u540C\u6B65\u5931\u8D25\uFF1A{0}
repository.sync.history.not.found=同步历史记录不存在 repository.sync.history.not.found=\u540C\u6B65\u5386\u53F2\u8BB0\u5F55\u4E0D\u5B58\u5728
# 工作流相关错误消息 # \u5DE5\u4F5C\u6D41\u76F8\u5173\u9519\u8BEF\u6D88\u606F
workflow.definition.not.found=工作流定义不存在 workflow.definition.not.found=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u4E0D\u5B58\u5728
workflow.definition.code.exists=工作流定义编码"{0}"已存在 workflow.definition.code.exists=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u7F16\u7801"{0}"\u5DF2\u5B58\u5728
workflow.definition.name.exists=工作流定义名称"{0}"已存在 workflow.definition.name.exists=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u540D\u79F0"{0}"\u5DF2\u5B58\u5728
workflow.definition.invalid.content=工作流定义内容无效:{0} workflow.definition.invalid.content=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u5185\u5BB9\u65E0\u6548\uFF1A{0}
workflow.definition.not.published=工作流定义未发布 workflow.definition.not.published=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u672A\u53D1\u5E03
workflow.definition.already.published=工作流定义已发布 workflow.definition.already.published=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u5DF2\u53D1\u5E03
workflow.definition.cannot.delete=工作流定义已被使用,无法删除 workflow.definition.cannot.delete=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u5DF2\u88AB\u4F7F\u7528\uFF0C\u65E0\u6CD5\u5220\u9664
workflow.instance.not.found=工作流实例不存在 workflow.instance.not.found=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u4E0D\u5B58\u5728
workflow.instance.cannot.start=工作流实例无法启动 workflow.instance.cannot.start=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u65E0\u6CD5\u542F\u52A8
workflow.instance.cannot.cancel=工作流实例无法取消 workflow.instance.cannot.cancel=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u65E0\u6CD5\u53D6\u6D88
workflow.instance.cannot.pause=工作流实例无法暂停 workflow.instance.cannot.pause=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u65E0\u6CD5\u6682\u505C
workflow.instance.cannot.resume=工作流实例无法恢复 workflow.instance.cannot.resume=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u65E0\u6CD5\u6062\u590D
workflow.instance.cannot.retry=工作流实例无法重试 workflow.instance.cannot.retry=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u65E0\u6CD5\u91CD\u8BD5
# 节点相关错误消息 # \u8282\u70B9\u76F8\u5173\u9519\u8BEF\u6D88\u606F
node.instance.not.found=节点实例不存在 node.instance.not.found=\u8282\u70B9\u5B9E\u4F8B\u4E0D\u5B58\u5728
node.instance.cannot.retry=节点实例无法重试 node.instance.cannot.retry=\u8282\u70B9\u5B9E\u4F8B\u65E0\u6CD5\u91CD\u8BD5
node.instance.cannot.skip=节点实例无法跳过 node.instance.cannot.skip=\u8282\u70B9\u5B9E\u4F8B\u65E0\u6CD5\u8DF3\u8FC7
node.executor.not.found=节点执行器不存在 node.executor.not.found=\u8282\u70B9\u6267\u884C\u5668\u4E0D\u5B58\u5728
# 工作流相关消息 # \u5DE5\u4F5C\u6D41\u76F8\u5173\u6D88\u606F
workflow.not.found=工作流定义不存在 workflow.not.found=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u4E0D\u5B58\u5728
workflow.code.exists=工作流编码已存在 workflow.code.exists=\u5DE5\u4F5C\u6D41\u7F16\u7801\u5DF2\u5B58\u5728
workflow.name.exists=工作流名称已存在 workflow.name.exists=\u5DE5\u4F5C\u6D41\u540D\u79F0\u5DF2\u5B58\u5728
workflow.disabled=工作流已禁用 workflow.disabled=\u5DE5\u4F5C\u6D41\u5DF2\u7981\u7528
workflow.not.published=工作流未发布 workflow.not.published=\u5DE5\u4F5C\u6D41\u672A\u53D1\u5E03
workflow.already.published=工作流已发布 workflow.already.published=\u5DE5\u4F5C\u6D41\u5DF2\u53D1\u5E03
workflow.already.disabled=工作流已禁用 workflow.already.disabled=\u5DE5\u4F5C\u6D41\u5DF2\u7981\u7528
workflow.instance.not.found=工作流实例不存在 workflow.instance.not.found=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u4E0D\u5B58\u5728
workflow.instance.already.completed=工作流实例已完成 workflow.instance.already.completed=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u5DF2\u5B8C\u6210
workflow.instance.already.canceled=工作流实例已取消 workflow.instance.already.canceled=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u5DF2\u53D6\u6D88
workflow.instance.not.running=工作流实例未运行 workflow.instance.not.running=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u672A\u8FD0\u884C
workflow.node.not.found=工作流节点不存在 workflow.node.not.found=\u5DE5\u4F5C\u6D41\u8282\u70B9\u4E0D\u5B58\u5728
workflow.node.type.not.supported=不支持的节点类型 workflow.node.type.not.supported=\u4E0D\u652F\u6301\u7684\u8282\u70B9\u7C7B\u578B
workflow.node.config.invalid=节点配置无效 workflow.node.config.invalid=\u8282\u70B9\u914D\u7F6E\u65E0\u6548
workflow.node.execution.failed=节点执行失败 workflow.node.execution.failed=\u8282\u70B9\u6267\u884C\u5931\u8D25
workflow.node.timeout=节点执行超时 workflow.node.timeout=\u8282\u70B9\u6267\u884C\u8D85\u65F6
workflow.variable.not.found=工作流变量不存在 workflow.variable.not.found=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u4E0D\u5B58\u5728
workflow.variable.type.invalid=工作流变量类型无效 workflow.variable.type.invalid=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u7C7B\u578B\u65E0\u6548
workflow.permission.denied=工作流权限不足 workflow.permission.denied=\u5DE5\u4F5C\u6D41\u6743\u9650\u4E0D\u8DB3
workflow.approval.required=需要审批 workflow.approval.required=\u9700\u8981\u5BA1\u6279
workflow.approval.rejected=审批被拒绝 workflow.approval.rejected=\u5BA1\u6279\u88AB\u62D2\u7EDD
workflow.dependency.not.satisfied=依赖条件不满足 workflow.dependency.not.satisfied=\u4F9D\u8D56\u6761\u4EF6\u4E0D\u6EE1\u8DB3
workflow.circular.dependency=存在循环依赖 workflow.circular.dependency=\u5B58\u5728\u5FAA\u73AF\u4F9D\u8D56
workflow.schedule.invalid=调度配置无效 workflow.schedule.invalid=\u8C03\u5EA6\u914D\u7F6E\u65E0\u6548
workflow.concurrent.limit.exceeded=超出并发限制 workflow.concurrent.limit.exceeded=\u8D85\u51FA\u5E76\u53D1\u9650\u5236
# Workflow error messages # Workflow error messages
workflow.not.found=工作流定义不存在 workflow.not.found=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u4E0D\u5B58\u5728
workflow.code.exists=工作流编码已存在 workflow.code.exists=\u5DE5\u4F5C\u6D41\u7F16\u7801\u5DF2\u5B58\u5728
workflow.name.exists=工作流名称已存在 workflow.name.exists=\u5DE5\u4F5C\u6D41\u540D\u79F0\u5DF2\u5B58\u5728
workflow.invalid.status=工作流状态无效 workflow.invalid.status=\u5DE5\u4F5C\u6D41\u72B6\u6001\u65E0\u6548
workflow.node.not.found=工作流节点不存在 workflow.node.not.found=\u5DE5\u4F5C\u6D41\u8282\u70B9\u4E0D\u5B58\u5728
workflow.node.config.error=工作流节点配置错误 workflow.node.config.error=\u5DE5\u4F5C\u6D41\u8282\u70B9\u914D\u7F6E\u9519\u8BEF
workflow.execution.error=工作流执行错误 workflow.execution.error=\u5DE5\u4F5C\u6D41\u6267\u884C\u9519\u8BEF
workflow.not.draft=只有草稿状态的工作流定义可以发布 workflow.not.draft=\u53EA\u6709\u8349\u7A3F\u72B6\u6001\u7684\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u53EF\u4EE5\u53D1\u5E03
workflow.not.published=只有已发布状态的工作流定义可以禁用 workflow.not.published=\u53EA\u6709\u5DF2\u53D1\u5E03\u72B6\u6001\u7684\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u53EF\u4EE5\u7981\u7528
workflow.not.disabled=只有已禁用状态的工作流定义可以启用 workflow.not.disabled=\u53EA\u6709\u5DF2\u7981\u7528\u72B6\u6001\u7684\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u53EF\u4EE5\u542F\u7528
# System level messages (1xxx) # System level messages (1xxx)
success=操作成功 success=\u64CD\u4F5C\u6210\u529F
system.error=系统错误 system.error=\u7CFB\u7EDF\u9519\u8BEF
param.error=参数错误 param.error=\u53C2\u6570\u9519\u8BEF
unauthorized=未授权 unauthorized=\u672A\u6388\u6743
forbidden=禁止访问 forbidden=\u7981\u6B62\u8BBF\u95EE
not.found=资源不存在 not.found=\u8D44\u6E90\u4E0D\u5B58\u5728
method.not.allowed=方法不允许 method.not.allowed=\u65B9\u6CD5\u4E0D\u5141\u8BB8
conflict=资源冲突 conflict=\u8D44\u6E90\u51B2\u7A81
too.many.requests=请求过于频繁 too.many.requests=\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41
internal.server.error=内部服务器错误 internal.server.error=\u5185\u90E8\u670D\u52A1\u5668\u9519\u8BEF
# Business level messages (2xxx) # Business level messages (2xxx)
# Common business messages (2000-2099) # Common business messages (2000-2099)
business.error=业务错误 business.error=\u4E1A\u52A1\u9519\u8BEF
data.not.found=数据不存在 data.not.found=\u6570\u636E\u4E0D\u5B58\u5728
data.already.exists=数据已存在 data.already.exists=\u6570\u636E\u5DF2\u5B58\u5728
data.validation.failed=数据验证失败 data.validation.failed=\u6570\u636E\u9A8C\u8BC1\u5931\u8D25
operation.not.allowed=操作不允许 operation.not.allowed=\u64CD\u4F5C\u4E0D\u5141\u8BB8
# Workflow related messages (2100-2199) # Workflow related messages (2100-2199)
workflow.not.found=工作流不存在 workflow.not.found=\u5DE5\u4F5C\u6D41\u4E0D\u5B58\u5728
workflow.already.exists=工作流已存在 workflow.already.exists=\u5DE5\u4F5C\u6D41\u5DF2\u5B58\u5728
workflow.not.published=工作流未发布 workflow.not.published=\u5DE5\u4F5C\u6D41\u672A\u53D1\u5E03
workflow.config.invalid=工作流配置无效 workflow.config.invalid=\u5DE5\u4F5C\u6D41\u914D\u7F6E\u65E0\u6548
workflow.node.not.found=工作流节点不存在 workflow.node.not.found=\u5DE5\u4F5C\u6D41\u8282\u70B9\u4E0D\u5B58\u5728
workflow.node.execution.failed=工作流节点执行失败 workflow.node.execution.failed=\u5DE5\u4F5C\u6D41\u8282\u70B9\u6267\u884C\u5931\u8D25
workflow.instance.not.found=工作流实例不存在 workflow.instance.not.found=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u4E0D\u5B58\u5728
workflow.instance.not.running=工作流实例未运行 workflow.instance.not.running=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u672A\u8FD0\u884C
workflow.variable.not.found=工作流变量不存在 workflow.variable.not.found=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u4E0D\u5B58\u5728
workflow.log.not.found=工作流日志不存在 workflow.log.not.found=\u5DE5\u4F5C\u6D41\u65E5\u5FD7\u4E0D\u5B58\u5728
workflow.transition.invalid=工作流流转配置无效 workflow.transition.invalid=\u5DE5\u4F5C\u6D41\u6D41\u8F6C\u914D\u7F6E\u65E0\u6548
workflow.node.type.not.supported=不支持的节点类型 workflow.node.type.not.supported=\u4E0D\u652F\u6301\u7684\u8282\u70B9\u7C7B\u578B
workflow.condition.invalid=工作流条件配置无效 workflow.condition.invalid=\u5DE5\u4F5C\u6D41\u6761\u4EF6\u914D\u7F6E\u65E0\u6548
# 工作流相关错误消息 # \u5DE5\u4F5C\u6D41\u76F8\u5173\u9519\u8BEF\u6D88\u606F
workflow.not.found=工作流定义不存在 workflow.not.found=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u4E0D\u5B58\u5728
workflow.code.exists=工作流编码已存在 workflow.code.exists=\u5DE5\u4F5C\u6D41\u7F16\u7801\u5DF2\u5B58\u5728
workflow.name.exists=工作流名称已存在 workflow.name.exists=\u5DE5\u4F5C\u6D41\u540D\u79F0\u5DF2\u5B58\u5728
workflow.disabled=工作流已禁用 workflow.disabled=\u5DE5\u4F5C\u6D41\u5DF2\u7981\u7528
workflow.not.published=工作流未发布 workflow.not.published=\u5DE5\u4F5C\u6D41\u672A\u53D1\u5E03
workflow.already.published=工作流已发布 workflow.already.published=\u5DE5\u4F5C\u6D41\u5DF2\u53D1\u5E03
workflow.already.disabled=工作流已禁用 workflow.already.disabled=\u5DE5\u4F5C\u6D41\u5DF2\u7981\u7528
workflow.not.draft=工作流不是草稿状态 workflow.not.draft=\u5DE5\u4F5C\u6D41\u4E0D\u662F\u8349\u7A3F\u72B6\u6001
workflow.not.disabled=工作流不是禁用状态 workflow.not.disabled=\u5DE5\u4F5C\u6D41\u4E0D\u662F\u7981\u7528\u72B6\u6001
workflow.invalid.status=工作流状态无效 workflow.invalid.status=\u5DE5\u4F5C\u6D41\u72B6\u6001\u65E0\u6548
workflow.config.invalid=工作流配置无效 workflow.config.invalid=\u5DE5\u4F5C\u6D41\u914D\u7F6E\u65E0\u6548
workflow.transition.invalid=工作流流转规则无效 workflow.transition.invalid=\u5DE5\u4F5C\u6D41\u6D41\u8F6C\u89C4\u5219\u65E0\u6548
workflow.condition.invalid=工作流条件配置无效 workflow.condition.invalid=\u5DE5\u4F5C\u6D41\u6761\u4EF6\u914D\u7F6E\u65E0\u6548
workflow.instance.not.found=工作流实例不存在 workflow.instance.not.found=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u4E0D\u5B58\u5728
workflow.instance.already.completed=工作流实例已完成 workflow.instance.already.completed=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u5DF2\u5B8C\u6210
workflow.instance.already.canceled=工作流实例已取消 workflow.instance.already.canceled=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u5DF2\u53D6\u6D88
workflow.instance.not.running=工作流实例未运行 workflow.instance.not.running=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u672A\u8FD0\u884C
workflow.instance.not.paused=工作流实例不是暂停状态 workflow.instance.not.paused=\u5DE5\u4F5C\u6D41\u5B9E\u4F8B\u4E0D\u662F\u6682\u505C\u72B6\u6001
workflow.node.not.found=工作流节点不存在 workflow.node.not.found=\u5DE5\u4F5C\u6D41\u8282\u70B9\u4E0D\u5B58\u5728
workflow.node.type.not.supported=不支持的节点类型 workflow.node.type.not.supported=\u4E0D\u652F\u6301\u7684\u8282\u70B9\u7C7B\u578B
workflow.node.config.invalid=节点配置无效 workflow.node.config.invalid=\u8282\u70B9\u914D\u7F6E\u65E0\u6548
workflow.node.execution.failed=节点执行失败 workflow.node.execution.failed=\u8282\u70B9\u6267\u884C\u5931\u8D25
workflow.node.timeout=节点执行超时 workflow.node.timeout=\u8282\u70B9\u6267\u884C\u8D85\u65F6
workflow.node.config.error=节点配置错误 workflow.node.config.error=\u8282\u70B9\u914D\u7F6E\u9519\u8BEF
workflow.execution.error=工作流执行错误 workflow.execution.error=\u5DE5\u4F5C\u6D41\u6267\u884C\u9519\u8BEF
workflow.variable.not.found=工作流变量不存在 workflow.variable.not.found=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u4E0D\u5B58\u5728
workflow.variable.type.invalid=工作流变量类型无效 workflow.variable.type.invalid=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u7C7B\u578B\u65E0\u6548
workflow.permission.denied=无权限操作工作流 workflow.permission.denied=\u65E0\u6743\u9650\u64CD\u4F5C\u5DE5\u4F5C\u6D41
workflow.approval.required=需要审批 workflow.approval.required=\u9700\u8981\u5BA1\u6279
workflow.approval.rejected=审批被拒绝 workflow.approval.rejected=\u5BA1\u6279\u88AB\u62D2\u7EDD
workflow.dependency.not.satisfied=工作流依赖条件未满足 workflow.dependency.not.satisfied=\u5DE5\u4F5C\u6D41\u4F9D\u8D56\u6761\u4EF6\u672A\u6EE1\u8DB3
workflow.circular.dependency=工作流存在循环依赖 workflow.circular.dependency=\u5DE5\u4F5C\u6D41\u5B58\u5728\u5FAA\u73AF\u4F9D\u8D56
workflow.schedule.invalid=工作流调度配置无效 workflow.schedule.invalid=\u5DE5\u4F5C\u6D41\u8C03\u5EA6\u914D\u7F6E\u65E0\u6548
workflow.concurrent.limit.exceeded=工作流并发限制超出 workflow.concurrent.limit.exceeded=\u5DE5\u4F5C\u6D41\u5E76\u53D1\u9650\u5236\u8D85\u51FA
# 工作流配置相关错误消息 # \u5DE5\u4F5C\u6D41\u914D\u7F6E\u76F8\u5173\u9519\u8BEF\u6D88\u606F
workflow.node.config.empty=节点配置不能为空 workflow.node.config.empty=\u8282\u70B9\u914D\u7F6E\u4E0D\u80FD\u4E3A\u7A7A
workflow.transition.config.empty=流转配置不能为空 workflow.transition.config.empty=\u6D41\u8F6C\u914D\u7F6E\u4E0D\u80FD\u4E3A\u7A7A
workflow.form.config.empty=表单配置不能为空 workflow.form.config.empty=\u8868\u5355\u914D\u7F6E\u4E0D\u80FD\u4E3A\u7A7A
workflow.graph.config.empty=图形配置不能为空 workflow.graph.config.empty=\u56FE\u5F62\u914D\u7F6E\u4E0D\u80FD\u4E3A\u7A7A
# 工作流节点类型错误 (2200-2299) # \u5DE5\u4F5C\u6D41\u8282\u70B9\u7C7B\u578B\u9519\u8BEF (2200-2299)
workflow.node.type.not.found=节点类型不存在或已删除 workflow.node.type.not.found=\u8282\u70B9\u7C7B\u578B\u4E0D\u5B58\u5728\u6216\u5DF2\u5220\u9664
workflow.node.type.disabled=节点类型已禁用,无法使用 workflow.node.type.disabled=\u8282\u70B9\u7C7B\u578B\u5DF2\u7981\u7528\uFF0C\u65E0\u6CD5\u4F7F\u7528
workflow.node.type.code.exists=节点类型编码已存在 workflow.node.type.code.exists=\u8282\u70B9\u7C7B\u578B\u7F16\u7801\u5DF2\u5B58\u5728
workflow.node.type.invalid.category=无效的节点类型分类 workflow.node.type.invalid.category=\u65E0\u6548\u7684\u8282\u70B9\u7C7B\u578B\u5206\u7C7B
workflow.node.type.invalid.executor=无效的执行器配置 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}

View File

@ -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<String, Object> 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<String, Object> expectedVariables = new HashMap<>();
expectedVariables.put("key1", "value1");
when(context.getVariables()).thenReturn(expectedVariables);
// 第一次调用
Map<String, Object> result1 = operations.getVariables(instance);
assertEquals(expectedVariables, result1);
// 第二次调用应该返回缓存的结果
Map<String, Object> 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);
}
}