打印了JENKINS节点日志
This commit is contained in:
parent
d0997f9e9f
commit
3f102ae1b2
@ -1,9 +1,6 @@
|
||||
package com.qqchen.deploy.backend.deploy.api;
|
||||
|
||||
import com.qqchen.deploy.backend.deploy.dto.DeployRequestDTO;
|
||||
import com.qqchen.deploy.backend.deploy.dto.DeployRecordFlowGraphDTO;
|
||||
import com.qqchen.deploy.backend.deploy.dto.DeployResultDTO;
|
||||
import com.qqchen.deploy.backend.deploy.dto.UserDeployableDTO;
|
||||
import com.qqchen.deploy.backend.deploy.dto.*;
|
||||
import com.qqchen.deploy.backend.deploy.service.IDeployRecordService;
|
||||
import com.qqchen.deploy.backend.deploy.service.IDeployService;
|
||||
import com.qqchen.deploy.backend.framework.api.Response;
|
||||
@ -16,6 +13,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* 部署管理API控制器
|
||||
@ -67,5 +66,26 @@ public class DeployApiController {
|
||||
) {
|
||||
return Response.success(deployRecordService.getDeployFlowGraph(deployRecordId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户的部署审批任务列表
|
||||
*/
|
||||
@Operation(summary = "获取我的部署审批任务", description = "查询当前登录用户待审批的部署任务,包含完整的部署业务上下文信息")
|
||||
@GetMapping("/my-approval-tasks")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public Response<List<DeployApprovalTaskDTO>> getMyApprovalTasks() {
|
||||
return Response.success(deployService.getMyApprovalTasks());
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成部署审批
|
||||
*/
|
||||
@Operation(summary = "完成部署审批", description = "完成部署审批任务,后续节点(Jenkins构建、通知等)将由Flowable异步执行")
|
||||
@PostMapping("/complete")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public Response<Void> completeApproval(@Validated @RequestBody DeployApprovalCompleteRequest request) {
|
||||
deployService.completeApproval(request);
|
||||
return Response.success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
package com.qqchen.deploy.backend.deploy.dto;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.enums.ApprovalResultEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 部署审批完成请求
|
||||
*
|
||||
* <p>用于审批部署任务,支持异步处理
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-05
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "部署审批完成请求")
|
||||
public class DeployApprovalCompleteRequest {
|
||||
|
||||
@NotBlank(message = "任务ID不能为空")
|
||||
@Schema(description = "Flowable任务ID", required = true, example = "c527e234-ba0a-11f0-a005-00ffaa86ab68")
|
||||
private String taskId;
|
||||
|
||||
@NotNull(message = "审批结果不能为空")
|
||||
@Schema(description = "审批结果: APPROVED=通过, REJECTED=拒绝", required = true, example = "APPROVED")
|
||||
private ApprovalResultEnum result;
|
||||
|
||||
@Schema(description = "审批意见", example = "同意部署到生产环境")
|
||||
private String comment;
|
||||
}
|
||||
|
||||
@ -0,0 +1,109 @@
|
||||
package com.qqchen.deploy.backend.deploy.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 部署审批任务DTO
|
||||
*
|
||||
* <p>扩展了审批任务的基本信息,增加部署相关的业务上下文
|
||||
* <p>用于部署审批列表,让审批人员快速了解审批内容
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-05
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "部署审批任务信息")
|
||||
public class DeployApprovalTaskDTO {
|
||||
|
||||
// ============ 审批任务基本信息 ============
|
||||
|
||||
@Schema(description = "任务ID")
|
||||
private String taskId;
|
||||
|
||||
@Schema(description = "任务名称")
|
||||
private String taskName;
|
||||
|
||||
@Schema(description = "任务描述")
|
||||
private String taskDescription;
|
||||
|
||||
@Schema(description = "流程实例ID")
|
||||
private String processInstanceId;
|
||||
|
||||
@Schema(description = "流程定义ID")
|
||||
private String processDefinitionId;
|
||||
|
||||
@Schema(description = "审批人")
|
||||
private String assignee;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "到期时间")
|
||||
private LocalDateTime dueDate;
|
||||
|
||||
@Schema(description = "审批标题")
|
||||
private String approvalTitle;
|
||||
|
||||
@Schema(description = "审批内容")
|
||||
private String approvalContent;
|
||||
|
||||
@Schema(description = "审批模式: SINGLE-单人审批, MULTI-多人会签, OR-多人或签")
|
||||
private String approvalMode;
|
||||
|
||||
@Schema(description = "是否允许转交")
|
||||
private Boolean allowDelegate;
|
||||
|
||||
@Schema(description = "是否允许加签")
|
||||
private Boolean allowAddSign;
|
||||
|
||||
@Schema(description = "是否必须填写意见")
|
||||
private Boolean requireComment;
|
||||
|
||||
// ============ 部署业务上下文信息 ============
|
||||
|
||||
@Schema(description = "部署记录ID")
|
||||
private Long deployRecordId;
|
||||
|
||||
@Schema(description = "业务标识(UUID)")
|
||||
private String businessKey;
|
||||
|
||||
@Schema(description = "团队ID")
|
||||
private Long teamId;
|
||||
|
||||
@Schema(description = "团队名称")
|
||||
private String teamName;
|
||||
|
||||
@Schema(description = "应用ID")
|
||||
private Long applicationId;
|
||||
|
||||
@Schema(description = "应用编码")
|
||||
private String applicationCode;
|
||||
|
||||
@Schema(description = "应用名称")
|
||||
private String applicationName;
|
||||
|
||||
@Schema(description = "环境ID")
|
||||
private Long environmentId;
|
||||
|
||||
@Schema(description = "环境编码")
|
||||
private String environmentCode;
|
||||
|
||||
@Schema(description = "环境名称")
|
||||
private String environmentName;
|
||||
|
||||
@Schema(description = "发起人")
|
||||
private String deployBy;
|
||||
|
||||
@Schema(description = "部署备注")
|
||||
private String deployRemark;
|
||||
|
||||
@Schema(description = "部署开始时间")
|
||||
private LocalDateTime deployStartTime;
|
||||
|
||||
@Schema(description = "待审批时长(毫秒)- 从任务创建到现在的时长")
|
||||
private Long pendingDuration;
|
||||
}
|
||||
|
||||
@ -3,9 +3,11 @@ package com.qqchen.deploy.backend.deploy.dto;
|
||||
import com.qqchen.deploy.backend.deploy.enums.DeployRecordStatusEnums;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowNodeInstanceDTO;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.workflow.WorkflowDefinitionGraph;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -19,22 +21,89 @@ import java.util.List;
|
||||
@Schema(description = "部署记录流程图信息")
|
||||
public class DeployRecordFlowGraphDTO {
|
||||
|
||||
// ============ 部署记录基本信息 ============
|
||||
|
||||
@Schema(description = "部署记录ID")
|
||||
private Long deployRecordId;
|
||||
|
||||
@Schema(description = "业务标识(UUID)")
|
||||
private String businessKey;
|
||||
|
||||
@Schema(description = "部署状态")
|
||||
private DeployRecordStatusEnums deployStatus;
|
||||
|
||||
@Schema(description = "部署人")
|
||||
private String deployBy;
|
||||
|
||||
@Schema(description = "部署备注")
|
||||
private String deployRemark;
|
||||
|
||||
@Schema(description = "部署开始时间")
|
||||
private LocalDateTime deployStartTime;
|
||||
|
||||
@Schema(description = "部署结束时间")
|
||||
private LocalDateTime deployEndTime;
|
||||
|
||||
@Schema(description = "部署总时长(毫秒)")
|
||||
private Long deployDuration;
|
||||
|
||||
// ============ 业务上下文信息 ============
|
||||
|
||||
@Schema(description = "应用名称")
|
||||
private String applicationName;
|
||||
|
||||
@Schema(description = "应用编码")
|
||||
private String applicationCode;
|
||||
|
||||
@Schema(description = "环境名称")
|
||||
private String environmentName;
|
||||
|
||||
@Schema(description = "团队名称")
|
||||
private String teamName;
|
||||
|
||||
// ============ 工作流实例信息 ============
|
||||
|
||||
@Schema(description = "工作流实例ID")
|
||||
private Long workflowInstanceId;
|
||||
|
||||
@Schema(description = "流程实例ID(Flowable)")
|
||||
private String processInstanceId;
|
||||
|
||||
@Schema(description = "部署状态")
|
||||
private DeployRecordStatusEnums deployStatus;
|
||||
@Schema(description = "工作流实例状态")
|
||||
private WorkflowInstanceStatusEnums workflowStatus;
|
||||
|
||||
@Schema(description = "工作流开始时间")
|
||||
private LocalDateTime workflowStartTime;
|
||||
|
||||
@Schema(description = "工作流结束时间")
|
||||
private LocalDateTime workflowEndTime;
|
||||
|
||||
@Schema(description = "工作流总时长(毫秒)")
|
||||
private Long workflowDuration;
|
||||
|
||||
// ============ 执行统计信息 ============
|
||||
|
||||
@Schema(description = "总节点数")
|
||||
private Integer totalNodeCount;
|
||||
|
||||
@Schema(description = "已执行节点数")
|
||||
private Integer executedNodeCount;
|
||||
|
||||
@Schema(description = "成功节点数")
|
||||
private Integer successNodeCount;
|
||||
|
||||
@Schema(description = "失败节点数")
|
||||
private Integer failedNodeCount;
|
||||
|
||||
@Schema(description = "运行中节点数")
|
||||
private Integer runningNodeCount;
|
||||
|
||||
// ============ 流程图数据 ============
|
||||
|
||||
@Schema(description = "流程图数据(画布快照,包含节点和边的位置信息)")
|
||||
private WorkflowDefinitionGraph graph;
|
||||
|
||||
@Schema(description = "节点执行状态列表(用于标记每个节点的执行状态)")
|
||||
@Schema(description = "节点执行状态列表(用于标记每个节点的执行状态,包含已执行和未执行节点)")
|
||||
private List<WorkflowNodeInstanceDTO> nodeInstances;
|
||||
|
||||
}
|
||||
|
||||
@ -25,6 +25,11 @@ public interface IDeployRecordRepository extends IBaseRepository<DeployRecord, L
|
||||
*/
|
||||
Optional<DeployRecord> findByWorkflowInstanceIdAndDeletedFalse(Long workflowInstanceId);
|
||||
|
||||
/**
|
||||
* 根据业务标识查询部署记录
|
||||
*/
|
||||
Optional<DeployRecord> findByBusinessKeyAndDeletedFalse(String businessKey);
|
||||
|
||||
/**
|
||||
* 根据团队应用ID查询最新部署记录
|
||||
*/
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
package com.qqchen.deploy.backend.deploy.service;
|
||||
|
||||
import com.qqchen.deploy.backend.deploy.dto.DeployRequestDTO;
|
||||
import com.qqchen.deploy.backend.deploy.dto.DeployResultDTO;
|
||||
import com.qqchen.deploy.backend.deploy.dto.UserDeployableDTO;
|
||||
import com.qqchen.deploy.backend.deploy.dto.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 部署服务接口
|
||||
@ -26,5 +26,22 @@ public interface IDeployService {
|
||||
* @return 部署结果
|
||||
*/
|
||||
DeployResultDTO executeDeploy(DeployRequestDTO request);
|
||||
|
||||
/**
|
||||
* 获取当前用户的部署审批任务列表
|
||||
* <p>查询当前登录用户待审批的部署任务,包含完整的部署业务上下文信息
|
||||
*
|
||||
* @return 部署审批任务列表
|
||||
*/
|
||||
List<DeployApprovalTaskDTO> getMyApprovalTasks();
|
||||
|
||||
/**
|
||||
* 完成部署审批
|
||||
* <p>完成部署审批任务,支持通过/拒绝两种结果
|
||||
* <p>后续节点(如Jenkins构建、通知)将由Flowable异步执行,不会阻塞HTTP请求
|
||||
*
|
||||
* @param request 审批完成请求
|
||||
*/
|
||||
void completeApproval(DeployApprovalCompleteRequest request);
|
||||
}
|
||||
|
||||
|
||||
@ -4,14 +4,19 @@ import com.qqchen.deploy.backend.deploy.converter.DeployRecordConverter;
|
||||
import com.qqchen.deploy.backend.deploy.dto.DeployRecordDTO;
|
||||
import com.qqchen.deploy.backend.deploy.dto.DeployRecordFlowGraphDTO;
|
||||
import com.qqchen.deploy.backend.deploy.entity.DeployRecord;
|
||||
import com.qqchen.deploy.backend.deploy.entity.Team;
|
||||
import com.qqchen.deploy.backend.deploy.entity.Application;
|
||||
import com.qqchen.deploy.backend.deploy.entity.Environment;
|
||||
import com.qqchen.deploy.backend.deploy.enums.DeployRecordStatusEnums;
|
||||
import com.qqchen.deploy.backend.deploy.query.DeployRecordQuery;
|
||||
import com.qqchen.deploy.backend.deploy.repository.IDeployRecordRepository;
|
||||
import com.qqchen.deploy.backend.deploy.repository.ITeamRepository;
|
||||
import com.qqchen.deploy.backend.deploy.repository.IApplicationRepository;
|
||||
import com.qqchen.deploy.backend.deploy.repository.IEnvironmentRepository;
|
||||
import com.qqchen.deploy.backend.deploy.service.IDeployRecordService;
|
||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.workflow.converter.WorkflowNodeInstanceConverter;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowNodeInstanceDTO;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowNodeInstance;
|
||||
@ -23,18 +28,23 @@ import com.qqchen.deploy.backend.workflow.util.FlowableUtils;
|
||||
import org.flowable.bpmn.model.BpmnModel;
|
||||
import org.flowable.bpmn.model.FlowElement;
|
||||
import org.flowable.bpmn.model.Process;
|
||||
import org.flowable.engine.HistoryService;
|
||||
import org.flowable.engine.RepositoryService;
|
||||
import org.flowable.variable.api.history.HistoricVariableInstance;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.workflow.WorkflowDefinitionGraph;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.workflow.WorkflowDefinitionGraphNode;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.workflow.WorkflowDefinitionGraphEdge;
|
||||
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.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@ -60,14 +70,17 @@ public class DeployRecordServiceImpl extends BaseServiceImpl<DeployRecord, Deplo
|
||||
@Resource
|
||||
private IWorkflowNodeInstanceRepository workflowNodeInstanceRepository;
|
||||
|
||||
@Resource
|
||||
private WorkflowNodeInstanceConverter workflowNodeInstanceConverter;
|
||||
|
||||
@Resource
|
||||
private RepositoryService repositoryService;
|
||||
|
||||
@Resource
|
||||
private HistoryService historyService;
|
||||
private ITeamRepository teamRepository;
|
||||
|
||||
@Resource
|
||||
private IApplicationRepository applicationRepository;
|
||||
|
||||
@Resource
|
||||
private IEnvironmentRepository environmentRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@ -217,110 +230,218 @@ public class DeployRecordServiceImpl extends BaseServiceImpl<DeployRecord, Deplo
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public DeployRecordFlowGraphDTO getDeployFlowGraph(Long deployRecordId) {
|
||||
// 1. 查询部署记录
|
||||
log.info("查询部署流程图: deployRecordId={}", deployRecordId);
|
||||
|
||||
// ============ 1. 查询基础数据 ============
|
||||
|
||||
// 1.1 查询部署记录
|
||||
DeployRecord deployRecord = deployRecordRepository.findById(deployRecordId)
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[]{"部署记录"}));
|
||||
|
||||
// 2. 查询工作流实例(包含流程图快照)
|
||||
// 1.2 查询工作流实例
|
||||
WorkflowInstance workflowInstance = workflowInstanceRepository.findById(deployRecord.getWorkflowInstanceId())
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[]{"工作流实例"}));
|
||||
|
||||
// 3. 查询节点实例列表(只查询实际执行过的节点)
|
||||
// 1.3 获取流程图快照
|
||||
WorkflowDefinitionGraph graphSnapshot = workflowInstance.getGraphSnapshot();
|
||||
if (graphSnapshot == null || graphSnapshot.getNodes() == null) {
|
||||
log.warn("工作流实例 {} 的流程图快照为空", workflowInstance.getId());
|
||||
return buildEmptyFlowGraphDTO(deployRecord, workflowInstance);
|
||||
}
|
||||
|
||||
// 1.4 查询业务上下文信息
|
||||
Team team = teamRepository.findById(deployRecord.getTeamId()).orElse(null);
|
||||
Application application = applicationRepository.findById(deployRecord.getApplicationId()).orElse(null);
|
||||
Environment environment = environmentRepository.findById(deployRecord.getEnvironmentId()).orElse(null);
|
||||
|
||||
// ============ 2. 查询并组装节点执行状态 ============
|
||||
|
||||
// 2.1 查询已执行的节点实例列表
|
||||
List<WorkflowNodeInstance> nodeInstances = workflowNodeInstanceRepository.findByWorkflowInstanceId(workflowInstance.getId());
|
||||
|
||||
// 4. 从BPMN模型中获取流程元素顺序(用于排序)
|
||||
// 2.2 构建已执行节点的映射(按nodeId索引)
|
||||
Map<String, WorkflowNodeInstance> nodeInstanceMap = nodeInstances.stream()
|
||||
.collect(Collectors.toMap(WorkflowNodeInstance::getNodeId, instance -> instance, (a, b) -> a));
|
||||
|
||||
// 2.3 从BPMN模型中获取流程元素顺序(用于排序)
|
||||
BpmnModel bpmnModel = repositoryService.getBpmnModel(workflowInstance.getProcessDefinitionId());
|
||||
Process process = bpmnModel.getMainProcess();
|
||||
List<FlowElement> flowElements = FlowableUtils.sortFlowElements(process);
|
||||
|
||||
// 5. 构建节点ID到流程顺序的映射(用于排序)
|
||||
// 2.4 构建节点ID到流程顺序的映射
|
||||
Map<String, Integer> nodeOrderMap = new HashMap<>();
|
||||
for (int i = 0; i < flowElements.size(); i++) {
|
||||
nodeOrderMap.put(flowElements.get(i).getId(), i);
|
||||
}
|
||||
|
||||
// 6. 按流程顺序排序节点实例(只包含实际执行过的节点)
|
||||
List<WorkflowNodeInstance> orderedNodeInstances = nodeInstances.stream()
|
||||
.sorted((a, b) -> {
|
||||
Integer orderA = nodeOrderMap.getOrDefault(a.getNodeId(), Integer.MAX_VALUE);
|
||||
Integer orderB = nodeOrderMap.getOrDefault(b.getNodeId(), Integer.MAX_VALUE);
|
||||
return orderA.compareTo(orderB);
|
||||
})
|
||||
// 2.5 遍历流程图中的所有节点,组装节点DTO(包括已执行和未执行的)
|
||||
List<WorkflowNodeInstanceDTO> nodeInstanceDTOs = graphSnapshot.getNodes().stream()
|
||||
.sorted(Comparator.comparingInt(node -> nodeOrderMap.getOrDefault(node.getId(), Integer.MAX_VALUE)))
|
||||
.map(graphNode -> buildNodeInstanceDTO(graphNode, nodeInstanceMap.get(graphNode.getId()), workflowInstance))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 7. 从历史流程变量中获取每个节点的 outputs 数据
|
||||
Map<String, Map<String, Object>> nodeOutputsMap = getNodeOutputsFromHistory(workflowInstance.getProcessInstanceId());
|
||||
// ============ 3. 计算执行统计 ============
|
||||
|
||||
// 8. 转换为 DTO 并填充 outputs 数据
|
||||
List<WorkflowNodeInstanceDTO> nodeInstanceDTOs = workflowNodeInstanceConverter.toDtoList(orderedNodeInstances);
|
||||
nodeInstanceDTOs.forEach(dto -> {
|
||||
// 从流程变量中获取该节点的 outputs 数据
|
||||
Map<String, Object> nodeOutputs = nodeOutputsMap.get(dto.getNodeId());
|
||||
if (nodeOutputs != null) {
|
||||
// 提取 outputs 部分(格式:{nodeId: {outputs: {...}}}
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> nodeData = (Map<String, Object>) nodeOutputs;
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> outputs = (Map<String, Object>) nodeData.get("outputs");
|
||||
if (outputs != null) {
|
||||
dto.setOutputs(outputs);
|
||||
}
|
||||
}
|
||||
});
|
||||
int totalNodeCount = nodeInstanceDTOs.size();
|
||||
int executedNodeCount = (int) nodeInstanceDTOs.stream()
|
||||
.filter(node -> node.getStatus() != WorkflowNodeInstanceStatusEnums.NOT_STARTED)
|
||||
.count();
|
||||
int successNodeCount = (int) nodeInstanceDTOs.stream()
|
||||
.filter(node -> node.getStatus() == WorkflowNodeInstanceStatusEnums.COMPLETED)
|
||||
.count();
|
||||
int failedNodeCount = (int) nodeInstanceDTOs.stream()
|
||||
.filter(node -> node.getStatus() == WorkflowNodeInstanceStatusEnums.FAILED)
|
||||
.count();
|
||||
int runningNodeCount = (int) nodeInstanceDTOs.stream()
|
||||
.filter(node -> node.getStatus() == WorkflowNodeInstanceStatusEnums.RUNNING)
|
||||
.count();
|
||||
|
||||
// ============ 4. 组装完整的DTO ============
|
||||
|
||||
// 9. 组装DTO
|
||||
DeployRecordFlowGraphDTO dto = new DeployRecordFlowGraphDTO();
|
||||
|
||||
// 4.1 部署记录基本信息
|
||||
dto.setDeployRecordId(deployRecord.getId());
|
||||
dto.setBusinessKey(deployRecord.getBusinessKey());
|
||||
dto.setDeployStatus(deployRecord.getStatus());
|
||||
dto.setDeployBy(deployRecord.getDeployBy());
|
||||
dto.setDeployRemark(deployRecord.getDeployRemark());
|
||||
dto.setDeployStartTime(deployRecord.getStartTime());
|
||||
dto.setDeployEndTime(deployRecord.getEndTime());
|
||||
dto.setDeployDuration(calculateDuration(deployRecord.getStartTime(), deployRecord.getEndTime()));
|
||||
|
||||
// 4.2 业务上下文信息
|
||||
dto.setApplicationName(application != null ? application.getAppName() : null);
|
||||
dto.setApplicationCode(application != null ? application.getAppCode() : null);
|
||||
dto.setEnvironmentName(environment != null ? environment.getEnvName() : null);
|
||||
dto.setTeamName(team != null ? team.getTeamName() : null);
|
||||
|
||||
// 4.3 工作流实例信息
|
||||
dto.setWorkflowInstanceId(workflowInstance.getId());
|
||||
dto.setProcessInstanceId(workflowInstance.getProcessInstanceId());
|
||||
dto.setDeployStatus(deployRecord.getStatus());
|
||||
dto.setGraph(workflowInstance.getGraphSnapshot()); // 流程图结构数据
|
||||
dto.setNodeInstances(nodeInstanceDTOs); // 节点执行状态(包含 outputs)
|
||||
dto.setWorkflowStatus(workflowInstance.getStatus());
|
||||
dto.setWorkflowStartTime(workflowInstance.getStartTime());
|
||||
dto.setWorkflowEndTime(workflowInstance.getEndTime());
|
||||
dto.setWorkflowDuration(calculateDuration(workflowInstance.getStartTime(), workflowInstance.getEndTime()));
|
||||
|
||||
// 4.4 执行统计信息
|
||||
dto.setTotalNodeCount(totalNodeCount);
|
||||
dto.setExecutedNodeCount(executedNodeCount);
|
||||
dto.setSuccessNodeCount(successNodeCount);
|
||||
dto.setFailedNodeCount(failedNodeCount);
|
||||
dto.setRunningNodeCount(runningNodeCount);
|
||||
|
||||
// 4.5 流程图数据
|
||||
dto.setGraph(graphSnapshot);
|
||||
dto.setNodeInstances(nodeInstanceDTOs);
|
||||
|
||||
log.info("获取部署流程图成功: deployRecordId={}, 总节点数={}, 已执行={}, 成功={}, 失败={}, 运行中={}",
|
||||
deployRecordId, totalNodeCount, executedNodeCount, successNodeCount, failedNodeCount, runningNodeCount);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从历史流程变量中获取所有节点的 outputs 数据
|
||||
* 构建节点实例DTO
|
||||
*
|
||||
* @param processInstanceId 流程实例ID
|
||||
* @return 节点ID到节点数据的映射(格式:{nodeId: {outputs: {...}}})
|
||||
* @param graphNode 流程图节点
|
||||
* @param nodeInstance 节点实例(可能为null,表示未执行)
|
||||
* @param workflowInstance 工作流实例
|
||||
* @return 节点实例DTO
|
||||
*/
|
||||
private Map<String, Map<String, Object>> getNodeOutputsFromHistory(String processInstanceId) {
|
||||
Map<String, Map<String, Object>> nodeOutputsMap = new HashMap<>();
|
||||
private WorkflowNodeInstanceDTO buildNodeInstanceDTO(
|
||||
WorkflowDefinitionGraphNode graphNode,
|
||||
WorkflowNodeInstance nodeInstance,
|
||||
WorkflowInstance workflowInstance) {
|
||||
|
||||
try {
|
||||
// 查询历史流程变量
|
||||
List<HistoricVariableInstance> variables = historyService
|
||||
.createHistoricVariableInstanceQuery()
|
||||
.processInstanceId(processInstanceId)
|
||||
.list();
|
||||
WorkflowNodeInstanceDTO dto = new WorkflowNodeInstanceDTO();
|
||||
|
||||
// 遍历变量,查找节点相关的变量(格式:{nodeId: {outputs: {...}}}
|
||||
for (HistoricVariableInstance variable : variables) {
|
||||
String variableName = variable.getVariableName();
|
||||
Object variableValue = variable.getValue();
|
||||
|
||||
// 检查是否是节点数据(节点ID通常是 sid_ 开头)
|
||||
if (variableName != null && variableValue instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> nodeData = (Map<String, Object>) variableValue;
|
||||
|
||||
// 检查是否包含 outputs 字段(这是节点数据的标识)
|
||||
if (nodeData.containsKey("outputs")) {
|
||||
nodeOutputsMap.put(variableName, nodeData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("从历史流程变量中获取节点 outputs: processInstanceId={}, nodeCount={}",
|
||||
processInstanceId, nodeOutputsMap.size());
|
||||
} catch (Exception e) {
|
||||
log.warn("获取节点 outputs 失败: processInstanceId={}", processInstanceId, e);
|
||||
if (nodeInstance != null) {
|
||||
// ✅ 已执行的节点:使用节点实例的数据
|
||||
dto.setId(nodeInstance.getId());
|
||||
dto.setNodeId(nodeInstance.getNodeId());
|
||||
dto.setNodeName(nodeInstance.getNodeName());
|
||||
dto.setNodeType(nodeInstance.getNodeType());
|
||||
dto.setStatus(nodeInstance.getStatus());
|
||||
dto.setStartTime(nodeInstance.getStartTime());
|
||||
dto.setEndTime(nodeInstance.getEndTime());
|
||||
dto.setDuration(calculateDuration(nodeInstance.getStartTime(), nodeInstance.getEndTime()));
|
||||
dto.setErrorMessage(nodeInstance.getErrorMessage()); // ✅ 包含错误信息
|
||||
dto.setProcessInstanceId(nodeInstance.getProcessInstanceId());
|
||||
dto.setCreateTime(nodeInstance.getCreateTime());
|
||||
dto.setUpdateTime(nodeInstance.getUpdateTime());
|
||||
} else {
|
||||
// ✅ 未执行的节点:从流程图节点获取基本信息
|
||||
dto.setId(null);
|
||||
dto.setNodeId(graphNode.getId());
|
||||
dto.setNodeName(graphNode.getNodeName());
|
||||
dto.setNodeType(graphNode.getNodeType() != null ? graphNode.getNodeType().name() : null);
|
||||
dto.setStatus(WorkflowNodeInstanceStatusEnums.NOT_STARTED);
|
||||
dto.setStartTime(null);
|
||||
dto.setEndTime(null);
|
||||
dto.setDuration(null);
|
||||
dto.setErrorMessage(null);
|
||||
dto.setProcessInstanceId(workflowInstance.getProcessInstanceId());
|
||||
dto.setCreateTime(null);
|
||||
dto.setUpdateTime(null);
|
||||
}
|
||||
|
||||
return nodeOutputsMap;
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算时长(毫秒)
|
||||
*
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return 时长(毫秒),如果任一时间为 null 则返回 null
|
||||
*/
|
||||
private Long calculateDuration(LocalDateTime startTime, LocalDateTime endTime) {
|
||||
if (startTime == null || endTime == null) {
|
||||
return null;
|
||||
}
|
||||
return java.time.Duration.between(startTime, endTime).toMillis();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建空的流程图DTO(当流程图快照为空时)
|
||||
*/
|
||||
private DeployRecordFlowGraphDTO buildEmptyFlowGraphDTO(DeployRecord deployRecord, WorkflowInstance workflowInstance) {
|
||||
DeployRecordFlowGraphDTO dto = new DeployRecordFlowGraphDTO();
|
||||
|
||||
// 部署记录基本信息
|
||||
dto.setDeployRecordId(deployRecord.getId());
|
||||
dto.setBusinessKey(deployRecord.getBusinessKey());
|
||||
dto.setDeployStatus(deployRecord.getStatus());
|
||||
dto.setDeployBy(deployRecord.getDeployBy());
|
||||
dto.setDeployRemark(deployRecord.getDeployRemark());
|
||||
dto.setDeployStartTime(deployRecord.getStartTime());
|
||||
dto.setDeployEndTime(deployRecord.getEndTime());
|
||||
dto.setDeployDuration(calculateDuration(deployRecord.getStartTime(), deployRecord.getEndTime()));
|
||||
|
||||
// 工作流实例信息
|
||||
dto.setWorkflowInstanceId(workflowInstance.getId());
|
||||
dto.setProcessInstanceId(workflowInstance.getProcessInstanceId());
|
||||
dto.setWorkflowStatus(workflowInstance.getStatus());
|
||||
dto.setWorkflowStartTime(workflowInstance.getStartTime());
|
||||
dto.setWorkflowEndTime(workflowInstance.getEndTime());
|
||||
dto.setWorkflowDuration(calculateDuration(workflowInstance.getStartTime(), workflowInstance.getEndTime()));
|
||||
|
||||
// 执行统计信息(都为0)
|
||||
dto.setTotalNodeCount(0);
|
||||
dto.setExecutedNodeCount(0);
|
||||
dto.setSuccessNodeCount(0);
|
||||
dto.setFailedNodeCount(0);
|
||||
dto.setRunningNodeCount(0);
|
||||
|
||||
// 流程图数据(空)
|
||||
dto.setGraph(new WorkflowDefinitionGraph());
|
||||
dto.setNodeInstances(new ArrayList<>());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -7,13 +7,17 @@ import com.qqchen.deploy.backend.deploy.service.IDeployService;
|
||||
import com.qqchen.deploy.backend.framework.security.SecurityUtils;
|
||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||
import com.qqchen.deploy.backend.framework.utils.SpelExpressionResolver;
|
||||
import com.qqchen.deploy.backend.system.entity.User;
|
||||
import com.qqchen.deploy.backend.system.repository.IUserRepository;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceStartRequest;
|
||||
import com.qqchen.deploy.backend.workflow.dto.inputmapping.ApprovalInputMapping;
|
||||
import com.qqchen.deploy.backend.workflow.dto.inputmapping.JenkinsBuildInputMapping;
|
||||
import com.qqchen.deploy.backend.workflow.dto.outputs.ApprovalOutputs;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||
import com.qqchen.deploy.backend.workflow.model.NodeContext;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
|
||||
import com.qqchen.deploy.backend.deploy.service.IDeployRecordService;
|
||||
@ -23,11 +27,17 @@ import com.qqchen.deploy.backend.deploy.enums.DeployRecordStatusEnums;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
|
||||
import org.flowable.engine.RuntimeService;
|
||||
import org.flowable.engine.TaskService;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -82,6 +92,12 @@ public class DeployServiceImpl implements IDeployService {
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Resource
|
||||
private TaskService taskService;
|
||||
|
||||
@Resource
|
||||
private RuntimeService runtimeService;
|
||||
|
||||
/**
|
||||
* 数据组装上下文(封装所有需要的Map,避免方法参数过多)
|
||||
*/
|
||||
@ -551,69 +567,198 @@ public class DeployServiceImpl implements IDeployService {
|
||||
@Override
|
||||
@Transactional
|
||||
public DeployResultDTO executeDeploy(DeployRequestDTO request) {
|
||||
// 1. 查询团队应用配置
|
||||
TeamApplication teamApp = teamApplicationRepository.findById(request.getTeamApplicationId()).orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND));
|
||||
log.info("开始执行部署: teamApplicationId={}, remark={}", request.getTeamApplicationId(), request.getRemark());
|
||||
|
||||
// 2. 查询工作流定义(获取 processKey)
|
||||
WorkflowDefinition workflowDefinition = workflowDefinitionRepository.findById(teamApp.getWorkflowDefinitionId()).orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[] {"工作流定义"}));
|
||||
// 1. 加载部署上下文数据
|
||||
DeployContext context = loadDeployContext(request.getTeamApplicationId());
|
||||
|
||||
// 3. 查询应用信息
|
||||
Application application = applicationRepository.findById(teamApp.getApplicationId()).orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[] {"应用"}));
|
||||
|
||||
// 4. 查询环境信息
|
||||
Environment environment = environmentRepository.findById(teamApp.getEnvironmentId()).orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[] {"环境"}));
|
||||
|
||||
// 5. 生成业务标识(UUID)
|
||||
// 2. 生成业务标识(UUID)
|
||||
String businessKey = UUID.randomUUID().toString();
|
||||
|
||||
// 6. 构造流程变量
|
||||
// 3. 构造流程变量
|
||||
Map<String, Object> variables = buildDeployVariables(context, request, businessKey);
|
||||
|
||||
// 4. 启动工作流并创建记录
|
||||
WorkflowInstanceDTO workflowInstance = startWorkflowAndCreateRecord(context, businessKey, variables, request.getRemark());
|
||||
|
||||
// 5. 返回结果
|
||||
return buildDeployResult(workflowInstance, businessKey, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载部署上下文数据
|
||||
*/
|
||||
private DeployContext loadDeployContext(Long teamApplicationId) {
|
||||
// 查询团队应用配置
|
||||
TeamApplication teamApp = teamApplicationRepository.findById(teamApplicationId)
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.TEAM_APPLICATION_NOT_FOUND));
|
||||
|
||||
// 查询工作流定义
|
||||
WorkflowDefinition workflowDefinition = workflowDefinitionRepository.findById(teamApp.getWorkflowDefinitionId())
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.DEPLOY_WORKFLOW_NOT_CONFIGURED));
|
||||
|
||||
// 查询应用信息
|
||||
Application application = applicationRepository.findById(teamApp.getApplicationId())
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND, new Object[]{"应用"}));
|
||||
|
||||
// 查询环境信息
|
||||
Environment environment = environmentRepository.findById(teamApp.getEnvironmentId())
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND, new Object[]{"环境"}));
|
||||
|
||||
// 查询团队环境配置
|
||||
TeamEnvironmentConfig teamEnvConfig = teamEnvironmentConfigRepository
|
||||
.findByTeamIdAndEnvironmentId(teamApp.getTeamId(), teamApp.getEnvironmentId())
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.DEPLOY_ENVIRONMENT_CONFIG_MISSING,
|
||||
new Object[]{teamApp.getTeamId(), teamApp.getEnvironmentId()}));
|
||||
|
||||
return new DeployContext(teamApp, workflowDefinition, application, environment, teamEnvConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建部署流程变量
|
||||
*/
|
||||
private Map<String, Object> buildDeployVariables(DeployContext context, DeployRequestDTO request, String businessKey) {
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
|
||||
// 部署上下文
|
||||
Map<String, Object> deployContext = new HashMap<>();
|
||||
deployContext.put("teamApplicationId", teamApp.getId());
|
||||
deployContext.put("teamId", teamApp.getTeamId());
|
||||
deployContext.put("applicationId", teamApp.getApplicationId());
|
||||
deployContext.put("applicationCode", application.getAppCode());
|
||||
deployContext.put("applicationName", application.getAppName());
|
||||
deployContext.put("environmentId", teamApp.getEnvironmentId());
|
||||
deployContext.put("environmentCode", environment.getEnvCode());
|
||||
deployContext.put("environmentName", environment.getEnvName());
|
||||
deployContext.put("by", SecurityUtils.getCurrentUsername());
|
||||
deployContext.put("remark", request.getRemark());
|
||||
variables.put("deploy", deployContext);
|
||||
variables.put("deploy", buildDeployContextMap(context, request));
|
||||
|
||||
// Jenkins 配置(使用强类型 JenkinsBuildInputMapping)
|
||||
if (teamApp.getDeploySystemId() != null && teamApp.getDeployJob() != null) {
|
||||
JenkinsBuildInputMapping jenkinsInput = new JenkinsBuildInputMapping();
|
||||
jenkinsInput.setServerId(teamApp.getDeploySystemId());
|
||||
jenkinsInput.setJobName(teamApp.getDeployJob());
|
||||
if (teamApp.getBranch() != null) {
|
||||
jenkinsInput.setBranch(teamApp.getBranch());
|
||||
}
|
||||
|
||||
// 转换为 Map(Flowable 只支持基本类型)
|
||||
variables.put("jenkins", objectMapper.convertValue(jenkinsInput, Map.class));
|
||||
variables.put("approval", Map.of("required", true, "userIds", "admin"));
|
||||
variables.put("notification", Map.of("channelId", 1));
|
||||
// Jenkins 配置
|
||||
Map<String, Object> jenkinsConfig = buildJenkinsConfig(context.teamApp);
|
||||
if (jenkinsConfig != null) {
|
||||
variables.put("jenkins", jenkinsConfig);
|
||||
}
|
||||
|
||||
// 审批配置
|
||||
variables.put("approval", buildApprovalConfig(context.teamEnvConfig));
|
||||
|
||||
// 7. 构造工作流启动请求
|
||||
// 通知配置
|
||||
variables.put("notification", buildNotificationConfig(context.teamEnvConfig));
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建部署上下文Map
|
||||
*/
|
||||
private Map<String, Object> buildDeployContextMap(DeployContext context, DeployRequestDTO request) {
|
||||
Map<String, Object> deployContext = new HashMap<>();
|
||||
deployContext.put("teamApplicationId", context.teamApp.getId());
|
||||
deployContext.put("teamId", context.teamApp.getTeamId());
|
||||
deployContext.put("applicationId", context.teamApp.getApplicationId());
|
||||
deployContext.put("applicationCode", context.application.getAppCode());
|
||||
deployContext.put("applicationName", context.application.getAppName());
|
||||
deployContext.put("environmentId", context.teamApp.getEnvironmentId());
|
||||
deployContext.put("environmentCode", context.environment.getEnvCode());
|
||||
deployContext.put("environmentName", context.environment.getEnvName());
|
||||
deployContext.put("by", SecurityUtils.getCurrentUsername());
|
||||
deployContext.put("remark", request.getRemark());
|
||||
return deployContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建Jenkins配置
|
||||
*/
|
||||
private Map<String, Object> buildJenkinsConfig(TeamApplication teamApp) {
|
||||
if (teamApp.getDeploySystemId() == null || teamApp.getDeployJob() == null) {
|
||||
log.warn("未配置Jenkins构建任务: teamApplicationId={}", teamApp.getId());
|
||||
return null;
|
||||
}
|
||||
|
||||
JenkinsBuildInputMapping jenkinsInput = new JenkinsBuildInputMapping();
|
||||
jenkinsInput.setServerId(teamApp.getDeploySystemId());
|
||||
jenkinsInput.setJobName(teamApp.getDeployJob());
|
||||
if (teamApp.getBranch() != null) {
|
||||
jenkinsInput.setBranch(teamApp.getBranch());
|
||||
}
|
||||
|
||||
// 转换为 Map(Flowable 只支持基本类型)
|
||||
return objectMapper.convertValue(jenkinsInput, Map.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建审批配置
|
||||
*/
|
||||
private Map<String, Object> buildApprovalConfig(TeamEnvironmentConfig teamEnvConfig) {
|
||||
Map<String, Object> approvalConfig = new HashMap<>();
|
||||
Boolean approvalRequired = teamEnvConfig.getApprovalRequired() != null ? teamEnvConfig.getApprovalRequired() : false;
|
||||
approvalConfig.put("required", approvalRequired);
|
||||
|
||||
// 处理审批人ID列表:转换为逗号分隔的用户名字符串
|
||||
if (teamEnvConfig.getApproverUserIds() != null && !teamEnvConfig.getApproverUserIds().isEmpty()) {
|
||||
List<User> approvers = userRepository.findAllById(teamEnvConfig.getApproverUserIds());
|
||||
if (approvers.isEmpty()) {
|
||||
throw new BusinessException(ResponseCode.USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
String userIds = approvers.stream()
|
||||
.map(User::getUsername)
|
||||
.collect(Collectors.joining(","));
|
||||
approvalConfig.put("userIds", userIds);
|
||||
log.debug("审批配置: required={}, userIds={}", approvalRequired, userIds);
|
||||
} else {
|
||||
// 如果要求审批但未配置审批人,抛出专属异常
|
||||
if (Boolean.TRUE.equals(approvalRequired)) {
|
||||
throw new BusinessException(ResponseCode.DEPLOY_APPROVER_NOT_CONFIGURED);
|
||||
}
|
||||
approvalConfig.put("userIds", "");
|
||||
}
|
||||
|
||||
return approvalConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建通知配置
|
||||
*/
|
||||
private Map<String, Object> buildNotificationConfig(TeamEnvironmentConfig teamEnvConfig) {
|
||||
Map<String, Object> notificationConfig = new HashMap<>();
|
||||
|
||||
if (teamEnvConfig.getNotificationChannelId() == null) {
|
||||
throw new BusinessException(ResponseCode.DEPLOY_NOTIFICATION_CONFIG_MISSING);
|
||||
}
|
||||
|
||||
notificationConfig.put("channelId", teamEnvConfig.getNotificationChannelId());
|
||||
log.debug("通知配置: channelId={}", teamEnvConfig.getNotificationChannelId());
|
||||
|
||||
return notificationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动工作流并创建部署记录
|
||||
*/
|
||||
private WorkflowInstanceDTO startWorkflowAndCreateRecord(DeployContext context, String businessKey,
|
||||
Map<String, Object> variables, String remark) {
|
||||
// 构造工作流启动请求
|
||||
WorkflowInstanceStartRequest workflowRequest = new WorkflowInstanceStartRequest();
|
||||
workflowRequest.setProcessKey(workflowDefinition.getKey());
|
||||
workflowRequest.setProcessKey(context.workflowDefinition.getKey());
|
||||
workflowRequest.setBusinessKey(businessKey);
|
||||
workflowRequest.setVariables(variables);
|
||||
|
||||
// 8. 启动工作流
|
||||
// 启动工作流
|
||||
WorkflowInstanceDTO workflowInstance = workflowInstanceService.startWorkflow(workflowRequest);
|
||||
|
||||
log.info("部署流程已启动: businessKey={}, workflowInstanceId={}, application={}, environment={}", businessKey, workflowInstance.getId(), application.getAppCode(), environment.getEnvCode());
|
||||
log.info("部署流程已启动: businessKey={}, workflowInstanceId={}, application={}, environment={}",
|
||||
businessKey, workflowInstance.getId(), context.application.getAppCode(), context.environment.getEnvCode());
|
||||
|
||||
// 9. 创建部署记录(此时已有实例ID)
|
||||
deployRecordService.createDeployRecord(workflowInstance.getId(), businessKey, teamApp.getId(), teamApp.getTeamId(), teamApp.getApplicationId(), teamApp.getEnvironmentId(), SecurityUtils.getCurrentUsername(), request.getRemark());
|
||||
// 创建部署记录
|
||||
deployRecordService.createDeployRecord(
|
||||
workflowInstance.getId(),
|
||||
businessKey,
|
||||
context.teamApp.getId(),
|
||||
context.teamApp.getTeamId(),
|
||||
context.teamApp.getApplicationId(),
|
||||
context.teamApp.getEnvironmentId(),
|
||||
SecurityUtils.getCurrentUsername(),
|
||||
remark
|
||||
);
|
||||
|
||||
// 10. 返回结果
|
||||
return workflowInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建部署结果DTO
|
||||
*/
|
||||
private DeployResultDTO buildDeployResult(WorkflowInstanceDTO workflowInstance, String businessKey, DeployContext context) {
|
||||
DeployResultDTO result = new DeployResultDTO();
|
||||
result.setWorkflowInstanceId(workflowInstance.getId());
|
||||
result.setBusinessKey(businessKey);
|
||||
@ -621,7 +766,283 @@ public class DeployServiceImpl implements IDeployService {
|
||||
result.setStatus(workflowInstance.getStatus().name());
|
||||
result.setMessage("部署流程已启动");
|
||||
|
||||
log.info("部署请求处理完成: businessKey={}, workflowInstanceId={}, application={}, environment={}",
|
||||
businessKey, workflowInstance.getId(), context.application.getAppCode(), context.environment.getEnvCode());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 部署上下文内部类
|
||||
*/
|
||||
private static class DeployContext {
|
||||
final TeamApplication teamApp;
|
||||
final WorkflowDefinition workflowDefinition;
|
||||
final Application application;
|
||||
final Environment environment;
|
||||
final TeamEnvironmentConfig teamEnvConfig;
|
||||
|
||||
DeployContext(TeamApplication teamApp, WorkflowDefinition workflowDefinition,
|
||||
Application application, Environment environment, TeamEnvironmentConfig teamEnvConfig) {
|
||||
this.teamApp = teamApp;
|
||||
this.workflowDefinition = workflowDefinition;
|
||||
this.application = application;
|
||||
this.environment = environment;
|
||||
this.teamEnvConfig = teamEnvConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeployApprovalTaskDTO> getMyApprovalTasks() {
|
||||
// 1. 获取当前登录用户
|
||||
String currentUsername = SecurityUtils.getCurrentUsername();
|
||||
log.info("查询用户 {} 的部署审批任务", currentUsername);
|
||||
|
||||
// 2. 查询用户的所有待办任务(模仿 ApprovalTaskService.getMyTasks)
|
||||
List<Task> tasks = taskService.createTaskQuery()
|
||||
.taskAssignee(currentUsername)
|
||||
.orderByTaskCreateTime()
|
||||
.desc()
|
||||
.list();
|
||||
|
||||
if (tasks.isEmpty()) {
|
||||
log.info("用户 {} 当前没有待办审批任务", currentUsername);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 3. 过滤出部署相关的任务并转换为DTO
|
||||
List<DeployApprovalTaskDTO> result = new ArrayList<>();
|
||||
for (Task task : tasks) {
|
||||
try {
|
||||
DeployApprovalTaskDTO dto = convertToDeployApprovalDTO(task);
|
||||
if (dto != null) {
|
||||
result.add(dto);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("转换审批任务失败: taskId={}, error={}", task.getId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
log.info("用户 {} 共有 {} 个部署审批任务", currentUsername, result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 Flowable Task 为部署审批 DTO
|
||||
* <p>模仿 ApprovalTaskService.convertToDTO 方法
|
||||
*
|
||||
* @param task Flowable 任务
|
||||
* @return 部署审批DTO,如果不是部署任务则返回 null
|
||||
*/
|
||||
private DeployApprovalTaskDTO convertToDeployApprovalDTO(Task task) {
|
||||
// 1. 获取流程变量
|
||||
Map<String, Object> variables = taskService.getVariables(task.getId());
|
||||
if (variables == null || variables.isEmpty()) {
|
||||
log.debug("任务 {} 没有流程变量,跳过", task.getId());
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 检查是否为部署流程(通过 deploy 变量判断)
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> deployContext = (Map<String, Object>) variables.get("deploy");
|
||||
|
||||
if (deployContext == null) {
|
||||
log.debug("任务 {} 不包含部署上下文,跳过", task.getId());
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 获取 BusinessKey(需要通过 RuntimeService 查询)
|
||||
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
|
||||
.processInstanceId(task.getProcessInstanceId())
|
||||
.singleResult();
|
||||
|
||||
if (processInstance == null) {
|
||||
log.debug("流程实例 {} 已结束,跳过", task.getProcessInstanceId());
|
||||
return null;
|
||||
}
|
||||
|
||||
String businessKey = processInstance.getBusinessKey();
|
||||
if (businessKey == null) {
|
||||
log.debug("任务 {} 的流程实例没有 businessKey,跳过", task.getId());
|
||||
return null;
|
||||
}
|
||||
|
||||
// 4. 查询部署记录
|
||||
Optional<DeployRecord> deployRecordOpt = deployRecordRepository
|
||||
.findByBusinessKeyAndDeletedFalse(businessKey);
|
||||
|
||||
if (deployRecordOpt.isEmpty()) {
|
||||
log.warn("找不到业务标识为 {} 的部署记录", businessKey);
|
||||
return null;
|
||||
}
|
||||
|
||||
DeployRecord deployRecord = deployRecordOpt.get();
|
||||
|
||||
// 5. 构建 DTO
|
||||
DeployApprovalTaskDTO dto = new DeployApprovalTaskDTO();
|
||||
|
||||
// 5.1 审批任务基本信息
|
||||
dto.setTaskId(task.getId());
|
||||
dto.setTaskName(task.getName());
|
||||
dto.setTaskDescription(task.getDescription());
|
||||
dto.setProcessInstanceId(task.getProcessInstanceId());
|
||||
dto.setProcessDefinitionId(task.getProcessDefinitionId());
|
||||
dto.setAssignee(task.getAssignee());
|
||||
|
||||
if (task.getCreateTime() != null) {
|
||||
dto.setCreateTime(LocalDateTime.ofInstant(
|
||||
task.getCreateTime().toInstant(),
|
||||
ZoneId.systemDefault()));
|
||||
}
|
||||
|
||||
if (task.getDueDate() != null) {
|
||||
dto.setDueDate(LocalDateTime.ofInstant(
|
||||
task.getDueDate().toInstant(),
|
||||
ZoneId.systemDefault()));
|
||||
}
|
||||
|
||||
// 5.2 审批配置信息(从流程变量获取)
|
||||
dto.setApprovalTitle((String) variables.get("approvalTitle"));
|
||||
dto.setApprovalContent((String) variables.get("approvalContent"));
|
||||
dto.setApprovalMode((String) variables.get("approvalMode"));
|
||||
dto.setAllowDelegate((Boolean) variables.get("allowDelegate"));
|
||||
dto.setAllowAddSign((Boolean) variables.get("allowAddSign"));
|
||||
dto.setRequireComment((Boolean) variables.get("requireComment"));
|
||||
|
||||
// 5.3 部署业务上下文信息
|
||||
dto.setDeployRecordId(deployRecord.getId());
|
||||
dto.setBusinessKey(businessKey);
|
||||
dto.setTeamId(getLongValue(deployContext, "teamId"));
|
||||
dto.setApplicationId(getLongValue(deployContext, "applicationId"));
|
||||
dto.setApplicationCode((String) deployContext.get("applicationCode"));
|
||||
dto.setApplicationName((String) deployContext.get("applicationName"));
|
||||
dto.setEnvironmentId(getLongValue(deployContext, "environmentId"));
|
||||
dto.setEnvironmentCode((String) deployContext.get("environmentCode"));
|
||||
dto.setEnvironmentName((String) deployContext.get("environmentName"));
|
||||
dto.setDeployBy((String) deployContext.get("by"));
|
||||
dto.setDeployRemark((String) deployContext.get("remark"));
|
||||
dto.setDeployStartTime(deployRecord.getStartTime());
|
||||
|
||||
// 5.4 查询团队名称
|
||||
Long teamId = dto.getTeamId();
|
||||
if (teamId != null) {
|
||||
teamRepository.findById(teamId).ifPresent(team ->
|
||||
dto.setTeamName(team.getTeamName())
|
||||
);
|
||||
}
|
||||
|
||||
// 5.5 计算待审批时长
|
||||
if (dto.getCreateTime() != null) {
|
||||
long pendingMillis = Duration.between(dto.getCreateTime(), LocalDateTime.now()).toMillis();
|
||||
dto.setPendingDuration(pendingMillis);
|
||||
}
|
||||
|
||||
// 5.6 自动解析 EL 表达式(如 ${deploy.applicationName})
|
||||
SpelExpressionResolver.resolveObject(dto, variables);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Map 中安全获取 Long 值
|
||||
*/
|
||||
private Long getLongValue(Map<String, Object> map, String key) {
|
||||
Object value = map.get(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Long) {
|
||||
return (Long) value;
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
return ((Integer) value).longValue();
|
||||
}
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return Long.parseLong((String) value);
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("无法将 {} 转换为 Long: {}", key, value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void completeApproval(DeployApprovalCompleteRequest request) {
|
||||
String taskId = request.getTaskId();
|
||||
log.info("开始处理部署审批: taskId={}, result={}", taskId, request.getResult());
|
||||
|
||||
try {
|
||||
// 1. 验证任务是否存在
|
||||
Task task = taskService.createTaskQuery()
|
||||
.taskId(taskId)
|
||||
.singleResult();
|
||||
|
||||
if (task == null) {
|
||||
log.error("审批任务不存在: taskId={}", taskId);
|
||||
throw new BusinessException(ResponseCode.DATA_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 2. 获取当前用户
|
||||
String currentUsername = SecurityUtils.getCurrentUsername();
|
||||
log.debug("审批人: {}, 任务负责人: {}", currentUsername, task.getAssignee());
|
||||
|
||||
// 3. 验证审批权限
|
||||
if (!currentUsername.equals(task.getAssignee())) {
|
||||
log.error("无权审批该任务: taskId={}, 当前用户={}, 任务负责人={}",
|
||||
taskId, currentUsername, task.getAssignee());
|
||||
throw new BusinessException(ResponseCode.PERMISSION_DENIED);
|
||||
}
|
||||
|
||||
// 4. 获取节点ID并更新 NodeContext(与 ApprovalTaskService 保持一致)
|
||||
String nodeId = task.getTaskDefinitionKey();
|
||||
|
||||
// 5. 读取现有 NodeContext
|
||||
Object nodeDataObj = taskService.getVariable(task.getId(), nodeId);
|
||||
NodeContext<ApprovalInputMapping, ApprovalOutputs> nodeContext;
|
||||
if (nodeDataObj instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> nodeDataMap = (Map<String, Object>) nodeDataObj;
|
||||
nodeContext = NodeContext.fromMap(nodeDataMap,
|
||||
ApprovalInputMapping.class, ApprovalOutputs.class, objectMapper);
|
||||
} else {
|
||||
nodeContext = new NodeContext<>();
|
||||
}
|
||||
|
||||
// 6. 创建审批结果(只设置核心字段,其他由 ApprovalEndExecutionListener 自动装配)
|
||||
ApprovalOutputs outputs = new ApprovalOutputs();
|
||||
outputs.setApprovalResult(request.getResult());
|
||||
outputs.setApprover(currentUsername);
|
||||
outputs.setApprovalTime(LocalDateTime.now());
|
||||
outputs.setApprovalComment(request.getComment());
|
||||
// ✅ 不需要手动设置 status 和 approvalDuration,监听器会自动装配
|
||||
|
||||
// 7. 设置到 NodeContext
|
||||
nodeContext.setOutputs(outputs);
|
||||
|
||||
// 8. 保存回流程变量
|
||||
taskService.setVariable(task.getId(), nodeId, nodeContext.toMap(objectMapper));
|
||||
|
||||
// 9. 添加任务评论(供历史查询)
|
||||
if (request.getComment() != null) {
|
||||
taskService.addComment(taskId, task.getProcessInstanceId(), request.getComment());
|
||||
}
|
||||
|
||||
// 10. 完成任务(触发 ApprovalEndExecutionListener,监听器会自动装配其他字段)
|
||||
taskService.complete(taskId);
|
||||
|
||||
log.info("部署审批已提交,后续节点将异步执行: taskId={}, result={}, comment={}",
|
||||
taskId, request.getResult(), request.getComment());
|
||||
|
||||
} catch (BusinessException e) {
|
||||
log.error("审批失败: taskId={}, error={}", taskId, e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("审批处理异常: taskId={}", taskId, e);
|
||||
throw new BusinessException(ResponseCode.ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -206,7 +206,22 @@ public enum ResponseCode {
|
||||
SERVER_IP_EXISTS(2951, "server.ip.exists"),
|
||||
SERVER_CATEGORY_NOT_FOUND(2952, "server.category.not.found"),
|
||||
SERVER_CATEGORY_CODE_EXISTS(2953, "server.category.code.exists"),
|
||||
SERVER_CATEGORY_HAS_SERVERS(2954, "server.category.has.servers");
|
||||
SERVER_CATEGORY_HAS_SERVERS(2954, "server.category.has.servers"),
|
||||
|
||||
// 部署相关错误码 (3000-3099)
|
||||
DEPLOY_CONFIG_NOT_FOUND(3000, "deploy.config.not.found"),
|
||||
DEPLOY_APPROVAL_CONFIG_MISSING(3001, "deploy.approval.config.missing"),
|
||||
DEPLOY_APPROVER_NOT_CONFIGURED(3002, "deploy.approver.not.configured"),
|
||||
DEPLOY_NOTIFICATION_CONFIG_MISSING(3003, "deploy.notification.config.missing"),
|
||||
DEPLOY_JENKINS_CONFIG_MISSING(3004, "deploy.jenkins.config.missing"),
|
||||
DEPLOY_WORKFLOW_NOT_CONFIGURED(3005, "deploy.workflow.not.configured"),
|
||||
DEPLOY_ENVIRONMENT_CONFIG_MISSING(3006, "deploy.environment.config.missing"),
|
||||
DEPLOY_APPLICATION_NOT_CONFIGURED(3007, "deploy.application.not.configured"),
|
||||
DEPLOY_ALREADY_RUNNING(3008, "deploy.already.running"),
|
||||
DEPLOY_PERMISSION_DENIED(3009, "deploy.permission.denied"),
|
||||
DEPLOY_ENVIRONMENT_LOCKED(3010, "deploy.environment.locked"),
|
||||
DEPLOY_APPROVAL_REQUIRED(3011, "deploy.approval.required"),
|
||||
DEPLOY_RECORD_NOT_FOUND(3012, "deploy.record.not.found");
|
||||
|
||||
private final int code;
|
||||
private final String messageKey; // 国际化消息key
|
||||
|
||||
@ -76,7 +76,7 @@ public class NotificationChannelServiceImpl
|
||||
try {
|
||||
adapter.testConnection(channel.getConfig());
|
||||
log.info("通知渠道连接测试成功: id={}, name={}", id, channel.getName());
|
||||
return true;
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("通知渠道连接测试失败: id={}, name={}, 错误: {}",
|
||||
id, channel.getName(), e.getMessage(), e);
|
||||
|
||||
@ -10,7 +10,7 @@ import java.util.Map;
|
||||
@Data
|
||||
public class WorkflowNodeInstanceDTO extends BaseDTO {
|
||||
|
||||
private Long id;
|
||||
// 注意:id, createTime, updateTime 继承自 BaseDTO
|
||||
|
||||
private String processInstanceId;
|
||||
|
||||
@ -28,9 +28,17 @@ public class WorkflowNodeInstanceDTO extends BaseDTO {
|
||||
|
||||
private LocalDateTime endTime;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
/**
|
||||
* 执行时长(毫秒)
|
||||
* 如果节点未执行或正在执行,则为null
|
||||
*/
|
||||
private Long duration;
|
||||
|
||||
private LocalDateTime updateTime;
|
||||
/**
|
||||
* 错误信息
|
||||
* 当节点执行失败时,记录失败原因
|
||||
*/
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 节点执行结果(outputs)
|
||||
|
||||
@ -396,6 +396,10 @@ public class BpmnConverter {
|
||||
serviceTask.setImplementation(delegateExpression);
|
||||
}
|
||||
|
||||
// ✅ 设置异步执行(关键修复:防止阻塞)
|
||||
serviceTask.setAsynchronous(true);
|
||||
log.debug("节点 {} 已配置为异步执行", validId);
|
||||
|
||||
// ✅ 添加 field 字段(nodeId, configs, inputMapping)
|
||||
addExecutionVariables(extensionElements, node, validId);
|
||||
|
||||
|
||||
@ -229,3 +229,18 @@ schedule.job.executor.not.found=找不到任务执行器:{0}
|
||||
|
||||
# 团队配置相关错误消息 (2920-2949)
|
||||
team.config.not.found=团队ID为 {0} 的配置不存在
|
||||
|
||||
# 部署相关错误码 (3000-3099)
|
||||
deploy.config.not.found=部署配置不存在
|
||||
deploy.approval.config.missing=未配置审批策略,无法执行部署
|
||||
deploy.approver.not.configured=该环境需要审批,但未配置审批人
|
||||
deploy.notification.config.missing=未配置通知渠道,无法发送部署通知
|
||||
deploy.jenkins.config.missing=未配置Jenkins构建任务,无法执行部署
|
||||
deploy.workflow.not.configured=应用未配置部署工作流
|
||||
deploy.environment.config.missing=团队环境配置不存在:teamId={0}, environmentId={1}
|
||||
deploy.application.not.configured=应用未配置到此环境
|
||||
deploy.already.running=该应用正在部署中,请等待当前部署完成
|
||||
deploy.permission.denied=无权限在此环境部署应用
|
||||
deploy.environment.locked=环境已锁定,禁止部署
|
||||
deploy.approval.required=该环境需要审批才能部署
|
||||
deploy.record.not.found=部署记录不存在
|
||||
@ -170,3 +170,18 @@ form.definition.not.found=Form definition not found or has been deleted
|
||||
form.data.not.found=Form data not found or has been deleted
|
||||
form.definition.key.exists=Form key {0} already exists, please use a different key
|
||||
form.definition.key.version.exists=Form key {0} version {1} already exists
|
||||
|
||||
# Deploy related error codes (3000-3099)
|
||||
deploy.config.not.found=Deployment configuration not found
|
||||
deploy.approval.config.missing=Approval policy not configured, cannot execute deployment
|
||||
deploy.approver.not.configured=Approval required for this environment, but no approvers configured
|
||||
deploy.notification.config.missing=Notification channel not configured, cannot send deployment notifications
|
||||
deploy.jenkins.config.missing=Jenkins build job not configured, cannot execute deployment
|
||||
deploy.workflow.not.configured=Application deployment workflow not configured
|
||||
deploy.environment.config.missing=Team environment configuration not found: teamId={0}, environmentId={1}
|
||||
deploy.application.not.configured=Application not configured for this environment
|
||||
deploy.already.running=Application deployment already in progress, please wait
|
||||
deploy.permission.denied=No permission to deploy application in this environment
|
||||
deploy.environment.locked=Environment is locked, deployment prohibited
|
||||
deploy.approval.required=Approval required for deployment in this environment
|
||||
deploy.record.not.found=Deployment record not found
|
||||
|
||||
@ -170,3 +170,18 @@ form.definition.not.found=表单定义不存在或已删除
|
||||
form.data.not.found=表单数据不存在或已删除
|
||||
form.definition.key.exists=表单标识{0}已存在,请使用不同的标识
|
||||
form.definition.key.version.exists=表单标识{0}的版本{1}已存在
|
||||
|
||||
# 部署相关错误码 (3000-3099)
|
||||
deploy.config.not.found=部署配置不存在
|
||||
deploy.approval.config.missing=未配置审批策略,无法执行部署
|
||||
deploy.approver.not.configured=该环境需要审批,但未配置审批人
|
||||
deploy.notification.config.missing=未配置通知渠道,无法发送部署通知
|
||||
deploy.jenkins.config.missing=未配置Jenkins构建任务,无法执行部署
|
||||
deploy.workflow.not.configured=应用未配置部署工作流
|
||||
deploy.environment.config.missing=团队环境配置不存在:teamId={0}, environmentId={1}
|
||||
deploy.application.not.configured=应用未配置到此环境
|
||||
deploy.already.running=该应用正在部署中,请等待当前部署完成
|
||||
deploy.permission.denied=无权限在此环境部署应用
|
||||
deploy.environment.locked=环境已锁定,禁止部署
|
||||
deploy.approval.required=该环境需要审批才能部署
|
||||
deploy.record.not.found=部署记录不存在
|
||||
Loading…
Reference in New Issue
Block a user