可正常启动
This commit is contained in:
parent
6050c5e189
commit
9fce2fc900
@ -190,6 +190,12 @@
|
|||||||
<artifactId>assertj-core</artifactId>
|
<artifactId>assertj-core</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Apache Commons Exec -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-exec</artifactId>
|
||||||
|
<version>1.3</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@ -98,6 +98,9 @@ public enum ResponseCode {
|
|||||||
WORKFLOW_NOT_PUBLISHED(2704, "workflow.not.published"),
|
WORKFLOW_NOT_PUBLISHED(2704, "workflow.not.published"),
|
||||||
WORKFLOW_ALREADY_PUBLISHED(2705, "workflow.already.published"),
|
WORKFLOW_ALREADY_PUBLISHED(2705, "workflow.already.published"),
|
||||||
WORKFLOW_ALREADY_DISABLED(2706, "workflow.already.disabled"),
|
WORKFLOW_ALREADY_DISABLED(2706, "workflow.already.disabled"),
|
||||||
|
WORKFLOW_NOT_DRAFT(2707, "workflow.not.draft"),
|
||||||
|
WORKFLOW_NOT_DISABLED(2708, "workflow.not.disabled"),
|
||||||
|
WORKFLOW_INVALID_STATUS(2709, "workflow.invalid.status"),
|
||||||
WORKFLOW_INSTANCE_NOT_FOUND(2710, "workflow.instance.not.found"),
|
WORKFLOW_INSTANCE_NOT_FOUND(2710, "workflow.instance.not.found"),
|
||||||
WORKFLOW_INSTANCE_ALREADY_COMPLETED(2711, "workflow.instance.already.completed"),
|
WORKFLOW_INSTANCE_ALREADY_COMPLETED(2711, "workflow.instance.already.completed"),
|
||||||
WORKFLOW_INSTANCE_ALREADY_CANCELED(2712, "workflow.instance.already.canceled"),
|
WORKFLOW_INSTANCE_ALREADY_CANCELED(2712, "workflow.instance.already.canceled"),
|
||||||
@ -107,6 +110,8 @@ public enum ResponseCode {
|
|||||||
WORKFLOW_NODE_CONFIG_INVALID(2722, "workflow.node.config.invalid"),
|
WORKFLOW_NODE_CONFIG_INVALID(2722, "workflow.node.config.invalid"),
|
||||||
WORKFLOW_NODE_EXECUTION_FAILED(2723, "workflow.node.execution.failed"),
|
WORKFLOW_NODE_EXECUTION_FAILED(2723, "workflow.node.execution.failed"),
|
||||||
WORKFLOW_NODE_TIMEOUT(2724, "workflow.node.timeout"),
|
WORKFLOW_NODE_TIMEOUT(2724, "workflow.node.timeout"),
|
||||||
|
WORKFLOW_NODE_CONFIG_ERROR(2725, "workflow.node.config.error"),
|
||||||
|
WORKFLOW_EXECUTION_ERROR(2726, "workflow.execution.error"),
|
||||||
WORKFLOW_VARIABLE_NOT_FOUND(2730, "workflow.variable.not.found"),
|
WORKFLOW_VARIABLE_NOT_FOUND(2730, "workflow.variable.not.found"),
|
||||||
WORKFLOW_VARIABLE_TYPE_INVALID(2731, "workflow.variable.type.invalid"),
|
WORKFLOW_VARIABLE_TYPE_INVALID(2731, "workflow.variable.type.invalid"),
|
||||||
WORKFLOW_PERMISSION_DENIED(2740, "workflow.permission.denied"),
|
WORKFLOW_PERMISSION_DENIED(2740, "workflow.permission.denied"),
|
||||||
|
|||||||
@ -62,7 +62,7 @@ public class ExternalSystemServiceImpl extends BaseServiceImpl<ExternalSystem, E
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void validateUniqueConstraints(ExternalSystemDTO dto) {
|
public void validateUniqueConstraints(ExternalSystemDTO dto) {
|
||||||
// 检查名称唯一性
|
// 检查名称唯一性
|
||||||
if (externalSystemRepository.existsByNameAndDeletedFalse(dto.getName())) {
|
if (externalSystemRepository.existsByNameAndDeletedFalse(dto.getName())) {
|
||||||
throw new UniqueConstraintException(ResponseCode.EXTERNAL_SYSTEM_NAME_EXISTS, "name", dto.getName());
|
throw new UniqueConstraintException(ResponseCode.EXTERNAL_SYSTEM_NAME_EXISTS, "name", dto.getName());
|
||||||
|
|||||||
@ -1,141 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.api;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
|
||||||
import com.qqchen.deploy.backend.enums.LogTypeEnum;
|
|
||||||
import com.qqchen.deploy.backend.framework.api.Response;
|
|
||||||
import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO;
|
|
||||||
import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO;
|
|
||||||
import com.qqchen.deploy.backend.workflow.api.request.WorkflowLogQueryRequest;
|
|
||||||
import com.qqchen.deploy.backend.workflow.api.request.WorkflowStartRequest;
|
|
||||||
import com.qqchen.deploy.backend.workflow.api.response.WorkflowLogDTO;
|
|
||||||
import com.qqchen.deploy.backend.workflow.converter.WorkflowLogConverter;
|
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
|
|
||||||
import com.qqchen.deploy.backend.workflow.service.INodeInstanceService;
|
|
||||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
|
|
||||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import jakarta.validation.Valid;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.data.domain.PageRequest;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流API控制器
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/v1/workflow")
|
|
||||||
@Tag(name = "工作流API", description = "工作流相关API接口")
|
|
||||||
public class WorkflowApiController {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private IWorkflowInstanceService workflowInstanceService;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private INodeInstanceService nodeInstanceService;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private IWorkflowLogService workflowLogService;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private WorkflowLogConverter workflowLogConverter;
|
|
||||||
|
|
||||||
@Operation(summary = "启动工作流")
|
|
||||||
@PostMapping("/start")
|
|
||||||
public Response<WorkflowInstanceDTO> startWorkflow(@Valid @RequestBody WorkflowStartRequest request) {
|
|
||||||
return Response.success(workflowInstanceService.start(
|
|
||||||
request.getDefinitionId(),
|
|
||||||
request.getProjectEnvId(),
|
|
||||||
request.getVariables() != null ? request.getVariables().toString() : null
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "取消工作流")
|
|
||||||
@PostMapping("/{instanceId}/cancel")
|
|
||||||
public Response<Boolean> cancelWorkflow(
|
|
||||||
@Parameter(description = "工作流实例ID", required = true) @PathVariable Long instanceId
|
|
||||||
) {
|
|
||||||
return Response.success(workflowInstanceService.cancel(instanceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "获取工作流实例详情")
|
|
||||||
@GetMapping("/{instanceId}")
|
|
||||||
public Response<WorkflowInstanceDTO> getWorkflowInstance(
|
|
||||||
@Parameter(description = "工作流实例ID", required = true) @PathVariable Long instanceId
|
|
||||||
) {
|
|
||||||
return Response.success(workflowInstanceService.findById(instanceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "获取工作流节点列表")
|
|
||||||
@GetMapping("/{instanceId}/nodes")
|
|
||||||
public Response<List<NodeInstanceDTO>> getWorkflowNodes(
|
|
||||||
@Parameter(description = "工作流实例ID", required = true) @PathVariable Long instanceId
|
|
||||||
) {
|
|
||||||
return Response.success(nodeInstanceService.findByWorkflowInstanceId(instanceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "重试工作流节点")
|
|
||||||
@PostMapping("/node/{nodeId}/retry")
|
|
||||||
public Response<Boolean> retryNode(
|
|
||||||
@Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId
|
|
||||||
) {
|
|
||||||
// TODO: 实现节点重试逻辑
|
|
||||||
return Response.success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "跳过工作流节点")
|
|
||||||
@PostMapping("/node/{nodeId}/skip")
|
|
||||||
public Response<Boolean> skipNode(
|
|
||||||
@Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId
|
|
||||||
) {
|
|
||||||
// TODO: 实现节点跳过逻辑
|
|
||||||
return Response.success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "查询工作流日志")
|
|
||||||
@PostMapping("/logs")
|
|
||||||
public Response<Page<WorkflowLogDTO>> getLogs(@Valid @RequestBody WorkflowLogQueryRequest request) {
|
|
||||||
Page<WorkflowLog> logs = workflowLogService.findLogs(
|
|
||||||
request.getWorkflowInstanceId(),
|
|
||||||
request.getType(),
|
|
||||||
request.getLevel(),
|
|
||||||
PageRequest.of(request.getPageNum() - 1, request.getPageSize())
|
|
||||||
);
|
|
||||||
return Response.success(logs.map(workflowLogConverter::toDto));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "查询节点日志")
|
|
||||||
@GetMapping("/node/{nodeId}/logs")
|
|
||||||
public Response<List<WorkflowLogDTO>> getNodeLogs(
|
|
||||||
@Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId,
|
|
||||||
@Parameter(description = "日志类型") @RequestParam(required = false) LogTypeEnum type,
|
|
||||||
@Parameter(description = "日志级别") @RequestParam(required = false) LogLevelEnum level
|
|
||||||
) {
|
|
||||||
List<WorkflowLog> logs = workflowLogService.findNodeLogs(nodeId, type, level);
|
|
||||||
return Response.success(logs.stream()
|
|
||||||
.map(workflowLogConverter::toDto)
|
|
||||||
.collect(Collectors.toList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "分页查询节点日志")
|
|
||||||
@GetMapping("/node/{nodeId}/logs/page")
|
|
||||||
public Response<Page<WorkflowLogDTO>> getNodeLogsPage(
|
|
||||||
@Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId,
|
|
||||||
@Parameter(description = "日志类型") @RequestParam(required = false) LogTypeEnum type,
|
|
||||||
@Parameter(description = "日志级别") @RequestParam(required = false) LogLevelEnum level,
|
|
||||||
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum,
|
|
||||||
@Parameter(description = "每页大小") @RequestParam(defaultValue = "20") Integer pageSize
|
|
||||||
) {
|
|
||||||
Page<WorkflowLog> logs = workflowLogService.findNodeLogs(
|
|
||||||
nodeId, type, level, PageRequest.of(pageNum - 1, pageSize)
|
|
||||||
);
|
|
||||||
return Response.success(logs.map(workflowLogConverter::toDto));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.api.dto;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import jakarta.validation.constraints.Size;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流定义DTO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
public class WorkflowDefinitionDTO extends BaseDTO {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流编码
|
|
||||||
*/
|
|
||||||
@NotBlank(message = "工作流编码不能为空")
|
|
||||||
@Size(max = 50, message = "工作流编码长度不能超过50")
|
|
||||||
private String code;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流名称
|
|
||||||
*/
|
|
||||||
@NotBlank(message = "工作流名称不能为空")
|
|
||||||
@Size(max = 100, message = "工作流名称长度不能超过100")
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 描述
|
|
||||||
*/
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流定义(JSON)
|
|
||||||
*/
|
|
||||||
@NotBlank(message = "工作流定义不能为空")
|
|
||||||
private String content;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 类型:DEPLOY/CONFIG_SYNC
|
|
||||||
*/
|
|
||||||
@NotBlank(message = "工作流类型不能为空")
|
|
||||||
@Size(max = 20, message = "工作流类型长度不能超过20")
|
|
||||||
private String type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 状态:DRAFT/PUBLISHED/DISABLED
|
|
||||||
*/
|
|
||||||
private String status;
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.api.query;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
|
||||||
import com.qqchen.deploy.backend.framework.enums.QueryType;
|
|
||||||
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点实例查询对象
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
public class NodeInstanceQuery extends BaseQuery {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流实例ID
|
|
||||||
*/
|
|
||||||
@QueryField(field = "workflowInstance.id")
|
|
||||||
private Long workflowInstanceId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点ID
|
|
||||||
*/
|
|
||||||
@QueryField(field = "nodeId")
|
|
||||||
private String nodeId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点类型
|
|
||||||
*/
|
|
||||||
@QueryField(field = "nodeType")
|
|
||||||
private String nodeType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点名称
|
|
||||||
*/
|
|
||||||
@QueryField(field = "name", type = QueryType.LIKE)
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点状态
|
|
||||||
*/
|
|
||||||
@QueryField(field = "status")
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * 开始时间范围(起始)
|
|
||||||
// */
|
|
||||||
// @QueryField(field = "startTime", type = QueryType.BETWEEN)
|
|
||||||
// private LocalDateTime startTimeBegin;
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * 开始时间范围(结束)
|
|
||||||
// */
|
|
||||||
// @QueryField(field = "startTime", type = QueryType.LESS_THAN_OR_EQUAL)
|
|
||||||
// private LocalDateTime startTimeEnd;
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.api.query;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
|
||||||
import com.qqchen.deploy.backend.framework.enums.QueryType;
|
|
||||||
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流实例查询对象
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
public class WorkflowInstanceQuery extends BaseQuery {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流定义ID
|
|
||||||
*/
|
|
||||||
@QueryField(field = "definition.id")
|
|
||||||
private Long definitionId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 项目环境ID
|
|
||||||
*/
|
|
||||||
@QueryField(field = "projectEnvId")
|
|
||||||
private Long projectEnvId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流状态
|
|
||||||
*/
|
|
||||||
@QueryField(field = "status")
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * 开始时间范围(起始)
|
|
||||||
// */
|
|
||||||
// @QueryField(field = "startTime", type = QueryType.GREATER_THAN_OR_EQUAL)
|
|
||||||
// private LocalDateTime startTimeBegin;
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * 开始时间范围(结束)
|
|
||||||
// */
|
|
||||||
// @QueryField(field = "startTime", type = QueryType.LESS_THAN_OR_EQUAL)
|
|
||||||
// private LocalDateTime startTimeEnd;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流定义名称
|
|
||||||
*/
|
|
||||||
@QueryField(field = "definition.name", type = QueryType.LIKE)
|
|
||||||
private String workflowName;
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.api.request;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
|
||||||
import com.qqchen.deploy.backend.enums.LogTypeEnum;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流日志查询请求
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Schema(description = "工作流日志查询请求")
|
|
||||||
public class WorkflowLogQueryRequest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流实例ID
|
|
||||||
*/
|
|
||||||
@NotNull(message = "工作流实例ID不能为空")
|
|
||||||
@Schema(description = "工作流实例ID", required = true)
|
|
||||||
private Long workflowInstanceId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点实例ID
|
|
||||||
*/
|
|
||||||
@Schema(description = "节点实例ID")
|
|
||||||
private Long nodeInstanceId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日志类型
|
|
||||||
*/
|
|
||||||
@Schema(description = "日志类型")
|
|
||||||
private LogTypeEnum type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日志级别
|
|
||||||
*/
|
|
||||||
@Schema(description = "日志级别")
|
|
||||||
private LogLevelEnum level;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 页码
|
|
||||||
*/
|
|
||||||
@Schema(description = "页码", defaultValue = "1")
|
|
||||||
private Integer pageNum = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 每页大小
|
|
||||||
*/
|
|
||||||
@Schema(description = "每页大小", defaultValue = "20")
|
|
||||||
private Integer pageSize = 20;
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.api.request;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流启动请求
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Schema(description = "工作流启动请求")
|
|
||||||
public class WorkflowStartRequest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流定义ID
|
|
||||||
*/
|
|
||||||
@NotNull(message = "工作流定义ID不能为空")
|
|
||||||
@Schema(description = "工作流定义ID", required = true)
|
|
||||||
private Long definitionId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 项目环境ID
|
|
||||||
*/
|
|
||||||
@NotNull(message = "项目环境ID不能为空")
|
|
||||||
@Schema(description = "项目环境ID", required = true)
|
|
||||||
private Long projectEnvId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流变量
|
|
||||||
*/
|
|
||||||
@Schema(description = "工作流变量")
|
|
||||||
private Map<String, Object> variables;
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.api.response;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
|
||||||
import com.qqchen.deploy.backend.enums.LogTypeEnum;
|
|
||||||
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流日志DTO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Schema(description = "工作流日志")
|
|
||||||
public class WorkflowLogDTO extends BaseDTO {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日志ID
|
|
||||||
*/
|
|
||||||
@Schema(description = "日志ID")
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流实例ID
|
|
||||||
*/
|
|
||||||
@Schema(description = "工作流实例ID")
|
|
||||||
private Long workflowInstanceId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点实例ID
|
|
||||||
*/
|
|
||||||
@Schema(description = "节点实例ID")
|
|
||||||
private Long nodeInstanceId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日志类型
|
|
||||||
*/
|
|
||||||
@Schema(description = "日志类型")
|
|
||||||
private LogTypeEnum type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日志级别
|
|
||||||
*/
|
|
||||||
@Schema(description = "日志级别")
|
|
||||||
private LogLevelEnum level;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日志内容
|
|
||||||
*/
|
|
||||||
@Schema(description = "日志内容")
|
|
||||||
private String content;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 详细信息
|
|
||||||
*/
|
|
||||||
@Schema(description = "详细信息")
|
|
||||||
private String detail;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建时间
|
|
||||||
*/
|
|
||||||
@Schema(description = "创建时间")
|
|
||||||
private LocalDateTime createTime;
|
|
||||||
}
|
|
||||||
@ -2,10 +2,10 @@ package com.qqchen.deploy.backend.workflow.controller;
|
|||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.api.Response;
|
import com.qqchen.deploy.backend.framework.api.Response;
|
||||||
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
||||||
import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO;
|
import com.qqchen.deploy.backend.workflow.dto.NodeInstanceDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.api.query.NodeInstanceQuery;
|
|
||||||
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
import com.qqchen.deploy.backend.workflow.service.INodeInstanceService;
|
import com.qqchen.deploy.backend.workflow.engine.WorkflowEngine;
|
||||||
|
import com.qqchen.deploy.backend.workflow.query.NodeInstanceQuery;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@ -15,58 +15,38 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点实例控制器
|
|
||||||
*/
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/workflow/node")
|
@RequestMapping("/api/v1/node-instances")
|
||||||
@Tag(name = "工作流节点管理", description = "工作流节点管理相关接口")
|
@Tag(name = "节点实例管理", description = "节点实例管理相关接口")
|
||||||
public class NodeInstanceApiController extends BaseController<NodeInstance, NodeInstanceDTO, Long, NodeInstanceQuery> {
|
public class NodeInstanceApiController extends BaseController<NodeInstance, NodeInstanceDTO, Long, NodeInstanceQuery> {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private INodeInstanceService nodeInstanceService;
|
private WorkflowEngine workflowEngine;
|
||||||
|
|
||||||
|
@Operation(summary = "完成节点")
|
||||||
@Operation(summary = "根据工作流实例ID查询节点实例列表")
|
@PostMapping("/{id}/complete")
|
||||||
@GetMapping("/workflow/{workflowInstanceId}")
|
public Response<Void> completeNode(
|
||||||
public Response<List<NodeInstanceDTO>> findByWorkflowInstanceId(
|
@Parameter(description = "节点实例ID", required = true) @PathVariable Long id,
|
||||||
@Parameter(description = "工作流实例ID", required = true)
|
@Parameter(description = "输出变量") @RequestBody(required = false) Map<String, Object> variables
|
||||||
@PathVariable Long workflowInstanceId) {
|
) {
|
||||||
return Response.success(nodeInstanceService.findByWorkflowInstanceId(workflowInstanceId));
|
workflowEngine.completeNode(id, variables);
|
||||||
|
return Response.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "根据工作流实例ID和状态查询节点实例列表")
|
@Operation(summary = "重试节点")
|
||||||
@GetMapping("/workflow/{workflowInstanceId}/status/{status}")
|
@PostMapping("/{id}/retry")
|
||||||
public Response<List<NodeInstanceDTO>> findByWorkflowInstanceIdAndStatus(
|
public Response<Void> retryNode(
|
||||||
@Parameter(description = "工作流实例ID", required = true)
|
@Parameter(description = "节点实例ID", required = true) @PathVariable Long id
|
||||||
@PathVariable Long workflowInstanceId,
|
) {
|
||||||
@Parameter(description = "状态", required = true)
|
workflowEngine.executeNode(id);
|
||||||
@PathVariable String status) {
|
return Response.success();
|
||||||
return Response.success(nodeInstanceService.findByWorkflowInstanceIdAndStatus(workflowInstanceId, status));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "更新节点状态")
|
|
||||||
@PostMapping("/{id}/status")
|
|
||||||
public Response<Boolean> updateStatus(
|
|
||||||
@Parameter(description = "节点实例ID", required = true)
|
|
||||||
@PathVariable Long id,
|
|
||||||
@Parameter(description = "状态", required = true)
|
|
||||||
@RequestParam String status,
|
|
||||||
@Parameter(description = "输出结果")
|
|
||||||
@RequestParam(required = false) String output,
|
|
||||||
@Parameter(description = "错误信息")
|
|
||||||
@RequestParam(required = false) String error) {
|
|
||||||
return Response.success(nodeInstanceService.updateStatus(id, status, output, error));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void exportData(HttpServletResponse response, List<NodeInstanceDTO> data) {
|
protected void exportData(HttpServletResponse response, List<NodeInstanceDTO> data) {
|
||||||
// TODO: 实现导出功能
|
// TODO: 实现导出功能
|
||||||
}
|
}
|
||||||
|
|
||||||
protected INodeInstanceService getService() {
|
|
||||||
return nodeInstanceService;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -2,9 +2,9 @@ package com.qqchen.deploy.backend.workflow.controller;
|
|||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.api.Response;
|
import com.qqchen.deploy.backend.framework.api.Response;
|
||||||
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
||||||
import com.qqchen.deploy.backend.workflow.api.dto.WorkflowDefinitionDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.api.query.WorkflowDefinitionQuery;
|
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||||
|
import com.qqchen.deploy.backend.workflow.query.WorkflowDefinitionQuery;
|
||||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService;
|
import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
@ -16,12 +16,9 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流定义控制器
|
|
||||||
*/
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/workflow/definition")
|
@RequestMapping("/api/v1/workflow-definitions")
|
||||||
@Tag(name = "工作流定义管理", description = "工作流定义管理相关接口")
|
@Tag(name = "工作流定义管理", description = "工作流定义管理相关接口")
|
||||||
public class WorkflowDefinitionApiController extends BaseController<WorkflowDefinition, WorkflowDefinitionDTO, Long, WorkflowDefinitionQuery> {
|
public class WorkflowDefinitionApiController extends BaseController<WorkflowDefinition, WorkflowDefinitionDTO, Long, WorkflowDefinitionQuery> {
|
||||||
|
|
||||||
@ -30,31 +27,33 @@ public class WorkflowDefinitionApiController extends BaseController<WorkflowDefi
|
|||||||
|
|
||||||
@Operation(summary = "发布工作流")
|
@Operation(summary = "发布工作流")
|
||||||
@PostMapping("/{id}/publish")
|
@PostMapping("/{id}/publish")
|
||||||
public Response<Boolean> publish(
|
public Response<Void> publish(
|
||||||
@Parameter(description = "工作流定义ID", required = true)
|
@Parameter(description = "工作流定义ID", required = true) @PathVariable Long id
|
||||||
@PathVariable Long id) {
|
) {
|
||||||
return Response.success(workflowDefinitionService.publish(id));
|
workflowDefinitionService.publish(id);
|
||||||
|
return Response.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "禁用工作流")
|
@Operation(summary = "禁用工作流")
|
||||||
@PostMapping("/{id}/disable")
|
@PostMapping("/{id}/disable")
|
||||||
public Response<Boolean> disable(
|
public Response<Void> disable(
|
||||||
@Parameter(description = "工作流定义ID", required = true)
|
@Parameter(description = "工作流定义ID", required = true) @PathVariable Long id
|
||||||
@PathVariable Long id) {
|
) {
|
||||||
return Response.success(workflowDefinitionService.disable(id));
|
workflowDefinitionService.disable(id);
|
||||||
|
return Response.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "根据编码查询工作流定义")
|
@Operation(summary = "启用工作流")
|
||||||
@GetMapping("/code/{code}")
|
@PostMapping("/{id}/enable")
|
||||||
public Response<WorkflowDefinitionDTO> findByCode(
|
public Response<Void> enable(
|
||||||
@Parameter(description = "工作流编码", required = true)
|
@Parameter(description = "工作流定义ID", required = true) @PathVariable Long id
|
||||||
@PathVariable String code) {
|
) {
|
||||||
return Response.success(workflowDefinitionService.findByCode(code));
|
workflowDefinitionService.enable(id);
|
||||||
|
return Response.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void exportData(HttpServletResponse response, List<WorkflowDefinitionDTO> data) {
|
protected void exportData(HttpServletResponse response, List<WorkflowDefinitionDTO> data) {
|
||||||
// TODO: 实现导出功能
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -2,78 +2,94 @@ package com.qqchen.deploy.backend.workflow.controller;
|
|||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.api.Response;
|
import com.qqchen.deploy.backend.framework.api.Response;
|
||||||
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
||||||
import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.api.query.WorkflowInstanceQuery;
|
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.WorkflowEngine;
|
||||||
|
import com.qqchen.deploy.backend.workflow.query.WorkflowInstanceQuery;
|
||||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
|
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
|
||||||
|
import com.qqchen.deploy.backend.workflow.converter.WorkflowInstanceConverter;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.Data;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流实例控制器
|
|
||||||
*/
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/workflow/instance")
|
@RequestMapping("/api/v1/workflow-instances")
|
||||||
@Tag(name = "工作流实例管理", description = "工作流实例管理相关接口")
|
@Tag(name = "工作流实例管理", description = "工作流实例管理相关接口")
|
||||||
public class WorkflowInstanceApiController extends BaseController<WorkflowInstance, WorkflowInstanceDTO, Long, WorkflowInstanceQuery> {
|
public class WorkflowInstanceApiController extends BaseController<WorkflowInstance, WorkflowInstanceDTO, Long, WorkflowInstanceQuery> {
|
||||||
|
|
||||||
private final IWorkflowInstanceService workflowInstanceService;
|
@Resource
|
||||||
|
private WorkflowEngine workflowEngine;
|
||||||
|
|
||||||
public WorkflowInstanceApiController(IWorkflowInstanceService workflowInstanceService) {
|
@Resource
|
||||||
this.workflowInstanceService = workflowInstanceService;
|
private IWorkflowInstanceService workflowInstanceService;
|
||||||
}
|
|
||||||
|
@Resource
|
||||||
|
private WorkflowInstanceConverter converter;
|
||||||
|
|
||||||
@Operation(summary = "启动工作流实例")
|
@Operation(summary = "启动工作流实例")
|
||||||
@PostMapping("/start")
|
@PostMapping("/start")
|
||||||
public Response<WorkflowInstanceDTO> start(
|
public Response<WorkflowInstanceDTO> startWorkflow(@RequestBody StartWorkflowRequest request) {
|
||||||
@Parameter(description = "工作流定义ID", required = true)
|
WorkflowInstance instance = workflowEngine.startWorkflow(
|
||||||
@RequestParam Long definitionId,
|
request.getWorkflowCode(),
|
||||||
@Parameter(description = "项目环境ID", required = true)
|
request.getBusinessKey(),
|
||||||
@RequestParam Long projectEnvId,
|
request.getVariables()
|
||||||
@Parameter(description = "工作流变量")
|
);
|
||||||
@RequestParam(required = false) String variables) {
|
return Response.success(converter.toDto(instance));
|
||||||
return Response.success(workflowInstanceService.start(definitionId, projectEnvId, variables));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "取消工作流实例")
|
@Operation(summary = "终止工作流实例")
|
||||||
@PostMapping("/{id}/cancel")
|
@PostMapping("/{id}/terminate")
|
||||||
public Response<Boolean> cancel(
|
public Response<Void> terminateWorkflow(
|
||||||
@Parameter(description = "工作流实例ID", required = true)
|
@Parameter(description = "工作流实例ID", required = true) @PathVariable Long id,
|
||||||
@PathVariable Long id) {
|
@Parameter(description = "终止原因") @RequestParam(required = false) String reason
|
||||||
return Response.success(workflowInstanceService.cancel(id));
|
) {
|
||||||
|
workflowEngine.terminateWorkflow(id, reason);
|
||||||
|
return Response.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "根据项目环境ID查询工作流实例列表")
|
@Operation(summary = "暂停工作流实例")
|
||||||
@GetMapping("/project-env/{projectEnvId}")
|
@PostMapping("/{id}/pause")
|
||||||
public Response<List<WorkflowInstanceDTO>> findByProjectEnvId(
|
public Response<Void> pauseWorkflow(
|
||||||
@Parameter(description = "项目环境ID", required = true)
|
@Parameter(description = "工作流实例ID", required = true) @PathVariable Long id
|
||||||
@PathVariable Long projectEnvId) {
|
) {
|
||||||
return Response.success(workflowInstanceService.findByProjectEnvId(projectEnvId));
|
workflowEngine.pauseWorkflow(id);
|
||||||
|
return Response.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "根据项目环境ID和状态查询工作流实例列表")
|
@Operation(summary = "恢复工作流实例")
|
||||||
@GetMapping("/project-env/{projectEnvId}/status/{status}")
|
@PostMapping("/{id}/resume")
|
||||||
public Response<List<WorkflowInstanceDTO>> findByProjectEnvIdAndStatus(
|
public Response<Void> resumeWorkflow(
|
||||||
@Parameter(description = "项目环境ID", required = true)
|
@Parameter(description = "工作流实例ID", required = true) @PathVariable Long id
|
||||||
@PathVariable Long projectEnvId,
|
) {
|
||||||
@Parameter(description = "状态", required = true)
|
workflowEngine.resumeWorkflow(id);
|
||||||
@PathVariable String status) {
|
return Response.success();
|
||||||
return Response.success(workflowInstanceService.findByProjectEnvIdAndStatus(projectEnvId, status));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void exportData(HttpServletResponse response, List<WorkflowInstanceDTO> data) {
|
public void exportData(HttpServletResponse response, List<WorkflowInstanceDTO> data) {
|
||||||
// TODO: 实现导出功能
|
// TODO: 实现导出功能
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IWorkflowInstanceService getService() {
|
|
||||||
return workflowInstanceService;
|
@Data
|
||||||
|
public static class StartWorkflowRequest {
|
||||||
|
|
||||||
|
@Parameter(description = "工作流编码", required = true)
|
||||||
|
private String workflowCode;
|
||||||
|
|
||||||
|
@Parameter(description = "业务标识", required = true)
|
||||||
|
private String businessKey;
|
||||||
|
|
||||||
|
@Parameter(description = "工作流变量")
|
||||||
|
private Map<String, Object> variables;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.controller;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
|
import com.qqchen.deploy.backend.framework.api.Response;
|
||||||
|
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
||||||
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowLogDTO;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
|
||||||
|
import com.qqchen.deploy.backend.workflow.query.WorkflowLogQuery;
|
||||||
|
import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/workflow-logs")
|
||||||
|
@Tag(name = "工作流日志管理", description = "工作流日志管理相关接口")
|
||||||
|
public class WorkflowLogApiController extends BaseController<WorkflowLog, WorkflowLogDTO, Long, WorkflowLogQuery> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IWorkflowLogService workflowLogService;
|
||||||
|
|
||||||
|
@Operation(summary = "查询工作流实例日志")
|
||||||
|
@GetMapping("/workflow/{workflowInstanceId}")
|
||||||
|
public Response<List<WorkflowLogDTO>> getWorkflowLogs(
|
||||||
|
@Parameter(description = "工作流实例ID", required = true) @PathVariable Long workflowInstanceId
|
||||||
|
) {
|
||||||
|
return Response.success(workflowLogService.getLogs(workflowInstanceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询节点实例日志")
|
||||||
|
@GetMapping("/node/{workflowInstanceId}/{nodeId}")
|
||||||
|
public Response<List<WorkflowLogDTO>> getNodeLogs(
|
||||||
|
@Parameter(description = "工作流实例ID", required = true) @PathVariable Long workflowInstanceId,
|
||||||
|
@Parameter(description = "节点ID", required = true) @PathVariable String nodeId
|
||||||
|
) {
|
||||||
|
return Response.success(workflowLogService.getNodeLogs(workflowInstanceId, nodeId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "记录日志")
|
||||||
|
@PostMapping
|
||||||
|
public Response<Void> log(
|
||||||
|
@Parameter(description = "工作流实例ID", required = true) @RequestParam Long workflowInstanceId,
|
||||||
|
@Parameter(description = "节点ID") @RequestParam(required = false) String nodeId,
|
||||||
|
@Parameter(description = "日志内容", required = true) @RequestParam String message,
|
||||||
|
@Parameter(description = "日志级别", required = true) @RequestParam LogLevelEnum level,
|
||||||
|
@Parameter(description = "详细信息") @RequestParam(required = false) String detail
|
||||||
|
) {
|
||||||
|
workflowLogService.log(workflowInstanceId, nodeId, message, level, detail);
|
||||||
|
return Response.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exportData(HttpServletResponse response, List<WorkflowLogDTO> data) {
|
||||||
|
// TODO: 实现导出功能
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.converter;
|
package com.qqchen.deploy.backend.workflow.converter;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||||
import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO;
|
import com.qqchen.deploy.backend.workflow.dto.NodeInstanceDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
import org.mapstruct.Mapping;
|
import org.mapstruct.Mapping;
|
||||||
@ -12,9 +12,11 @@ import org.mapstruct.Mapping;
|
|||||||
@Mapper(config = BaseConverter.class)
|
@Mapper(config = BaseConverter.class)
|
||||||
public interface NodeInstanceConverter extends BaseConverter<NodeInstance, NodeInstanceDTO> {
|
public interface NodeInstanceConverter extends BaseConverter<NodeInstance, NodeInstanceDTO> {
|
||||||
|
|
||||||
|
@Override
|
||||||
@Mapping(target = "workflowInstanceId", source = "workflowInstance.id")
|
@Mapping(target = "workflowInstanceId", source = "workflowInstance.id")
|
||||||
NodeInstanceDTO toDto(NodeInstance entity);
|
NodeInstanceDTO toDto(NodeInstance entity);
|
||||||
|
|
||||||
|
@Override
|
||||||
@Mapping(target = "workflowInstance.id", source = "workflowInstanceId")
|
@Mapping(target = "workflowInstance.id", source = "workflowInstanceId")
|
||||||
NodeInstance toEntity(NodeInstanceDTO dto);
|
NodeInstance toEntity(NodeInstanceDTO dto);
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.converter;
|
package com.qqchen.deploy.backend.workflow.converter;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||||
import com.qqchen.deploy.backend.workflow.api.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;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.converter;
|
package com.qqchen.deploy.backend.workflow.converter;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||||
import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
import org.mapstruct.Mapping;
|
import org.mapstruct.Mapping;
|
||||||
@ -12,10 +12,11 @@ import org.mapstruct.Mapping;
|
|||||||
@Mapper(config = BaseConverter.class)
|
@Mapper(config = BaseConverter.class)
|
||||||
public interface WorkflowInstanceConverter extends BaseConverter<WorkflowInstance, WorkflowInstanceDTO> {
|
public interface WorkflowInstanceConverter extends BaseConverter<WorkflowInstance, WorkflowInstanceDTO> {
|
||||||
|
|
||||||
|
@Override
|
||||||
@Mapping(target = "definitionId", source = "definition.id")
|
@Mapping(target = "definitionId", source = "definition.id")
|
||||||
@Mapping(target = "workflowName", source = "definition.name")
|
|
||||||
WorkflowInstanceDTO toDto(WorkflowInstance entity);
|
WorkflowInstanceDTO toDto(WorkflowInstance entity);
|
||||||
|
|
||||||
|
@Override
|
||||||
@Mapping(target = "definition.id", source = "definitionId")
|
@Mapping(target = "definition.id", source = "definitionId")
|
||||||
WorkflowInstance toEntity(WorkflowInstanceDTO dto);
|
WorkflowInstance toEntity(WorkflowInstanceDTO dto);
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.converter;
|
package com.qqchen.deploy.backend.workflow.converter;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||||
import com.qqchen.deploy.backend.workflow.api.response.WorkflowLogDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowLogDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.converter;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||||
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowVariableDTO;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowVariable;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流变量转换器
|
||||||
|
*/
|
||||||
|
@Mapper(config = BaseConverter.class)
|
||||||
|
public interface WorkflowVariableConverter extends BaseConverter<WorkflowVariable, WorkflowVariableDTO> {
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.dto;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class NodeDefinitionDTO extends BaseDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流定义ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "工作流定义ID不能为空")
|
||||||
|
private Long workflowDefinitionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点ID
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "节点ID不能为空")
|
||||||
|
private String nodeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点名称
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "节点名称不能为空")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点类型
|
||||||
|
*/
|
||||||
|
@NotNull(message = "节点类型不能为空")
|
||||||
|
private NodeTypeEnum type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点配置(JSON)
|
||||||
|
*/
|
||||||
|
private String config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序号
|
||||||
|
*/
|
||||||
|
private Integer sequence;
|
||||||
|
}
|
||||||
@ -1,16 +1,15 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.api.dto;
|
package com.qqchen.deploy.backend.workflow.dto;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import jakarta.validation.constraints.Size;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点实例DTO
|
|
||||||
*/
|
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class NodeInstanceDTO extends BaseDTO {
|
public class NodeInstanceDTO extends BaseDTO {
|
||||||
@ -25,27 +24,24 @@ public class NodeInstanceDTO extends BaseDTO {
|
|||||||
* 节点ID
|
* 节点ID
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "节点ID不能为空")
|
@NotBlank(message = "节点ID不能为空")
|
||||||
@Size(max = 50, message = "节点ID长度不能超过50")
|
|
||||||
private String nodeId;
|
private String nodeId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点类型
|
* 节点类型
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "节点类型不能为空")
|
@NotNull(message = "节点类型不能为空")
|
||||||
@Size(max = 50, message = "节点类型长度不能超过50")
|
private NodeTypeEnum nodeType;
|
||||||
private String nodeType;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点名称
|
* 节点名称
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "节点名称不能为空")
|
@NotBlank(message = "节点名称不能为空")
|
||||||
@Size(max = 100, message = "节点名称长度不能超过100")
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态:PENDING/RUNNING/COMPLETED/FAILED/CANCELED
|
* 节点状态
|
||||||
*/
|
*/
|
||||||
private String status;
|
private NodeStatusEnum status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始时间
|
* 开始时间
|
||||||
@ -57,6 +53,11 @@ public class NodeInstanceDTO extends BaseDTO {
|
|||||||
*/
|
*/
|
||||||
private LocalDateTime endTime;
|
private LocalDateTime endTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点配置(JSON)
|
||||||
|
*/
|
||||||
|
private String config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入参数(JSON)
|
* 输入参数(JSON)
|
||||||
*/
|
*/
|
||||||
@ -71,4 +72,9 @@ public class NodeInstanceDTO extends BaseDTO {
|
|||||||
* 错误信息
|
* 错误信息
|
||||||
*/
|
*/
|
||||||
private String error;
|
private String error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前置节点ID
|
||||||
|
*/
|
||||||
|
private String preNodeId;
|
||||||
}
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.dto;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class WorkflowDefinitionDTO extends BaseDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流名称
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "工作流名称不能为空")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流编码
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "工作流编码不能为空")
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态
|
||||||
|
*/
|
||||||
|
@NotNull(message = "状态不能为空")
|
||||||
|
private WorkflowStatusEnum status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点配置(JSON)
|
||||||
|
*/
|
||||||
|
private String nodeConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流转配置(JSON)
|
||||||
|
*/
|
||||||
|
private String transitionConfig;
|
||||||
|
}
|
||||||
@ -1,14 +1,13 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.api.dto;
|
package com.qqchen.deploy.backend.workflow.dto;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流实例DTO
|
|
||||||
*/
|
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class WorkflowInstanceDTO extends BaseDTO {
|
public class WorkflowInstanceDTO extends BaseDTO {
|
||||||
@ -20,15 +19,15 @@ public class WorkflowInstanceDTO extends BaseDTO {
|
|||||||
private Long definitionId;
|
private Long definitionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 项目环境ID
|
* 业务标识
|
||||||
*/
|
*/
|
||||||
@NotNull(message = "项目环境ID不能为空")
|
@NotNull(message = "业务标识不能为空")
|
||||||
private Long projectEnvId;
|
private String businessKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态:RUNNING/COMPLETED/FAILED/CANCELED
|
* 状态
|
||||||
*/
|
*/
|
||||||
private String status;
|
private WorkflowStatusEnum status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始时间
|
* 开始时间
|
||||||
@ -49,9 +48,4 @@ public class WorkflowInstanceDTO extends BaseDTO {
|
|||||||
* 错误信息
|
* 错误信息
|
||||||
*/
|
*/
|
||||||
private String error;
|
private String error;
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流定义名称(冗余字段)
|
|
||||||
*/
|
|
||||||
private String workflowName;
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.dto;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
|
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class WorkflowLogDTO extends BaseDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流实例ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "工作流实例ID不能为空")
|
||||||
|
private Long workflowInstanceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点ID
|
||||||
|
*/
|
||||||
|
private String nodeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志内容
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "日志内容不能为空")
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志级别
|
||||||
|
*/
|
||||||
|
@NotNull(message = "日志级别不能为空")
|
||||||
|
private LogLevelEnum level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详细信息
|
||||||
|
*/
|
||||||
|
private String detail;
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.dto;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流变量DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class WorkflowVariableDTO extends BaseDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流实例ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "工作流实例ID不能为空")
|
||||||
|
private Long workflowInstanceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变量名称
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "变量名称不能为空")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变量值(JSON)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "变量值不能为空")
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变量类型
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "变量类型不能为空")
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变量作用域
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "变量作用域不能为空")
|
||||||
|
private String scope;
|
||||||
|
}
|
||||||
@ -0,0 +1,222 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.context.DefaultWorkflowContext;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.executor.NodeExecutor;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository;
|
||||||
|
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
||||||
|
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class DefaultWorkflowEngine implements WorkflowEngine {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IWorkflowDefinitionRepository workflowDefinitionRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IWorkflowInstanceRepository workflowInstanceRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private INodeInstanceRepository nodeInstanceRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private Map<NodeTypeEnum, NodeExecutor> nodeExecutors;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DefaultWorkflowContext.Factory workflowContextFactory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public WorkflowInstance startWorkflow(String workflowCode, String businessKey, Map<String, Object> variables) {
|
||||||
|
// 1. 检查工作流定义
|
||||||
|
WorkflowDefinition definition = workflowDefinitionRepository.findByCodeAndDeletedFalse(workflowCode);
|
||||||
|
if (definition == null) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (definition.getStatus() != WorkflowStatusEnum.PUBLISHED) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NOT_PUBLISHED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 创建工作流实例
|
||||||
|
WorkflowInstance instance = new WorkflowInstance();
|
||||||
|
instance.setDefinition(definition);
|
||||||
|
instance.setStatus(WorkflowStatusEnum.RUNNING);
|
||||||
|
instance.setStartTime(LocalDateTime.now());
|
||||||
|
workflowInstanceRepository.save(instance);
|
||||||
|
|
||||||
|
// 3. 创建工作流上下文
|
||||||
|
WorkflowContext context = workflowContextFactory.create(instance);
|
||||||
|
if (variables != null) {
|
||||||
|
variables.forEach((key, value) -> context.setVariable(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 创建开始节点实例
|
||||||
|
NodeInstance startNode = createStartNode(definition, instance.getId());
|
||||||
|
executeNode(startNode.getId());
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void executeNode(Long nodeInstanceId) {
|
||||||
|
NodeInstance nodeInstance = nodeInstanceRepository.findById(nodeInstanceId)
|
||||||
|
.orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_NOT_FOUND));
|
||||||
|
|
||||||
|
WorkflowInstance instance = nodeInstance.getWorkflowInstance();
|
||||||
|
if (instance.getStatus() != WorkflowStatusEnum.RUNNING) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 获取节点执行器
|
||||||
|
NodeExecutor executor = nodeExecutors.get(nodeInstance.getNodeType());
|
||||||
|
if (executor == null) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_TYPE_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 执行节点
|
||||||
|
WorkflowContext context = workflowContextFactory.create(instance);
|
||||||
|
executor.execute(nodeInstance, context);
|
||||||
|
nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
|
||||||
|
nodeInstance.setEndTime(LocalDateTime.now());
|
||||||
|
nodeInstanceRepository.save(nodeInstance);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
nodeInstance.setStatus(NodeStatusEnum.FAILED);
|
||||||
|
nodeInstance.setEndTime(LocalDateTime.now());
|
||||||
|
nodeInstance.setError(e.getMessage());
|
||||||
|
nodeInstanceRepository.save(nodeInstance);
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void completeNode(Long nodeInstanceId, Map<String, Object> variables) {
|
||||||
|
NodeInstance nodeInstance = nodeInstanceRepository.findById(nodeInstanceId)
|
||||||
|
.orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_NOT_FOUND));
|
||||||
|
|
||||||
|
if (nodeInstance.getStatus() != NodeStatusEnum.RUNNING) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED,
|
||||||
|
"Node is not in running status");
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkflowInstance instance = nodeInstance.getWorkflowInstance();
|
||||||
|
WorkflowContext context = workflowContextFactory.create(instance);
|
||||||
|
if (variables != null) {
|
||||||
|
variables.forEach((key, value) -> context.setVariable(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
|
||||||
|
nodeInstance.setEndTime(LocalDateTime.now());
|
||||||
|
nodeInstanceRepository.save(nodeInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void terminateWorkflow(Long instanceId, String reason) {
|
||||||
|
WorkflowInstance instance = workflowInstanceRepository.findById(instanceId)
|
||||||
|
.orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND));
|
||||||
|
|
||||||
|
if (instance.getStatus() != WorkflowStatusEnum.RUNNING) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 终止所有运行中的节点
|
||||||
|
List<NodeInstance> runningNodes = nodeInstanceRepository.findByInstanceIdAndStatus(
|
||||||
|
instanceId, NodeStatusEnum.RUNNING);
|
||||||
|
|
||||||
|
WorkflowContext context = workflowContextFactory.create(instance);
|
||||||
|
for (NodeInstance node : runningNodes) {
|
||||||
|
NodeExecutor executor = nodeExecutors.get(node.getNodeType());
|
||||||
|
if (executor != null) {
|
||||||
|
executor.terminate(node, context);
|
||||||
|
}
|
||||||
|
node.setStatus(NodeStatusEnum.TERMINATED);
|
||||||
|
node.setEndTime(LocalDateTime.now());
|
||||||
|
nodeInstanceRepository.save(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.setStatus(WorkflowStatusEnum.TERMINATED);
|
||||||
|
instance.setEndTime(LocalDateTime.now());
|
||||||
|
instance.setError(reason);
|
||||||
|
workflowInstanceRepository.save(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void pauseWorkflow(Long instanceId) {
|
||||||
|
WorkflowInstance instance = workflowInstanceRepository.findById(instanceId)
|
||||||
|
.orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND));
|
||||||
|
|
||||||
|
if (instance.getStatus() != WorkflowStatusEnum.RUNNING) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂停所有运行中的节点
|
||||||
|
List<NodeInstance> runningNodes = nodeInstanceRepository.findByInstanceIdAndStatus(
|
||||||
|
instanceId, NodeStatusEnum.RUNNING);
|
||||||
|
|
||||||
|
for (NodeInstance node : runningNodes) {
|
||||||
|
node.setStatus(NodeStatusEnum.PAUSED);
|
||||||
|
nodeInstanceRepository.save(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.setStatus(WorkflowStatusEnum.PAUSED);
|
||||||
|
workflowInstanceRepository.save(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void resumeWorkflow(Long instanceId) {
|
||||||
|
WorkflowInstance instance = workflowInstanceRepository.findById(instanceId)
|
||||||
|
.orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND));
|
||||||
|
|
||||||
|
if (instance.getStatus() != WorkflowStatusEnum.PAUSED) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复所有暂停的节点
|
||||||
|
List<NodeInstance> pausedNodes = nodeInstanceRepository.findByInstanceIdAndStatus(
|
||||||
|
instanceId, NodeStatusEnum.PAUSED);
|
||||||
|
|
||||||
|
for (NodeInstance node : pausedNodes) {
|
||||||
|
node.setStatus(NodeStatusEnum.RUNNING);
|
||||||
|
nodeInstanceRepository.save(node);
|
||||||
|
executeNode(node.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.setStatus(WorkflowStatusEnum.RUNNING);
|
||||||
|
workflowInstanceRepository.save(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodeInstance createStartNode(WorkflowDefinition definition, Long instanceId) {
|
||||||
|
NodeInstance startNode = new NodeInstance();
|
||||||
|
startNode.setWorkflowInstanceId(instanceId);
|
||||||
|
startNode.setNodeId("start");
|
||||||
|
startNode.setNodeType(NodeTypeEnum.START);
|
||||||
|
startNode.setName("开始节点");
|
||||||
|
startNode.setStatus(NodeStatusEnum.PENDING);
|
||||||
|
startNode.setCreateTime(LocalDateTime.now());
|
||||||
|
return nodeInstanceRepository.save(startNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,54 +1,41 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.engine;
|
package com.qqchen.deploy.backend.workflow.engine;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO;
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流引擎接口
|
* 工作流执行引擎接口
|
||||||
*/
|
*/
|
||||||
public interface WorkflowEngine {
|
public interface WorkflowEngine {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动工作流实例
|
* 启动工作流实例
|
||||||
*
|
|
||||||
* @param instanceId 工作流实例ID
|
|
||||||
* @param variables 工作流变量
|
|
||||||
*/
|
*/
|
||||||
void startInstance(Long instanceId, Map<String, Object> variables);
|
WorkflowInstance startWorkflow(String workflowCode, String businessKey, Map<String, Object> variables);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取消工作流实例
|
* 执行节点
|
||||||
*
|
|
||||||
* @param instanceId 工作流实例ID
|
|
||||||
*/
|
*/
|
||||||
void cancelInstance(Long instanceId);
|
void executeNode(Long nodeInstanceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完成节点
|
||||||
|
*/
|
||||||
|
void completeNode(Long nodeInstanceId, Map<String, Object> variables);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终止工作流实例
|
||||||
|
*/
|
||||||
|
void terminateWorkflow(Long instanceId, String reason);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 暂停工作流实例
|
* 暂停工作流实例
|
||||||
*
|
|
||||||
* @param instanceId 工作流实例ID
|
|
||||||
*/
|
*/
|
||||||
void pauseInstance(Long instanceId);
|
void pauseWorkflow(Long instanceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 恢复工作流实例
|
* 恢复工作流实例
|
||||||
*
|
|
||||||
* @param instanceId 工作流实例ID
|
|
||||||
*/
|
*/
|
||||||
void resumeInstance(Long instanceId);
|
void resumeWorkflow(Long instanceId);
|
||||||
|
|
||||||
/**
|
|
||||||
* 重试工作流节点
|
|
||||||
*
|
|
||||||
* @param nodeInstanceId 节点实例ID
|
|
||||||
*/
|
|
||||||
void retryNode(Long nodeInstanceId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 跳过工作流节点
|
|
||||||
*
|
|
||||||
* @param nodeInstanceId 节点实例ID
|
|
||||||
*/
|
|
||||||
void skipNode(Long nodeInstanceId);
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.context;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||||
|
import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService;
|
||||||
|
import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class DefaultWorkflowContext implements WorkflowContext {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final WorkflowInstance instance;
|
||||||
|
|
||||||
|
private final Map<String, Object> variables;
|
||||||
|
|
||||||
|
private final IWorkflowVariableService variableService;
|
||||||
|
|
||||||
|
private final IWorkflowLogService logService;
|
||||||
|
|
||||||
|
private DefaultWorkflowContext(WorkflowInstance instance, IWorkflowVariableService variableService, IWorkflowLogService logService) {
|
||||||
|
this.instance = instance;
|
||||||
|
this.variables = new HashMap<>();
|
||||||
|
this.variableService = variableService;
|
||||||
|
this.logService = logService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getVariables() {
|
||||||
|
return new HashMap<>(variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getVariable(String name) {
|
||||||
|
return variables.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVariable(String name, Object value) {
|
||||||
|
variables.put(name, value);
|
||||||
|
variableService.setVariable(instance.getId(), name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(String message, LogLevelEnum level) {
|
||||||
|
log(message, null, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(String message, String detail, LogLevelEnum level) {
|
||||||
|
logService.log(instance.getId(), null, message, level, detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流上下文工厂类
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public static class Factory {
|
||||||
|
private final IWorkflowVariableService variableService;
|
||||||
|
private final IWorkflowLogService logService;
|
||||||
|
|
||||||
|
public Factory(IWorkflowVariableService variableService, IWorkflowLogService logService) {
|
||||||
|
this.variableService = variableService;
|
||||||
|
this.logService = logService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultWorkflowContext create(WorkflowInstance instance) {
|
||||||
|
return new DefaultWorkflowContext(instance, variableService, logService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.context;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流上下文
|
||||||
|
*/
|
||||||
|
public interface WorkflowContext {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取工作流实例
|
||||||
|
*/
|
||||||
|
WorkflowInstance getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有变量
|
||||||
|
*/
|
||||||
|
Map<String, Object> getVariables();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取变量
|
||||||
|
*/
|
||||||
|
Object getVariable(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置变量
|
||||||
|
*/
|
||||||
|
void setVariable(String name, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录日志
|
||||||
|
*/
|
||||||
|
void log(String message, LogLevelEnum level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录日志(带详情)
|
||||||
|
*/
|
||||||
|
void log(String message, String detail, LogLevelEnum level);
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.exception;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
|
||||||
|
public class WorkflowEngineException extends BusinessException {
|
||||||
|
|
||||||
|
public WorkflowEngineException(ResponseCode code) {
|
||||||
|
super(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorkflowEngineException(ResponseCode code, String... args) {
|
||||||
|
super(code, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorkflowEngineException(ResponseCode code, Throwable cause) {
|
||||||
|
super(code, new Object[]{cause.getMessage()});
|
||||||
|
initCause(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext;
|
||||||
|
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.NodeTypeEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class EndNodeExecutor implements NodeExecutor {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IWorkflowInstanceRepository workflowInstanceRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodeTypeEnum getNodeType() {
|
||||||
|
return NodeTypeEnum.END;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// 1. 完成结束节点
|
||||||
|
nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
|
||||||
|
nodeInstance.setEndTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
// 2. 完成工作流实例
|
||||||
|
WorkflowInstance instance = context.getInstance();
|
||||||
|
instance.setStatus(WorkflowStatusEnum.COMPLETED);
|
||||||
|
instance.setEndTime(LocalDateTime.now());
|
||||||
|
workflowInstanceRepository.save(instance);
|
||||||
|
|
||||||
|
context.log("工作流执行完成", LogLevelEnum.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(String config) {
|
||||||
|
// 结束节点无需配置
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void terminate(NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// 结束节点无需终止操作
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class GatewayCondition {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件表达式(SpEL表达式)
|
||||||
|
*/
|
||||||
|
private String expression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下一个节点ID
|
||||||
|
*/
|
||||||
|
private String nextNodeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class GatewayConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网关类型
|
||||||
|
*/
|
||||||
|
private GatewayType type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网关名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网关描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件列表(排他网关和包容网关使用)
|
||||||
|
*/
|
||||||
|
private List<GatewayCondition> conditions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认节点ID(排他网关使用)
|
||||||
|
*/
|
||||||
|
private String defaultNodeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 并行节点ID列表(并行网关使用)
|
||||||
|
*/
|
||||||
|
private List<String> parallelNodeIds;
|
||||||
|
}
|
||||||
@ -0,0 +1,149 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.expression.ExpressionParser;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class GatewayNodeExecutor implements NodeExecutor {
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
private final ExpressionParser expressionParser = new SpelExpressionParser();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodeTypeEnum getNodeType() {
|
||||||
|
return NodeTypeEnum.GATEWAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
try {
|
||||||
|
GatewayConfig config = objectMapper.readValue(nodeInstance.getConfig(), GatewayConfig.class);
|
||||||
|
|
||||||
|
if (config.getType() == null) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Gateway type is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (config.getType()) {
|
||||||
|
case EXCLUSIVE:
|
||||||
|
handleExclusiveGateway(nodeInstance, context, config);
|
||||||
|
break;
|
||||||
|
case PARALLEL:
|
||||||
|
handleParallelGateway(nodeInstance, context, config);
|
||||||
|
break;
|
||||||
|
case INCLUSIVE:
|
||||||
|
handleInclusiveGateway(nodeInstance, context, config);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_TYPE_NOT_SUPPORTED,
|
||||||
|
"Unsupported gateway type: " + config.getType());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleExclusiveGateway(NodeInstance nodeInstance, WorkflowContext context, GatewayConfig config) {
|
||||||
|
try {
|
||||||
|
for (GatewayCondition condition : config.getConditions()) {
|
||||||
|
if (evaluateCondition(condition.getExpression(), context)) {
|
||||||
|
nodeInstance.setOutput(objectMapper.createObjectNode()
|
||||||
|
.put("nextNodeId", condition.getNextNodeId())
|
||||||
|
.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED,
|
||||||
|
"No condition matched in exclusive gateway");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleParallelGateway(NodeInstance nodeInstance, WorkflowContext context, GatewayConfig config) {
|
||||||
|
try {
|
||||||
|
List<String> nextNodeIds = new ArrayList<>();
|
||||||
|
for (GatewayCondition condition : config.getConditions()) {
|
||||||
|
nextNodeIds.add(condition.getNextNodeId());
|
||||||
|
}
|
||||||
|
nodeInstance.setOutput(objectMapper.createObjectNode()
|
||||||
|
.put("nextNodeIds", String.join(",", nextNodeIds))
|
||||||
|
.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleInclusiveGateway(NodeInstance nodeInstance, WorkflowContext context, GatewayConfig config) {
|
||||||
|
try {
|
||||||
|
List<String> nextNodeIds = new ArrayList<>();
|
||||||
|
for (GatewayCondition condition : config.getConditions()) {
|
||||||
|
if (evaluateCondition(condition.getExpression(), context)) {
|
||||||
|
nextNodeIds.add(condition.getNextNodeId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nextNodeIds.isEmpty()) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED,
|
||||||
|
"No condition matched in inclusive gateway");
|
||||||
|
}
|
||||||
|
nodeInstance.setOutput(objectMapper.createObjectNode()
|
||||||
|
.put("nextNodeIds", String.join(",", nextNodeIds))
|
||||||
|
.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean evaluateCondition(String expression, WorkflowContext context) {
|
||||||
|
try {
|
||||||
|
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||||
|
evaluationContext.setVariables(context.getVariables());
|
||||||
|
Expression exp = expressionParser.parseExpression(expression);
|
||||||
|
return exp.getValue(evaluationContext, Boolean.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void terminate(NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// Gateway nodes are instant operations, no need to terminate
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(String config) {
|
||||||
|
try {
|
||||||
|
GatewayConfig gatewayConfig = objectMapper.readValue(config, GatewayConfig.class);
|
||||||
|
if (gatewayConfig.getType() == null) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Gateway type is required");
|
||||||
|
}
|
||||||
|
// 验证条件表达式
|
||||||
|
if (gatewayConfig.getConditions() != null) {
|
||||||
|
for (GatewayCondition condition : gatewayConfig.getConditions()) {
|
||||||
|
try {
|
||||||
|
expressionParser.parseExpression(condition.getExpression());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR,
|
||||||
|
"Invalid condition expression: " + condition.getExpression());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum GatewayType {
|
||||||
|
|
||||||
|
EXCLUSIVE("EXCLUSIVE", "排他网关"),
|
||||||
|
PARALLEL("PARALLEL", "并行网关"),
|
||||||
|
INCLUSIVE("INCLUSIVE", "包容网关");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点执行器接口
|
||||||
|
*/
|
||||||
|
public interface NodeExecutor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取支持的节点类型
|
||||||
|
*/
|
||||||
|
NodeTypeEnum getNodeType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行节点
|
||||||
|
*/
|
||||||
|
void execute(NodeInstance nodeInstance, WorkflowContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证节点配置
|
||||||
|
*/
|
||||||
|
void validate(String config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终止节点执行
|
||||||
|
*/
|
||||||
|
void terminate(NodeInstance nodeInstance, WorkflowContext context);
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class StartNodeExecutor implements NodeExecutor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodeTypeEnum getNodeType() {
|
||||||
|
return NodeTypeEnum.START;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// 开始节点直接完成
|
||||||
|
nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
|
||||||
|
context.log("开始节点执行完成", LogLevelEnum.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(String config) {
|
||||||
|
// 开始节点无需配置
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void terminate(NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// 开始节点无需终止操作
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TaskConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务类型
|
||||||
|
*/
|
||||||
|
private TaskType type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务超时时间(秒)
|
||||||
|
*/
|
||||||
|
private Integer timeout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重试次数
|
||||||
|
*/
|
||||||
|
private Integer retryCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重试间隔(秒)
|
||||||
|
*/
|
||||||
|
private Integer retryInterval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务参数
|
||||||
|
*/
|
||||||
|
private Map<String, Object> parameters;
|
||||||
|
}
|
||||||
@ -0,0 +1,131 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class TaskNodeExecutor implements NodeExecutor {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodeTypeEnum getNodeType() {
|
||||||
|
return NodeTypeEnum.TASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
try {
|
||||||
|
// 1. 解析任务配置
|
||||||
|
TaskConfig config = parseConfig(nodeInstance.getConfig());
|
||||||
|
|
||||||
|
// 2. 执行具体任务
|
||||||
|
executeTask(config, nodeInstance, context);
|
||||||
|
|
||||||
|
// 3. 更新节点状态
|
||||||
|
nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
|
||||||
|
context.log("任务节点执行完成", LogLevelEnum.INFO);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("任务节点执行失败", e);
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(String config) {
|
||||||
|
try {
|
||||||
|
TaskConfig taskConfig = parseConfig(config);
|
||||||
|
// 验证必填字段
|
||||||
|
if (taskConfig.getType() == null) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "任务类型不能为空");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void terminate(NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// 终止任务执行
|
||||||
|
TaskConfig config = parseConfig(nodeInstance.getConfig());
|
||||||
|
terminateTask(config, nodeInstance, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaskConfig parseConfig(String config) {
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(config, TaskConfig.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
switch (config.getType()) {
|
||||||
|
case SHELL:
|
||||||
|
executeShellTask(config, nodeInstance, context);
|
||||||
|
break;
|
||||||
|
case HTTP:
|
||||||
|
executeHttpTask(config, nodeInstance, context);
|
||||||
|
break;
|
||||||
|
case JAVA:
|
||||||
|
executeJavaTask(config, nodeInstance, context);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_TYPE_NOT_SUPPORTED, "不支持的任务类型: " + config.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void terminateTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// 根据任务类型执行终止操作
|
||||||
|
switch (config.getType()) {
|
||||||
|
case SHELL:
|
||||||
|
terminateShellTask(config, nodeInstance, context);
|
||||||
|
break;
|
||||||
|
case HTTP:
|
||||||
|
terminateHttpTask(config, nodeInstance, context);
|
||||||
|
break;
|
||||||
|
case JAVA:
|
||||||
|
terminateJavaTask(config, nodeInstance, context);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 具体任务执行方法...
|
||||||
|
private void executeShellTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// TODO: 实现Shell脚本执行
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeHttpTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// TODO: 实现HTTP请求执行
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeJavaTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// TODO: 实现Java方法调用
|
||||||
|
}
|
||||||
|
|
||||||
|
private void terminateShellTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// TODO: 实现Shell脚本终止
|
||||||
|
}
|
||||||
|
|
||||||
|
private void terminateHttpTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// TODO: 实现HTTP请求终止
|
||||||
|
}
|
||||||
|
|
||||||
|
private void terminateJavaTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// TODO: 实现Java方法终止
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum TaskType {
|
||||||
|
|
||||||
|
SHELL("SHELL", "Shell脚本"),
|
||||||
|
HTTP("HTTP", "HTTP请求"),
|
||||||
|
JAVA("JAVA", "Java方法");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
}
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor.task;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.*;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class HttpTaskExecutor implements TaskExecutor {
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(NodeInstance nodeInstance, WorkflowContext context, Map<String, Object> parameters) {
|
||||||
|
String url = (String) parameters.get("url");
|
||||||
|
HttpMethod method = HttpMethod.valueOf((String) parameters.getOrDefault("method", "GET"));
|
||||||
|
Object body = parameters.get("body");
|
||||||
|
Map<String, String> headers = (Map<String, String>) parameters.get("headers");
|
||||||
|
|
||||||
|
try {
|
||||||
|
HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
|
if (headers != null) {
|
||||||
|
headers.forEach(httpHeaders::add);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpEntity<?> requestEntity = new HttpEntity<>(body, httpHeaders);
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(url, method, requestEntity, String.class);
|
||||||
|
|
||||||
|
// 记录执行结果
|
||||||
|
nodeInstance.setOutput(objectMapper.createObjectNode()
|
||||||
|
.put("statusCode", response.getStatusCode().value())
|
||||||
|
.put("body", response.getBody())
|
||||||
|
.toString());
|
||||||
|
|
||||||
|
if (!response.getStatusCode().is2xxSuccessful()) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED,
|
||||||
|
"HTTP request failed with status: " + response.getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
context.log("HTTP请求执行成功", response.getBody(), LogLevelEnum.INFO);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void terminate(NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// HTTP请求无需终止操作
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor.task;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class JavaTaskExecutor implements TaskExecutor {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(NodeInstance nodeInstance, WorkflowContext context, Map<String, Object> parameters) {
|
||||||
|
String className = parameters.get("className").toString();
|
||||||
|
String methodName = parameters.get("methodName").toString();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> clazz = Class.forName(className);
|
||||||
|
Object instance = applicationContext.getBean(clazz);
|
||||||
|
Method method = clazz.getMethod(methodName, NodeInstance.class, WorkflowContext.class, Map.class);
|
||||||
|
method.invoke(instance, nodeInstance, context, parameters);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Class not found: " + className);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Method not found: " + methodName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void terminate(NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// Java任务无法中断,记录日志
|
||||||
|
context.log("Java task cannot be terminated: " + nodeInstance.getNodeId(), LogLevelEnum.WARN);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor.task;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.exec.*;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class ShellTaskExecutor implements TaskExecutor {
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(NodeInstance nodeInstance, WorkflowContext context, Map<String, Object> parameters) {
|
||||||
|
String command = (String) parameters.get("command");
|
||||||
|
Integer timeout = (Integer) parameters.getOrDefault("timeout", 300);
|
||||||
|
|
||||||
|
CommandLine cmdLine = CommandLine.parse(command);
|
||||||
|
DefaultExecutor executor = new DefaultExecutor();
|
||||||
|
executor.setExitValues(null);
|
||||||
|
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
|
||||||
|
executor.setStreamHandler(new PumpStreamHandler(outputStream, errorStream));
|
||||||
|
|
||||||
|
ExecuteWatchdog watchdog = new ExecuteWatchdog(TimeUnit.SECONDS.toMillis(timeout));
|
||||||
|
executor.setWatchdog(watchdog);
|
||||||
|
|
||||||
|
try {
|
||||||
|
int exitValue = executor.execute(cmdLine);
|
||||||
|
String output = outputStream.toString();
|
||||||
|
String error = errorStream.toString();
|
||||||
|
|
||||||
|
// 记录执行结果
|
||||||
|
nodeInstance.setOutput(objectMapper.createObjectNode()
|
||||||
|
.put("exitValue", exitValue)
|
||||||
|
.put("output", output)
|
||||||
|
.put("error", error)
|
||||||
|
.toString());
|
||||||
|
|
||||||
|
if (exitValue != 0) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.log("Shell命令执行成功", output, LogLevelEnum.INFO);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void terminate(NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
|
// TODO: 实现Shell命令终止逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor.task;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务执行器接口
|
||||||
|
*/
|
||||||
|
public interface TaskExecutor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行任务
|
||||||
|
*/
|
||||||
|
void execute(NodeInstance nodeInstance, WorkflowContext context, Map<String, Object> parameters);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终止任务
|
||||||
|
*/
|
||||||
|
void terminate(NodeInstance nodeInstance, WorkflowContext context);
|
||||||
|
}
|
||||||
@ -1,220 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.engine.impl;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
|
||||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
|
||||||
import com.qqchen.deploy.backend.workflow.engine.WorkflowContext;
|
|
||||||
import com.qqchen.deploy.backend.workflow.engine.WorkflowEngine;
|
|
||||||
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.WorkflowStatusEnum;
|
|
||||||
import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository;
|
|
||||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
|
|
||||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService;
|
|
||||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流引擎默认实现
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Service
|
|
||||||
public class DefaultWorkflowEngine implements WorkflowEngine {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private IWorkflowInstanceRepository workflowInstanceRepository;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private INodeInstanceRepository nodeInstanceRepository;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private IWorkflowVariableService workflowVariableService;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private IWorkflowLogService workflowLogService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void startInstance(Long instanceId, Map<String, Object> variables) {
|
|
||||||
WorkflowInstance instance = getWorkflowInstance(instanceId);
|
|
||||||
if (instance.getStatus() != WorkflowStatusEnum.CREATED) {
|
|
||||||
throw new BusinessException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING);
|
|
||||||
}
|
|
||||||
|
|
||||||
WorkflowContext context = initContext(instance, variables);
|
|
||||||
|
|
||||||
try {
|
|
||||||
instance.setStatus(WorkflowStatusEnum.RUNNING);
|
|
||||||
workflowInstanceRepository.save(instance);
|
|
||||||
|
|
||||||
workflowVariableService.saveVariables(instanceId, variables);
|
|
||||||
workflowLogService.logWorkflowStart(instance);
|
|
||||||
|
|
||||||
executeNextNode(context);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to start workflow instance: {}", instanceId, e);
|
|
||||||
instance.setStatus(WorkflowStatusEnum.FAILED);
|
|
||||||
workflowInstanceRepository.save(instance);
|
|
||||||
workflowLogService.logWorkflowError(instance, e.getMessage());
|
|
||||||
throw new BusinessException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void cancelInstance(Long instanceId) {
|
|
||||||
WorkflowInstance instance = getWorkflowInstance(instanceId);
|
|
||||||
if (!instance.getStatus().canCancel()) {
|
|
||||||
throw new BusinessException(ResponseCode.WORKFLOW_INSTANCE_ALREADY_CANCELED);
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.setStatus(WorkflowStatusEnum.CANCELLED);
|
|
||||||
workflowInstanceRepository.save(instance);
|
|
||||||
|
|
||||||
List<NodeInstance> runningNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(
|
|
||||||
instanceId, NodeStatusEnum.RUNNING.name());
|
|
||||||
runningNodes.forEach(node -> {
|
|
||||||
node.setStatus(NodeStatusEnum.CANCELLED);
|
|
||||||
nodeInstanceRepository.save(node);
|
|
||||||
});
|
|
||||||
|
|
||||||
workflowLogService.logWorkflowCancel(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void pauseInstance(Long instanceId) {
|
|
||||||
WorkflowInstance instance = getWorkflowInstance(instanceId);
|
|
||||||
if (!instance.getStatus().canPause()) {
|
|
||||||
throw new BusinessException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING);
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.setStatus(WorkflowStatusEnum.PAUSED);
|
|
||||||
workflowInstanceRepository.save(instance);
|
|
||||||
|
|
||||||
List<NodeInstance> runningNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(
|
|
||||||
instanceId, NodeStatusEnum.RUNNING.name());
|
|
||||||
runningNodes.forEach(node -> {
|
|
||||||
node.setStatus(NodeStatusEnum.PAUSED);
|
|
||||||
nodeInstanceRepository.save(node);
|
|
||||||
});
|
|
||||||
|
|
||||||
workflowLogService.logWorkflowPause(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void resumeInstance(Long instanceId) {
|
|
||||||
WorkflowInstance instance = getWorkflowInstance(instanceId);
|
|
||||||
if (!instance.getStatus().canResume()) {
|
|
||||||
throw new BusinessException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING);
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.setStatus(WorkflowStatusEnum.RUNNING);
|
|
||||||
workflowInstanceRepository.save(instance);
|
|
||||||
|
|
||||||
List<NodeInstance> pausedNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(
|
|
||||||
instanceId, NodeStatusEnum.PAUSED.name());
|
|
||||||
pausedNodes.forEach(node -> {
|
|
||||||
node.setStatus(NodeStatusEnum.RUNNING);
|
|
||||||
nodeInstanceRepository.save(node);
|
|
||||||
});
|
|
||||||
|
|
||||||
workflowLogService.logWorkflowResume(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void retryNode(Long nodeInstanceId) {
|
|
||||||
NodeInstance node = getNodeInstance(nodeInstanceId);
|
|
||||||
if (node.getStatus() != NodeStatusEnum.FAILED) {
|
|
||||||
throw new BusinessException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
WorkflowInstance instance = getWorkflowInstance(node.getWorkflowInstanceId());
|
|
||||||
if (instance.getStatus() != WorkflowStatusEnum.FAILED) {
|
|
||||||
throw new BusinessException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING);
|
|
||||||
}
|
|
||||||
|
|
||||||
node.setStatus(NodeStatusEnum.RUNNING);
|
|
||||||
nodeInstanceRepository.save(node);
|
|
||||||
|
|
||||||
instance.setStatus(WorkflowStatusEnum.RUNNING);
|
|
||||||
workflowInstanceRepository.save(instance);
|
|
||||||
|
|
||||||
workflowLogService.logNodeRetry(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void skipNode(Long nodeInstanceId) {
|
|
||||||
NodeInstance node = getNodeInstance(nodeInstanceId);
|
|
||||||
if (node.getStatus() != NodeStatusEnum.FAILED) {
|
|
||||||
throw new BusinessException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
node.setStatus(NodeStatusEnum.SKIPPED);
|
|
||||||
nodeInstanceRepository.save(node);
|
|
||||||
|
|
||||||
WorkflowInstance instance = getWorkflowInstance(node.getWorkflowInstanceId());
|
|
||||||
WorkflowContext context = initContext(instance, workflowVariableService.getVariables(instance.getId()));
|
|
||||||
context.setCurrentNode(node);
|
|
||||||
|
|
||||||
executeNextNode(context);
|
|
||||||
workflowLogService.logNodeSkip(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
private WorkflowInstance getWorkflowInstance(Long instanceId) {
|
|
||||||
return workflowInstanceRepository.findById(instanceId)
|
|
||||||
.orElseThrow(() -> new BusinessException(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND));
|
|
||||||
}
|
|
||||||
|
|
||||||
private NodeInstance getNodeInstance(Long nodeInstanceId) {
|
|
||||||
return nodeInstanceRepository.findById(nodeInstanceId)
|
|
||||||
.orElseThrow(() -> new BusinessException(ResponseCode.WORKFLOW_NODE_NOT_FOUND));
|
|
||||||
}
|
|
||||||
|
|
||||||
private WorkflowContext initContext(WorkflowInstance instance, Map<String, Object> variables) {
|
|
||||||
WorkflowContext context = new WorkflowContext();
|
|
||||||
context.setWorkflowInstance(instance);
|
|
||||||
context.setVariables(variables);
|
|
||||||
context.setAllNodes(nodeInstanceRepository.findByWorkflowInstanceId(instance.getId()));
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void executeNextNode(WorkflowContext context) {
|
|
||||||
NodeInstance currentNode = context.getCurrentNode();
|
|
||||||
List<NodeInstance> allNodes = context.getAllNodes();
|
|
||||||
|
|
||||||
Optional<NodeInstance> nextNode;
|
|
||||||
if (currentNode == null) {
|
|
||||||
nextNode = allNodes.stream()
|
|
||||||
.filter(node -> node.getPreNodeId() == null)
|
|
||||||
.findFirst();
|
|
||||||
} else {
|
|
||||||
nextNode = allNodes.stream()
|
|
||||||
.filter(node -> currentNode.getId().equals(node.getPreNodeId()))
|
|
||||||
.findFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextNode.isPresent()) {
|
|
||||||
NodeInstance node = nextNode.get();
|
|
||||||
node.setStatus(NodeStatusEnum.RUNNING);
|
|
||||||
nodeInstanceRepository.save(node);
|
|
||||||
workflowLogService.logNodeStart(node);
|
|
||||||
// TODO: 实际节点执行逻辑
|
|
||||||
} else {
|
|
||||||
WorkflowInstance instance = context.getWorkflowInstance();
|
|
||||||
instance.setStatus(WorkflowStatusEnum.COMPLETED);
|
|
||||||
workflowInstanceRepository.save(instance);
|
|
||||||
workflowLogService.logWorkflowComplete(instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
|
||||||
@ -1,9 +1,11 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.engine.node;
|
package com.qqchen.deploy.backend.workflow.engine.node;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
import com.qqchen.deploy.backend.workflow.engine.WorkflowContext;
|
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext;
|
||||||
|
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.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;
|
||||||
@ -17,99 +19,70 @@ public abstract class AbstractNodeExecutor implements NodeExecutor {
|
|||||||
@Resource
|
@Resource
|
||||||
protected IWorkflowLogService workflowLogService;
|
protected IWorkflowLogService workflowLogService;
|
||||||
|
|
||||||
@Resource
|
|
||||||
protected ObjectMapper objectMapper;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean execute(NodeInstance nodeInstance, WorkflowContext context) {
|
public void execute(NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
try {
|
try {
|
||||||
// 解析节点配置
|
// 1. 记录系统日志
|
||||||
NodeConfig config = parseConfig(nodeInstance.getConfig());
|
logSystem(context.getInstance(), LogLevelEnum.INFO,
|
||||||
|
String.format("开始执行节点: %s[%s]", nodeInstance.getName(), nodeInstance.getNodeId()),
|
||||||
// 执行前处理
|
null);
|
||||||
beforeExecute(nodeInstance, context, config);
|
|
||||||
|
// 2. 记录节点开始日志
|
||||||
// 执行节点
|
logNodeStart(nodeInstance);
|
||||||
boolean result = doExecute(nodeInstance, context, config);
|
|
||||||
|
// 3. 执行节点
|
||||||
// 执行后处理
|
doExecute(nodeInstance, context);
|
||||||
afterExecute(nodeInstance, context, config, result);
|
|
||||||
|
// 4. 记录节点完成日志
|
||||||
return result;
|
logNodeComplete(nodeInstance);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Execute node failed: {}", e.getMessage(), e);
|
// 5. 记录错误日志
|
||||||
workflowLogService.logSystem(context.getWorkflowInstance(), LogLevelEnum.ERROR,
|
logSystem(context.getInstance(), LogLevelEnum.ERROR,
|
||||||
"Execute node failed: " + e.getMessage(), e.toString());
|
String.format("节点执行失败: %s[%s]", nodeInstance.getName(), nodeInstance.getNodeId()),
|
||||||
return false;
|
e.getMessage());
|
||||||
|
logNodeError(nodeInstance, e.getMessage());
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cancel(NodeInstance nodeInstance, WorkflowContext context) {
|
|
||||||
try {
|
|
||||||
// 解析节点配置
|
|
||||||
NodeConfig config = parseConfig(nodeInstance.getConfig());
|
|
||||||
|
|
||||||
// 执行取消操作
|
|
||||||
doCancel(nodeInstance, context, config);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Cancel node failed: {}", e.getMessage(), e);
|
|
||||||
workflowLogService.logSystem(context.getWorkflowInstance(), LogLevelEnum.ERROR,
|
|
||||||
"Cancel node failed: " + e.getMessage(), e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析节点配置
|
|
||||||
*
|
|
||||||
* @param configJson 配置JSON
|
|
||||||
* @return 节点配置
|
|
||||||
*/
|
|
||||||
protected abstract NodeConfig parseConfig(String configJson) throws Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行前处理
|
|
||||||
*
|
|
||||||
* @param nodeInstance 节点实例
|
|
||||||
* @param context 工作流上下文
|
|
||||||
* @param config 节点配置
|
|
||||||
*/
|
|
||||||
protected void beforeExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) {
|
|
||||||
workflowLogService.logNodeStart(nodeInstance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行节点
|
* 执行节点
|
||||||
*
|
|
||||||
* @param nodeInstance 节点实例
|
|
||||||
* @param context 工作流上下文
|
|
||||||
* @param config 节点配置
|
|
||||||
* @return 执行结果
|
|
||||||
*/
|
*/
|
||||||
protected abstract boolean doExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) throws Exception;
|
protected abstract void doExecute(NodeInstance nodeInstance, WorkflowContext context);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行后处理
|
* 记录系统日志
|
||||||
*
|
|
||||||
* @param nodeInstance 节点实例
|
|
||||||
* @param context 工作流上下文
|
|
||||||
* @param config 节点配置
|
|
||||||
* @param success 是否成功
|
|
||||||
*/
|
*/
|
||||||
protected void afterExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config, boolean success) {
|
protected void logSystem(WorkflowInstance instance, LogLevelEnum level, String message, String detail) {
|
||||||
if (success) {
|
workflowLogService.log(instance.getId(), null, message, level, detail);
|
||||||
workflowLogService.logNodeComplete(nodeInstance);
|
|
||||||
} else {
|
|
||||||
workflowLogService.logNodeError(nodeInstance, "Node execution failed");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行取消操作
|
* 记录节点开始日志
|
||||||
*
|
|
||||||
* @param nodeInstance 节点实例
|
|
||||||
* @param context 工作流上下文
|
|
||||||
* @param config 节点配置
|
|
||||||
*/
|
*/
|
||||||
protected abstract void doCancel(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) throws Exception;
|
protected void logNodeStart(NodeInstance nodeInstance) {
|
||||||
|
workflowLogService.log(nodeInstance.getWorkflowInstanceId(), nodeInstance.getNodeId(),
|
||||||
|
String.format("节点开始执行: %s", nodeInstance.getName()), LogLevelEnum.INFO, null);
|
||||||
|
nodeInstance.setStatus(NodeStatusEnum.RUNNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录节点完成日志
|
||||||
|
*/
|
||||||
|
protected void logNodeComplete(NodeInstance nodeInstance) {
|
||||||
|
workflowLogService.log(nodeInstance.getWorkflowInstanceId(), nodeInstance.getNodeId(),
|
||||||
|
String.format("节点执行完成: %s", nodeInstance.getName()), LogLevelEnum.INFO, null);
|
||||||
|
nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录节点错误日志
|
||||||
|
*/
|
||||||
|
protected void logNodeError(NodeInstance nodeInstance, String error) {
|
||||||
|
workflowLogService.log(nodeInstance.getWorkflowInstanceId(), nodeInstance.getNodeId(),
|
||||||
|
String.format("节点执行失败: %s", nodeInstance.getName()), LogLevelEnum.ERROR, error);
|
||||||
|
nodeInstance.setStatus(NodeStatusEnum.FAILED);
|
||||||
|
nodeInstance.setError(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,13 +1,14 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.engine.node.executor;
|
package com.qqchen.deploy.backend.workflow.engine.node.executor;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.workflow.engine.WorkflowContext;
|
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.engine.node.AbstractNodeExecutor;
|
||||||
import com.qqchen.deploy.backend.workflow.engine.node.NodeConfig;
|
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||||
import com.qqchen.deploy.backend.workflow.engine.node.NodeType;
|
|
||||||
import com.qqchen.deploy.backend.workflow.engine.node.ScriptNodeConfig;
|
|
||||||
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
@ -19,68 +20,115 @@ import java.util.Map;
|
|||||||
@Component
|
@Component
|
||||||
public class ShellNodeExecutor extends AbstractNodeExecutor {
|
public class ShellNodeExecutor extends AbstractNodeExecutor {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NodeType getNodeType() {
|
public NodeTypeEnum getNodeType() {
|
||||||
return NodeType.SCRIPT;
|
return NodeTypeEnum.TASK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected NodeConfig parseConfig(String configJson) throws Exception {
|
public void validate(String config) {
|
||||||
return objectMapper.readValue(configJson, ScriptNodeConfig.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) throws Exception {
|
protected void doExecute(NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
ScriptNodeConfig scriptConfig = (ScriptNodeConfig) config;
|
try {
|
||||||
ProcessBuilder processBuilder = new ProcessBuilder();
|
String configJson = nodeInstance.getConfig();
|
||||||
processBuilder.command("sh", "-c", scriptConfig.getScript());
|
ShellConfig config = objectMapper.readValue(configJson, ShellConfig.class);
|
||||||
|
ProcessBuilder processBuilder = new ProcessBuilder();
|
||||||
if (scriptConfig.getWorkingDirectory() != null) {
|
processBuilder.command("sh", "-c", config.getScript());
|
||||||
processBuilder.directory(new java.io.File(scriptConfig.getWorkingDirectory()));
|
|
||||||
}
|
if (config.getWorkingDirectory() != null) {
|
||||||
|
processBuilder.directory(new java.io.File(config.getWorkingDirectory()));
|
||||||
|
}
|
||||||
|
|
||||||
if (scriptConfig.getEnvironment() != null) {
|
if (config.getEnvironment() != null) {
|
||||||
Map<String, String> env = processBuilder.environment();
|
Map<String, String> env = processBuilder.environment();
|
||||||
env.putAll(scriptConfig.getEnvironment());
|
env.putAll(config.getEnvironment());
|
||||||
}
|
}
|
||||||
|
|
||||||
Process process = processBuilder.start();
|
Process process = processBuilder.start();
|
||||||
|
|
||||||
// 读取标准输出
|
// 读取标准输出
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||||
List<String> output = new ArrayList<>();
|
List<String> output = new ArrayList<>();
|
||||||
String line;
|
String line;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
output.add(line);
|
output.add(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取错误输出
|
// 读取错误输出
|
||||||
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
|
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
|
||||||
List<String> error = new ArrayList<>();
|
List<String> error = new ArrayList<>();
|
||||||
while ((line = errorReader.readLine()) != null) {
|
while ((line = errorReader.readLine()) != null) {
|
||||||
error.add(line);
|
error.add(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待进程结束
|
// 等待进程结束
|
||||||
int exitCode = process.waitFor();
|
int exitCode = process.waitFor();
|
||||||
|
|
||||||
// 检查退出码
|
// 检查退出码
|
||||||
if (scriptConfig.getSuccessExitCode() != null && exitCode != scriptConfig.getSuccessExitCode()) {
|
if (config.getSuccessExitCode() != null && exitCode != config.getSuccessExitCode()) {
|
||||||
throw new RuntimeException("Shell script execution failed with exit code: " + exitCode +
|
throw new RuntimeException("Shell script execution failed with exit code: " + exitCode +
|
||||||
"\nError output: " + String.join("\n", error));
|
"\nError output: " + String.join("\n", error));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置输出结果
|
// 设置输出结果
|
||||||
nodeInstance.setOutput(String.join("\n", output));
|
nodeInstance.setOutput(String.join("\n", output));
|
||||||
if (!error.isEmpty()) {
|
if (!error.isEmpty()) {
|
||||||
nodeInstance.setError(String.join("\n", error));
|
nodeInstance.setError(String.join("\n", error));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Shell script execution failed", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return exitCode == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doCancel(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) throws Exception {
|
public void terminate(NodeInstance nodeInstance, WorkflowContext context) {
|
||||||
// TODO: 实现取消逻辑,例如终止正在运行的进程
|
// TODO: 实现终止Shell进程的逻辑
|
||||||
|
context.log("Shell node termination is not implemented yet", LogLevelEnum.WARN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ShellConfig {
|
||||||
|
private String script;
|
||||||
|
private String workingDirectory;
|
||||||
|
private Map<String, String> environment;
|
||||||
|
private Integer successExitCode;
|
||||||
|
|
||||||
|
// Getters and setters
|
||||||
|
public String getScript() {
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScript(String script) {
|
||||||
|
this.script = script;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWorkingDirectory() {
|
||||||
|
return workingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWorkingDirectory(String workingDirectory) {
|
||||||
|
this.workingDirectory = workingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getEnvironment() {
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnvironment(Map<String, String> environment) {
|
||||||
|
this.environment = environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSuccessExitCode() {
|
||||||
|
return successExitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuccessExitCode(Integer successExitCode) {
|
||||||
|
this.successExitCode = successExitCode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,151 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.parser;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.node.NodeConfig;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流定义解析器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class WorkflowDefinitionParser {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析节点配置
|
||||||
|
*
|
||||||
|
* @param nodeConfig 节点配置JSON字符串
|
||||||
|
* @return 节点配置列表
|
||||||
|
*/
|
||||||
|
public List<NodeConfig> parseNodeConfig(String nodeConfig) {
|
||||||
|
try {
|
||||||
|
log.debug("Parsing node config: {}", nodeConfig);
|
||||||
|
JsonNode rootNode = objectMapper.readTree(nodeConfig);
|
||||||
|
JsonNode nodesNode = rootNode.get("nodes");
|
||||||
|
if (nodesNode == null || !nodesNode.isArray()) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<NodeConfig> nodes = new ArrayList<>();
|
||||||
|
for (JsonNode node : nodesNode) {
|
||||||
|
NodeConfig config = objectMapper.treeToValue(node, NodeConfig.class);
|
||||||
|
nodes.add(config);
|
||||||
|
log.debug("Parsed node: id={}, type={}", config.getId(), config.getType());
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Failed to parse node config: {}", e.getMessage(), e);
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析流转配置
|
||||||
|
*
|
||||||
|
* @param transitionConfig 流转配置JSON字符串
|
||||||
|
* @return 流转配置列表
|
||||||
|
*/
|
||||||
|
public List<TransitionConfig> parseTransitionConfig(String transitionConfig) {
|
||||||
|
try {
|
||||||
|
log.debug("Parsing transition config: {}", transitionConfig);
|
||||||
|
List<TransitionConfig> transitions = objectMapper.readValue(
|
||||||
|
transitionConfig,
|
||||||
|
new TypeReference<List<TransitionConfig>>() {}
|
||||||
|
);
|
||||||
|
|
||||||
|
transitions.forEach(transition ->
|
||||||
|
log.debug("Parsed transition: {} -> {}, priority={}",
|
||||||
|
transition.getSourceNodeId(),
|
||||||
|
transition.getTargetNodeId(),
|
||||||
|
transition.getPriority())
|
||||||
|
);
|
||||||
|
|
||||||
|
return transitions;
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Failed to parse transition config: {}", e.getMessage(), e);
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_TRANSITION_CONFIG_INVALID, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证节点配置的完整性
|
||||||
|
*
|
||||||
|
* @param nodes 节点配置列表
|
||||||
|
* @param transitions 流转配置列表
|
||||||
|
*/
|
||||||
|
public void validateConfig(List<NodeConfig> nodes, List<TransitionConfig> transitions) {
|
||||||
|
// 1. 检查是否有开始节点和结束节点
|
||||||
|
boolean hasStart = false;
|
||||||
|
boolean hasEnd = false;
|
||||||
|
for (NodeConfig node : nodes) {
|
||||||
|
switch (node.getType()) {
|
||||||
|
case START -> hasStart = true;
|
||||||
|
case END -> hasEnd = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasStart || !hasEnd) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID,
|
||||||
|
"Workflow must have both START and END nodes");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查流转配置中的节点ID是否都存在
|
||||||
|
for (TransitionConfig transition : transitions) {
|
||||||
|
boolean sourceExists = false;
|
||||||
|
boolean targetExists = false;
|
||||||
|
|
||||||
|
for (NodeConfig node : nodes) {
|
||||||
|
if (node.getId().equals(transition.getSourceNodeId())) {
|
||||||
|
sourceExists = true;
|
||||||
|
}
|
||||||
|
if (node.getId().equals(transition.getTargetNodeId())) {
|
||||||
|
targetExists = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sourceExists || !targetExists) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_TRANSITION_CONFIG_INVALID,
|
||||||
|
String.format("Invalid node reference in transition: %s -> %s",
|
||||||
|
transition.getSourceNodeId(), transition.getTargetNodeId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查是否存在孤立节点(没有入边或出边的非开始/结束节点)
|
||||||
|
for (NodeConfig node : nodes) {
|
||||||
|
if (node.getType() == NodeTypeEnum.START || node.getType() == NodeTypeEnum.END) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasIncoming = false;
|
||||||
|
boolean hasOutgoing = false;
|
||||||
|
|
||||||
|
for (TransitionConfig transition : transitions) {
|
||||||
|
if (transition.getTargetNodeId().equals(node.getId())) {
|
||||||
|
hasIncoming = true;
|
||||||
|
}
|
||||||
|
if (transition.getSourceNodeId().equals(node.getId())) {
|
||||||
|
hasOutgoing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasIncoming || !hasOutgoing) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID,
|
||||||
|
String.format("Isolated node found: %s", node.getId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Workflow configuration validation passed");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.transition;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeDefinition;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.repository.INodeDefinitionRepository;
|
||||||
|
import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class TransitionExecutor {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TransitionRuleEngine transitionRuleEngine;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private INodeDefinitionRepository nodeDefinitionRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private INodeInstanceRepository nodeInstanceRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行节点流转
|
||||||
|
*/
|
||||||
|
public void executeTransition(NodeInstance currentNode, WorkflowDefinition definition, WorkflowContext context) {
|
||||||
|
// 1. 获取下一个节点ID列表
|
||||||
|
List<String> nextNodeIds = transitionRuleEngine.getNextNodeIds(currentNode, definition, context);
|
||||||
|
|
||||||
|
// 2. 创建下一个节点实例
|
||||||
|
for (String nodeId : nextNodeIds) {
|
||||||
|
NodeDefinition nextNodeDef = nodeDefinitionRepository.findByWorkflowDefinitionIdAndNodeIdAndDeletedFalse(
|
||||||
|
definition.getId(), nodeId);
|
||||||
|
if (nextNodeDef == null) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_NOT_FOUND, "未找到节点定义: " + nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeInstance nextNode = new NodeInstance();
|
||||||
|
nextNode.setWorkflowInstanceId(currentNode.getWorkflowInstanceId());
|
||||||
|
nextNode.setNodeId(nodeId);
|
||||||
|
nextNode.setNodeType(nextNodeDef.getType());
|
||||||
|
nextNode.setName(nextNodeDef.getName());
|
||||||
|
nextNode.setConfig(nextNodeDef.getConfig());
|
||||||
|
nextNode.setStatus(NodeStatusEnum.PENDING);
|
||||||
|
nextNode.setCreateTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
nodeInstanceRepository.save(nextNode);
|
||||||
|
|
||||||
|
context.log(String.format("创建下一个节点实例: %s[%s]", nextNode.getName(), nextNode.getNodeId()),
|
||||||
|
LogLevelEnum.INFO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.transition;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TransitionRule {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 源节点ID
|
||||||
|
*/
|
||||||
|
private String sourceNodeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 目标节点ID
|
||||||
|
*/
|
||||||
|
private String targetNodeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件表达式
|
||||||
|
*/
|
||||||
|
private String condition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先级
|
||||||
|
*/
|
||||||
|
private Integer priority;
|
||||||
|
}
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.transition;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.expression.ExpressionParser;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class TransitionRuleEngine {
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
private final ExpressionParser expressionParser = new SpelExpressionParser();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取下一个节点ID列表
|
||||||
|
*/
|
||||||
|
public List<String> getNextNodeIds(NodeInstance currentNode, WorkflowDefinition definition, WorkflowContext context) {
|
||||||
|
try {
|
||||||
|
// 解析流转规则
|
||||||
|
List<TransitionRule> rules = parseTransitionRules(definition.getTransitionConfig());
|
||||||
|
|
||||||
|
// 过滤当前节点的规则并按优先级排序
|
||||||
|
List<TransitionRule> nodeRules = rules.stream()
|
||||||
|
.filter(rule -> rule.getSourceNodeId().equals(currentNode.getNodeId()))
|
||||||
|
.sorted(Comparator.comparing(TransitionRule::getPriority))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// 执行规则匹配
|
||||||
|
List<String> nextNodeIds = new ArrayList<>();
|
||||||
|
for (TransitionRule rule : nodeRules) {
|
||||||
|
if (evaluateCondition(rule.getCondition(), context)) {
|
||||||
|
nextNodeIds.add(rule.getTargetNodeId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextNodeIds;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TransitionRule> parseTransitionRules(String config) {
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(config, new TypeReference<List<TransitionRule>>() {});
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean evaluateCondition(String condition, WorkflowContext context) {
|
||||||
|
if (condition == null || condition.trim().isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Expression expression = expressionParser.parseExpression(condition);
|
||||||
|
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||||
|
evaluationContext.setVariables(context.getVariables());
|
||||||
|
return expression.getValue(evaluationContext, Boolean.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.entity;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
|
||||||
|
import com.qqchen.deploy.backend.framework.domain.Entity;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@jakarta.persistence.Entity
|
||||||
|
@Table(name = "wf_node_definition")
|
||||||
|
@LogicDelete
|
||||||
|
public class NodeDefinition extends Entity<Long> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流定义ID
|
||||||
|
*/
|
||||||
|
@Column(name = "workflow_definition_id", nullable = false)
|
||||||
|
private Long workflowDefinitionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点ID
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String nodeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点名称
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点类型
|
||||||
|
*/
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false)
|
||||||
|
private NodeTypeEnum type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点配置(JSON)
|
||||||
|
*/
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
|
private String config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序号
|
||||||
|
*/
|
||||||
|
private Integer sequence;
|
||||||
|
}
|
||||||
@ -2,8 +2,8 @@ package com.qqchen.deploy.backend.workflow.entity;
|
|||||||
|
|
||||||
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 jakarta.persistence.Column;
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.*;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
@ -14,43 +14,43 @@ import lombok.EqualsAndHashCode;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@jakarta.persistence.Entity
|
@jakarta.persistence.Entity
|
||||||
@Table(name = "sys_workflow_definition")
|
@Table(name = "wf_definition")
|
||||||
@LogicDelete
|
@LogicDelete
|
||||||
public class WorkflowDefinition extends Entity<Long> {
|
public class WorkflowDefinition extends Entity<Long> {
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流编码
|
|
||||||
*/
|
|
||||||
@Column(name = "code", nullable = false, length = 50)
|
|
||||||
private String code;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流名称
|
* 工作流名称
|
||||||
*/
|
*/
|
||||||
@Column(name = "name", nullable = false, length = 100)
|
@Column(nullable = false)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流编码
|
||||||
|
*/
|
||||||
|
@Column(nullable = false, unique = true)
|
||||||
|
private String code;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 描述
|
* 描述
|
||||||
*/
|
*/
|
||||||
@Column(name = "description", columnDefinition = "TEXT")
|
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流定义(JSON)
|
* 状态
|
||||||
*/
|
*/
|
||||||
@Column(name = "content", nullable = false, columnDefinition = "TEXT")
|
@Enumerated(EnumType.STRING)
|
||||||
private String content;
|
@Column(nullable = false)
|
||||||
|
private WorkflowStatusEnum status = WorkflowStatusEnum.DRAFT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类型:DEPLOY/CONFIG_SYNC
|
* 节点配置(JSON)
|
||||||
*/
|
*/
|
||||||
@Column(name = "type", nullable = false, length = 20)
|
@Column(columnDefinition = "TEXT")
|
||||||
private String type;
|
private String nodeConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态:DRAFT/PUBLISHED/DISABLED
|
* 流转配置(JSON)
|
||||||
*/
|
*/
|
||||||
@Column(name = "status", nullable = false, length = 20)
|
@Column(columnDefinition = "TEXT")
|
||||||
private String status = "DRAFT";
|
private String transitionConfig;
|
||||||
}
|
}
|
||||||
@ -20,7 +20,7 @@ import java.time.LocalDateTime;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@jakarta.persistence.Entity
|
@jakarta.persistence.Entity
|
||||||
@Table(name = "sys_workflow_instance")
|
@Table(name = "wf_instance")
|
||||||
@LogicDelete
|
@LogicDelete
|
||||||
|
|
||||||
public class WorkflowInstance extends Entity<Long> {
|
public class WorkflowInstance extends Entity<Long> {
|
||||||
|
|||||||
@ -1,55 +1,47 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.entity;
|
package com.qqchen.deploy.backend.workflow.entity;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
import com.qqchen.deploy.backend.enums.LogTypeEnum;
|
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 jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.EnumType;
|
|
||||||
import jakarta.persistence.Enumerated;
|
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流日志
|
* 工作流日志实体
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@jakarta.persistence.Entity
|
@jakarta.persistence.Entity
|
||||||
@Table(name = "wf_workflow_log")
|
@Table(name = "wf_log")
|
||||||
|
@LogicDelete
|
||||||
public class WorkflowLog extends Entity<Long> {
|
public class WorkflowLog extends Entity<Long> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流实例ID
|
* 工作流实例ID
|
||||||
*/
|
*/
|
||||||
@Column(nullable = false)
|
@Column(name = "workflow_instance_id", nullable = false)
|
||||||
private Long workflowInstanceId;
|
private Long workflowInstanceId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点实例ID
|
* 节点ID
|
||||||
*/
|
*/
|
||||||
private Long nodeInstanceId;
|
@Column(name = "node_id")
|
||||||
|
private String nodeId;
|
||||||
/**
|
|
||||||
* 日志类型
|
|
||||||
*/
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
@Column(nullable = false)
|
|
||||||
private LogTypeEnum type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日志级别
|
|
||||||
*/
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
@Column(nullable = false)
|
|
||||||
private LogLevelEnum level;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志内容
|
* 日志内容
|
||||||
*/
|
*/
|
||||||
@Column(columnDefinition = "TEXT", nullable = false)
|
@Column(nullable = false)
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志级别
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LogLevelEnum level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 详细信息
|
* 详细信息
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.entity;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.enums.PermissionTypeEnum;
|
|
||||||
import com.qqchen.deploy.backend.framework.domain.Entity;
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.EnumType;
|
|
||||||
import jakarta.persistence.Enumerated;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流权限
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@jakarta.persistence.Entity
|
|
||||||
@Table(name = "wf_workflow_permission")
|
|
||||||
public class WorkflowPermission extends Entity<Long> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流定义ID
|
|
||||||
*/
|
|
||||||
@Column(nullable = false)
|
|
||||||
private Long workflowDefinitionId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 权限类型
|
|
||||||
*/
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
@Column(nullable = false)
|
|
||||||
private PermissionTypeEnum type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户ID
|
|
||||||
*/
|
|
||||||
private Long userId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 角色ID
|
|
||||||
*/
|
|
||||||
private Long roleId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 部门ID
|
|
||||||
*/
|
|
||||||
private Long departmentId;
|
|
||||||
}
|
|
||||||
@ -1,36 +1,39 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.entity;
|
package com.qqchen.deploy.backend.workflow.entity;
|
||||||
|
|
||||||
|
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.workflow.enums.VariableScopeEnum;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流变量
|
* 工作流变量实体
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@jakarta.persistence.Entity
|
@jakarta.persistence.Entity
|
||||||
@Table(name = "wf_workflow_variable")
|
@Table(name = "wf_variable")
|
||||||
|
@LogicDelete
|
||||||
public class WorkflowVariable extends Entity<Long> {
|
public class WorkflowVariable extends Entity<Long> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流实例ID
|
* 工作流实例ID
|
||||||
*/
|
*/
|
||||||
@Column(nullable = false)
|
@Column(name = "workflow_instance_id", nullable = false)
|
||||||
private Long workflowInstanceId;
|
private Long workflowInstanceId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 变量名
|
* 变量名称
|
||||||
*/
|
*/
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 变量值
|
* 变量值(JSON)
|
||||||
*/
|
*/
|
||||||
@Column(columnDefinition = "TEXT")
|
@Column(columnDefinition = "TEXT", nullable = false)
|
||||||
private String value;
|
private String value;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,4 +41,10 @@ public class WorkflowVariable extends Entity<Long> {
|
|||||||
*/
|
*/
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变量作用域
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String scope = VariableScopeEnum.INSTANCE.name();
|
||||||
}
|
}
|
||||||
@ -16,7 +16,9 @@ public enum NodeStatusEnum {
|
|||||||
COMPLETED("COMPLETED", "已完成"),
|
COMPLETED("COMPLETED", "已完成"),
|
||||||
FAILED("FAILED", "执行失败"),
|
FAILED("FAILED", "执行失败"),
|
||||||
CANCELLED("CANCELLED", "已取消"),
|
CANCELLED("CANCELLED", "已取消"),
|
||||||
SKIPPED("SKIPPED", "已跳过");
|
SKIPPED("SKIPPED", "已跳过"),
|
||||||
|
SUSPENDED("SUSPENDED", "已暂停"),
|
||||||
|
TERMINATED("TERMINATED", "已终止");
|
||||||
|
|
||||||
private final String code;
|
private final String code;
|
||||||
private final String description;
|
private final String description;
|
||||||
|
|||||||
@ -1,29 +1,21 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.enums;
|
package com.qqchen.deploy.backend.workflow.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点类型枚举
|
* 节点类型枚举
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
public enum NodeTypeEnum {
|
public enum NodeTypeEnum {
|
||||||
|
|
||||||
START("START", "开始节点"),
|
START("START", "开始节点"),
|
||||||
END("END", "结束节点"),
|
END("END", "结束节点"),
|
||||||
DEPLOY("DEPLOY", "部署节点"),
|
TASK("TASK", "任务节点"),
|
||||||
APPROVAL("APPROVAL", "审批节点"),
|
GATEWAY("GATEWAY", "网关节点"),
|
||||||
SCRIPT("SCRIPT", "脚本节点"),
|
SUB_PROCESS("SUB_PROCESS", "子流程节点");
|
||||||
SHELL("SHELL", "Shell脚本节点"),
|
|
||||||
CONFIG_SYNC("CONFIG_SYNC", "配置同步节点"),
|
|
||||||
CONDITION("CONDITION", "条件节点"),
|
|
||||||
PARALLEL("PARALLEL", "并行节点"),
|
|
||||||
SERIAL("SERIAL", "串行节点");
|
|
||||||
|
|
||||||
private final String code;
|
private final String code;
|
||||||
private final String desc;
|
private final String description;
|
||||||
|
|
||||||
NodeTypeEnum(String code, String desc) {
|
|
||||||
this.code = code;
|
|
||||||
this.desc = desc;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变量作用域枚举
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum VariableScopeEnum {
|
||||||
|
|
||||||
|
INSTANCE("INSTANCE", "工作流实例"),
|
||||||
|
NODE("NODE", "节点实例"),
|
||||||
|
GLOBAL("GLOBAL", "全局");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
}
|
||||||
@ -22,7 +22,9 @@ public enum WorkflowStatusEnum {
|
|||||||
PAUSED("PAUSED", "已暂停"),
|
PAUSED("PAUSED", "已暂停"),
|
||||||
COMPLETED("COMPLETED", "已完成"),
|
COMPLETED("COMPLETED", "已完成"),
|
||||||
FAILED("FAILED", "执行失败"),
|
FAILED("FAILED", "执行失败"),
|
||||||
CANCELLED("CANCELLED", "已取消");
|
CANCELLED("CANCELLED", "已取消"),
|
||||||
|
SUSPENDED("SUSPENDED", "已暂停"),
|
||||||
|
TERMINATED("TERMINATED", "已终止");
|
||||||
|
|
||||||
private final String code;
|
private final String code;
|
||||||
private final String description;
|
private final String description;
|
||||||
@ -31,7 +33,7 @@ public enum WorkflowStatusEnum {
|
|||||||
* 判断是否为终态
|
* 判断是否为终态
|
||||||
*/
|
*/
|
||||||
public boolean isFinalStatus() {
|
public boolean isFinalStatus() {
|
||||||
return this == COMPLETED || this == FAILED || this == CANCELLED;
|
return this == COMPLETED || this == FAILED || this == CANCELLED || this == SUSPENDED || this == TERMINATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.query;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.QueryType;
|
||||||
|
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class NodeInstanceQuery extends BaseQuery {
|
||||||
|
|
||||||
|
@QueryField(field = "workflowInstanceId")
|
||||||
|
private Long workflowInstanceId;
|
||||||
|
|
||||||
|
@QueryField(field = "nodeId")
|
||||||
|
private String nodeId;
|
||||||
|
|
||||||
|
@QueryField(field = "nodeType")
|
||||||
|
private NodeTypeEnum nodeType;
|
||||||
|
|
||||||
|
@QueryField(field = "name", type = QueryType.LIKE)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@QueryField(field = "status")
|
||||||
|
private NodeStatusEnum status;
|
||||||
|
}
|
||||||
@ -1,39 +1,22 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.api.query;
|
package com.qqchen.deploy.backend.workflow.query;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
||||||
import com.qqchen.deploy.backend.framework.enums.QueryType;
|
import com.qqchen.deploy.backend.framework.enums.QueryType;
|
||||||
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流定义查询对象
|
|
||||||
*/
|
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class WorkflowDefinitionQuery extends BaseQuery {
|
public class WorkflowDefinitionQuery extends BaseQuery {
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流编码
|
|
||||||
*/
|
|
||||||
@QueryField(field = "code", type = QueryType.LIKE)
|
|
||||||
private String code;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流名称
|
|
||||||
*/
|
|
||||||
@QueryField(field = "name", type = QueryType.LIKE)
|
@QueryField(field = "name", type = QueryType.LIKE)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/**
|
@QueryField(field = "code", type = QueryType.LIKE)
|
||||||
* 工作流类型
|
private String code;
|
||||||
*/
|
|
||||||
@QueryField(field = "type")
|
|
||||||
private String type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流状态
|
|
||||||
*/
|
|
||||||
@QueryField(field = "status")
|
@QueryField(field = "status")
|
||||||
private String status;
|
private WorkflowStatusEnum status;
|
||||||
}
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.query;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.QueryType;
|
||||||
|
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class WorkflowInstanceQuery extends BaseQuery {
|
||||||
|
|
||||||
|
@QueryField(field = "businessKey", type = QueryType.LIKE)
|
||||||
|
private String businessKey;
|
||||||
|
|
||||||
|
@QueryField(field = "definitionId")
|
||||||
|
private Long definitionId;
|
||||||
|
|
||||||
|
@QueryField(field = "status")
|
||||||
|
private WorkflowStatusEnum status;
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.query;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
|
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.QueryType;
|
||||||
|
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class WorkflowLogQuery extends BaseQuery {
|
||||||
|
|
||||||
|
@QueryField(field = "workflowInstanceId")
|
||||||
|
private Long workflowInstanceId;
|
||||||
|
|
||||||
|
@QueryField(field = "nodeId")
|
||||||
|
private String nodeId;
|
||||||
|
|
||||||
|
@QueryField(field = "level")
|
||||||
|
private LogLevelEnum level;
|
||||||
|
|
||||||
|
@QueryField(field = "message", type = QueryType.LIKE)
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.repository;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeDefinition;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface INodeDefinitionRepository extends IBaseRepository<NodeDefinition, Long> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询工作流定义的所有节点
|
||||||
|
*/
|
||||||
|
@Query("SELECT n FROM NodeDefinition n WHERE n.workflowDefinitionId = :definitionId AND n.deleted = false ORDER BY n.sequence")
|
||||||
|
List<NodeDefinition> findByWorkflowDefinitionIdOrderBySequence(@Param("definitionId") Long definitionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据节点ID查询节点定义
|
||||||
|
*/
|
||||||
|
NodeDefinition findByWorkflowDefinitionIdAndNodeIdAndDeletedFalse(Long workflowDefinitionId, String nodeId);
|
||||||
|
}
|
||||||
@ -2,33 +2,30 @@ 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.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;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点实例仓库接口
|
|
||||||
*/
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface INodeInstanceRepository extends IBaseRepository<NodeInstance, Long> {
|
public interface INodeInstanceRepository extends IBaseRepository<NodeInstance, Long> {
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据工作流实例ID查询节点实例列表
|
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
* @return 节点实例列表
|
|
||||||
*/
|
|
||||||
@Query("SELECT n FROM NodeInstance n WHERE n.workflowInstance.id = :workflowInstanceId AND n.deleted = false ORDER BY n.createTime ASC")
|
|
||||||
List<NodeInstance> findByWorkflowInstanceId(@Param("workflowInstanceId") Long workflowInstanceId);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据工作流实例ID和状态查询节点实例列表
|
* 查询工作流实例的所有节点
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
* @param status 状态
|
|
||||||
* @return 节点实例列表
|
|
||||||
*/
|
*/
|
||||||
@Query("SELECT n FROM NodeInstance n WHERE n.workflowInstance.id = :workflowInstanceId AND n.status = :status AND n.deleted = false ORDER BY n.createTime ASC")
|
@Query("SELECT n FROM NodeInstance n WHERE n.workflowInstanceId = :instanceId AND n.deleted = false ORDER BY n.createTime")
|
||||||
List<NodeInstance> findByWorkflowInstanceIdAndStatus(@Param("workflowInstanceId") Long workflowInstanceId, @Param("status") String status);
|
List<NodeInstance> findByWorkflowInstanceIdOrderByCreateTime(@Param("instanceId") Long instanceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定状态的节点实例
|
||||||
|
*/
|
||||||
|
List<NodeInstance> findByWorkflowInstanceIdAndStatusAndDeletedFalse(Long workflowInstanceId, NodeStatusEnum status);
|
||||||
|
|
||||||
|
List<NodeInstance> findByInstanceIdAndStatus(Long instanceId, NodeStatusEnum status);
|
||||||
|
|
||||||
|
List<NodeInstance> findByInstanceId(Long instanceId);
|
||||||
|
|
||||||
|
void deleteByInstanceId(Long instanceId);
|
||||||
}
|
}
|
||||||
@ -11,18 +11,17 @@ import org.springframework.stereotype.Repository;
|
|||||||
public interface IWorkflowDefinitionRepository extends IBaseRepository<WorkflowDefinition, Long> {
|
public interface IWorkflowDefinitionRepository extends IBaseRepository<WorkflowDefinition, Long> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据编码查询未删除的工作流定义
|
* 根据编码查询工作流定义
|
||||||
*
|
|
||||||
* @param code 工作流编码
|
|
||||||
* @return 工作流定义
|
|
||||||
*/
|
*/
|
||||||
WorkflowDefinition findByCodeAndDeletedFalse(String code);
|
WorkflowDefinition findByCodeAndDeletedFalse(String code);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查编码是否存在(排除已删除的)
|
* 检查编码是否存在
|
||||||
*
|
|
||||||
* @param code 工作流编码
|
|
||||||
* @return 是否存在
|
|
||||||
*/
|
*/
|
||||||
boolean existsByCodeAndDeletedFalse(String code);
|
boolean existsByCodeAndDeletedFalse(String code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查名称是否存在
|
||||||
|
*/
|
||||||
|
boolean existsByNameAndDeletedFalse(String name);
|
||||||
}
|
}
|
||||||
@ -2,9 +2,11 @@ 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.WorkflowInstance;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||||
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;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,21 +16,13 @@ import java.util.List;
|
|||||||
public interface IWorkflowInstanceRepository extends IBaseRepository<WorkflowInstance, Long> {
|
public interface IWorkflowInstanceRepository extends IBaseRepository<WorkflowInstance, Long> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据项目环境ID查询工作流实例列表
|
* 查询指定状态的工作流实例
|
||||||
*
|
|
||||||
* @param projectEnvId 项目环境ID
|
|
||||||
* @return 工作流实例列表
|
|
||||||
*/
|
*/
|
||||||
@Query("SELECT w FROM WorkflowInstance w WHERE w.projectEnvId = :projectEnvId AND w.deleted = false ORDER BY w.createTime DESC")
|
@Query("SELECT i FROM WorkflowInstance i WHERE i.status IN :statuses AND i.deleted = false")
|
||||||
List<WorkflowInstance> findByProjectEnvId(@Param("projectEnvId") Long projectEnvId);
|
List<WorkflowInstance> findByStatusIn(@Param("statuses") List<WorkflowStatusEnum> statuses);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据项目环境ID和状态查询工作流实例列表
|
* 查询项目环境的工作流实例
|
||||||
*
|
|
||||||
* @param projectEnvId 项目环境ID
|
|
||||||
* @param status 状态
|
|
||||||
* @return 工作流实例列表
|
|
||||||
*/
|
*/
|
||||||
@Query("SELECT w FROM WorkflowInstance w WHERE w.projectEnvId = :projectEnvId AND w.status = :status AND w.deleted = false ORDER BY w.createTime DESC")
|
List<WorkflowInstance> findByProjectEnvIdAndDeletedFalseOrderByCreateTimeDesc(Long projectEnvId);
|
||||||
List<WorkflowInstance> findByProjectEnvIdAndStatus(@Param("projectEnvId") Long projectEnvId, @Param("status") String status);
|
|
||||||
}
|
}
|
||||||
@ -1,11 +1,7 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.repository;
|
package com.qqchen.deploy.backend.workflow.repository;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
|
||||||
import com.qqchen.deploy.backend.enums.LogTypeEnum;
|
|
||||||
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
|
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
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;
|
||||||
@ -19,70 +15,19 @@ import java.util.List;
|
|||||||
public interface IWorkflowLogRepository extends IBaseRepository<WorkflowLog, Long> {
|
public interface IWorkflowLogRepository extends IBaseRepository<WorkflowLog, Long> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询节点日志
|
* 查询工作流实例的所有日志
|
||||||
*
|
|
||||||
* @param nodeInstanceId 节点实例ID
|
|
||||||
* @param type 日志类型
|
|
||||||
* @param level 日志级别
|
|
||||||
* @return 日志列表
|
|
||||||
*/
|
*/
|
||||||
@Query("SELECT l FROM WorkflowLog l WHERE l.nodeInstanceId = :nodeInstanceId " +
|
@Query("SELECT l FROM WorkflowLog l WHERE l.workflowInstanceId = :instanceId AND l.deleted = false ORDER BY l.createTime")
|
||||||
"AND (:type IS NULL OR l.type = :type) " +
|
List<WorkflowLog> findByWorkflowInstanceId(@Param("instanceId") Long instanceId);
|
||||||
"AND (:level IS NULL OR l.level = :level) " +
|
|
||||||
"AND l.deleted = false " +
|
|
||||||
"ORDER BY l.createTime DESC")
|
|
||||||
List<WorkflowLog> findNodeLogs(
|
|
||||||
@Param("nodeInstanceId") Long nodeInstanceId,
|
|
||||||
@Param("type") LogTypeEnum type,
|
|
||||||
@Param("level") LogLevelEnum level
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询节点日志
|
* 查询节点实例的所有日志
|
||||||
*
|
|
||||||
* @param nodeInstanceId 节点实例ID
|
|
||||||
* @param type 日志类型
|
|
||||||
* @param level 日志级别
|
|
||||||
* @param pageable 分页参数
|
|
||||||
* @return 日志分页
|
|
||||||
*/
|
*/
|
||||||
@Query("SELECT l FROM WorkflowLog l WHERE l.nodeInstanceId = :nodeInstanceId " +
|
@Query("SELECT l FROM WorkflowLog l WHERE l.workflowInstanceId = :instanceId AND l.nodeId = :nodeId AND l.deleted = false ORDER BY l.createTime")
|
||||||
"AND (:type IS NULL OR l.type = :type) " +
|
List<WorkflowLog> findByWorkflowInstanceIdAndNodeId(@Param("instanceId") Long instanceId, @Param("nodeId") String nodeId);
|
||||||
"AND (:level IS NULL OR l.level = :level) " +
|
|
||||||
"AND l.deleted = false " +
|
|
||||||
"ORDER BY l.createTime DESC")
|
|
||||||
Page<WorkflowLog> findNodeLogs(
|
|
||||||
@Param("nodeInstanceId") Long nodeInstanceId,
|
|
||||||
@Param("type") LogTypeEnum type,
|
|
||||||
@Param("level") LogLevelEnum level,
|
|
||||||
Pageable pageable
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 分页查询工作流日志
|
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
* @param type 日志类型
|
|
||||||
* @param level 日志级别
|
|
||||||
* @param pageable 分页参数
|
|
||||||
* @return 日志分页
|
|
||||||
*/
|
|
||||||
@Query("SELECT l FROM WorkflowLog l WHERE l.workflowInstanceId = :workflowInstanceId " +
|
|
||||||
"AND (:type IS NULL OR l.type = :type) " +
|
|
||||||
"AND (:level IS NULL OR l.level = :level) " +
|
|
||||||
"AND l.deleted = false " +
|
|
||||||
"ORDER BY l.createTime DESC")
|
|
||||||
Page<WorkflowLog> findByWorkflowInstanceIdAndTypeAndLevel(
|
|
||||||
@Param("workflowInstanceId") Long workflowInstanceId,
|
|
||||||
@Param("type") LogTypeEnum type,
|
|
||||||
@Param("level") LogLevelEnum level,
|
|
||||||
Pageable pageable
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除工作流实例的所有日志
|
* 删除工作流实例的所有日志
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
*/
|
*/
|
||||||
void deleteByWorkflowInstanceId(Long workflowInstanceId);
|
void deleteByWorkflowInstanceId(Long instanceId);
|
||||||
}
|
}
|
||||||
@ -1,123 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.repository;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.enums.PermissionTypeEnum;
|
|
||||||
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
|
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowPermission;
|
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流权限仓库接口
|
|
||||||
*/
|
|
||||||
@Repository
|
|
||||||
public interface IWorkflowPermissionRepository extends IBaseRepository<WorkflowPermission, Long> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据工作流定义ID查询权限列表
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @return 权限列表
|
|
||||||
*/
|
|
||||||
List<WorkflowPermission> findByWorkflowDefinitionId(Long workflowDefinitionId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据工作流定义ID和权限类型分页查询权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param type 权限类型
|
|
||||||
* @param pageable 分页参数
|
|
||||||
* @return 权限分页
|
|
||||||
*/
|
|
||||||
Page<WorkflowPermission> findByWorkflowDefinitionIdAndType(Long workflowDefinitionId, PermissionTypeEnum type, Pageable pageable);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询用户权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param userId 用户ID
|
|
||||||
* @param type 权限类型
|
|
||||||
* @return 权限
|
|
||||||
*/
|
|
||||||
Optional<WorkflowPermission> findByWorkflowDefinitionIdAndUserIdAndType(Long workflowDefinitionId, Long userId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询角色权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param roleId 角色ID
|
|
||||||
* @param type 权限类型
|
|
||||||
* @return 权限
|
|
||||||
*/
|
|
||||||
Optional<WorkflowPermission> findByWorkflowDefinitionIdAndRoleIdAndType(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询部门权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param departmentId 部门ID
|
|
||||||
* @param type 权限类型
|
|
||||||
* @return 权限
|
|
||||||
*/
|
|
||||||
Optional<WorkflowPermission> findByWorkflowDefinitionIdAndDepartmentIdAndType(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查用户权限是否存在
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param userId 用户ID
|
|
||||||
* @param type 权限类型
|
|
||||||
* @return 是否存在
|
|
||||||
*/
|
|
||||||
boolean existsByWorkflowDefinitionIdAndUserIdAndType(Long workflowDefinitionId, Long userId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查角色权限是否存在
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param roleId 角色ID
|
|
||||||
* @param type 权限类型
|
|
||||||
* @return 是否存在
|
|
||||||
*/
|
|
||||||
boolean existsByWorkflowDefinitionIdAndRoleIdAndType(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查部门权限是否存在
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param departmentId 部门ID
|
|
||||||
* @param type 权限类型
|
|
||||||
* @return 是否存在
|
|
||||||
*/
|
|
||||||
boolean existsByWorkflowDefinitionIdAndDepartmentIdAndType(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除用户权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param userId 用户ID
|
|
||||||
* @param type 权限类型
|
|
||||||
*/
|
|
||||||
void deleteByWorkflowDefinitionIdAndUserIdAndType(Long workflowDefinitionId, Long userId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除角色权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param roleId 角色ID
|
|
||||||
* @param type 权限类型
|
|
||||||
*/
|
|
||||||
void deleteByWorkflowDefinitionIdAndRoleIdAndType(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除部门权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param departmentId 部门ID
|
|
||||||
* @param type 权限类型
|
|
||||||
*/
|
|
||||||
void deleteByWorkflowDefinitionIdAndDepartmentIdAndType(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type);
|
|
||||||
}
|
|
||||||
@ -2,6 +2,8 @@ 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.WorkflowVariable;
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowVariable;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -14,34 +16,24 @@ import java.util.Optional;
|
|||||||
public interface IWorkflowVariableRepository extends IBaseRepository<WorkflowVariable, Long> {
|
public interface IWorkflowVariableRepository extends IBaseRepository<WorkflowVariable, Long> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据工作流实例ID查询变量列表
|
* 查询工作流实例的所有变量
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
* @return 变量列表
|
|
||||||
*/
|
*/
|
||||||
List<WorkflowVariable> findByWorkflowInstanceId(Long workflowInstanceId);
|
@Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstanceId = :instanceId AND v.deleted = false")
|
||||||
|
List<WorkflowVariable> findByWorkflowInstanceId(@Param("instanceId") Long instanceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据工作流实例ID和变量名查询变量
|
* 查询工作流实例的指定作用域的变量
|
||||||
*
|
*/
|
||||||
* @param workflowInstanceId 工作流实例ID
|
@Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstanceId = :instanceId AND v.scope = :scope AND v.deleted = false")
|
||||||
* @param name 变量名
|
List<WorkflowVariable> findByWorkflowInstanceIdAndScope(@Param("instanceId") Long instanceId, @Param("scope") String scope);
|
||||||
* @return 变量
|
|
||||||
|
/**
|
||||||
|
* 查询工作流实例的指定变量
|
||||||
*/
|
*/
|
||||||
Optional<WorkflowVariable> findByWorkflowInstanceIdAndName(Long workflowInstanceId, String name);
|
Optional<WorkflowVariable> findByWorkflowInstanceIdAndName(Long workflowInstanceId, String name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据工作流实例ID和变量名删除变量
|
* 删除工作流实例的所有变量
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
* @param name 变量名
|
|
||||||
*/
|
*/
|
||||||
void deleteByWorkflowInstanceIdAndName(Long workflowInstanceId, String name);
|
void deleteByWorkflowInstanceId(Long instanceId);
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据工作流实例ID删除所有变量
|
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
*/
|
|
||||||
void deleteByWorkflowInstanceId(Long workflowInstanceId);
|
|
||||||
}
|
}
|
||||||
@ -1,48 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.service;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
|
||||||
import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO;
|
|
||||||
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
|
||||||
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点实例服务接口
|
|
||||||
*/
|
|
||||||
public interface INodeInstanceService extends IBaseService<NodeInstance, NodeInstanceDTO, Long> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据工作流实例ID查询节点实例列表
|
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
* @return 节点实例列表
|
|
||||||
*/
|
|
||||||
List<NodeInstanceDTO> findByWorkflowInstanceId(Long workflowInstanceId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据工作流实例ID和状态查询节点实例列表
|
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
* @param status 状态
|
|
||||||
* @return 节点实例列表
|
|
||||||
*/
|
|
||||||
List<NodeInstanceDTO> findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, String status);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新节点状态
|
|
||||||
*
|
|
||||||
* @param id 节点实例ID
|
|
||||||
* @param status 状态
|
|
||||||
* @param output 输出结果
|
|
||||||
* @param error 错误信息
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean updateStatus(Long id, String status, String output, String error);
|
|
||||||
|
|
||||||
List<NodeInstance> findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, NodeStatusEnum status);
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
void cancelRunningNodes(Long workflowInstanceId);
|
|
||||||
}
|
|
||||||
@ -1,8 +1,9 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.service;
|
package com.qqchen.deploy.backend.workflow.service;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||||
import com.qqchen.deploy.backend.workflow.api.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 com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流定义服务接口
|
* 工作流定义服务接口
|
||||||
@ -10,26 +11,32 @@ import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
|||||||
public interface IWorkflowDefinitionService extends IBaseService<WorkflowDefinition, WorkflowDefinitionDTO, Long> {
|
public interface IWorkflowDefinitionService extends IBaseService<WorkflowDefinition, WorkflowDefinitionDTO, Long> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发布工作流
|
* 发布工作流定义
|
||||||
*
|
|
||||||
* @param id 工作流定义ID
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
*/
|
||||||
boolean publish(Long id);
|
void publish(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用工作流定义
|
||||||
|
*/
|
||||||
|
void disable(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用工作流定义
|
||||||
|
*/
|
||||||
|
void enable(Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 禁用工作流
|
* 检查工作流名称是否唯一
|
||||||
*
|
|
||||||
* @param id 工作流定义ID
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
*/
|
||||||
boolean disable(Long id);
|
void checkNameUnique(String name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据编码查询工作流定义
|
* 检查工作流编码是否唯一
|
||||||
*
|
|
||||||
* @param code 工作流编码
|
|
||||||
* @return 工作流定义
|
|
||||||
*/
|
*/
|
||||||
WorkflowDefinitionDTO findByCode(String code);
|
void checkCodeUnique(String code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证工作流状态
|
||||||
|
*/
|
||||||
|
void validateStatus(WorkflowStatusEnum status);
|
||||||
}
|
}
|
||||||
@ -1,53 +1,11 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.service;
|
package com.qqchen.deploy.backend.workflow.service;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||||
import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流实例服务接口
|
* 工作流实例服务接口
|
||||||
*/
|
*/
|
||||||
public interface IWorkflowInstanceService extends IBaseService<WorkflowInstance, WorkflowInstanceDTO, Long> {
|
public interface IWorkflowInstanceService extends IBaseService<WorkflowInstance, WorkflowInstanceDTO, Long> {
|
||||||
|
|
||||||
@Transactional
|
|
||||||
void updateStatus(Long id, WorkflowStatusEnum status, String error);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动工作流实例
|
|
||||||
*
|
|
||||||
* @param definitionId 工作流定义ID
|
|
||||||
* @param projectEnvId 项目环境ID
|
|
||||||
* @param variables 工作流变量
|
|
||||||
* @return 工作流实例
|
|
||||||
*/
|
|
||||||
WorkflowInstanceDTO start(Long definitionId, Long projectEnvId, String variables);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 取消工作流实例
|
|
||||||
*
|
|
||||||
* @param id 工作流实例ID
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean cancel(Long id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据项目环境ID查询工作流实例列表
|
|
||||||
*
|
|
||||||
* @param projectEnvId 项目环境ID
|
|
||||||
* @return 工作流实例列表
|
|
||||||
*/
|
|
||||||
List<WorkflowInstanceDTO> findByProjectEnvId(Long projectEnvId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据项目环境ID和状态查询工作流实例列表
|
|
||||||
*
|
|
||||||
* @param projectEnvId 项目环境ID
|
|
||||||
* @param status 状态
|
|
||||||
* @return 工作流实例列表
|
|
||||||
*/
|
|
||||||
List<WorkflowInstanceDTO> findByProjectEnvIdAndStatus(Long projectEnvId, String status);
|
|
||||||
}
|
}
|
||||||
@ -1,147 +1,34 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.service;
|
package com.qqchen.deploy.backend.workflow.service;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
import com.qqchen.deploy.backend.enums.LogTypeEnum;
|
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
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 org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流日志服务接口
|
* 工作流日志服务接口
|
||||||
*/
|
*/
|
||||||
public interface IWorkflowLogService {
|
public interface IWorkflowLogService extends IBaseService<WorkflowLog, WorkflowLogDTO, Long> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 记录工作流开始日志
|
* 记录日志
|
||||||
*
|
|
||||||
* @param instance 工作流实例
|
|
||||||
*/
|
*/
|
||||||
void logWorkflowStart(WorkflowInstance instance);
|
void log(Long workflowInstanceId, String nodeId, String message, LogLevelEnum level, String detail);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 记录工作流完成日志
|
* 查询工作流实例的所有日志
|
||||||
*
|
|
||||||
* @param instance 工作流实例
|
|
||||||
*/
|
*/
|
||||||
void logWorkflowComplete(WorkflowInstance instance);
|
List<WorkflowLogDTO> getLogs(Long workflowInstanceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 记录工作流暂停日志
|
* 查询节点实例的所有日志
|
||||||
*
|
|
||||||
* @param instance 工作流实例
|
|
||||||
*/
|
*/
|
||||||
void logWorkflowPause(WorkflowInstance instance);
|
List<WorkflowLogDTO> getNodeLogs(Long workflowInstanceId, String nodeId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 记录工作流恢复日志
|
* 删除工作流实例的所有日志
|
||||||
*
|
|
||||||
* @param instance 工作流实例
|
|
||||||
*/
|
*/
|
||||||
void logWorkflowResume(WorkflowInstance instance);
|
void deleteLogs(Long workflowInstanceId);
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录工作流取消日志
|
|
||||||
*
|
|
||||||
* @param instance 工作流实例
|
|
||||||
*/
|
|
||||||
void logWorkflowCancel(WorkflowInstance instance);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录工作流错误日志
|
|
||||||
*
|
|
||||||
* @param instance 工作流实例
|
|
||||||
* @param error 错误信息
|
|
||||||
*/
|
|
||||||
void logWorkflowError(WorkflowInstance instance, String error);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录节点开始日志
|
|
||||||
*
|
|
||||||
* @param node 节点实例
|
|
||||||
*/
|
|
||||||
void logNodeStart(NodeInstance node);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录节点完成日志
|
|
||||||
*
|
|
||||||
* @param node 节点实例
|
|
||||||
*/
|
|
||||||
void logNodeComplete(NodeInstance node);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录节点错误日志
|
|
||||||
*
|
|
||||||
* @param node 节点实例
|
|
||||||
* @param error 错误信息
|
|
||||||
*/
|
|
||||||
void logNodeError(NodeInstance node, String error);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录节点重试日志
|
|
||||||
*
|
|
||||||
* @param node 节点实例
|
|
||||||
*/
|
|
||||||
void logNodeRetry(NodeInstance node);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录节点跳过日志
|
|
||||||
*
|
|
||||||
* @param node 节点实例
|
|
||||||
*/
|
|
||||||
void logNodeSkip(NodeInstance node);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录变量更新日志
|
|
||||||
*
|
|
||||||
* @param instance 工作流实例
|
|
||||||
* @param variableName 变量名
|
|
||||||
* @param value 变量值
|
|
||||||
*/
|
|
||||||
void logVariableUpdate(WorkflowInstance instance, String variableName, Object value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录系统日志
|
|
||||||
*
|
|
||||||
* @param instance 工作流实例
|
|
||||||
* @param level 日志级别
|
|
||||||
* @param content 日志内容
|
|
||||||
* @param detail 详细信息
|
|
||||||
*/
|
|
||||||
void logSystem(WorkflowInstance instance, LogLevelEnum level, String content, String detail);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 分页查询工作流日志
|
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
* @param type 日志类型
|
|
||||||
* @param level 日志级别
|
|
||||||
* @param pageable 分页参数
|
|
||||||
* @return 日志分页
|
|
||||||
*/
|
|
||||||
Page<WorkflowLog> findLogs(Long workflowInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询节点日志
|
|
||||||
*
|
|
||||||
* @param nodeInstanceId 节点实例ID
|
|
||||||
* @param type 日志类型
|
|
||||||
* @param level 日志级别
|
|
||||||
* @return 日志列表
|
|
||||||
*/
|
|
||||||
List<WorkflowLog> findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 分页查询节点日志
|
|
||||||
*
|
|
||||||
* @param nodeInstanceId 节点实例ID
|
|
||||||
* @param type 日志类型
|
|
||||||
* @param level 日志级别
|
|
||||||
* @param pageable 分页参数
|
|
||||||
* @return 日志分页
|
|
||||||
*/
|
|
||||||
Page<WorkflowLog> findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable);
|
|
||||||
}
|
}
|
||||||
@ -1,96 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.service;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.enums.PermissionTypeEnum;
|
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowPermission;
|
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流权限服务接口
|
|
||||||
*/
|
|
||||||
public interface IWorkflowPermissionService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加用户权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param userId 用户ID
|
|
||||||
* @param type 权限类型
|
|
||||||
*/
|
|
||||||
void addUserPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加角色权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param roleId 角色ID
|
|
||||||
* @param type 权限类型
|
|
||||||
*/
|
|
||||||
void addRolePermission(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加部门权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param departmentId 部门ID
|
|
||||||
* @param type 权限类型
|
|
||||||
*/
|
|
||||||
void addDepartmentPermission(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除用户权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param userId 用户ID
|
|
||||||
* @param type 权限类型
|
|
||||||
*/
|
|
||||||
void removeUserPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除角色权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param roleId 角色ID
|
|
||||||
* @param type 权限类型
|
|
||||||
*/
|
|
||||||
void removeRolePermission(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除部门权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param departmentId 部门ID
|
|
||||||
* @param type 权限类型
|
|
||||||
*/
|
|
||||||
void removeDepartmentPermission(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查用户是否有指定权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param userId 用户ID
|
|
||||||
* @param type 权限类型
|
|
||||||
* @return 是否有权限
|
|
||||||
*/
|
|
||||||
boolean hasPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取工作流定义的所有权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @return 权限列表
|
|
||||||
*/
|
|
||||||
List<WorkflowPermission> getPermissions(Long workflowDefinitionId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 分页查询工作流权限
|
|
||||||
*
|
|
||||||
* @param workflowDefinitionId 工作流定义ID
|
|
||||||
* @param type 权限类型
|
|
||||||
* @param pageable 分页参数
|
|
||||||
* @return 权限分页
|
|
||||||
*/
|
|
||||||
Page<WorkflowPermission> findPermissions(Long workflowDefinitionId, PermissionTypeEnum type, Pageable pageable);
|
|
||||||
}
|
|
||||||
@ -1,58 +1,34 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.service;
|
package com.qqchen.deploy.backend.workflow.service;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||||
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowVariableDTO;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowVariable;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.VariableScopeEnum;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流变量服务接口
|
* 工作流变量服务接口
|
||||||
*/
|
*/
|
||||||
public interface IWorkflowVariableService {
|
public interface IWorkflowVariableService extends IBaseService<WorkflowVariable, WorkflowVariableDTO, Long> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存工作流变量
|
* 设置变量
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
* @param variables 变量Map
|
|
||||||
*/
|
|
||||||
void saveVariables(Long workflowInstanceId, Map<String, Object> variables);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取工作流变量
|
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
* @return 变量Map
|
|
||||||
*/
|
|
||||||
Map<String, Object> getVariables(Long workflowInstanceId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取工作流变量值
|
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
* @param name 变量名
|
|
||||||
* @return 变量值
|
|
||||||
*/
|
|
||||||
Object getVariable(Long workflowInstanceId, String name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置工作流变量值
|
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
* @param name 变量名
|
|
||||||
* @param value 变量值
|
|
||||||
*/
|
*/
|
||||||
void setVariable(Long workflowInstanceId, String name, Object value);
|
void setVariable(Long workflowInstanceId, String name, Object value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除工作流变量
|
* 获取所有变量
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
* @param name 变量名
|
|
||||||
*/
|
*/
|
||||||
void deleteVariable(Long workflowInstanceId, String name);
|
Map<String, Object> getVariables(Long workflowInstanceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清空工作流变量
|
* 获取指定作用域的变量
|
||||||
*
|
|
||||||
* @param workflowInstanceId 工作流实例ID
|
|
||||||
*/
|
*/
|
||||||
void clearVariables(Long workflowInstanceId);
|
Map<String, Object> getVariablesByScope(Long workflowInstanceId, VariableScopeEnum scope);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除所有变量
|
||||||
|
*/
|
||||||
|
void deleteVariables(Long workflowInstanceId);
|
||||||
}
|
}
|
||||||
@ -1,80 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.service.impl;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
|
||||||
import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO;
|
|
||||||
import com.qqchen.deploy.backend.workflow.converter.NodeInstanceConverter;
|
|
||||||
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
|
||||||
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
|
||||||
import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository;
|
|
||||||
import com.qqchen.deploy.backend.workflow.service.INodeInstanceService;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class NodeInstanceServiceImpl extends BaseServiceImpl<NodeInstance, NodeInstanceDTO, Long> implements INodeInstanceService {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private INodeInstanceRepository nodeInstanceRepository;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private NodeInstanceConverter nodeInstanceConverter;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<NodeInstanceDTO> findByWorkflowInstanceId(Long workflowInstanceId) {
|
|
||||||
return nodeInstanceRepository.findByWorkflowInstanceId(workflowInstanceId)
|
|
||||||
.stream()
|
|
||||||
.map(nodeInstanceConverter::toDto)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<NodeInstanceDTO> findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, String status) {
|
|
||||||
return nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(workflowInstanceId, status)
|
|
||||||
.stream()
|
|
||||||
.map(nodeInstanceConverter::toDto)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public boolean updateStatus(Long id, String status, String output, String error) {
|
|
||||||
NodeInstance node = super.converter.toEntity(super.findById(id));
|
|
||||||
node.setStatus(NodeStatusEnum.valueOf(status));
|
|
||||||
node.setOutput(output);
|
|
||||||
node.setError(error);
|
|
||||||
|
|
||||||
if (NodeStatusEnum.RUNNING.name().equals(status)) {
|
|
||||||
node.setStartTime(LocalDateTime.now());
|
|
||||||
} else if (NodeStatusEnum.COMPLETED.name().equals(status) ||
|
|
||||||
NodeStatusEnum.FAILED.name().equals(status) ||
|
|
||||||
NodeStatusEnum.CANCELLED.name().equals(status) ||
|
|
||||||
NodeStatusEnum.SKIPPED.name().equals(status)) {
|
|
||||||
node.setEndTime(LocalDateTime.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeInstanceRepository.save(node);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<NodeInstance> findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, NodeStatusEnum status) {
|
|
||||||
return nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(workflowInstanceId, status.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void cancelRunningNodes(Long workflowInstanceId) {
|
|
||||||
List<NodeInstance> runningNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(
|
|
||||||
workflowInstanceId, NodeStatusEnum.RUNNING.name());
|
|
||||||
runningNodes.forEach(node -> {
|
|
||||||
node.setStatus(NodeStatusEnum.CANCELLED);
|
|
||||||
node.setEndTime(LocalDateTime.now());
|
|
||||||
nodeInstanceRepository.save(node);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +1,9 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.service.impl;
|
package com.qqchen.deploy.backend.workflow.service.impl;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
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.api.dto.WorkflowDefinitionDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.converter.WorkflowDefinitionConverter;
|
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
||||||
@ -12,52 +13,69 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流定义服务实现类
|
|
||||||
*/
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefinition, WorkflowDefinitionDTO, Long>
|
public class WorkflowDefinitionServiceImpl
|
||||||
implements IWorkflowDefinitionService {
|
extends BaseServiceImpl<WorkflowDefinition, WorkflowDefinitionDTO, Long>
|
||||||
|
implements IWorkflowDefinitionService {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private IWorkflowDefinitionRepository workflowDefinitionRepository;
|
private IWorkflowDefinitionRepository workflowDefinitionRepository;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private WorkflowDefinitionConverter workflowDefinitionConverter;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public boolean publish(Long id) {
|
public void publish(Long id) {
|
||||||
WorkflowDefinition definition = findEntityById(id);
|
WorkflowDefinition definition = findByIdWithLock(id);
|
||||||
definition.setStatus(WorkflowStatusEnum.PUBLISHED.getCode());
|
if (definition.getStatus() != WorkflowStatusEnum.DRAFT) {
|
||||||
workflowDefinitionRepository.save(definition);
|
throw new BusinessException(ResponseCode.WORKFLOW_NOT_DRAFT);
|
||||||
return true;
|
}
|
||||||
}
|
definition.setStatus(WorkflowStatusEnum.PUBLISHED);
|
||||||
|
repository.save(definition);
|
||||||
@Override
|
}
|
||||||
@Transactional
|
|
||||||
public boolean disable(Long id) {
|
@Override
|
||||||
WorkflowDefinition definition = findEntityById(id);
|
@Transactional
|
||||||
definition.setStatus(WorkflowStatusEnum.DISABLED.getCode());
|
public void disable(Long id) {
|
||||||
workflowDefinitionRepository.save(definition);
|
WorkflowDefinition definition = findByIdWithLock(id);
|
||||||
return true;
|
if (definition.getStatus() != WorkflowStatusEnum.PUBLISHED) {
|
||||||
}
|
throw new BusinessException(ResponseCode.WORKFLOW_NOT_PUBLISHED);
|
||||||
|
}
|
||||||
@Override
|
definition.setStatus(WorkflowStatusEnum.DISABLED);
|
||||||
public WorkflowDefinitionDTO findByCode(String code) {
|
repository.save(definition);
|
||||||
WorkflowDefinition definition = workflowDefinitionRepository.findByCodeAndDeletedFalse(code);
|
}
|
||||||
return workflowDefinitionConverter.toDto(definition);
|
|
||||||
}
|
@Override
|
||||||
|
@Transactional
|
||||||
@Transactional
|
public void enable(Long id) {
|
||||||
public WorkflowDefinitionDTO save(WorkflowDefinitionDTO dto) {
|
WorkflowDefinition definition = findByIdWithLock(id);
|
||||||
// 检查编码是否已存在
|
if (definition.getStatus() != WorkflowStatusEnum.DISABLED) {
|
||||||
if (workflowDefinitionRepository.existsByCodeAndDeletedFalse(dto.getCode())) {
|
throw new BusinessException(ResponseCode.WORKFLOW_NOT_DISABLED);
|
||||||
throw new IllegalArgumentException("工作流编码已存在");
|
}
|
||||||
|
definition.setStatus(WorkflowStatusEnum.PUBLISHED);
|
||||||
|
repository.save(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void checkNameUnique(String name) {
|
||||||
|
if (workflowDefinitionRepository.existsByNameAndDeletedFalse(name)) {
|
||||||
|
throw new BusinessException(ResponseCode.WORKFLOW_NAME_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void checkCodeUnique(String code) {
|
||||||
|
if (workflowDefinitionRepository.existsByCodeAndDeletedFalse(code)) {
|
||||||
|
throw new BusinessException(ResponseCode.WORKFLOW_CODE_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void validateStatus(WorkflowStatusEnum status) {
|
||||||
|
if (status == null) {
|
||||||
|
throw new BusinessException(ResponseCode.WORKFLOW_INVALID_STATUS);
|
||||||
}
|
}
|
||||||
// 设置初始状态为草稿
|
|
||||||
dto.setStatus(WorkflowStatusEnum.DRAFT.getCode());
|
|
||||||
return super.create(dto);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,66 +1,16 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.service.impl;
|
package com.qqchen.deploy.backend.workflow.service.impl;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||||
import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
|
||||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
|
|
||||||
import com.qqchen.deploy.backend.workflow.service.INodeInstanceService;
|
|
||||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
|
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
|
||||||
import jakarta.annotation.Resource;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流实例服务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstance, WorkflowInstanceDTO, Long> implements IWorkflowInstanceService {
|
public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstance, WorkflowInstanceDTO, Long> implements IWorkflowInstanceService {
|
||||||
|
}
|
||||||
@Resource
|
|
||||||
private IWorkflowInstanceRepository workflowInstanceRepository;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private INodeInstanceService nodeInstanceService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void updateStatus(Long id, WorkflowStatusEnum status, String error) {
|
|
||||||
WorkflowInstance instance = super.converter.toEntity(super.findById(id));
|
|
||||||
instance.setStatus(status);
|
|
||||||
instance.setError(error);
|
|
||||||
|
|
||||||
if (status == WorkflowStatusEnum.RUNNING) {
|
|
||||||
instance.setStartTime(LocalDateTime.now());
|
|
||||||
} else if (status == WorkflowStatusEnum.COMPLETED || status == WorkflowStatusEnum.FAILED ||
|
|
||||||
status == WorkflowStatusEnum.CANCELLED) {
|
|
||||||
instance.setEndTime(LocalDateTime.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status == WorkflowStatusEnum.CANCELLED) {
|
|
||||||
nodeInstanceService.cancelRunningNodes(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
workflowInstanceRepository.save(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WorkflowInstanceDTO start(Long definitionId, Long projectEnvId, String variables) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean cancel(Long id) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<WorkflowInstanceDTO> findByProjectEnvId(Long projectEnvId) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<WorkflowInstanceDTO> findByProjectEnvIdAndStatus(Long projectEnvId, String status) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,181 +1,59 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.service.impl;
|
package com.qqchen.deploy.backend.workflow.service.impl;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||||
import com.qqchen.deploy.backend.enums.LogTypeEnum;
|
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
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.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;
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流日志服务实现类
|
* 工作流日志服务实现类
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class WorkflowLogServiceImpl implements IWorkflowLogService {
|
public class WorkflowLogServiceImpl extends BaseServiceImpl<WorkflowLog, WorkflowLogDTO, Long> implements IWorkflowLogService {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private IWorkflowLogRepository workflowLogRepository;
|
private IWorkflowLogRepository logRepository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<WorkflowLog> findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level) {
|
@Transactional
|
||||||
return workflowLogRepository.findNodeLogs(nodeInstanceId, type, level);
|
public void log(Long workflowInstanceId, String nodeId, String message, LogLevelEnum level, String detail) {
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Page<WorkflowLog> findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable) {
|
|
||||||
return workflowLogRepository.findNodeLogs(nodeInstanceId, type, level, pageable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Page<WorkflowLog> findLogs(Long workflowInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable) {
|
|
||||||
return workflowLogRepository.findByWorkflowInstanceIdAndTypeAndLevel(workflowInstanceId, type, level, pageable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logWorkflowStart(WorkflowInstance instance) {
|
|
||||||
WorkflowLog log = new WorkflowLog();
|
WorkflowLog log = new WorkflowLog();
|
||||||
log.setWorkflowInstanceId(instance.getId());
|
log.setWorkflowInstanceId(workflowInstanceId);
|
||||||
log.setType(LogTypeEnum.WORKFLOW_START);
|
log.setNodeId(nodeId);
|
||||||
log.setLevel(LogLevelEnum.INFO);
|
log.setContent(message);
|
||||||
log.setContent("工作流开始执行");
|
|
||||||
workflowLogRepository.save(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logWorkflowComplete(WorkflowInstance instance) {
|
|
||||||
WorkflowLog log = new WorkflowLog();
|
|
||||||
log.setWorkflowInstanceId(instance.getId());
|
|
||||||
log.setType(LogTypeEnum.WORKFLOW_COMPLETE);
|
|
||||||
log.setLevel(LogLevelEnum.INFO);
|
|
||||||
log.setContent("工作流执行完成");
|
|
||||||
workflowLogRepository.save(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logWorkflowPause(WorkflowInstance instance) {
|
|
||||||
WorkflowLog log = new WorkflowLog();
|
|
||||||
log.setWorkflowInstanceId(instance.getId());
|
|
||||||
log.setType(LogTypeEnum.WORKFLOW_PAUSE);
|
|
||||||
log.setLevel(LogLevelEnum.INFO);
|
|
||||||
log.setContent("工作流已暂停");
|
|
||||||
workflowLogRepository.save(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logWorkflowResume(WorkflowInstance instance) {
|
|
||||||
WorkflowLog log = new WorkflowLog();
|
|
||||||
log.setWorkflowInstanceId(instance.getId());
|
|
||||||
log.setType(LogTypeEnum.WORKFLOW_RESUME);
|
|
||||||
log.setLevel(LogLevelEnum.INFO);
|
|
||||||
log.setContent("工作流已恢复");
|
|
||||||
workflowLogRepository.save(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logWorkflowCancel(WorkflowInstance instance) {
|
|
||||||
WorkflowLog log = new WorkflowLog();
|
|
||||||
log.setWorkflowInstanceId(instance.getId());
|
|
||||||
log.setType(LogTypeEnum.WORKFLOW_CANCEL);
|
|
||||||
log.setLevel(LogLevelEnum.INFO);
|
|
||||||
log.setContent("工作流已取消");
|
|
||||||
workflowLogRepository.save(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logWorkflowError(WorkflowInstance instance, String error) {
|
|
||||||
WorkflowLog log = new WorkflowLog();
|
|
||||||
log.setWorkflowInstanceId(instance.getId());
|
|
||||||
log.setType(LogTypeEnum.WORKFLOW_ERROR);
|
|
||||||
log.setLevel(LogLevelEnum.ERROR);
|
|
||||||
log.setContent("工作流执行出错");
|
|
||||||
log.setDetail(error);
|
|
||||||
workflowLogRepository.save(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logNodeStart(NodeInstance node) {
|
|
||||||
WorkflowLog log = new WorkflowLog();
|
|
||||||
log.setWorkflowInstanceId(node.getWorkflowInstance().getId());
|
|
||||||
log.setNodeInstanceId(node.getId());
|
|
||||||
log.setType(LogTypeEnum.NODE_START);
|
|
||||||
log.setLevel(LogLevelEnum.INFO);
|
|
||||||
log.setContent(String.format("节点[%s]开始执行", node.getName()));
|
|
||||||
workflowLogRepository.save(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logNodeComplete(NodeInstance node) {
|
|
||||||
WorkflowLog log = new WorkflowLog();
|
|
||||||
log.setWorkflowInstanceId(node.getWorkflowInstance().getId());
|
|
||||||
log.setNodeInstanceId(node.getId());
|
|
||||||
log.setType(LogTypeEnum.NODE_COMPLETE);
|
|
||||||
log.setLevel(LogLevelEnum.INFO);
|
|
||||||
log.setContent(String.format("节点[%s]执行完成", node.getName()));
|
|
||||||
workflowLogRepository.save(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logNodeError(NodeInstance node, String error) {
|
|
||||||
WorkflowLog log = new WorkflowLog();
|
|
||||||
log.setWorkflowInstanceId(node.getWorkflowInstance().getId());
|
|
||||||
log.setNodeInstanceId(node.getId());
|
|
||||||
log.setType(LogTypeEnum.NODE_ERROR);
|
|
||||||
log.setLevel(LogLevelEnum.ERROR);
|
|
||||||
log.setContent(String.format("节点[%s]执行出错", node.getName()));
|
|
||||||
log.setDetail(error);
|
|
||||||
workflowLogRepository.save(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logNodeRetry(NodeInstance node) {
|
|
||||||
WorkflowLog log = new WorkflowLog();
|
|
||||||
log.setWorkflowInstanceId(node.getWorkflowInstance().getId());
|
|
||||||
log.setNodeInstanceId(node.getId());
|
|
||||||
log.setType(LogTypeEnum.NODE_RETRY);
|
|
||||||
log.setLevel(LogLevelEnum.WARN);
|
|
||||||
log.setContent(String.format("节点[%s]重试执行", node.getName()));
|
|
||||||
workflowLogRepository.save(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logNodeSkip(NodeInstance node) {
|
|
||||||
WorkflowLog log = new WorkflowLog();
|
|
||||||
log.setWorkflowInstanceId(node.getWorkflowInstance().getId());
|
|
||||||
log.setNodeInstanceId(node.getId());
|
|
||||||
log.setType(LogTypeEnum.NODE_SKIP);
|
|
||||||
log.setLevel(LogLevelEnum.WARN);
|
|
||||||
log.setContent(String.format("节点[%s]已跳过", node.getName()));
|
|
||||||
workflowLogRepository.save(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logVariableUpdate(WorkflowInstance instance, String variableName, Object value) {
|
|
||||||
WorkflowLog log = new WorkflowLog();
|
|
||||||
log.setWorkflowInstanceId(instance.getId());
|
|
||||||
log.setType(LogTypeEnum.VARIABLE_UPDATE);
|
|
||||||
log.setLevel(LogLevelEnum.INFO);
|
|
||||||
log.setContent(String.format("变量[%s]更新为[%s]", variableName, value));
|
|
||||||
workflowLogRepository.save(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logSystem(WorkflowInstance instance, LogLevelEnum level, String content, String detail) {
|
|
||||||
WorkflowLog log = new WorkflowLog();
|
|
||||||
log.setWorkflowInstanceId(instance.getId());
|
|
||||||
log.setType(LogTypeEnum.SYSTEM);
|
|
||||||
log.setLevel(level);
|
log.setLevel(level);
|
||||||
log.setContent(content);
|
|
||||||
log.setDetail(detail);
|
log.setDetail(detail);
|
||||||
workflowLogRepository.save(log);
|
repository.save(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<WorkflowLogDTO> getLogs(Long workflowInstanceId) {
|
||||||
|
List<WorkflowLog> logs = logRepository.findByWorkflowInstanceId(workflowInstanceId);
|
||||||
|
return logs.stream().map(converter::toDto).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<WorkflowLogDTO> getNodeLogs(Long workflowInstanceId, String nodeId) {
|
||||||
|
List<WorkflowLog> logs = logRepository.findByWorkflowInstanceIdAndNodeId(workflowInstanceId, nodeId);
|
||||||
|
return logs.stream().map(converter::toDto).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void deleteLogs(Long workflowInstanceId) {
|
||||||
|
logRepository.deleteByWorkflowInstanceId(workflowInstanceId);
|
||||||
|
log.debug("删除工作流日志成功: instanceId={}", workflowInstanceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,127 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.service.impl;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.enums.PermissionTypeEnum;
|
|
||||||
import com.qqchen.deploy.backend.system.entity.User;
|
|
||||||
import com.qqchen.deploy.backend.system.repository.IUserRepository;
|
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowPermission;
|
|
||||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowPermissionRepository;
|
|
||||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowPermissionService;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作流权限服务实现
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Service
|
|
||||||
public class WorkflowPermissionServiceImpl implements IWorkflowPermissionService {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private IWorkflowPermissionRepository workflowPermissionRepository;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private IUserRepository userRepository;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void addUserPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type) {
|
|
||||||
Optional<WorkflowPermission> existingPermission = workflowPermissionRepository
|
|
||||||
.findByWorkflowDefinitionIdAndUserIdAndType(workflowDefinitionId, userId, type);
|
|
||||||
if (existingPermission.isEmpty()) {
|
|
||||||
WorkflowPermission permission = new WorkflowPermission();
|
|
||||||
permission.setWorkflowDefinitionId(workflowDefinitionId);
|
|
||||||
permission.setUserId(userId);
|
|
||||||
permission.setType(type);
|
|
||||||
workflowPermissionRepository.save(permission);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void addRolePermission(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type) {
|
|
||||||
Optional<WorkflowPermission> existingPermission = workflowPermissionRepository
|
|
||||||
.findByWorkflowDefinitionIdAndRoleIdAndType(workflowDefinitionId, roleId, type);
|
|
||||||
if (existingPermission.isEmpty()) {
|
|
||||||
WorkflowPermission permission = new WorkflowPermission();
|
|
||||||
permission.setWorkflowDefinitionId(workflowDefinitionId);
|
|
||||||
permission.setRoleId(roleId);
|
|
||||||
permission.setType(type);
|
|
||||||
workflowPermissionRepository.save(permission);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void addDepartmentPermission(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type) {
|
|
||||||
Optional<WorkflowPermission> existingPermission = workflowPermissionRepository
|
|
||||||
.findByWorkflowDefinitionIdAndDepartmentIdAndType(workflowDefinitionId, departmentId, type);
|
|
||||||
if (existingPermission.isEmpty()) {
|
|
||||||
WorkflowPermission permission = new WorkflowPermission();
|
|
||||||
permission.setWorkflowDefinitionId(workflowDefinitionId);
|
|
||||||
permission.setDepartmentId(departmentId);
|
|
||||||
permission.setType(type);
|
|
||||||
workflowPermissionRepository.save(permission);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void removeUserPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type) {
|
|
||||||
workflowPermissionRepository.deleteByWorkflowDefinitionIdAndUserIdAndType(workflowDefinitionId, userId, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void removeRolePermission(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type) {
|
|
||||||
workflowPermissionRepository.deleteByWorkflowDefinitionIdAndRoleIdAndType(workflowDefinitionId, roleId, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void removeDepartmentPermission(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type) {
|
|
||||||
workflowPermissionRepository.deleteByWorkflowDefinitionIdAndDepartmentIdAndType(workflowDefinitionId, departmentId, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type) {
|
|
||||||
// 检查用户直接权限
|
|
||||||
if (workflowPermissionRepository.existsByWorkflowDefinitionIdAndUserIdAndType(workflowDefinitionId, userId, type)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取用户信息
|
|
||||||
Optional<User> userOpt = userRepository.findById(userId);
|
|
||||||
if (userOpt.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
User user = userOpt.get();
|
|
||||||
|
|
||||||
// // 检查角色权限
|
|
||||||
// if (user.getRoleId() != null && workflowPermissionRepository
|
|
||||||
// .existsByWorkflowDefinitionIdAndRoleIdAndType(workflowDefinitionId, user.getRoleId(), type)) {
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 检查部门权限
|
|
||||||
// return user.getDepartmentId() != null && workflowPermissionRepository
|
|
||||||
// .existsByWorkflowDefinitionIdAndDepartmentIdAndType(workflowDefinitionId, user.getDepartmentId(), type);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<WorkflowPermission> getPermissions(Long workflowDefinitionId) {
|
|
||||||
return workflowPermissionRepository.findByWorkflowDefinitionId(workflowDefinitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Page<WorkflowPermission> findPermissions(Long workflowDefinitionId, PermissionTypeEnum type, Pageable pageable) {
|
|
||||||
return workflowPermissionRepository.findByWorkflowDefinitionIdAndType(workflowDefinitionId, type, pageable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,107 +1,106 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.service.impl;
|
package com.qqchen.deploy.backend.workflow.service.impl;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
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.framework.exception.BusinessException;
|
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||||
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowVariableDTO;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
|
||||||
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.repository.IWorkflowVariableRepository;
|
import com.qqchen.deploy.backend.workflow.repository.IWorkflowVariableRepository;
|
||||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService;
|
import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流变量服务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class WorkflowVariableServiceImpl implements IWorkflowVariableService {
|
public class WorkflowVariableServiceImpl extends BaseServiceImpl<WorkflowVariable, WorkflowVariableDTO, Long> implements IWorkflowVariableService {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private IWorkflowVariableRepository workflowVariableRepository;
|
private IWorkflowVariableRepository variableRepository;
|
||||||
|
|
||||||
@Override
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
@Transactional
|
|
||||||
public void saveVariables(Long workflowInstanceId, Map<String, Object> variables) {
|
|
||||||
if (variables == null || variables.isEmpty()) {
|
|
||||||
throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除旧的变量
|
|
||||||
workflowVariableRepository.deleteByWorkflowInstanceId(workflowInstanceId);
|
|
||||||
|
|
||||||
// 保存新的变量
|
|
||||||
variables.forEach((name, value) -> {
|
|
||||||
WorkflowVariable variable = new WorkflowVariable();
|
|
||||||
variable.setWorkflowInstanceId(workflowInstanceId);
|
|
||||||
variable.setName(name);
|
|
||||||
variable.setValue(value.toString());
|
|
||||||
variable.setType(value.getClass().getSimpleName());
|
|
||||||
workflowVariableRepository.save(variable);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getVariables(Long workflowInstanceId) {
|
|
||||||
return workflowVariableRepository.findByWorkflowInstanceId(workflowInstanceId)
|
|
||||||
.stream()
|
|
||||||
.collect(Collectors.toMap(
|
|
||||||
WorkflowVariable::getName,
|
|
||||||
this::convertValue
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getVariable(Long workflowInstanceId, String name) {
|
|
||||||
Optional<WorkflowVariable> variable = workflowVariableRepository.findByWorkflowInstanceIdAndName(workflowInstanceId, name);
|
|
||||||
return variable.map(this::convertValue).orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void setVariable(Long workflowInstanceId, String name, Object value) {
|
public void setVariable(Long workflowInstanceId, String name, Object value) {
|
||||||
WorkflowVariable variable = workflowVariableRepository.findByWorkflowInstanceIdAndName(workflowInstanceId, name)
|
|
||||||
.orElseGet(() -> {
|
|
||||||
WorkflowVariable newVar = new WorkflowVariable();
|
|
||||||
newVar.setWorkflowInstanceId(workflowInstanceId);
|
|
||||||
newVar.setName(name);
|
|
||||||
return newVar;
|
|
||||||
});
|
|
||||||
|
|
||||||
variable.setValue(value.toString());
|
|
||||||
variable.setType(value.getClass().getSimpleName());
|
|
||||||
workflowVariableRepository.save(variable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void deleteVariable(Long workflowInstanceId, String name) {
|
|
||||||
workflowVariableRepository.deleteByWorkflowInstanceIdAndName(workflowInstanceId, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void clearVariables(Long workflowInstanceId) {
|
|
||||||
workflowVariableRepository.deleteByWorkflowInstanceId(workflowInstanceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object convertValue(WorkflowVariable variable) {
|
|
||||||
try {
|
try {
|
||||||
switch (variable.getType()) {
|
WorkflowVariable variable = variableRepository.findByWorkflowInstanceIdAndName(workflowInstanceId, name)
|
||||||
case "String":
|
.orElse(new WorkflowVariable());
|
||||||
return variable.getValue();
|
|
||||||
case "Integer":
|
variable.setWorkflowInstanceId(workflowInstanceId);
|
||||||
return Integer.parseInt(variable.getValue());
|
variable.setName(name);
|
||||||
case "Long":
|
variable.setValue(objectMapper.writeValueAsString(value));
|
||||||
return Long.parseLong(variable.getValue());
|
variable.setType(value.getClass().getName());
|
||||||
case "Double":
|
|
||||||
return Double.parseDouble(variable.getValue());
|
repository.save(variable);
|
||||||
case "Boolean":
|
|
||||||
return Boolean.parseBoolean(variable.getValue());
|
|
||||||
default:
|
|
||||||
throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID);
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Map<String, Object> getVariables(Long workflowInstanceId) {
|
||||||
|
try {
|
||||||
|
List<WorkflowVariable> variables = variableRepository.findByWorkflowInstanceId(workflowInstanceId);
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
for (WorkflowVariable variable : variables) {
|
||||||
|
Object value = deserializeValue(variable);
|
||||||
|
result.put(variable.getName(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_EXECUTION_ERROR, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Map<String, Object> getVariablesByScope(Long workflowInstanceId, VariableScopeEnum scope) {
|
||||||
|
try {
|
||||||
|
List<WorkflowVariable> variables = variableRepository.findByWorkflowInstanceIdAndScope(
|
||||||
|
workflowInstanceId, scope.name());
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
for (WorkflowVariable variable : variables) {
|
||||||
|
Object value = deserializeValue(variable);
|
||||||
|
result.put(variable.getName(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_EXECUTION_ERROR, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void deleteVariables(Long workflowInstanceId) {
|
||||||
|
variableRepository.deleteByWorkflowInstanceId(workflowInstanceId);
|
||||||
|
log.debug("删除工作流变量成功: instanceId={}", workflowInstanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object deserializeValue(WorkflowVariable variable) {
|
||||||
|
try {
|
||||||
|
Class<?> type = Class.forName(variable.getType());
|
||||||
|
return objectMapper.readValue(variable.getValue(), type);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID, e);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,7 +145,7 @@ CREATE TABLE sys_role_tag (
|
|||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
|
|
||||||
name VARCHAR(50) NOT NULL COMMENT '标签名称',
|
name VARCHAR(50) NOT NULL COMMENT '标签名称',
|
||||||
color VARCHAR(20) NULL COMMENT '标签<EFBFBD><EFBFBD><EFBFBD>六进制颜色码)'
|
color VARCHAR(20) NULL COMMENT '标签进制颜色码)'
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色标签表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色标签表';
|
||||||
|
|
||||||
-- 角色标签关联表
|
-- 角色标签关联表
|
||||||
@ -378,111 +378,123 @@ CREATE TABLE deploy_repo_branch (
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代码仓库分支表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代码仓库分支表';
|
||||||
|
|
||||||
-- 工作流定义表
|
-- 工作流定义表
|
||||||
CREATE TABLE sys_workflow_definition (
|
CREATE TABLE wf_definition (
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
name VARCHAR(100) NOT NULL COMMENT '工作流名称',
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
code VARCHAR(50) NOT NULL COMMENT '工作流编码',
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
description TEXT COMMENT '描述',
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
status VARCHAR(20) NOT NULL COMMENT '状态',
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
node_config TEXT COMMENT '节点配置(JSON)',
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
transition_config TEXT COMMENT '流转配置(JSON)',
|
||||||
tenant_id BIGINT NOT NULL COMMENT '租户ID',
|
create_time DATETIME NOT NULL COMMENT '创建时间',
|
||||||
|
create_by VARCHAR(50) NOT NULL COMMENT '创建人',
|
||||||
code VARCHAR(50) NOT NULL COMMENT '工作流编码',
|
update_time DATETIME NOT NULL COMMENT '更新时间',
|
||||||
name VARCHAR(100) NOT NULL COMMENT '工作流名称',
|
update_by VARCHAR(50) NOT NULL COMMENT '更新人',
|
||||||
description TEXT COMMENT '描述',
|
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
|
||||||
content TEXT NOT NULL COMMENT '工作流定义(JSON)',
|
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||||
type VARCHAR(20) NOT NULL COMMENT '类型:DEPLOY/CONFIG_SYNC',
|
UNIQUE KEY uk_code (code)
|
||||||
status VARCHAR(20) NOT NULL DEFAULT 'DRAFT' COMMENT '状态:DRAFT/PUBLISHED/DISABLED',
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流定义表';
|
||||||
|
|
||||||
CONSTRAINT uk_workflow_code UNIQUE (tenant_id, code),
|
|
||||||
CONSTRAINT uk_workflow_name UNIQUE (tenant_id, name)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流定义表';
|
|
||||||
|
|
||||||
-- 工作流实例表
|
-- 节点定义表
|
||||||
CREATE TABLE sys_workflow_instance (
|
CREATE TABLE wf_node_definition (
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
node_id VARCHAR(50) NOT NULL COMMENT '节点ID',
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
name VARCHAR(100) NOT NULL COMMENT '节点名称',
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
type VARCHAR(50) NOT NULL COMMENT '节点类型',
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
config TEXT COMMENT '节点配置(JSON)',
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
sequence INT NOT NULL DEFAULT 0 COMMENT '序号',
|
||||||
tenant_id BIGINT NOT NULL COMMENT '租户ID',
|
create_time DATETIME NOT NULL COMMENT '创建时间',
|
||||||
|
create_by VARCHAR(50) NOT NULL COMMENT '创建人',
|
||||||
|
update_time DATETIME NOT NULL COMMENT '更新时间',
|
||||||
|
update_by VARCHAR(50) NOT NULL COMMENT '更新人',
|
||||||
|
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
|
||||||
|
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||||
|
UNIQUE KEY uk_workflow_node (workflow_definition_id, node_id),
|
||||||
|
CONSTRAINT fk_node_workflow FOREIGN KEY (workflow_definition_id) REFERENCES wf_definition(id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='节点定义表';
|
||||||
|
|
||||||
|
-- 工作流实例表(修改现有表)
|
||||||
|
DROP TABLE IF EXISTS sys_workflow_instance;
|
||||||
|
CREATE TABLE wf_instance (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||||
definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
|
definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
|
||||||
project_env_id BIGINT NOT NULL COMMENT '项目环境ID',
|
business_key VARCHAR(100) NOT NULL COMMENT '业务标识',
|
||||||
status VARCHAR(20) NOT NULL COMMENT '状态:RUNNING/COMPLETED/FAILED/CANCELED',
|
status VARCHAR(20) NOT NULL COMMENT '状态',
|
||||||
start_time DATETIME NOT NULL COMMENT '开始时间',
|
variables TEXT COMMENT '工作流变量(JSON)',
|
||||||
end_time DATETIME COMMENT '结束时间',
|
start_time DATETIME NOT NULL COMMENT '开始时间',
|
||||||
variables TEXT COMMENT '工作流变量(JSON)',
|
end_time DATETIME COMMENT '结束时间',
|
||||||
error TEXT COMMENT '错误信息',
|
error TEXT COMMENT '错误信息',
|
||||||
|
create_time DATETIME NOT NULL COMMENT '创建时间',
|
||||||
CONSTRAINT fk_instance_definition FOREIGN KEY (definition_id) REFERENCES sys_workflow_definition(id)
|
create_by VARCHAR(50) NOT NULL COMMENT '创建人',
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流实例表';
|
update_time DATETIME NOT NULL COMMENT '更新时间',
|
||||||
|
update_by VARCHAR(50) NOT NULL COMMENT '更新人',
|
||||||
|
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
|
||||||
|
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||||
|
CONSTRAINT fk_instance_definition FOREIGN KEY (definition_id) REFERENCES wf_definition(id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流实例表';
|
||||||
|
|
||||||
-- 节点实例表
|
-- 节点实例表(修改现有表)
|
||||||
CREATE TABLE sys_node_instance (
|
DROP TABLE IF EXISTS sys_node_instance;
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
CREATE TABLE wf_node_instance (
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
tenant_id BIGINT NOT NULL COMMENT '租户ID',
|
|
||||||
|
|
||||||
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
|
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
|
||||||
node_id VARCHAR(50) NOT NULL COMMENT '节点ID',
|
node_id VARCHAR(50) NOT NULL COMMENT '节点ID',
|
||||||
node_type VARCHAR(50) NOT NULL COMMENT '节点类型',
|
node_type VARCHAR(50) NOT NULL COMMENT '节点类型',
|
||||||
name VARCHAR(100) NOT NULL COMMENT '节点名称',
|
name VARCHAR(100) NOT NULL COMMENT '节点名称',
|
||||||
status VARCHAR(20) NOT NULL COMMENT '状态:PENDING/RUNNING/COMPLETED/FAILED/CANCELED',
|
status VARCHAR(20) NOT NULL COMMENT '状<EFBFBD><EFBFBD><EFBFBD>',
|
||||||
start_time DATETIME COMMENT '开始时间',
|
config TEXT COMMENT '节点配置(JSON)',
|
||||||
end_time DATETIME COMMENT '结束时间',
|
input TEXT COMMENT '输入参数(JSON)',
|
||||||
input TEXT COMMENT '输入参数(JSON)',
|
output TEXT COMMENT '输出结果(JSON)',
|
||||||
output TEXT COMMENT '输出结果(JSON)',
|
error TEXT COMMENT '错误信息',
|
||||||
error TEXT COMMENT '错误信息',
|
start_time DATETIME COMMENT '开始时间',
|
||||||
|
end_time DATETIME COMMENT '结束时间',
|
||||||
CONSTRAINT fk_node_workflow_instance FOREIGN KEY (workflow_instance_id) REFERENCES sys_workflow_instance(id)
|
create_time DATETIME NOT NULL COMMENT '创建时间',
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='节点实例表';
|
create_by VARCHAR(50) NOT NULL COMMENT '创建人',
|
||||||
|
update_time DATETIME NOT NULL COMMENT '更新时间',
|
||||||
|
update_by VARCHAR(50) NOT NULL COMMENT '更新人',
|
||||||
|
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
|
||||||
|
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||||
|
CONSTRAINT fk_node_instance_workflow FOREIGN KEY (workflow_instance_id) REFERENCES wf_instance(id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='节点实例表';
|
||||||
|
|
||||||
-- 工作流变量表
|
-- 工作流变量表
|
||||||
CREATE TABLE wf_workflow_variable (
|
CREATE TABLE wf_variable (
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
|
workflow_instance_id BIGINT NOT NULL,
|
||||||
name VARCHAR(255) NOT NULL COMMENT '变量名',
|
name VARCHAR(255) NOT NULL,
|
||||||
value TEXT COMMENT '变量值',
|
value TEXT NOT NULL,
|
||||||
type VARCHAR(255) NOT NULL COMMENT '变量类型',
|
type VARCHAR(255),
|
||||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
scope VARCHAR(50),
|
||||||
create_by BIGINT NOT NULL COMMENT '创建人',
|
create_time DATETIME NOT NULL,
|
||||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
create_by VARCHAR(50),
|
||||||
update_by BIGINT NOT NULL COMMENT '更新人',
|
update_time DATETIME,
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
|
update_by VARCHAR(50),
|
||||||
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
version INT DEFAULT 0,
|
||||||
INDEX idx_workflow_instance_id (workflow_instance_id),
|
deleted BOOLEAN DEFAULT FALSE,
|
||||||
INDEX idx_name (name)
|
CONSTRAINT uk_wf_variable UNIQUE (workflow_instance_id, name, deleted)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流变量';
|
);
|
||||||
|
|
||||||
-- 工作流日志表
|
-- 工作流日志表
|
||||||
CREATE TABLE wf_workflow_log (
|
CREATE TABLE wf_log (
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
|
workflow_instance_id BIGINT NOT NULL,
|
||||||
node_instance_id BIGINT COMMENT '节点实例ID',
|
node_id VARCHAR(50),
|
||||||
type VARCHAR(50) NOT NULL COMMENT '日志类型',
|
message VARCHAR(1000) NOT NULL,
|
||||||
level VARCHAR(20) NOT NULL COMMENT '日志级别',
|
level VARCHAR(20) NOT NULL,
|
||||||
content TEXT NOT NULL COMMENT '日志内容',
|
detail TEXT,
|
||||||
detail TEXT COMMENT '详细信息',
|
create_time DATETIME NOT NULL,
|
||||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
create_by VARCHAR(50),
|
||||||
create_by BIGINT NOT NULL COMMENT '创建人',
|
update_time DATETIME,
|
||||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
update_by VARCHAR(50),
|
||||||
update_by BIGINT NOT NULL COMMENT '更新人',
|
version INT DEFAULT 0,
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
|
deleted BOOLEAN DEFAULT FALSE
|
||||||
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
);
|
||||||
INDEX idx_workflow_instance_id (workflow_instance_id),
|
|
||||||
INDEX idx_node_instance_id (node_instance_id),
|
-- 创建索引
|
||||||
INDEX idx_type (type),
|
CREATE INDEX idx_wf_variable_instance ON wf_variable (workflow_instance_id);
|
||||||
INDEX idx_level (level)
|
CREATE INDEX idx_wf_log_instance ON wf_log (workflow_instance_id);
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流日志';
|
CREATE INDEX idx_wf_log_node ON wf_log (workflow_instance_id, node_id);
|
||||||
|
|
||||||
-- 工作流权限表
|
-- 工作流权限表
|
||||||
CREATE TABLE wf_workflow_permission (
|
CREATE TABLE wf_workflow_permission (
|
||||||
|
|||||||
@ -150,121 +150,4 @@ INSERT INTO sys_external_system (
|
|||||||
'链宇GIT', 'GIT', 'http://119.3.203.210:8088/', NULL, 1, 1,
|
'链宇GIT', 'GIT', 'http://119.3.203.210:8088/', NULL, 1, 1,
|
||||||
'TOKEN', NULL, NULL, 'cNSud7D1GmYQKEMco7s5',
|
'TOKEN', NULL, NULL, 'cNSud7D1GmYQKEMco7s5',
|
||||||
NULL, NULL, NULL, '{}'
|
NULL, NULL, NULL, '{}'
|
||||||
);
|
|
||||||
|
|
||||||
-- 初始化工作流定义数据
|
|
||||||
INSERT INTO sys_workflow_definition (
|
|
||||||
create_by, create_time, deleted, update_by, update_time, version, tenant_id,
|
|
||||||
code, name, description, content, type, status
|
|
||||||
) VALUES (
|
|
||||||
'admin', NOW(), 0, 'admin', NOW(), 0, 1,
|
|
||||||
'DEPLOY_JAVA_APP', 'Java应用部署流程', 'Java应用标准部署流程',
|
|
||||||
'{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"nodeId": "start",
|
|
||||||
"type": "START",
|
|
||||||
"name": "开始",
|
|
||||||
"nextNodeId": "build"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nodeId": "build",
|
|
||||||
"type": "JENKINS",
|
|
||||||
"name": "构建应用",
|
|
||||||
"jenkinsServerId": 1,
|
|
||||||
"jobName": "$${projectName}-build",
|
|
||||||
"parameters": {
|
|
||||||
"BRANCH": "$${branch}",
|
|
||||||
"ENV": "$${env}"
|
|
||||||
},
|
|
||||||
"nextNodeId": "approval"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nodeId": "approval",
|
|
||||||
"type": "APPROVAL",
|
|
||||||
"name": "部署审批",
|
|
||||||
"approverType": "ROLE",
|
|
||||||
"approverIds": [1],
|
|
||||||
"strategy": "ANY",
|
|
||||||
"nextNodeId": "deploy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nodeId": "deploy",
|
|
||||||
"type": "SCRIPT",
|
|
||||||
"name": "部署应用",
|
|
||||||
"scriptType": "SHELL",
|
|
||||||
"content": "kubectl apply -f $${deployYaml}",
|
|
||||||
"nextNodeId": "end"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nodeId": "end",
|
|
||||||
"type": "END",
|
|
||||||
"name": "结束"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}',
|
|
||||||
'DEPLOY',
|
|
||||||
'PUBLISHED'
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT INTO sys_workflow_definition (
|
|
||||||
create_by, create_time, deleted, update_by, update_time, version, tenant_id,
|
|
||||||
code, name, description, content, type, status
|
|
||||||
) VALUES (
|
|
||||||
'admin', NOW(), 0, 'admin', NOW(), 0, 1,
|
|
||||||
'SYNC_NACOS_CONFIG', 'Nacos配置同步流程', 'Nacos配置跨环境同步流程',
|
|
||||||
'{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"nodeId": "start",
|
|
||||||
"type": "START",
|
|
||||||
"name": "开始",
|
|
||||||
"nextNodeId": "export"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nodeId": "export",
|
|
||||||
"type": "NACOS",
|
|
||||||
"name": "导出配置",
|
|
||||||
"nacosServerId": 1,
|
|
||||||
"operation": "EXPORT",
|
|
||||||
"namespace": "$${sourceNamespace}",
|
|
||||||
"nextNodeId": "approval"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nodeId": "approval",
|
|
||||||
"type": "APPROVAL",
|
|
||||||
"name": "同步审批",
|
|
||||||
"approverType": "ROLE",
|
|
||||||
"approverIds": [1],
|
|
||||||
"strategy": "ANY",
|
|
||||||
"nextNodeId": "import"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nodeId": "import",
|
|
||||||
"type": "NACOS",
|
|
||||||
"name": "导入配置",
|
|
||||||
"nacosServerId": 2,
|
|
||||||
"operation": "IMPORT",
|
|
||||||
"namespace": "$${targetNamespace}",
|
|
||||||
"nextNodeId": "notify"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nodeId": "notify",
|
|
||||||
"type": "NOTIFY",
|
|
||||||
"name": "通知结果",
|
|
||||||
"notifyType": "DINGTALK",
|
|
||||||
"receiverType": "ROLE",
|
|
||||||
"receiverIds": [1],
|
|
||||||
"title": "配置同步完成通知",
|
|
||||||
"nextNodeId": "end"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nodeId": "end",
|
|
||||||
"type": "END",
|
|
||||||
"name": "结束"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}',
|
|
||||||
'CONFIG_SYNC',
|
|
||||||
'PUBLISHED'
|
|
||||||
);
|
);
|
||||||
@ -109,4 +109,43 @@ workflow.instance.cannot.retry=工作流实例无法重试
|
|||||||
node.instance.not.found=节点实例不存在
|
node.instance.not.found=节点实例不存在
|
||||||
node.instance.cannot.retry=节点实例无法重试
|
node.instance.cannot.retry=节点实例无法重试
|
||||||
node.instance.cannot.skip=节点实例无法跳过
|
node.instance.cannot.skip=节点实例无法跳过
|
||||||
node.executor.not.found=节点执行器不存在
|
node.executor.not.found=节点执行器不存在
|
||||||
|
|
||||||
|
# 工作流相关消息
|
||||||
|
workflow.not.found=工作流定义不存在
|
||||||
|
workflow.code.exists=工作流编码已存在
|
||||||
|
workflow.name.exists=工作流名称已存在
|
||||||
|
workflow.disabled=工作流已禁用
|
||||||
|
workflow.not.published=工作流未发布
|
||||||
|
workflow.already.published=工作流已发布
|
||||||
|
workflow.already.disabled=工作流已禁用
|
||||||
|
workflow.instance.not.found=工作流实例不存在
|
||||||
|
workflow.instance.already.completed=工作流实例已完成
|
||||||
|
workflow.instance.already.canceled=工作流实例已取消
|
||||||
|
workflow.instance.not.running=工作流实例未运行
|
||||||
|
workflow.node.not.found=工作流节点不存在
|
||||||
|
workflow.node.type.not.supported=不支持的节点类型
|
||||||
|
workflow.node.config.invalid=节点配置无效
|
||||||
|
workflow.node.execution.failed=节点执行失败
|
||||||
|
workflow.node.timeout=节点执行超时
|
||||||
|
workflow.variable.not.found=工作流变量不存在
|
||||||
|
workflow.variable.type.invalid=工作流变量类型无效
|
||||||
|
workflow.permission.denied=工作流权限不足
|
||||||
|
workflow.approval.required=需要审批
|
||||||
|
workflow.approval.rejected=审批被拒绝
|
||||||
|
workflow.dependency.not.satisfied=依赖条件不满足
|
||||||
|
workflow.circular.dependency=存在循环依赖
|
||||||
|
workflow.schedule.invalid=调度配置无效
|
||||||
|
workflow.concurrent.limit.exceeded=超出并发限制
|
||||||
|
|
||||||
|
# Workflow error messages
|
||||||
|
workflow.not.found=工作流定义不存在
|
||||||
|
workflow.code.exists=工作流编码已存在
|
||||||
|
workflow.name.exists=工作流名称已存在
|
||||||
|
workflow.invalid.status=工作流状态无效
|
||||||
|
workflow.node.not.found=工作流节点不存在
|
||||||
|
workflow.node.config.error=工作流节点配置错误
|
||||||
|
workflow.execution.error=工作流执行错误
|
||||||
|
workflow.not.draft=只有草稿状态的工作流定义可以发布
|
||||||
|
workflow.not.published=只有已发布状态的工作流定义可以禁用
|
||||||
|
workflow.not.disabled=只有已禁用状态的工作流定义可以启用
|
||||||
@ -4,6 +4,9 @@ import com.qqchen.deploy.backend.system.entity.ExternalSystem;
|
|||||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
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.exception.UniqueConstraintException;
|
import com.qqchen.deploy.backend.framework.exception.UniqueConstraintException;
|
||||||
|
import com.qqchen.deploy.backend.system.enums.ExternalSystemAuthTypeEnum;
|
||||||
|
import com.qqchen.deploy.backend.system.enums.ExternalSystemSyncStatusEnum;
|
||||||
|
import com.qqchen.deploy.backend.system.enums.ExternalSystemTypeEnum;
|
||||||
import com.qqchen.deploy.backend.system.model.ExternalSystemDTO;
|
import com.qqchen.deploy.backend.system.model.ExternalSystemDTO;
|
||||||
import com.qqchen.deploy.backend.system.repository.IExternalSystemRepository;
|
import com.qqchen.deploy.backend.system.repository.IExternalSystemRepository;
|
||||||
import com.qqchen.deploy.backend.system.service.impl.ExternalSystemServiceImpl;
|
import com.qqchen.deploy.backend.system.service.impl.ExternalSystemServiceImpl;
|
||||||
@ -38,9 +41,9 @@ class ExternalSystemServiceImplTest {
|
|||||||
system = new ExternalSystem();
|
system = new ExternalSystem();
|
||||||
system.setId(1L);
|
system.setId(1L);
|
||||||
system.setName("测试Jenkins");
|
system.setName("测试Jenkins");
|
||||||
system.setType(ExternalSystem.ExternalSystemSystemType.JENKINS);
|
system.setType(ExternalSystemTypeEnum.JENKINS);
|
||||||
system.setUrl("http://jenkins.test.com");
|
system.setUrl("http://jenkins.test.com");
|
||||||
system.setAuthType(ExternalSystem.AuthType.BASIC);
|
system.setAuthType(ExternalSystemAuthTypeEnum.BASIC);
|
||||||
system.setUsername("admin");
|
system.setUsername("admin");
|
||||||
system.setPassword("password");
|
system.setPassword("password");
|
||||||
system.setEnabled(true);
|
system.setEnabled(true);
|
||||||
@ -48,9 +51,9 @@ class ExternalSystemServiceImplTest {
|
|||||||
|
|
||||||
systemDTO = new ExternalSystemDTO();
|
systemDTO = new ExternalSystemDTO();
|
||||||
systemDTO.setName("测试Jenkins");
|
systemDTO.setName("测试Jenkins");
|
||||||
systemDTO.setType(ExternalSystem.ExternalSystemSystemType.JENKINS);
|
systemDTO.setType(ExternalSystemTypeEnum.JENKINS);
|
||||||
systemDTO.setUrl("http://jenkins.test.com");
|
systemDTO.setUrl("http://jenkins.test.com");
|
||||||
systemDTO.setAuthType(ExternalSystem.AuthType.BASIC);
|
systemDTO.setAuthType(ExternalSystemAuthTypeEnum.BASIC);
|
||||||
systemDTO.setUsername("admin");
|
systemDTO.setUsername("admin");
|
||||||
systemDTO.setPassword("password");
|
systemDTO.setPassword("password");
|
||||||
systemDTO.setEnabled(true);
|
systemDTO.setEnabled(true);
|
||||||
@ -127,7 +130,7 @@ class ExternalSystemServiceImplTest {
|
|||||||
externalSystemService.syncData(1L);
|
externalSystemService.syncData(1L);
|
||||||
|
|
||||||
// 验证
|
// 验证
|
||||||
assertThat(system.getSyncStatus()).isEqualTo(ExternalSystem.SyncStatus.SUCCESS);
|
assertThat(system.getSyncStatus()).isEqualTo(ExternalSystemSyncStatusEnum.SUCCESS);
|
||||||
assertThat(system.getLastSyncTime()).isNotNull();
|
assertThat(system.getLastSyncTime()).isNotNull();
|
||||||
verify(externalSystemRepository, times(2)).save(any(ExternalSystem.class));
|
verify(externalSystemRepository, times(2)).save(any(ExternalSystem.class));
|
||||||
}
|
}
|
||||||
@ -149,8 +152,8 @@ class ExternalSystemServiceImplTest {
|
|||||||
@Test
|
@Test
|
||||||
void validateUniqueConstraints_WhenGitWithoutToken_ShouldThrowException() {
|
void validateUniqueConstraints_WhenGitWithoutToken_ShouldThrowException() {
|
||||||
// 准备数据
|
// 准备数据
|
||||||
systemDTO.setType(ExternalSystem.ExternalSystemSystemType.GIT);
|
systemDTO.setType(ExternalSystemTypeEnum.GIT);
|
||||||
systemDTO.setAuthType(ExternalSystem.AuthType.TOKEN);
|
systemDTO.setAuthType(ExternalSystemAuthTypeEnum.TOKEN);
|
||||||
systemDTO.setToken(null);
|
systemDTO.setToken(null);
|
||||||
|
|
||||||
// Mock
|
// Mock
|
||||||
@ -167,8 +170,8 @@ class ExternalSystemServiceImplTest {
|
|||||||
@Test
|
@Test
|
||||||
void validateUniqueConstraints_WhenGitWithWrongAuthType_ShouldThrowException() {
|
void validateUniqueConstraints_WhenGitWithWrongAuthType_ShouldThrowException() {
|
||||||
// 准备数据
|
// 准备数据
|
||||||
systemDTO.setType(ExternalSystem.ExternalSystemSystemType.GIT);
|
systemDTO.setType(ExternalSystemTypeEnum.GIT);
|
||||||
systemDTO.setAuthType(ExternalSystem.AuthType.BASIC);
|
systemDTO.setAuthType(ExternalSystemAuthTypeEnum.BASIC);
|
||||||
|
|
||||||
// Mock
|
// Mock
|
||||||
when(externalSystemRepository.existsByNameAndDeletedFalse(systemDTO.getName())).thenReturn(false);
|
when(externalSystemRepository.existsByNameAndDeletedFalse(systemDTO.getName())).thenReturn(false);
|
||||||
@ -184,8 +187,8 @@ class ExternalSystemServiceImplTest {
|
|||||||
@Test
|
@Test
|
||||||
void update_WhenGitWithoutToken_ShouldThrowException() {
|
void update_WhenGitWithoutToken_ShouldThrowException() {
|
||||||
// 准备数据
|
// 准备数据
|
||||||
systemDTO.setType(ExternalSystem.ExternalSystemSystemType.GIT);
|
systemDTO.setType(ExternalSystemTypeEnum.GIT);
|
||||||
systemDTO.setAuthType(ExternalSystem.AuthType.TOKEN);
|
systemDTO.setAuthType(ExternalSystemAuthTypeEnum.TOKEN);
|
||||||
systemDTO.setToken(null);
|
systemDTO.setToken(null);
|
||||||
|
|
||||||
// 验证
|
// 验证
|
||||||
@ -197,8 +200,8 @@ class ExternalSystemServiceImplTest {
|
|||||||
@Test
|
@Test
|
||||||
void update_WhenGitWithWrongAuthType_ShouldThrowException() {
|
void update_WhenGitWithWrongAuthType_ShouldThrowException() {
|
||||||
// 准备数据
|
// 准备数据
|
||||||
systemDTO.setType(ExternalSystem.ExternalSystemSystemType.GIT);
|
systemDTO.setType(ExternalSystemTypeEnum.GIT);
|
||||||
systemDTO.setAuthType(ExternalSystem.AuthType.BASIC);
|
systemDTO.setAuthType(ExternalSystemAuthTypeEnum.BASIC);
|
||||||
|
|
||||||
// 验证
|
// 验证
|
||||||
assertThatThrownBy(() -> externalSystemService.update(1L, systemDTO))
|
assertThatThrownBy(() -> externalSystemService.update(1L, systemDTO))
|
||||||
|
|||||||
@ -0,0 +1,325 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.context.DefaultWorkflowContext;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.executor.*;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository;
|
||||||
|
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
||||||
|
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
|
||||||
|
import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService;
|
||||||
|
import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService;
|
||||||
|
import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@SpringBootTest(classes = {
|
||||||
|
WorkflowEngineTest.TestConfig.class,
|
||||||
|
DefaultWorkflowEngine.class,
|
||||||
|
ObjectMapper.class
|
||||||
|
})
|
||||||
|
class WorkflowEngineTest {
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class TestConfig {
|
||||||
|
@Bean
|
||||||
|
Map<NodeTypeEnum, NodeExecutor> nodeExecutors() {
|
||||||
|
Map<NodeTypeEnum, NodeExecutor> executors = new HashMap<>();
|
||||||
|
StartNodeExecutor startExecutor = mock(StartNodeExecutor.class);
|
||||||
|
doNothing().when(startExecutor).execute(any(), any());
|
||||||
|
|
||||||
|
EndNodeExecutor endExecutor = mock(EndNodeExecutor.class);
|
||||||
|
doNothing().when(endExecutor).execute(any(), any());
|
||||||
|
|
||||||
|
TaskNodeExecutor taskExecutor = mock(TaskNodeExecutor.class);
|
||||||
|
doNothing().when(taskExecutor).execute(any(), any());
|
||||||
|
|
||||||
|
GatewayNodeExecutor gatewayExecutor = mock(GatewayNodeExecutor.class);
|
||||||
|
doNothing().when(gatewayExecutor).execute(any(), any());
|
||||||
|
|
||||||
|
executors.put(NodeTypeEnum.START, startExecutor);
|
||||||
|
executors.put(NodeTypeEnum.END, endExecutor);
|
||||||
|
executors.put(NodeTypeEnum.TASK, taskExecutor);
|
||||||
|
executors.put(NodeTypeEnum.GATEWAY, gatewayExecutor);
|
||||||
|
return executors;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
IWorkflowVariableService workflowVariableService() {
|
||||||
|
IWorkflowVariableService service = mock(IWorkflowVariableService.class);
|
||||||
|
doNothing().when(service).setVariable(any(), anyString(), any());
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
IWorkflowLogService workflowLogService() {
|
||||||
|
return mock(IWorkflowLogService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
DefaultWorkflowContext.Factory workflowContextFactory(
|
||||||
|
IWorkflowVariableService variableService,
|
||||||
|
IWorkflowLogService logService
|
||||||
|
) {
|
||||||
|
return new DefaultWorkflowContext.Factory(variableService, logService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private WorkflowEngine workflowEngine;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private IWorkflowDefinitionService workflowDefinitionService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private IWorkflowDefinitionRepository workflowDefinitionRepository;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private IWorkflowInstanceRepository workflowInstanceRepository;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private INodeInstanceRepository nodeInstanceRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
private NodeInstance savedStartNode;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
log.info("Setting up workflow engine test mocks...");
|
||||||
|
|
||||||
|
// Mock workflowDefinitionService
|
||||||
|
when(workflowDefinitionService.create(any())).thenAnswer(i -> {
|
||||||
|
WorkflowDefinitionDTO dto = i.getArgument(0);
|
||||||
|
dto.setId(1L);
|
||||||
|
log.info("Created workflow definition with ID: {}", dto.getId());
|
||||||
|
return dto;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock workflowDefinitionRepository
|
||||||
|
when(workflowDefinitionRepository.findByCodeAndDeletedFalse(anyString()))
|
||||||
|
.thenAnswer(i -> {
|
||||||
|
WorkflowDefinition definition = new WorkflowDefinition();
|
||||||
|
definition.setId(1L);
|
||||||
|
definition.setName("简单测试工作流");
|
||||||
|
definition.setCode("SIMPLE_TEST_WORKFLOW");
|
||||||
|
definition.setStatus(WorkflowStatusEnum.PUBLISHED);
|
||||||
|
definition.setNodeConfig(createSimpleWorkflow().getNodeConfig());
|
||||||
|
definition.setTransitionConfig(createSimpleWorkflow().getTransitionConfig());
|
||||||
|
log.info("Found workflow definition: code={}, status={}", definition.getCode(), definition.getStatus());
|
||||||
|
return definition;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock workflowInstanceRepository
|
||||||
|
when(workflowInstanceRepository.save(any())).thenAnswer(i -> {
|
||||||
|
WorkflowInstance instance = i.getArgument(0);
|
||||||
|
if (instance.getId() == null) {
|
||||||
|
instance.setId(1L);
|
||||||
|
}
|
||||||
|
log.info("Saved workflow instance: id={}, status={}", instance.getId(), instance.getStatus());
|
||||||
|
return instance;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock nodeInstanceRepository
|
||||||
|
when(nodeInstanceRepository.save(any())).thenAnswer(i -> {
|
||||||
|
NodeInstance node = i.getArgument(0);
|
||||||
|
if (node.getId() == null) {
|
||||||
|
node.setId(System.nanoTime());
|
||||||
|
}
|
||||||
|
savedStartNode = node;
|
||||||
|
|
||||||
|
// Create and set the workflow instance if it doesn't exist
|
||||||
|
if (node.getWorkflowInstance() == null) {
|
||||||
|
WorkflowInstance instance = new WorkflowInstance();
|
||||||
|
instance.setId(1L);
|
||||||
|
instance.setStatus(WorkflowStatusEnum.RUNNING);
|
||||||
|
node.setWorkflowInstance(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Saved node instance: id={}, type={}, status={}, workflowInstanceId={}",
|
||||||
|
node.getId(), node.getNodeType(), node.getStatus(), node.getWorkflowInstance().getId());
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
|
||||||
|
when(nodeInstanceRepository.findById(any())).thenAnswer(i -> {
|
||||||
|
if (savedStartNode != null && savedStartNode.getId().equals(i.getArgument(0))) {
|
||||||
|
log.info("Found node instance by id: {}, type={}, status={}",
|
||||||
|
savedStartNode.getId(), savedStartNode.getNodeType(), savedStartNode.getStatus());
|
||||||
|
return Optional.of(savedStartNode);
|
||||||
|
}
|
||||||
|
log.warn("Node instance not found with id: {}", (Object)i.getArgument(0));
|
||||||
|
return Optional.empty();
|
||||||
|
});
|
||||||
|
|
||||||
|
when(nodeInstanceRepository.findByWorkflowInstanceIdOrderByCreateTime(any()))
|
||||||
|
.thenAnswer(i -> {
|
||||||
|
WorkflowInstance instance = new WorkflowInstance();
|
||||||
|
instance.setId(1L);
|
||||||
|
instance.setStatus(WorkflowStatusEnum.RUNNING);
|
||||||
|
|
||||||
|
List<NodeInstance> nodes = List.of(
|
||||||
|
createNodeInstance(NodeTypeEnum.START, NodeStatusEnum.COMPLETED),
|
||||||
|
createNodeInstance(NodeTypeEnum.TASK, NodeStatusEnum.COMPLETED),
|
||||||
|
createNodeInstance(NodeTypeEnum.END, NodeStatusEnum.COMPLETED)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set workflow instance for each node
|
||||||
|
nodes.forEach(node -> node.setWorkflowInstance(instance));
|
||||||
|
|
||||||
|
log.info("Found {} nodes for workflow instance id={}", nodes.size(), i.getArgument(0));
|
||||||
|
nodes.forEach(node -> log.info("Node: type={}, status={}", node.getNodeType(), node.getStatus()));
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info("Test setup completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSimpleWorkflow() {
|
||||||
|
log.info("Starting simple workflow test");
|
||||||
|
|
||||||
|
// 1. 创建工作流定义
|
||||||
|
WorkflowDefinitionDTO definition = createSimpleWorkflow();
|
||||||
|
log.info("Created workflow definition: name={}, code={}", definition.getName(), definition.getCode());
|
||||||
|
|
||||||
|
// 2. 启动工作流实例
|
||||||
|
Map<String, Object> variables = new HashMap<>();
|
||||||
|
variables.put("input", "test");
|
||||||
|
log.info("Starting workflow with variables: {}", variables);
|
||||||
|
|
||||||
|
WorkflowInstance instance = workflowEngine.startWorkflow(
|
||||||
|
definition.getCode(),
|
||||||
|
"TEST-" + System.currentTimeMillis(),
|
||||||
|
variables
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. 验证工作流实例状态
|
||||||
|
assertNotNull(instance);
|
||||||
|
assertEquals(WorkflowStatusEnum.RUNNING, instance.getStatus());
|
||||||
|
log.info("Workflow instance started: id={}, status={}", instance.getId(), instance.getStatus());
|
||||||
|
|
||||||
|
// 4. 验证节点执行状态
|
||||||
|
List<NodeInstance> nodes = nodeInstanceRepository.findByWorkflowInstanceIdOrderByCreateTime(instance.getId());
|
||||||
|
assertEquals(3, nodes.size());
|
||||||
|
log.info("Found {} nodes in workflow instance", nodes.size());
|
||||||
|
|
||||||
|
// 验证开始节点
|
||||||
|
NodeInstance startNode = nodes.get(0);
|
||||||
|
assertEquals(NodeTypeEnum.START, startNode.getNodeType());
|
||||||
|
assertEquals(NodeStatusEnum.COMPLETED, startNode.getStatus());
|
||||||
|
log.info("Start node validation passed: id={}, status={}", startNode.getId(), startNode.getStatus());
|
||||||
|
|
||||||
|
// 验证Shell节点
|
||||||
|
NodeInstance shellNode = nodes.get(1);
|
||||||
|
assertEquals(NodeTypeEnum.TASK, shellNode.getNodeType());
|
||||||
|
assertEquals(NodeStatusEnum.COMPLETED, shellNode.getStatus());
|
||||||
|
log.info("Shell node validation passed: id={}, status={}", shellNode.getId(), shellNode.getStatus());
|
||||||
|
|
||||||
|
// 验证结束节点
|
||||||
|
NodeInstance endNode = nodes.get(2);
|
||||||
|
assertEquals(NodeTypeEnum.END, endNode.getNodeType());
|
||||||
|
assertEquals(NodeStatusEnum.COMPLETED, endNode.getStatus());
|
||||||
|
log.info("End node validation passed: id={}, status={}", endNode.getId(), endNode.getStatus());
|
||||||
|
|
||||||
|
log.info("Simple workflow test completed successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
private WorkflowDefinitionDTO createSimpleWorkflow() {
|
||||||
|
WorkflowDefinitionDTO dto = new WorkflowDefinitionDTO();
|
||||||
|
dto.setId(1L);
|
||||||
|
dto.setName("简单测试工作流");
|
||||||
|
dto.setCode("SIMPLE_TEST_WORKFLOW");
|
||||||
|
dto.setStatus(WorkflowStatusEnum.PUBLISHED);
|
||||||
|
|
||||||
|
// 节点配置
|
||||||
|
String nodeConfig = """
|
||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "start",
|
||||||
|
"name": "开始",
|
||||||
|
"type": "START"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "shell",
|
||||||
|
"name": "Shell任务",
|
||||||
|
"type": "TASK",
|
||||||
|
"config": {
|
||||||
|
"type": "SHELL",
|
||||||
|
"script": "echo 'Hello World'",
|
||||||
|
"workingDirectory": "/tmp",
|
||||||
|
"successExitCode": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "end",
|
||||||
|
"name": "结束",
|
||||||
|
"type": "END"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
dto.setNodeConfig(nodeConfig);
|
||||||
|
|
||||||
|
// 流转配置
|
||||||
|
String transitionConfig = """
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sourceNodeId": "start",
|
||||||
|
"targetNodeId": "shell",
|
||||||
|
"condition": null,
|
||||||
|
"priority": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sourceNodeId": "shell",
|
||||||
|
"targetNodeId": "end",
|
||||||
|
"condition": null,
|
||||||
|
"priority": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
""";
|
||||||
|
dto.setTransitionConfig(transitionConfig);
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodeInstance createNodeInstance(NodeTypeEnum type, NodeStatusEnum status) {
|
||||||
|
NodeInstance node = new NodeInstance();
|
||||||
|
node.setId(System.nanoTime());
|
||||||
|
node.setNodeType(type);
|
||||||
|
node.setStatus(status);
|
||||||
|
|
||||||
|
// Create and set a workflow instance
|
||||||
|
WorkflowInstance instance = new WorkflowInstance();
|
||||||
|
instance.setId(1L);
|
||||||
|
instance.setStatus(WorkflowStatusEnum.RUNNING);
|
||||||
|
node.setWorkflowInstance(instance);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.service;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||||
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class WorkflowDefinitionServiceTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IWorkflowDefinitionService workflowDefinitionService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private IWorkflowDefinitionRepository workflowDefinitionRepository;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateWorkflowDefinition() {
|
||||||
|
// 准备测试数据
|
||||||
|
WorkflowDefinitionDTO dto = new WorkflowDefinitionDTO();
|
||||||
|
dto.setName("测试工作流");
|
||||||
|
dto.setCode("TEST_WORKFLOW");
|
||||||
|
dto.setStatus(WorkflowStatusEnum.DRAFT);
|
||||||
|
|
||||||
|
// Mock repository方法
|
||||||
|
when(workflowDefinitionRepository.existsByCodeAndDeletedFalse(anyString())).thenReturn(false);
|
||||||
|
when(workflowDefinitionRepository.save(any())).thenAnswer(i -> i.getArgument(0));
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
WorkflowDefinitionDTO result = workflowDefinitionService.create(dto);
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(dto.getName(), result.getName());
|
||||||
|
assertEquals(dto.getCode(), result.getCode());
|
||||||
|
assertEquals(WorkflowStatusEnum.DRAFT, result.getStatus());
|
||||||
|
|
||||||
|
// 验证repository方法调用
|
||||||
|
verify(workflowDefinitionRepository).existsByCodeAndDeletedFalse(dto.getCode());
|
||||||
|
verify(workflowDefinitionRepository).save(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPublishWorkflow() {
|
||||||
|
// 准备测试数据
|
||||||
|
Long id = 1L;
|
||||||
|
WorkflowDefinition definition = new WorkflowDefinition();
|
||||||
|
definition.setId(id);
|
||||||
|
definition.setStatus(WorkflowStatusEnum.DRAFT);
|
||||||
|
|
||||||
|
// Mock repository方法
|
||||||
|
when(workflowDefinitionRepository.findByIdWithLock(id)).thenReturn(Optional.of(definition));
|
||||||
|
when(workflowDefinitionRepository.save(any())).thenAnswer(i -> i.getArgument(0));
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
workflowDefinitionService.publish(id);
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
assertEquals(WorkflowStatusEnum.PUBLISHED, definition.getStatus());
|
||||||
|
|
||||||
|
// 验证repository方法调用
|
||||||
|
verify(workflowDefinitionRepository).findByIdWithLock(id);
|
||||||
|
verify(workflowDefinitionRepository).save(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPublishWorkflowWithInvalidStatus() {
|
||||||
|
// 准备测试数据
|
||||||
|
Long id = 1L;
|
||||||
|
WorkflowDefinition definition = new WorkflowDefinition();
|
||||||
|
definition.setId(id);
|
||||||
|
definition.setStatus(WorkflowStatusEnum.PUBLISHED);
|
||||||
|
|
||||||
|
// Mock repository方法
|
||||||
|
when(workflowDefinitionRepository.findByIdWithLock(id)).thenReturn(Optional.of(definition));
|
||||||
|
|
||||||
|
// 执行测试并验证异常
|
||||||
|
assertThrows(BusinessException.class, () -> workflowDefinitionService.publish(id));
|
||||||
|
|
||||||
|
// 验证repository方法调用
|
||||||
|
verify(workflowDefinitionRepository).findByIdWithLock(id);
|
||||||
|
verify(workflowDefinitionRepository, never()).save(any());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user