打印了JENKINS节点日志
This commit is contained in:
parent
d0997f9e9f
commit
3f102ae1b2
@ -1,9 +1,6 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.api;
|
package com.qqchen.deploy.backend.deploy.api;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.DeployRequestDTO;
|
import com.qqchen.deploy.backend.deploy.dto.*;
|
||||||
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.service.IDeployRecordService;
|
import com.qqchen.deploy.backend.deploy.service.IDeployRecordService;
|
||||||
import com.qqchen.deploy.backend.deploy.service.IDeployService;
|
import com.qqchen.deploy.backend.deploy.service.IDeployService;
|
||||||
import com.qqchen.deploy.backend.framework.api.Response;
|
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.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部署管理API控制器
|
* 部署管理API控制器
|
||||||
@ -67,5 +66,26 @@ public class DeployApiController {
|
|||||||
) {
|
) {
|
||||||
return Response.success(deployRecordService.getDeployFlowGraph(deployRecordId));
|
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.deploy.enums.DeployRecordStatusEnums;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowNodeInstanceDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowNodeInstanceDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.definition.workflow.WorkflowDefinitionGraph;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,22 +21,89 @@ import java.util.List;
|
|||||||
@Schema(description = "部署记录流程图信息")
|
@Schema(description = "部署记录流程图信息")
|
||||||
public class DeployRecordFlowGraphDTO {
|
public class DeployRecordFlowGraphDTO {
|
||||||
|
|
||||||
|
// ============ 部署记录基本信息 ============
|
||||||
|
|
||||||
@Schema(description = "部署记录ID")
|
@Schema(description = "部署记录ID")
|
||||||
private Long deployRecordId;
|
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")
|
@Schema(description = "工作流实例ID")
|
||||||
private Long workflowInstanceId;
|
private Long workflowInstanceId;
|
||||||
|
|
||||||
@Schema(description = "流程实例ID(Flowable)")
|
@Schema(description = "流程实例ID(Flowable)")
|
||||||
private String processInstanceId;
|
private String processInstanceId;
|
||||||
|
|
||||||
@Schema(description = "部署状态")
|
@Schema(description = "工作流实例状态")
|
||||||
private DeployRecordStatusEnums deployStatus;
|
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 = "流程图数据(画布快照,包含节点和边的位置信息)")
|
@Schema(description = "流程图数据(画布快照,包含节点和边的位置信息)")
|
||||||
private WorkflowDefinitionGraph graph;
|
private WorkflowDefinitionGraph graph;
|
||||||
|
|
||||||
@Schema(description = "节点执行状态列表(用于标记每个节点的执行状态)")
|
@Schema(description = "节点执行状态列表(用于标记每个节点的执行状态,包含已执行和未执行节点)")
|
||||||
private List<WorkflowNodeInstanceDTO> nodeInstances;
|
private List<WorkflowNodeInstanceDTO> nodeInstances;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,11 @@ public interface IDeployRecordRepository extends IBaseRepository<DeployRecord, L
|
|||||||
*/
|
*/
|
||||||
Optional<DeployRecord> findByWorkflowInstanceIdAndDeletedFalse(Long workflowInstanceId);
|
Optional<DeployRecord> findByWorkflowInstanceIdAndDeletedFalse(Long workflowInstanceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据业务标识查询部署记录
|
||||||
|
*/
|
||||||
|
Optional<DeployRecord> findByBusinessKeyAndDeletedFalse(String businessKey);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据团队应用ID查询最新部署记录
|
* 根据团队应用ID查询最新部署记录
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.service;
|
package com.qqchen.deploy.backend.deploy.service;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.DeployRequestDTO;
|
import com.qqchen.deploy.backend.deploy.dto.*;
|
||||||
import com.qqchen.deploy.backend.deploy.dto.DeployResultDTO;
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.UserDeployableDTO;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部署服务接口
|
* 部署服务接口
|
||||||
@ -26,5 +26,22 @@ public interface IDeployService {
|
|||||||
* @return 部署结果
|
* @return 部署结果
|
||||||
*/
|
*/
|
||||||
DeployResultDTO executeDeploy(DeployRequestDTO request);
|
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.DeployRecordDTO;
|
||||||
import com.qqchen.deploy.backend.deploy.dto.DeployRecordFlowGraphDTO;
|
import com.qqchen.deploy.backend.deploy.dto.DeployRecordFlowGraphDTO;
|
||||||
import com.qqchen.deploy.backend.deploy.entity.DeployRecord;
|
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.enums.DeployRecordStatusEnums;
|
||||||
import com.qqchen.deploy.backend.deploy.query.DeployRecordQuery;
|
import com.qqchen.deploy.backend.deploy.query.DeployRecordQuery;
|
||||||
import com.qqchen.deploy.backend.deploy.repository.IDeployRecordRepository;
|
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.deploy.service.IDeployRecordService;
|
||||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
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.dto.WorkflowNodeInstanceDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowNodeInstance;
|
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.BpmnModel;
|
||||||
import org.flowable.bpmn.model.FlowElement;
|
import org.flowable.bpmn.model.FlowElement;
|
||||||
import org.flowable.bpmn.model.Process;
|
import org.flowable.bpmn.model.Process;
|
||||||
import org.flowable.engine.HistoryService;
|
|
||||||
import org.flowable.engine.RepositoryService;
|
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 jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,14 +70,17 @@ public class DeployRecordServiceImpl extends BaseServiceImpl<DeployRecord, Deplo
|
|||||||
@Resource
|
@Resource
|
||||||
private IWorkflowNodeInstanceRepository workflowNodeInstanceRepository;
|
private IWorkflowNodeInstanceRepository workflowNodeInstanceRepository;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private WorkflowNodeInstanceConverter workflowNodeInstanceConverter;
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private RepositoryService repositoryService;
|
private RepositoryService repositoryService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private HistoryService historyService;
|
private ITeamRepository teamRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IApplicationRepository applicationRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IEnvironmentRepository environmentRepository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
@ -217,110 +230,218 @@ public class DeployRecordServiceImpl extends BaseServiceImpl<DeployRecord, Deplo
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
public DeployRecordFlowGraphDTO getDeployFlowGraph(Long deployRecordId) {
|
public DeployRecordFlowGraphDTO getDeployFlowGraph(Long deployRecordId) {
|
||||||
// 1. 查询部署记录
|
log.info("查询部署流程图: deployRecordId={}", deployRecordId);
|
||||||
|
|
||||||
|
// ============ 1. 查询基础数据 ============
|
||||||
|
|
||||||
|
// 1.1 查询部署记录
|
||||||
DeployRecord deployRecord = deployRecordRepository.findById(deployRecordId)
|
DeployRecord deployRecord = deployRecordRepository.findById(deployRecordId)
|
||||||
.orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[]{"部署记录"}));
|
.orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[]{"部署记录"}));
|
||||||
|
|
||||||
// 2. 查询工作流实例(包含流程图快照)
|
// 1.2 查询工作流实例
|
||||||
WorkflowInstance workflowInstance = workflowInstanceRepository.findById(deployRecord.getWorkflowInstanceId())
|
WorkflowInstance workflowInstance = workflowInstanceRepository.findById(deployRecord.getWorkflowInstanceId())
|
||||||
.orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[]{"工作流实例"}));
|
.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());
|
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());
|
BpmnModel bpmnModel = repositoryService.getBpmnModel(workflowInstance.getProcessDefinitionId());
|
||||||
Process process = bpmnModel.getMainProcess();
|
Process process = bpmnModel.getMainProcess();
|
||||||
List<FlowElement> flowElements = FlowableUtils.sortFlowElements(process);
|
List<FlowElement> flowElements = FlowableUtils.sortFlowElements(process);
|
||||||
|
|
||||||
// 5. 构建节点ID到流程顺序的映射(用于排序)
|
// 2.4 构建节点ID到流程顺序的映射
|
||||||
Map<String, Integer> nodeOrderMap = new HashMap<>();
|
Map<String, Integer> nodeOrderMap = new HashMap<>();
|
||||||
for (int i = 0; i < flowElements.size(); i++) {
|
for (int i = 0; i < flowElements.size(); i++) {
|
||||||
nodeOrderMap.put(flowElements.get(i).getId(), i);
|
nodeOrderMap.put(flowElements.get(i).getId(), i);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 按流程顺序排序节点实例(只包含实际执行过的节点)
|
// 2.5 遍历流程图中的所有节点,组装节点DTO(包括已执行和未执行的)
|
||||||
List<WorkflowNodeInstance> orderedNodeInstances = nodeInstances.stream()
|
List<WorkflowNodeInstanceDTO> nodeInstanceDTOs = graphSnapshot.getNodes().stream()
|
||||||
.sorted((a, b) -> {
|
.sorted(Comparator.comparingInt(node -> nodeOrderMap.getOrDefault(node.getId(), Integer.MAX_VALUE)))
|
||||||
Integer orderA = nodeOrderMap.getOrDefault(a.getNodeId(), Integer.MAX_VALUE);
|
.map(graphNode -> buildNodeInstanceDTO(graphNode, nodeInstanceMap.get(graphNode.getId()), workflowInstance))
|
||||||
Integer orderB = nodeOrderMap.getOrDefault(b.getNodeId(), Integer.MAX_VALUE);
|
|
||||||
return orderA.compareTo(orderB);
|
|
||||||
})
|
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
// 7. 从历史流程变量中获取每个节点的 outputs 数据
|
// ============ 3. 计算执行统计 ============
|
||||||
Map<String, Map<String, Object>> nodeOutputsMap = getNodeOutputsFromHistory(workflowInstance.getProcessInstanceId());
|
|
||||||
|
|
||||||
// 8. 转换为 DTO 并填充 outputs 数据
|
int totalNodeCount = nodeInstanceDTOs.size();
|
||||||
List<WorkflowNodeInstanceDTO> nodeInstanceDTOs = workflowNodeInstanceConverter.toDtoList(orderedNodeInstances);
|
int executedNodeCount = (int) nodeInstanceDTOs.stream()
|
||||||
nodeInstanceDTOs.forEach(dto -> {
|
.filter(node -> node.getStatus() != WorkflowNodeInstanceStatusEnums.NOT_STARTED)
|
||||||
// 从流程变量中获取该节点的 outputs 数据
|
.count();
|
||||||
Map<String, Object> nodeOutputs = nodeOutputsMap.get(dto.getNodeId());
|
int successNodeCount = (int) nodeInstanceDTOs.stream()
|
||||||
if (nodeOutputs != null) {
|
.filter(node -> node.getStatus() == WorkflowNodeInstanceStatusEnums.COMPLETED)
|
||||||
// 提取 outputs 部分(格式:{nodeId: {outputs: {...}}}
|
.count();
|
||||||
@SuppressWarnings("unchecked")
|
int failedNodeCount = (int) nodeInstanceDTOs.stream()
|
||||||
Map<String, Object> nodeData = (Map<String, Object>) nodeOutputs;
|
.filter(node -> node.getStatus() == WorkflowNodeInstanceStatusEnums.FAILED)
|
||||||
@SuppressWarnings("unchecked")
|
.count();
|
||||||
Map<String, Object> outputs = (Map<String, Object>) nodeData.get("outputs");
|
int runningNodeCount = (int) nodeInstanceDTOs.stream()
|
||||||
if (outputs != null) {
|
.filter(node -> node.getStatus() == WorkflowNodeInstanceStatusEnums.RUNNING)
|
||||||
dto.setOutputs(outputs);
|
.count();
|
||||||
}
|
|
||||||
}
|
// ============ 4. 组装完整的DTO ============
|
||||||
});
|
|
||||||
|
|
||||||
// 9. 组装DTO
|
|
||||||
DeployRecordFlowGraphDTO dto = new DeployRecordFlowGraphDTO();
|
DeployRecordFlowGraphDTO dto = new DeployRecordFlowGraphDTO();
|
||||||
|
|
||||||
|
// 4.1 部署记录基本信息
|
||||||
dto.setDeployRecordId(deployRecord.getId());
|
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.setWorkflowInstanceId(workflowInstance.getId());
|
||||||
dto.setProcessInstanceId(workflowInstance.getProcessInstanceId());
|
dto.setProcessInstanceId(workflowInstance.getProcessInstanceId());
|
||||||
dto.setDeployStatus(deployRecord.getStatus());
|
dto.setWorkflowStatus(workflowInstance.getStatus());
|
||||||
dto.setGraph(workflowInstance.getGraphSnapshot()); // 流程图结构数据
|
dto.setWorkflowStartTime(workflowInstance.getStartTime());
|
||||||
dto.setNodeInstances(nodeInstanceDTOs); // 节点执行状态(包含 outputs)
|
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;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从历史流程变量中获取所有节点的 outputs 数据
|
* 构建节点实例DTO
|
||||||
*
|
*
|
||||||
* @param processInstanceId 流程实例ID
|
* @param graphNode 流程图节点
|
||||||
* @return 节点ID到节点数据的映射(格式:{nodeId: {outputs: {...}}})
|
* @param nodeInstance 节点实例(可能为null,表示未执行)
|
||||||
|
* @param workflowInstance 工作流实例
|
||||||
|
* @return 节点实例DTO
|
||||||
*/
|
*/
|
||||||
private Map<String, Map<String, Object>> getNodeOutputsFromHistory(String processInstanceId) {
|
private WorkflowNodeInstanceDTO buildNodeInstanceDTO(
|
||||||
Map<String, Map<String, Object>> nodeOutputsMap = new HashMap<>();
|
WorkflowDefinitionGraphNode graphNode,
|
||||||
|
WorkflowNodeInstance nodeInstance,
|
||||||
|
WorkflowInstance workflowInstance) {
|
||||||
|
|
||||||
try {
|
WorkflowNodeInstanceDTO dto = new WorkflowNodeInstanceDTO();
|
||||||
// 查询历史流程变量
|
|
||||||
List<HistoricVariableInstance> variables = historyService
|
|
||||||
.createHistoricVariableInstanceQuery()
|
|
||||||
.processInstanceId(processInstanceId)
|
|
||||||
.list();
|
|
||||||
|
|
||||||
// 遍历变量,查找节点相关的变量(格式:{nodeId: {outputs: {...}}}
|
if (nodeInstance != null) {
|
||||||
for (HistoricVariableInstance variable : variables) {
|
// ✅ 已执行的节点:使用节点实例的数据
|
||||||
String variableName = variable.getVariableName();
|
dto.setId(nodeInstance.getId());
|
||||||
Object variableValue = variable.getValue();
|
dto.setNodeId(nodeInstance.getNodeId());
|
||||||
|
dto.setNodeName(nodeInstance.getNodeName());
|
||||||
// 检查是否是节点数据(节点ID通常是 sid_ 开头)
|
dto.setNodeType(nodeInstance.getNodeType());
|
||||||
if (variableName != null && variableValue instanceof Map) {
|
dto.setStatus(nodeInstance.getStatus());
|
||||||
@SuppressWarnings("unchecked")
|
dto.setStartTime(nodeInstance.getStartTime());
|
||||||
Map<String, Object> nodeData = (Map<String, Object>) variableValue;
|
dto.setEndTime(nodeInstance.getEndTime());
|
||||||
|
dto.setDuration(calculateDuration(nodeInstance.getStartTime(), nodeInstance.getEndTime()));
|
||||||
// 检查是否包含 outputs 字段(这是节点数据的标识)
|
dto.setErrorMessage(nodeInstance.getErrorMessage()); // ✅ 包含错误信息
|
||||||
if (nodeData.containsKey("outputs")) {
|
dto.setProcessInstanceId(nodeInstance.getProcessInstanceId());
|
||||||
nodeOutputsMap.put(variableName, nodeData);
|
dto.setCreateTime(nodeInstance.getCreateTime());
|
||||||
}
|
dto.setUpdateTime(nodeInstance.getUpdateTime());
|
||||||
}
|
} else {
|
||||||
}
|
// ✅ 未执行的节点:从流程图节点获取基本信息
|
||||||
|
dto.setId(null);
|
||||||
log.debug("从历史流程变量中获取节点 outputs: processInstanceId={}, nodeCount={}",
|
dto.setNodeId(graphNode.getId());
|
||||||
processInstanceId, nodeOutputsMap.size());
|
dto.setNodeName(graphNode.getNodeName());
|
||||||
} catch (Exception e) {
|
dto.setNodeType(graphNode.getNodeType() != null ? graphNode.getNodeType().name() : null);
|
||||||
log.warn("获取节点 outputs 失败: processInstanceId={}", processInstanceId, e);
|
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.security.SecurityUtils;
|
||||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||||
|
import com.qqchen.deploy.backend.framework.utils.SpelExpressionResolver;
|
||||||
import com.qqchen.deploy.backend.system.entity.User;
|
import com.qqchen.deploy.backend.system.entity.User;
|
||||||
import com.qqchen.deploy.backend.system.repository.IUserRepository;
|
import com.qqchen.deploy.backend.system.repository.IUserRepository;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceStartRequest;
|
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.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.entity.WorkflowDefinition;
|
||||||
|
import com.qqchen.deploy.backend.workflow.model.NodeContext;
|
||||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
||||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
|
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
|
||||||
import com.qqchen.deploy.backend.deploy.service.IDeployRecordService;
|
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 jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
|
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.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -82,6 +92,12 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
@Resource
|
@Resource
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TaskService taskService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RuntimeService runtimeService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据组装上下文(封装所有需要的Map,避免方法参数过多)
|
* 数据组装上下文(封装所有需要的Map,避免方法参数过多)
|
||||||
*/
|
*/
|
||||||
@ -551,69 +567,198 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public DeployResultDTO executeDeploy(DeployRequestDTO request) {
|
public DeployResultDTO executeDeploy(DeployRequestDTO request) {
|
||||||
// 1. 查询团队应用配置
|
log.info("开始执行部署: teamApplicationId={}, remark={}", request.getTeamApplicationId(), request.getRemark());
|
||||||
TeamApplication teamApp = teamApplicationRepository.findById(request.getTeamApplicationId()).orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND));
|
|
||||||
|
|
||||||
// 2. 查询工作流定义(获取 processKey)
|
// 1. 加载部署上下文数据
|
||||||
WorkflowDefinition workflowDefinition = workflowDefinitionRepository.findById(teamApp.getWorkflowDefinitionId()).orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[] {"工作流定义"}));
|
DeployContext context = loadDeployContext(request.getTeamApplicationId());
|
||||||
|
|
||||||
// 3. 查询应用信息
|
// 2. 生成业务标识(UUID)
|
||||||
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)
|
|
||||||
String businessKey = UUID.randomUUID().toString();
|
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> variables = new HashMap<>();
|
||||||
|
|
||||||
// 部署上下文
|
// 部署上下文
|
||||||
Map<String, Object> deployContext = new HashMap<>();
|
variables.put("deploy", buildDeployContextMap(context, request));
|
||||||
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);
|
|
||||||
|
|
||||||
// Jenkins 配置(使用强类型 JenkinsBuildInputMapping)
|
// Jenkins 配置
|
||||||
if (teamApp.getDeploySystemId() != null && teamApp.getDeployJob() != null) {
|
Map<String, Object> jenkinsConfig = buildJenkinsConfig(context.teamApp);
|
||||||
JenkinsBuildInputMapping jenkinsInput = new JenkinsBuildInputMapping();
|
if (jenkinsConfig != null) {
|
||||||
jenkinsInput.setServerId(teamApp.getDeploySystemId());
|
variables.put("jenkins", jenkinsConfig);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 审批配置
|
||||||
|
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();
|
WorkflowInstanceStartRequest workflowRequest = new WorkflowInstanceStartRequest();
|
||||||
workflowRequest.setProcessKey(workflowDefinition.getKey());
|
workflowRequest.setProcessKey(context.workflowDefinition.getKey());
|
||||||
workflowRequest.setBusinessKey(businessKey);
|
workflowRequest.setBusinessKey(businessKey);
|
||||||
workflowRequest.setVariables(variables);
|
workflowRequest.setVariables(variables);
|
||||||
|
|
||||||
// 8. 启动工作流
|
// 启动工作流
|
||||||
WorkflowInstanceDTO workflowInstance = workflowInstanceService.startWorkflow(workflowRequest);
|
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();
|
DeployResultDTO result = new DeployResultDTO();
|
||||||
result.setWorkflowInstanceId(workflowInstance.getId());
|
result.setWorkflowInstanceId(workflowInstance.getId());
|
||||||
result.setBusinessKey(businessKey);
|
result.setBusinessKey(businessKey);
|
||||||
@ -621,7 +766,283 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
result.setStatus(workflowInstance.getStatus().name());
|
result.setStatus(workflowInstance.getStatus().name());
|
||||||
result.setMessage("部署流程已启动");
|
result.setMessage("部署流程已启动");
|
||||||
|
|
||||||
|
log.info("部署请求处理完成: businessKey={}, workflowInstanceId={}, application={}, environment={}",
|
||||||
|
businessKey, workflowInstance.getId(), context.application.getAppCode(), context.environment.getEnvCode());
|
||||||
|
|
||||||
return result;
|
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_IP_EXISTS(2951, "server.ip.exists"),
|
||||||
SERVER_CATEGORY_NOT_FOUND(2952, "server.category.not.found"),
|
SERVER_CATEGORY_NOT_FOUND(2952, "server.category.not.found"),
|
||||||
SERVER_CATEGORY_CODE_EXISTS(2953, "server.category.code.exists"),
|
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 int code;
|
||||||
private final String messageKey; // 国际化消息key
|
private final String messageKey; // 国际化消息key
|
||||||
|
|||||||
@ -76,7 +76,7 @@ public class NotificationChannelServiceImpl
|
|||||||
try {
|
try {
|
||||||
adapter.testConnection(channel.getConfig());
|
adapter.testConnection(channel.getConfig());
|
||||||
log.info("通知渠道连接测试成功: id={}, name={}", id, channel.getName());
|
log.info("通知渠道连接测试成功: id={}, name={}", id, channel.getName());
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("通知渠道连接测试失败: id={}, name={}, 错误: {}",
|
log.error("通知渠道连接测试失败: id={}, name={}, 错误: {}",
|
||||||
id, channel.getName(), e.getMessage(), e);
|
id, channel.getName(), e.getMessage(), e);
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import java.util.Map;
|
|||||||
@Data
|
@Data
|
||||||
public class WorkflowNodeInstanceDTO extends BaseDTO {
|
public class WorkflowNodeInstanceDTO extends BaseDTO {
|
||||||
|
|
||||||
private Long id;
|
// 注意:id, createTime, updateTime 继承自 BaseDTO
|
||||||
|
|
||||||
private String processInstanceId;
|
private String processInstanceId;
|
||||||
|
|
||||||
@ -28,9 +28,17 @@ public class WorkflowNodeInstanceDTO extends BaseDTO {
|
|||||||
|
|
||||||
private LocalDateTime endTime;
|
private LocalDateTime endTime;
|
||||||
|
|
||||||
private LocalDateTime createTime;
|
/**
|
||||||
|
* 执行时长(毫秒)
|
||||||
|
* 如果节点未执行或正在执行,则为null
|
||||||
|
*/
|
||||||
|
private Long duration;
|
||||||
|
|
||||||
private LocalDateTime updateTime;
|
/**
|
||||||
|
* 错误信息
|
||||||
|
* 当节点执行失败时,记录失败原因
|
||||||
|
*/
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点执行结果(outputs)
|
* 节点执行结果(outputs)
|
||||||
|
|||||||
@ -396,6 +396,10 @@ public class BpmnConverter {
|
|||||||
serviceTask.setImplementation(delegateExpression);
|
serviceTask.setImplementation(delegateExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ 设置异步执行(关键修复:防止阻塞)
|
||||||
|
serviceTask.setAsynchronous(true);
|
||||||
|
log.debug("节点 {} 已配置为异步执行", validId);
|
||||||
|
|
||||||
// ✅ 添加 field 字段(nodeId, configs, inputMapping)
|
// ✅ 添加 field 字段(nodeId, configs, inputMapping)
|
||||||
addExecutionVariables(extensionElements, node, validId);
|
addExecutionVariables(extensionElements, node, validId);
|
||||||
|
|
||||||
|
|||||||
@ -229,3 +229,18 @@ schedule.job.executor.not.found=找不到任务执行器:{0}
|
|||||||
|
|
||||||
# 团队配置相关错误消息 (2920-2949)
|
# 团队配置相关错误消息 (2920-2949)
|
||||||
team.config.not.found=团队ID为 {0} 的配置不存在
|
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.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.exists=Form key {0} already exists, please use a different key
|
||||||
form.definition.key.version.exists=Form key {0} version {1} already exists
|
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.data.not.found=表单数据不存在或已删除
|
||||||
form.definition.key.exists=表单标识{0}已存在,请使用不同的标识
|
form.definition.key.exists=表单标识{0}已存在,请使用不同的标识
|
||||||
form.definition.key.version.exists=表单标识{0}的版本{1}已存在
|
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