可正常启动
This commit is contained in:
parent
6050c5e189
commit
9fce2fc900
@ -190,6 +190,12 @@
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- Apache Commons Exec -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-exec</artifactId>
|
||||
<version>1.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -98,6 +98,9 @@ public enum ResponseCode {
|
||||
WORKFLOW_NOT_PUBLISHED(2704, "workflow.not.published"),
|
||||
WORKFLOW_ALREADY_PUBLISHED(2705, "workflow.already.published"),
|
||||
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_ALREADY_COMPLETED(2711, "workflow.instance.already.completed"),
|
||||
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_EXECUTION_FAILED(2723, "workflow.node.execution.failed"),
|
||||
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_TYPE_INVALID(2731, "workflow.variable.type.invalid"),
|
||||
WORKFLOW_PERMISSION_DENIED(2740, "workflow.permission.denied"),
|
||||
|
||||
@ -62,7 +62,7 @@ public class ExternalSystemServiceImpl extends BaseServiceImpl<ExternalSystem, E
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateUniqueConstraints(ExternalSystemDTO dto) {
|
||||
public void validateUniqueConstraints(ExternalSystemDTO dto) {
|
||||
// 检查名称唯一性
|
||||
if (externalSystemRepository.existsByNameAndDeletedFalse(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.controller.BaseController;
|
||||
import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO;
|
||||
import com.qqchen.deploy.backend.workflow.api.query.NodeInstanceQuery;
|
||||
import com.qqchen.deploy.backend.workflow.dto.NodeInstanceDTO;
|
||||
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.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@ -15,58 +15,38 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 节点实例控制器
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/workflow/node")
|
||||
@Tag(name = "工作流节点管理", description = "工作流节点管理相关接口")
|
||||
@RequestMapping("/api/v1/node-instances")
|
||||
@Tag(name = "节点实例管理", description = "节点实例管理相关接口")
|
||||
public class NodeInstanceApiController extends BaseController<NodeInstance, NodeInstanceDTO, Long, NodeInstanceQuery> {
|
||||
|
||||
@Resource
|
||||
private INodeInstanceService nodeInstanceService;
|
||||
private WorkflowEngine workflowEngine;
|
||||
|
||||
|
||||
@Operation(summary = "根据工作流实例ID查询节点实例列表")
|
||||
@GetMapping("/workflow/{workflowInstanceId}")
|
||||
public Response<List<NodeInstanceDTO>> findByWorkflowInstanceId(
|
||||
@Parameter(description = "工作流实例ID", required = true)
|
||||
@PathVariable Long workflowInstanceId) {
|
||||
return Response.success(nodeInstanceService.findByWorkflowInstanceId(workflowInstanceId));
|
||||
@Operation(summary = "完成节点")
|
||||
@PostMapping("/{id}/complete")
|
||||
public Response<Void> completeNode(
|
||||
@Parameter(description = "节点实例ID", required = true) @PathVariable Long id,
|
||||
@Parameter(description = "输出变量") @RequestBody(required = false) Map<String, Object> variables
|
||||
) {
|
||||
workflowEngine.completeNode(id, variables);
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "根据工作流实例ID和状态查询节点实例列表")
|
||||
@GetMapping("/workflow/{workflowInstanceId}/status/{status}")
|
||||
public Response<List<NodeInstanceDTO>> findByWorkflowInstanceIdAndStatus(
|
||||
@Parameter(description = "工作流实例ID", required = true)
|
||||
@PathVariable Long workflowInstanceId,
|
||||
@Parameter(description = "状态", required = true)
|
||||
@PathVariable String status) {
|
||||
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));
|
||||
@Operation(summary = "重试节点")
|
||||
@PostMapping("/{id}/retry")
|
||||
public Response<Void> retryNode(
|
||||
@Parameter(description = "节点实例ID", required = true) @PathVariable Long id
|
||||
) {
|
||||
workflowEngine.executeNode(id);
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void exportData(HttpServletResponse response, List<NodeInstanceDTO> data) {
|
||||
// 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.controller.BaseController;
|
||||
import com.qqchen.deploy.backend.workflow.api.dto.WorkflowDefinitionDTO;
|
||||
import com.qqchen.deploy.backend.workflow.api.query.WorkflowDefinitionQuery;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
||||
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 io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@ -16,12 +16,9 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流定义控制器
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/workflow/definition")
|
||||
@RequestMapping("/api/v1/workflow-definitions")
|
||||
@Tag(name = "工作流定义管理", description = "工作流定义管理相关接口")
|
||||
public class WorkflowDefinitionApiController extends BaseController<WorkflowDefinition, WorkflowDefinitionDTO, Long, WorkflowDefinitionQuery> {
|
||||
|
||||
@ -30,31 +27,33 @@ public class WorkflowDefinitionApiController extends BaseController<WorkflowDefi
|
||||
|
||||
@Operation(summary = "发布工作流")
|
||||
@PostMapping("/{id}/publish")
|
||||
public Response<Boolean> publish(
|
||||
@Parameter(description = "工作流定义ID", required = true)
|
||||
@PathVariable Long id) {
|
||||
return Response.success(workflowDefinitionService.publish(id));
|
||||
public Response<Void> publish(
|
||||
@Parameter(description = "工作流定义ID", required = true) @PathVariable Long id
|
||||
) {
|
||||
workflowDefinitionService.publish(id);
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "禁用工作流")
|
||||
@PostMapping("/{id}/disable")
|
||||
public Response<Boolean> disable(
|
||||
@Parameter(description = "工作流定义ID", required = true)
|
||||
@PathVariable Long id) {
|
||||
return Response.success(workflowDefinitionService.disable(id));
|
||||
public Response<Void> disable(
|
||||
@Parameter(description = "工作流定义ID", required = true) @PathVariable Long id
|
||||
) {
|
||||
workflowDefinitionService.disable(id);
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "根据编码查询工作流定义")
|
||||
@GetMapping("/code/{code}")
|
||||
public Response<WorkflowDefinitionDTO> findByCode(
|
||||
@Parameter(description = "工作流编码", required = true)
|
||||
@PathVariable String code) {
|
||||
return Response.success(workflowDefinitionService.findByCode(code));
|
||||
@Operation(summary = "启用工作流")
|
||||
@PostMapping("/{id}/enable")
|
||||
public Response<Void> enable(
|
||||
@Parameter(description = "工作流定义ID", required = true) @PathVariable Long id
|
||||
) {
|
||||
workflowDefinitionService.enable(id);
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
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.controller.BaseController;
|
||||
import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO;
|
||||
import com.qqchen.deploy.backend.workflow.api.query.WorkflowInstanceQuery;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
||||
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.converter.WorkflowInstanceConverter;
|
||||
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.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 工作流实例控制器
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/workflow/instance")
|
||||
@RequestMapping("/api/v1/workflow-instances")
|
||||
@Tag(name = "工作流实例管理", description = "工作流实例管理相关接口")
|
||||
public class WorkflowInstanceApiController extends BaseController<WorkflowInstance, WorkflowInstanceDTO, Long, WorkflowInstanceQuery> {
|
||||
|
||||
private final IWorkflowInstanceService workflowInstanceService;
|
||||
@Resource
|
||||
private WorkflowEngine workflowEngine;
|
||||
|
||||
public WorkflowInstanceApiController(IWorkflowInstanceService workflowInstanceService) {
|
||||
this.workflowInstanceService = workflowInstanceService;
|
||||
}
|
||||
@Resource
|
||||
private IWorkflowInstanceService workflowInstanceService;
|
||||
|
||||
@Resource
|
||||
private WorkflowInstanceConverter converter;
|
||||
|
||||
@Operation(summary = "启动工作流实例")
|
||||
@PostMapping("/start")
|
||||
public Response<WorkflowInstanceDTO> start(
|
||||
@Parameter(description = "工作流定义ID", required = true)
|
||||
@RequestParam Long definitionId,
|
||||
@Parameter(description = "项目环境ID", required = true)
|
||||
@RequestParam Long projectEnvId,
|
||||
@Parameter(description = "工作流变量")
|
||||
@RequestParam(required = false) String variables) {
|
||||
return Response.success(workflowInstanceService.start(definitionId, projectEnvId, variables));
|
||||
public Response<WorkflowInstanceDTO> startWorkflow(@RequestBody StartWorkflowRequest request) {
|
||||
WorkflowInstance instance = workflowEngine.startWorkflow(
|
||||
request.getWorkflowCode(),
|
||||
request.getBusinessKey(),
|
||||
request.getVariables()
|
||||
);
|
||||
return Response.success(converter.toDto(instance));
|
||||
}
|
||||
|
||||
@Operation(summary = "取消工作流实例")
|
||||
@PostMapping("/{id}/cancel")
|
||||
public Response<Boolean> cancel(
|
||||
@Parameter(description = "工作流实例ID", required = true)
|
||||
@PathVariable Long id) {
|
||||
return Response.success(workflowInstanceService.cancel(id));
|
||||
@Operation(summary = "终止工作流实例")
|
||||
@PostMapping("/{id}/terminate")
|
||||
public Response<Void> terminateWorkflow(
|
||||
@Parameter(description = "工作流实例ID", required = true) @PathVariable Long id,
|
||||
@Parameter(description = "终止原因") @RequestParam(required = false) String reason
|
||||
) {
|
||||
workflowEngine.terminateWorkflow(id, reason);
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "根据项目环境ID查询工作流实例列表")
|
||||
@GetMapping("/project-env/{projectEnvId}")
|
||||
public Response<List<WorkflowInstanceDTO>> findByProjectEnvId(
|
||||
@Parameter(description = "项目环境ID", required = true)
|
||||
@PathVariable Long projectEnvId) {
|
||||
return Response.success(workflowInstanceService.findByProjectEnvId(projectEnvId));
|
||||
@Operation(summary = "暂停工作流实例")
|
||||
@PostMapping("/{id}/pause")
|
||||
public Response<Void> pauseWorkflow(
|
||||
@Parameter(description = "工作流实例ID", required = true) @PathVariable Long id
|
||||
) {
|
||||
workflowEngine.pauseWorkflow(id);
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "根据项目环境ID和状态查询工作流实例列表")
|
||||
@GetMapping("/project-env/{projectEnvId}/status/{status}")
|
||||
public Response<List<WorkflowInstanceDTO>> findByProjectEnvIdAndStatus(
|
||||
@Parameter(description = "项目环境ID", required = true)
|
||||
@PathVariable Long projectEnvId,
|
||||
@Parameter(description = "状态", required = true)
|
||||
@PathVariable String status) {
|
||||
return Response.success(workflowInstanceService.findByProjectEnvIdAndStatus(projectEnvId, status));
|
||||
@Operation(summary = "恢复工作流实例")
|
||||
@PostMapping("/{id}/resume")
|
||||
public Response<Void> resumeWorkflow(
|
||||
@Parameter(description = "工作流实例ID", required = true) @PathVariable Long id
|
||||
) {
|
||||
workflowEngine.resumeWorkflow(id);
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void exportData(HttpServletResponse response, List<WorkflowInstanceDTO> data) {
|
||||
public void exportData(HttpServletResponse response, List<WorkflowInstanceDTO> data) {
|
||||
// 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;
|
||||
|
||||
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 org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
@ -12,9 +12,11 @@ import org.mapstruct.Mapping;
|
||||
@Mapper(config = BaseConverter.class)
|
||||
public interface NodeInstanceConverter extends BaseConverter<NodeInstance, NodeInstanceDTO> {
|
||||
|
||||
@Override
|
||||
@Mapping(target = "workflowInstanceId", source = "workflowInstance.id")
|
||||
NodeInstanceDTO toDto(NodeInstance entity);
|
||||
|
||||
@Override
|
||||
@Mapping(target = "workflowInstance.id", source = "workflowInstanceId")
|
||||
NodeInstance toEntity(NodeInstanceDTO dto);
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
package com.qqchen.deploy.backend.workflow.converter;
|
||||
|
||||
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 org.mapstruct.Mapper;
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package com.qqchen.deploy.backend.workflow.converter;
|
||||
|
||||
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 org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
@ -12,10 +12,11 @@ import org.mapstruct.Mapping;
|
||||
@Mapper(config = BaseConverter.class)
|
||||
public interface WorkflowInstanceConverter extends BaseConverter<WorkflowInstance, WorkflowInstanceDTO> {
|
||||
|
||||
@Override
|
||||
@Mapping(target = "definitionId", source = "definition.id")
|
||||
@Mapping(target = "workflowName", source = "definition.name")
|
||||
WorkflowInstanceDTO toDto(WorkflowInstance entity);
|
||||
|
||||
@Override
|
||||
@Mapping(target = "definition.id", source = "definitionId")
|
||||
WorkflowInstance toEntity(WorkflowInstanceDTO dto);
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
package com.qqchen.deploy.backend.workflow.converter;
|
||||
|
||||
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 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.workflow.enums.NodeStatusEnum;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 节点实例DTO
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class NodeInstanceDTO extends BaseDTO {
|
||||
@ -25,27 +24,24 @@ public class NodeInstanceDTO extends BaseDTO {
|
||||
* 节点ID
|
||||
*/
|
||||
@NotBlank(message = "节点ID不能为空")
|
||||
@Size(max = 50, message = "节点ID长度不能超过50")
|
||||
private String nodeId;
|
||||
|
||||
/**
|
||||
* 节点类型
|
||||
*/
|
||||
@NotBlank(message = "节点类型不能为空")
|
||||
@Size(max = 50, message = "节点类型长度不能超过50")
|
||||
private String nodeType;
|
||||
@NotNull(message = "节点类型不能为空")
|
||||
private NodeTypeEnum nodeType;
|
||||
|
||||
/**
|
||||
* 节点名称
|
||||
*/
|
||||
@NotBlank(message = "节点名称不能为空")
|
||||
@Size(max = 100, message = "节点名称长度不能超过100")
|
||||
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;
|
||||
|
||||
/**
|
||||
* 节点配置(JSON)
|
||||
*/
|
||||
private String config;
|
||||
|
||||
/**
|
||||
* 输入参数(JSON)
|
||||
*/
|
||||
@ -71,4 +72,9 @@ public class NodeInstanceDTO extends BaseDTO {
|
||||
* 错误信息
|
||||
*/
|
||||
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.workflow.enums.WorkflowStatusEnum;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 工作流实例DTO
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WorkflowInstanceDTO extends BaseDTO {
|
||||
@ -20,15 +19,15 @@ public class WorkflowInstanceDTO extends BaseDTO {
|
||||
private Long definitionId;
|
||||
|
||||
/**
|
||||
* 项目环境ID
|
||||
* 业务标识
|
||||
*/
|
||||
@NotNull(message = "项目环境ID不能为空")
|
||||
private Long projectEnvId;
|
||||
@NotNull(message = "业务标识不能为空")
|
||||
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 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;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 工作流引擎接口
|
||||
* 工作流执行引擎接口
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 重试工作流节点
|
||||
*
|
||||
* @param nodeInstanceId 节点实例ID
|
||||
*/
|
||||
void retryNode(Long nodeInstanceId);
|
||||
|
||||
/**
|
||||
* 跳过工作流节点
|
||||
*
|
||||
* @param nodeInstanceId 节点实例ID
|
||||
*/
|
||||
void skipNode(Long nodeInstanceId);
|
||||
void resumeWorkflow(Long instanceId);
|
||||
}
|
||||
@ -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;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
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.WorkflowInstance;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -17,99 +19,70 @@ public abstract class AbstractNodeExecutor implements NodeExecutor {
|
||||
@Resource
|
||||
protected IWorkflowLogService workflowLogService;
|
||||
|
||||
@Resource
|
||||
protected ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public boolean execute(NodeInstance nodeInstance, WorkflowContext context) {
|
||||
public void execute(NodeInstance nodeInstance, WorkflowContext context) {
|
||||
try {
|
||||
// 解析节点配置
|
||||
NodeConfig config = parseConfig(nodeInstance.getConfig());
|
||||
// 1. 记录系统日志
|
||||
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. 记录节点完成日志
|
||||
logNodeComplete(nodeInstance);
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("Execute node failed: {}", e.getMessage(), e);
|
||||
workflowLogService.logSystem(context.getWorkflowInstance(), LogLevelEnum.ERROR,
|
||||
"Execute node failed: " + e.getMessage(), e.toString());
|
||||
return false;
|
||||
// 5. 记录错误日志
|
||||
logSystem(context.getInstance(), LogLevelEnum.ERROR,
|
||||
String.format("节点执行失败: %s[%s]", nodeInstance.getName(), nodeInstance.getNodeId()),
|
||||
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) {
|
||||
if (success) {
|
||||
workflowLogService.logNodeComplete(nodeInstance);
|
||||
} else {
|
||||
workflowLogService.logNodeError(nodeInstance, "Node execution failed");
|
||||
}
|
||||
protected void logSystem(WorkflowInstance instance, LogLevelEnum level, String message, String detail) {
|
||||
workflowLogService.log(instance.getId(), null, message, level, detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行取消操作
|
||||
*
|
||||
* @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;
|
||||
|
||||
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.NodeConfig;
|
||||
import com.qqchen.deploy.backend.workflow.engine.node.NodeType;
|
||||
import com.qqchen.deploy.backend.workflow.engine.node.ScriptNodeConfig;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
@ -19,29 +20,34 @@ import java.util.Map;
|
||||
@Component
|
||||
public class ShellNodeExecutor extends AbstractNodeExecutor {
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public NodeType getNodeType() {
|
||||
return NodeType.SCRIPT;
|
||||
public NodeTypeEnum getNodeType() {
|
||||
return NodeTypeEnum.TASK;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NodeConfig parseConfig(String configJson) throws Exception {
|
||||
return objectMapper.readValue(configJson, ScriptNodeConfig.class);
|
||||
public void validate(String config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) throws Exception {
|
||||
ScriptNodeConfig scriptConfig = (ScriptNodeConfig) config;
|
||||
protected void doExecute(NodeInstance nodeInstance, WorkflowContext context) {
|
||||
try {
|
||||
String configJson = nodeInstance.getConfig();
|
||||
ShellConfig config = objectMapper.readValue(configJson, ShellConfig.class);
|
||||
ProcessBuilder processBuilder = new ProcessBuilder();
|
||||
processBuilder.command("sh", "-c", scriptConfig.getScript());
|
||||
processBuilder.command("sh", "-c", config.getScript());
|
||||
|
||||
if (scriptConfig.getWorkingDirectory() != null) {
|
||||
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();
|
||||
env.putAll(scriptConfig.getEnvironment());
|
||||
env.putAll(config.getEnvironment());
|
||||
}
|
||||
|
||||
Process process = processBuilder.start();
|
||||
@ -65,7 +71,7 @@ public class ShellNodeExecutor extends AbstractNodeExecutor {
|
||||
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 +
|
||||
"\nError output: " + String.join("\n", error));
|
||||
}
|
||||
@ -75,12 +81,54 @@ public class ShellNodeExecutor extends AbstractNodeExecutor {
|
||||
if (!error.isEmpty()) {
|
||||
nodeInstance.setError(String.join("\n", error));
|
||||
}
|
||||
|
||||
return exitCode == 0;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Shell script execution failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doCancel(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) throws Exception {
|
||||
// TODO: 实现取消逻辑,例如终止正在运行的进程
|
||||
public void terminate(NodeInstance nodeInstance, WorkflowContext context) {
|
||||
// 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.domain.Entity;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Table;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@ -14,43 +14,43 @@ import lombok.EqualsAndHashCode;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@jakarta.persistence.Entity
|
||||
@Table(name = "sys_workflow_definition")
|
||||
@Table(name = "wf_definition")
|
||||
@LogicDelete
|
||||
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;
|
||||
|
||||
/**
|
||||
* 工作流编码
|
||||
*/
|
||||
@Column(nullable = false, unique = true)
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@Column(name = "description", columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 工作流定义(JSON)
|
||||
* 状态
|
||||
*/
|
||||
@Column(name = "content", nullable = false, columnDefinition = "TEXT")
|
||||
private String content;
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private WorkflowStatusEnum status = WorkflowStatusEnum.DRAFT;
|
||||
|
||||
/**
|
||||
* 类型:DEPLOY/CONFIG_SYNC
|
||||
* 节点配置(JSON)
|
||||
*/
|
||||
@Column(name = "type", nullable = false, length = 20)
|
||||
private String type;
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String nodeConfig;
|
||||
|
||||
/**
|
||||
* 状态:DRAFT/PUBLISHED/DISABLED
|
||||
* 流转配置(JSON)
|
||||
*/
|
||||
@Column(name = "status", nullable = false, length = 20)
|
||||
private String status = "DRAFT";
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String transitionConfig;
|
||||
}
|
||||
@ -20,7 +20,7 @@ import java.time.LocalDateTime;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@jakarta.persistence.Entity
|
||||
@Table(name = "sys_workflow_instance")
|
||||
@Table(name = "wf_instance")
|
||||
@LogicDelete
|
||||
|
||||
public class WorkflowInstance extends Entity<Long> {
|
||||
|
||||
@ -1,55 +1,47 @@
|
||||
package com.qqchen.deploy.backend.workflow.entity;
|
||||
|
||||
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 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_log")
|
||||
@Table(name = "wf_log")
|
||||
@LogicDelete
|
||||
public class WorkflowLog extends Entity<Long> {
|
||||
|
||||
/**
|
||||
* 工作流实例ID
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Column(name = "workflow_instance_id", nullable = false)
|
||||
private Long workflowInstanceId;
|
||||
|
||||
/**
|
||||
* 节点实例ID
|
||||
* 节点ID
|
||||
*/
|
||||
private Long nodeInstanceId;
|
||||
|
||||
/**
|
||||
* 日志类型
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private LogTypeEnum type;
|
||||
|
||||
/**
|
||||
* 日志级别
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private LogLevelEnum level;
|
||||
@Column(name = "node_id")
|
||||
private String nodeId;
|
||||
|
||||
/**
|
||||
* 日志内容
|
||||
*/
|
||||
@Column(columnDefinition = "TEXT", nullable = false)
|
||||
@Column(nullable = false)
|
||||
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;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
|
||||
import com.qqchen.deploy.backend.framework.domain.Entity;
|
||||
import com.qqchen.deploy.backend.workflow.enums.VariableScopeEnum;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 工作流变量
|
||||
* 工作流变量实体
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@jakarta.persistence.Entity
|
||||
@Table(name = "wf_workflow_variable")
|
||||
@Table(name = "wf_variable")
|
||||
@LogicDelete
|
||||
public class WorkflowVariable extends Entity<Long> {
|
||||
|
||||
/**
|
||||
* 工作流实例ID
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Column(name = "workflow_instance_id", nullable = false)
|
||||
private Long workflowInstanceId;
|
||||
|
||||
/**
|
||||
* 变量名
|
||||
* 变量名称
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 变量值
|
||||
* 变量值(JSON)
|
||||
*/
|
||||
@Column(columnDefinition = "TEXT")
|
||||
@Column(columnDefinition = "TEXT", nullable = false)
|
||||
private String value;
|
||||
|
||||
/**
|
||||
@ -38,4 +41,10 @@ public class WorkflowVariable extends Entity<Long> {
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 变量作用域
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
private String scope = VariableScopeEnum.INSTANCE.name();
|
||||
}
|
||||
@ -16,7 +16,9 @@ public enum NodeStatusEnum {
|
||||
COMPLETED("COMPLETED", "已完成"),
|
||||
FAILED("FAILED", "执行失败"),
|
||||
CANCELLED("CANCELLED", "已取消"),
|
||||
SKIPPED("SKIPPED", "已跳过");
|
||||
SKIPPED("SKIPPED", "已跳过"),
|
||||
SUSPENDED("SUSPENDED", "已暂停"),
|
||||
TERMINATED("TERMINATED", "已终止");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
@ -1,29 +1,21 @@
|
||||
package com.qqchen.deploy.backend.workflow.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 节点类型枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum NodeTypeEnum {
|
||||
|
||||
START("START", "开始节点"),
|
||||
END("END", "结束节点"),
|
||||
DEPLOY("DEPLOY", "部署节点"),
|
||||
APPROVAL("APPROVAL", "审批节点"),
|
||||
SCRIPT("SCRIPT", "脚本节点"),
|
||||
SHELL("SHELL", "Shell脚本节点"),
|
||||
CONFIG_SYNC("CONFIG_SYNC", "配置同步节点"),
|
||||
CONDITION("CONDITION", "条件节点"),
|
||||
PARALLEL("PARALLEL", "并行节点"),
|
||||
SERIAL("SERIAL", "串行节点");
|
||||
TASK("TASK", "任务节点"),
|
||||
GATEWAY("GATEWAY", "网关节点"),
|
||||
SUB_PROCESS("SUB_PROCESS", "子流程节点");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
NodeTypeEnum(String code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
private final String description;
|
||||
}
|
||||
@ -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", "已暂停"),
|
||||
COMPLETED("COMPLETED", "已完成"),
|
||||
FAILED("FAILED", "执行失败"),
|
||||
CANCELLED("CANCELLED", "已取消");
|
||||
CANCELLED("CANCELLED", "已取消"),
|
||||
SUSPENDED("SUSPENDED", "已暂停"),
|
||||
TERMINATED("TERMINATED", "已终止");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
@ -31,7 +33,7 @@ public enum WorkflowStatusEnum {
|
||||
* 判断是否为终态
|
||||
*/
|
||||
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.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 WorkflowDefinitionQuery extends BaseQuery {
|
||||
|
||||
/**
|
||||
* 工作流编码
|
||||
*/
|
||||
@QueryField(field = "code", type = QueryType.LIKE)
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 工作流名称
|
||||
*/
|
||||
@QueryField(field = "name", type = QueryType.LIKE)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 工作流类型
|
||||
*/
|
||||
@QueryField(field = "type")
|
||||
private String type;
|
||||
@QueryField(field = "code", type = QueryType.LIKE)
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 工作流状态
|
||||
*/
|
||||
@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.workflow.entity.NodeInstance;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
||||
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 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);
|
||||
@Query("SELECT n FROM NodeInstance n WHERE n.workflowInstanceId = :instanceId AND n.deleted = false ORDER BY n.createTime")
|
||||
List<NodeInstance> findByWorkflowInstanceIdOrderByCreateTime(@Param("instanceId") Long instanceId);
|
||||
|
||||
/**
|
||||
* 根据工作流实例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")
|
||||
List<NodeInstance> findByWorkflowInstanceIdAndStatus(@Param("workflowInstanceId") Long workflowInstanceId, @Param("status") String status);
|
||||
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> {
|
||||
|
||||
/**
|
||||
* 根据编码查询未删除的工作流定义
|
||||
*
|
||||
* @param code 工作流编码
|
||||
* @return 工作流定义
|
||||
* 根据编码查询工作流定义
|
||||
*/
|
||||
WorkflowDefinition findByCodeAndDeletedFalse(String code);
|
||||
|
||||
/**
|
||||
* 检查编码是否存在(排除已删除的)
|
||||
*
|
||||
* @param code 工作流编码
|
||||
* @return 是否存在
|
||||
* 检查编码是否存在
|
||||
*/
|
||||
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.workflow.entity.WorkflowInstance;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -14,21 +16,13 @@ import java.util.List;
|
||||
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")
|
||||
List<WorkflowInstance> findByProjectEnvId(@Param("projectEnvId") Long projectEnvId);
|
||||
@Query("SELECT i FROM WorkflowInstance i WHERE i.status IN :statuses AND i.deleted = false")
|
||||
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> findByProjectEnvIdAndStatus(@Param("projectEnvId") Long projectEnvId, @Param("status") String status);
|
||||
List<WorkflowInstance> findByProjectEnvIdAndDeletedFalseOrderByCreateTimeDesc(Long projectEnvId);
|
||||
}
|
||||
@ -1,11 +1,7 @@
|
||||
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.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.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@ -19,70 +15,19 @@ import java.util.List;
|
||||
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 " +
|
||||
"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")
|
||||
List<WorkflowLog> findNodeLogs(
|
||||
@Param("nodeInstanceId") Long nodeInstanceId,
|
||||
@Param("type") LogTypeEnum type,
|
||||
@Param("level") LogLevelEnum level
|
||||
);
|
||||
@Query("SELECT l FROM WorkflowLog l WHERE l.workflowInstanceId = :instanceId AND l.deleted = false ORDER BY l.createTime")
|
||||
List<WorkflowLog> findByWorkflowInstanceId(@Param("instanceId") Long instanceId);
|
||||
|
||||
/**
|
||||
* 分页查询节点日志
|
||||
*
|
||||
* @param nodeInstanceId 节点实例ID
|
||||
* @param type 日志类型
|
||||
* @param level 日志级别
|
||||
* @param pageable 分页参数
|
||||
* @return 日志分页
|
||||
* 查询节点实例的所有日志
|
||||
*/
|
||||
@Query("SELECT l FROM WorkflowLog l WHERE l.nodeInstanceId = :nodeInstanceId " +
|
||||
"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> 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
|
||||
);
|
||||
@Query("SELECT l FROM WorkflowLog l WHERE l.workflowInstanceId = :instanceId AND l.nodeId = :nodeId AND l.deleted = false ORDER BY l.createTime")
|
||||
List<WorkflowLog> findByWorkflowInstanceIdAndNodeId(@Param("instanceId") Long instanceId, @Param("nodeId") String nodeId);
|
||||
|
||||
/**
|
||||
* 删除工作流实例的所有日志
|
||||
*
|
||||
* @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.workflow.entity.WorkflowVariable;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
@ -14,34 +16,24 @@ import java.util.Optional;
|
||||
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
|
||||
* @param name 变量名
|
||||
* @return 变量
|
||||
* 查询工作流实例的指定作用域的变量
|
||||
*/
|
||||
@Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstanceId = :instanceId AND v.scope = :scope AND v.deleted = false")
|
||||
List<WorkflowVariable> findByWorkflowInstanceIdAndScope(@Param("instanceId") Long instanceId, @Param("scope") String scope);
|
||||
|
||||
/**
|
||||
* 查询工作流实例的指定变量
|
||||
*/
|
||||
Optional<WorkflowVariable> findByWorkflowInstanceIdAndName(Long workflowInstanceId, String name);
|
||||
|
||||
/**
|
||||
* 根据工作流实例ID和变量名删除变量
|
||||
*
|
||||
* @param workflowInstanceId 工作流实例ID
|
||||
* @param name 变量名
|
||||
* 删除工作流实例的所有变量
|
||||
*/
|
||||
void deleteByWorkflowInstanceIdAndName(Long workflowInstanceId, String name);
|
||||
|
||||
/**
|
||||
* 根据工作流实例ID删除所有变量
|
||||
*
|
||||
* @param workflowInstanceId 工作流实例ID
|
||||
*/
|
||||
void deleteByWorkflowInstanceId(Long workflowInstanceId);
|
||||
void deleteByWorkflowInstanceId(Long instanceId);
|
||||
}
|
||||
@ -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;
|
||||
|
||||
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.enums.WorkflowStatusEnum;
|
||||
|
||||
/**
|
||||
* 工作流定义服务接口
|
||||
@ -10,26 +11,32 @@ import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||
public interface IWorkflowDefinitionService extends IBaseService<WorkflowDefinition, WorkflowDefinitionDTO, Long> {
|
||||
|
||||
/**
|
||||
* 发布工作流
|
||||
*
|
||||
* @param id 工作流定义ID
|
||||
* @return 是否成功
|
||||
* 发布工作流定义
|
||||
*/
|
||||
boolean publish(Long id);
|
||||
void publish(Long id);
|
||||
|
||||
/**
|
||||
* 禁用工作流
|
||||
*
|
||||
* @param id 工作流定义ID
|
||||
* @return 是否成功
|
||||
* 禁用工作流定义
|
||||
*/
|
||||
boolean disable(Long id);
|
||||
void disable(Long id);
|
||||
|
||||
/**
|
||||
* 根据编码查询工作流定义
|
||||
*
|
||||
* @param code 工作流编码
|
||||
* @return 工作流定义
|
||||
* 启用工作流定义
|
||||
*/
|
||||
WorkflowDefinitionDTO findByCode(String code);
|
||||
void enable(Long id);
|
||||
|
||||
/**
|
||||
* 检查工作流名称是否唯一
|
||||
*/
|
||||
void checkNameUnique(String name);
|
||||
|
||||
/**
|
||||
* 检查工作流编码是否唯一
|
||||
*/
|
||||
void checkCodeUnique(String code);
|
||||
|
||||
/**
|
||||
* 验证工作流状态
|
||||
*/
|
||||
void validateStatus(WorkflowStatusEnum status);
|
||||
}
|
||||
@ -1,53 +1,11 @@
|
||||
package com.qqchen.deploy.backend.workflow.service;
|
||||
|
||||
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.enums.WorkflowStatusEnum;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流实例服务接口
|
||||
*/
|
||||
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;
|
||||
|
||||
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||
import com.qqchen.deploy.backend.enums.LogTypeEnum;
|
||||
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowLogDTO;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* 记录工作流取消日志
|
||||
*
|
||||
* @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);
|
||||
void deleteLogs(Long workflowInstanceId);
|
||||
}
|
||||
@ -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;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 工作流变量服务接口
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 删除工作流变量
|
||||
*
|
||||
* @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;
|
||||
|
||||
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.api.dto.WorkflowDefinitionDTO;
|
||||
import com.qqchen.deploy.backend.workflow.converter.WorkflowDefinitionConverter;
|
||||
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;
|
||||
@ -12,52 +13,69 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 工作流定义服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefinition, WorkflowDefinitionDTO, Long>
|
||||
public class WorkflowDefinitionServiceImpl
|
||||
extends BaseServiceImpl<WorkflowDefinition, WorkflowDefinitionDTO, Long>
|
||||
implements IWorkflowDefinitionService {
|
||||
|
||||
@Resource
|
||||
private IWorkflowDefinitionRepository workflowDefinitionRepository;
|
||||
|
||||
@Resource
|
||||
private WorkflowDefinitionConverter workflowDefinitionConverter;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean publish(Long id) {
|
||||
WorkflowDefinition definition = findEntityById(id);
|
||||
definition.setStatus(WorkflowStatusEnum.PUBLISHED.getCode());
|
||||
workflowDefinitionRepository.save(definition);
|
||||
return true;
|
||||
public void publish(Long id) {
|
||||
WorkflowDefinition definition = findByIdWithLock(id);
|
||||
if (definition.getStatus() != WorkflowStatusEnum.DRAFT) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_NOT_DRAFT);
|
||||
}
|
||||
definition.setStatus(WorkflowStatusEnum.PUBLISHED);
|
||||
repository.save(definition);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean disable(Long id) {
|
||||
WorkflowDefinition definition = findEntityById(id);
|
||||
definition.setStatus(WorkflowStatusEnum.DISABLED.getCode());
|
||||
workflowDefinitionRepository.save(definition);
|
||||
return true;
|
||||
public void disable(Long id) {
|
||||
WorkflowDefinition definition = findByIdWithLock(id);
|
||||
if (definition.getStatus() != WorkflowStatusEnum.PUBLISHED) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_NOT_PUBLISHED);
|
||||
}
|
||||
definition.setStatus(WorkflowStatusEnum.DISABLED);
|
||||
repository.save(definition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkflowDefinitionDTO findByCode(String code) {
|
||||
WorkflowDefinition definition = workflowDefinitionRepository.findByCodeAndDeletedFalse(code);
|
||||
return workflowDefinitionConverter.toDto(definition);
|
||||
@Transactional
|
||||
public void enable(Long id) {
|
||||
WorkflowDefinition definition = findByIdWithLock(id);
|
||||
if (definition.getStatus() != WorkflowStatusEnum.DISABLED) {
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_NOT_DISABLED);
|
||||
}
|
||||
definition.setStatus(WorkflowStatusEnum.PUBLISHED);
|
||||
repository.save(definition);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public WorkflowDefinitionDTO save(WorkflowDefinitionDTO dto) {
|
||||
// 检查编码是否已存在
|
||||
if (workflowDefinitionRepository.existsByCodeAndDeletedFalse(dto.getCode())) {
|
||||
throw new IllegalArgumentException("工作流编码已存在");
|
||||
}
|
||||
// 设置初始状态为草稿
|
||||
dto.setStatus(WorkflowStatusEnum.DRAFT.getCode());
|
||||
return super.create(dto);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,66 +1,16 @@
|
||||
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.WorkflowInstanceDTO;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
||||
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 jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流实例服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
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;
|
||||
|
||||
import com.qqchen.deploy.backend.enums.LogLevelEnum;
|
||||
import com.qqchen.deploy.backend.enums.LogTypeEnum;
|
||||
import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowLogDTO;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowLog;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowLogRepository;
|
||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService;
|
||||
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.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 工作流日志服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WorkflowLogServiceImpl implements IWorkflowLogService {
|
||||
public class WorkflowLogServiceImpl extends BaseServiceImpl<WorkflowLog, WorkflowLogDTO, Long> implements IWorkflowLogService {
|
||||
|
||||
@Resource
|
||||
private IWorkflowLogRepository workflowLogRepository;
|
||||
private IWorkflowLogRepository logRepository;
|
||||
|
||||
@Override
|
||||
public List<WorkflowLog> findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level) {
|
||||
return workflowLogRepository.findNodeLogs(nodeInstanceId, type, level);
|
||||
}
|
||||
|
||||
@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) {
|
||||
@Transactional
|
||||
public void log(Long workflowInstanceId, String nodeId, String message, LogLevelEnum level, String detail) {
|
||||
WorkflowLog log = new WorkflowLog();
|
||||
log.setWorkflowInstanceId(instance.getId());
|
||||
log.setType(LogTypeEnum.WORKFLOW_START);
|
||||
log.setLevel(LogLevelEnum.INFO);
|
||||
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.setWorkflowInstanceId(workflowInstanceId);
|
||||
log.setNodeId(nodeId);
|
||||
log.setContent(message);
|
||||
log.setLevel(level);
|
||||
log.setContent(content);
|
||||
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;
|
||||
|
||||
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.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.enums.VariableScopeEnum;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowVariableRepository;
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 工作流变量服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WorkflowVariableServiceImpl implements IWorkflowVariableService {
|
||||
public class WorkflowVariableServiceImpl extends BaseServiceImpl<WorkflowVariable, WorkflowVariableDTO, Long> implements IWorkflowVariableService {
|
||||
|
||||
@Resource
|
||||
private IWorkflowVariableRepository workflowVariableRepository;
|
||||
private IWorkflowVariableRepository variableRepository;
|
||||
|
||||
@Override
|
||||
@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);
|
||||
}
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
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 {
|
||||
switch (variable.getType()) {
|
||||
case "String":
|
||||
return variable.getValue();
|
||||
case "Integer":
|
||||
return Integer.parseInt(variable.getValue());
|
||||
case "Long":
|
||||
return Long.parseLong(variable.getValue());
|
||||
case "Double":
|
||||
return Double.parseDouble(variable.getValue());
|
||||
case "Boolean":
|
||||
return Boolean.parseBoolean(variable.getValue());
|
||||
default:
|
||||
throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID);
|
||||
}
|
||||
WorkflowVariable variable = variableRepository.findByWorkflowInstanceIdAndName(workflowInstanceId, name)
|
||||
.orElse(new WorkflowVariable());
|
||||
|
||||
variable.setWorkflowInstanceId(workflowInstanceId);
|
||||
variable.setName(name);
|
||||
variable.setValue(objectMapper.writeValueAsString(value));
|
||||
variable.setType(value.getClass().getName());
|
||||
|
||||
repository.save(variable);
|
||||
} 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 '乐观锁版本号',
|
||||
|
||||
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='角色标签表';
|
||||
|
||||
-- 角色标签关联表
|
||||
@ -378,111 +378,123 @@ CREATE TABLE deploy_repo_branch (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代码仓库分支表';
|
||||
|
||||
-- 工作流定义表
|
||||
CREATE TABLE sys_workflow_definition (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||
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',
|
||||
|
||||
code VARCHAR(50) NOT NULL COMMENT '工作流编码',
|
||||
CREATE TABLE wf_definition (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||
name VARCHAR(100) NOT NULL COMMENT '工作流名称',
|
||||
code VARCHAR(50) NOT NULL COMMENT '工作流编码',
|
||||
description TEXT COMMENT '描述',
|
||||
content TEXT NOT NULL COMMENT '工作流定义(JSON)',
|
||||
type VARCHAR(20) NOT NULL COMMENT '类型:DEPLOY/CONFIG_SYNC',
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'DRAFT' COMMENT '状态:DRAFT/PUBLISHED/DISABLED',
|
||||
status VARCHAR(20) NOT NULL COMMENT '状态',
|
||||
node_config TEXT COMMENT '节点配置(JSON)',
|
||||
transition_config TEXT COMMENT '流转配置(JSON)',
|
||||
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_code (code)
|
||||
) 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 (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||
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',
|
||||
-- 节点定义表
|
||||
CREATE TABLE wf_node_definition (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||
workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
|
||||
node_id VARCHAR(50) NOT NULL COMMENT '节点ID',
|
||||
name VARCHAR(100) NOT NULL COMMENT '节点名称',
|
||||
type VARCHAR(50) NOT NULL COMMENT '节点类型',
|
||||
config TEXT COMMENT '节点配置(JSON)',
|
||||
sequence INT NOT NULL DEFAULT 0 COMMENT '序号',
|
||||
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',
|
||||
project_env_id BIGINT NOT NULL COMMENT '项目环境ID',
|
||||
status VARCHAR(20) NOT NULL COMMENT '状态:RUNNING/COMPLETED/FAILED/CANCELED',
|
||||
business_key VARCHAR(100) NOT NULL COMMENT '业务标识',
|
||||
status VARCHAR(20) NOT NULL COMMENT '状态',
|
||||
variables TEXT COMMENT '工作流变量(JSON)',
|
||||
start_time DATETIME NOT NULL COMMENT '开始时间',
|
||||
end_time DATETIME COMMENT '结束时间',
|
||||
variables TEXT COMMENT '工作流变量(JSON)',
|
||||
error TEXT COMMENT '错误信息',
|
||||
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 '是否删除',
|
||||
CONSTRAINT fk_instance_definition FOREIGN KEY (definition_id) REFERENCES wf_definition(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流实例表';
|
||||
|
||||
CONSTRAINT fk_instance_definition FOREIGN KEY (definition_id) REFERENCES sys_workflow_definition(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流实例表';
|
||||
|
||||
-- 节点实例表
|
||||
CREATE TABLE sys_node_instance (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||
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',
|
||||
|
||||
-- 节点实例表(修改现有表)
|
||||
DROP TABLE IF EXISTS sys_node_instance;
|
||||
CREATE TABLE wf_node_instance (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
|
||||
node_id VARCHAR(50) NOT NULL COMMENT '节点ID',
|
||||
node_type VARCHAR(50) NOT NULL COMMENT '节点类型',
|
||||
name VARCHAR(100) NOT NULL COMMENT '节点名称',
|
||||
status VARCHAR(20) NOT NULL COMMENT '状态:PENDING/RUNNING/COMPLETED/FAILED/CANCELED',
|
||||
start_time DATETIME COMMENT '开始时间',
|
||||
end_time DATETIME COMMENT '结束时间',
|
||||
status VARCHAR(20) NOT NULL COMMENT '状<EFBFBD><EFBFBD><EFBFBD>',
|
||||
config TEXT COMMENT '节点配置(JSON)',
|
||||
input TEXT COMMENT '输入参数(JSON)',
|
||||
output TEXT COMMENT '输出结果(JSON)',
|
||||
error TEXT COMMENT '错误信息',
|
||||
|
||||
CONSTRAINT fk_node_workflow_instance FOREIGN KEY (workflow_instance_id) REFERENCES sys_workflow_instance(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='节点实例表';
|
||||
start_time DATETIME COMMENT '开始时间',
|
||||
end_time DATETIME COMMENT '结束时间',
|
||||
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 '是否删除',
|
||||
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 (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
|
||||
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
|
||||
name VARCHAR(255) NOT NULL COMMENT '变量名',
|
||||
value TEXT COMMENT '变量值',
|
||||
type VARCHAR(255) NOT NULL COMMENT '变量类型',
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
create_by BIGINT NOT NULL COMMENT '创建人',
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
update_by BIGINT NOT NULL COMMENT '更新人',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
|
||||
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
INDEX idx_workflow_instance_id (workflow_instance_id),
|
||||
INDEX idx_name (name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流变量';
|
||||
CREATE TABLE wf_variable (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
workflow_instance_id BIGINT NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
type VARCHAR(255),
|
||||
scope VARCHAR(50),
|
||||
create_time DATETIME NOT NULL,
|
||||
create_by VARCHAR(50),
|
||||
update_time DATETIME,
|
||||
update_by VARCHAR(50),
|
||||
version INT DEFAULT 0,
|
||||
deleted BOOLEAN DEFAULT FALSE,
|
||||
CONSTRAINT uk_wf_variable UNIQUE (workflow_instance_id, name, deleted)
|
||||
);
|
||||
|
||||
-- 工作流日志表
|
||||
CREATE TABLE wf_workflow_log (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
|
||||
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
|
||||
node_instance_id BIGINT COMMENT '节点实例ID',
|
||||
type VARCHAR(50) NOT NULL COMMENT '日志类型',
|
||||
level VARCHAR(20) NOT NULL COMMENT '日志级别',
|
||||
content TEXT NOT NULL COMMENT '日志内容',
|
||||
detail TEXT COMMENT '详细信息',
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
create_by BIGINT NOT NULL COMMENT '创建人',
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
update_by BIGINT NOT NULL COMMENT '更新人',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
|
||||
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),
|
||||
INDEX idx_level (level)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流日志';
|
||||
CREATE TABLE wf_log (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
workflow_instance_id BIGINT NOT NULL,
|
||||
node_id VARCHAR(50),
|
||||
message VARCHAR(1000) NOT NULL,
|
||||
level VARCHAR(20) NOT NULL,
|
||||
detail TEXT,
|
||||
create_time DATETIME NOT NULL,
|
||||
create_by VARCHAR(50),
|
||||
update_time DATETIME,
|
||||
update_by VARCHAR(50),
|
||||
version INT DEFAULT 0,
|
||||
deleted BOOLEAN DEFAULT FALSE
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_wf_variable_instance ON wf_variable (workflow_instance_id);
|
||||
CREATE INDEX idx_wf_log_instance ON wf_log (workflow_instance_id);
|
||||
CREATE INDEX idx_wf_log_node ON wf_log (workflow_instance_id, node_id);
|
||||
|
||||
-- 工作流权限表
|
||||
CREATE TABLE wf_workflow_permission (
|
||||
|
||||
@ -151,120 +151,3 @@ INSERT INTO sys_external_system (
|
||||
'TOKEN', NULL, NULL, 'cNSud7D1GmYQKEMco7s5',
|
||||
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'
|
||||
);
|
||||
@ -110,3 +110,42 @@ node.instance.not.found=节点实例不存在
|
||||
node.instance.cannot.retry=节点实例无法重试
|
||||
node.instance.cannot.skip=节点实例无法跳过
|
||||
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.exception.BusinessException;
|
||||
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.repository.IExternalSystemRepository;
|
||||
import com.qqchen.deploy.backend.system.service.impl.ExternalSystemServiceImpl;
|
||||
@ -38,9 +41,9 @@ class ExternalSystemServiceImplTest {
|
||||
system = new ExternalSystem();
|
||||
system.setId(1L);
|
||||
system.setName("测试Jenkins");
|
||||
system.setType(ExternalSystem.ExternalSystemSystemType.JENKINS);
|
||||
system.setType(ExternalSystemTypeEnum.JENKINS);
|
||||
system.setUrl("http://jenkins.test.com");
|
||||
system.setAuthType(ExternalSystem.AuthType.BASIC);
|
||||
system.setAuthType(ExternalSystemAuthTypeEnum.BASIC);
|
||||
system.setUsername("admin");
|
||||
system.setPassword("password");
|
||||
system.setEnabled(true);
|
||||
@ -48,9 +51,9 @@ class ExternalSystemServiceImplTest {
|
||||
|
||||
systemDTO = new ExternalSystemDTO();
|
||||
systemDTO.setName("测试Jenkins");
|
||||
systemDTO.setType(ExternalSystem.ExternalSystemSystemType.JENKINS);
|
||||
systemDTO.setType(ExternalSystemTypeEnum.JENKINS);
|
||||
systemDTO.setUrl("http://jenkins.test.com");
|
||||
systemDTO.setAuthType(ExternalSystem.AuthType.BASIC);
|
||||
systemDTO.setAuthType(ExternalSystemAuthTypeEnum.BASIC);
|
||||
systemDTO.setUsername("admin");
|
||||
systemDTO.setPassword("password");
|
||||
systemDTO.setEnabled(true);
|
||||
@ -127,7 +130,7 @@ class ExternalSystemServiceImplTest {
|
||||
externalSystemService.syncData(1L);
|
||||
|
||||
// 验证
|
||||
assertThat(system.getSyncStatus()).isEqualTo(ExternalSystem.SyncStatus.SUCCESS);
|
||||
assertThat(system.getSyncStatus()).isEqualTo(ExternalSystemSyncStatusEnum.SUCCESS);
|
||||
assertThat(system.getLastSyncTime()).isNotNull();
|
||||
verify(externalSystemRepository, times(2)).save(any(ExternalSystem.class));
|
||||
}
|
||||
@ -149,8 +152,8 @@ class ExternalSystemServiceImplTest {
|
||||
@Test
|
||||
void validateUniqueConstraints_WhenGitWithoutToken_ShouldThrowException() {
|
||||
// 准备数据
|
||||
systemDTO.setType(ExternalSystem.ExternalSystemSystemType.GIT);
|
||||
systemDTO.setAuthType(ExternalSystem.AuthType.TOKEN);
|
||||
systemDTO.setType(ExternalSystemTypeEnum.GIT);
|
||||
systemDTO.setAuthType(ExternalSystemAuthTypeEnum.TOKEN);
|
||||
systemDTO.setToken(null);
|
||||
|
||||
// Mock
|
||||
@ -167,8 +170,8 @@ class ExternalSystemServiceImplTest {
|
||||
@Test
|
||||
void validateUniqueConstraints_WhenGitWithWrongAuthType_ShouldThrowException() {
|
||||
// 准备数据
|
||||
systemDTO.setType(ExternalSystem.ExternalSystemSystemType.GIT);
|
||||
systemDTO.setAuthType(ExternalSystem.AuthType.BASIC);
|
||||
systemDTO.setType(ExternalSystemTypeEnum.GIT);
|
||||
systemDTO.setAuthType(ExternalSystemAuthTypeEnum.BASIC);
|
||||
|
||||
// Mock
|
||||
when(externalSystemRepository.existsByNameAndDeletedFalse(systemDTO.getName())).thenReturn(false);
|
||||
@ -184,8 +187,8 @@ class ExternalSystemServiceImplTest {
|
||||
@Test
|
||||
void update_WhenGitWithoutToken_ShouldThrowException() {
|
||||
// 准备数据
|
||||
systemDTO.setType(ExternalSystem.ExternalSystemSystemType.GIT);
|
||||
systemDTO.setAuthType(ExternalSystem.AuthType.TOKEN);
|
||||
systemDTO.setType(ExternalSystemTypeEnum.GIT);
|
||||
systemDTO.setAuthType(ExternalSystemAuthTypeEnum.TOKEN);
|
||||
systemDTO.setToken(null);
|
||||
|
||||
// 验证
|
||||
@ -197,8 +200,8 @@ class ExternalSystemServiceImplTest {
|
||||
@Test
|
||||
void update_WhenGitWithWrongAuthType_ShouldThrowException() {
|
||||
// 准备数据
|
||||
systemDTO.setType(ExternalSystem.ExternalSystemSystemType.GIT);
|
||||
systemDTO.setAuthType(ExternalSystem.AuthType.BASIC);
|
||||
systemDTO.setType(ExternalSystemTypeEnum.GIT);
|
||||
systemDTO.setAuthType(ExternalSystemAuthTypeEnum.BASIC);
|
||||
|
||||
// 验证
|
||||
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