增加部署日志

This commit is contained in:
dengqichen 2025-11-04 21:15:40 +08:00
parent 649b14a739
commit 4b554a8a2a
5 changed files with 125 additions and 17 deletions

View File

@ -76,7 +76,7 @@ public class DeployRecord extends Entity<Long> {
/** /**
* 部署状态 * 部署状态
*/ */
@Column(name = "status", nullable = false, length = 20) @Column(name = "status", nullable = false, length = 50)
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private DeployRecordStatusEnums status; private DeployRecordStatusEnums status;

View File

@ -42,19 +42,20 @@ public enum DeployRecordStatusEnums {
PARTIAL_SUCCESS("PARTIAL_SUCCESS", "部分成功"), PARTIAL_SUCCESS("PARTIAL_SUCCESS", "部分成功"),
/** /**
* 已取消 * 审批被拒绝终态
*/
REJECTED("REJECTED", "审批被拒绝"),
/**
* 已取消终态
* 用于手动取消部署超时自动取消等场景
*/ */
CANCELLED("CANCELLED", "已取消"), CANCELLED("CANCELLED", "已取消"),
/** /**
* 已终止 * 已终止终态
*/ */
TERMINATED("TERMINATED", "已终止"), TERMINATED("TERMINATED", "已终止");
/**
* 已暂停
*/
SUSPENDED("SUSPENDED", "已暂停");
private final String code; private final String code;
private final String description; private final String description;
@ -75,7 +76,7 @@ public enum DeployRecordStatusEnums {
case "COMPLETED_WITH_ERRORS" -> PARTIAL_SUCCESS; case "COMPLETED_WITH_ERRORS" -> PARTIAL_SUCCESS;
case "FAILED" -> FAILED; case "FAILED" -> FAILED;
case "TERMINATED" -> TERMINATED; case "TERMINATED" -> TERMINATED;
case "SUSPENDED" -> SUSPENDED; case "SUSPENDED" -> RUNNING; // 工作流暂停时部署记录仍视为运行中可恢复
default -> CREATED; default -> CREATED;
}; };
} }

View File

@ -0,0 +1,106 @@
package com.qqchen.deploy.backend.deploy.listener;
import com.qqchen.deploy.backend.deploy.service.IDeployRecordService;
import com.qqchen.deploy.backend.workflow.dto.event.WorkflowInstanceStatusChangeEvent;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowCategoryRepository;
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
import com.qqchen.deploy.backend.workflow.entity.WorkflowCategory;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* 部署记录工作流状态同步监听器
* 监听工作流实例状态变化事件同步更新部署记录状态
*
* @author qqchen
* @since 2025-11-02
*/
@Slf4j
@Component
public class DeployRecordWorkflowStatusSyncListener {
private static final String DEPLOYMENT_CATEGORY_CODE = "DEPLOYMENT";
@Resource
private IDeployRecordService deployRecordService;
@Resource
private IWorkflowInstanceRepository workflowInstanceRepository;
@Resource
private IWorkflowDefinitionRepository workflowDefinitionRepository;
@Resource
private IWorkflowCategoryRepository workflowCategoryRepository;
/**
* 监听工作流实例状态变化事件
* 同步更新部署记录状态
*/
@EventListener
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handleWorkflowStatusChange(WorkflowInstanceStatusChangeEvent event) {
try {
// 1. 查询工作流实例
WorkflowInstance instance = workflowInstanceRepository
.findByProcessInstanceId(event.getProcessInstanceId())
.orElse(null);
if (instance == null) {
log.warn("工作流实例不存在: processInstanceId={}", event.getProcessInstanceId());
return;
}
// 2. 判断是否为部署类型的工作流提前校验避免不必要的操作
if (!isDeploymentWorkflow(instance)) {
return;
}
// 3. 更新工作流实例的结束时间如果事件中有且实例中还没有
if (event.getEndTime() != null && instance.getEndTime() == null) {
instance.setEndTime(event.getEndTime());
workflowInstanceRepository.save(instance);
}
// 4. 同步状态处理状态优先级
// 注意这里需要确保 instance endTime 已更新因为 syncStatusFromWorkflowInstance 会使用它
deployRecordService.syncStatusFromWorkflowInstance(instance, event.getStatus());
} catch (Exception e) {
log.error("同步部署记录状态失败(工作流状态变化): processInstanceId={}",
event.getProcessInstanceId(), e);
// 不影响主流程只记录错误
}
}
/**
* 判断是否为部署类型的工作流
*/
private boolean isDeploymentWorkflow(WorkflowInstance instance) {
if (instance.getWorkflowDefinitionId() == null) {
return false;
}
WorkflowDefinition definition = workflowDefinitionRepository
.findById(instance.getWorkflowDefinitionId())
.orElse(null);
if (definition == null || definition.getCategoryId() == null) {
return false;
}
WorkflowCategory category = workflowCategoryRepository
.findById(definition.getCategoryId())
.orElse(null);
return category != null && DEPLOYMENT_CATEGORY_CODE.equals(category.getCode());
}
}

View File

@ -53,15 +53,15 @@ public interface IDeployRecordRepository extends IBaseRepository<DeployRecord, L
* *
* 统计规则 * 统计规则
* - 成功SUCCESS状态 * - 成功SUCCESS状态
* - 失败FAILED/CANCELLED/TERMINATED/PARTIAL_SUCCESS状态或CREATED状态超过30分钟仍未更新视为启动失败 * - 失败FAILED/REJECTED/CANCELLED/TERMINATED/PARTIAL_SUCCESS状态或CREATED状态超过30分钟仍未更新视为启动失败
* PARTIAL_SUCCESS虽然工作流完成但存在失败节点视为部署不完整计入失败 * REJECTED=审批被拒绝PARTIAL_SUCCESS虽然工作流完成但存在失败节点视为部署不完整计入失败
* - 运行中RUNNING状态或CREATED状态在30分钟内视为正在启动 * - 运行中RUNNING状态或CREATED状态在30分钟内视为正在启动
*/ */
@Query(value = "SELECT " + @Query(value = "SELECT " +
" dr.team_application_id as teamApplicationId, " + " dr.team_application_id as teamApplicationId, " +
" COUNT(dr.id) as totalCount, " + " COUNT(dr.id) as totalCount, " +
" SUM(CASE WHEN dr.status = 'SUCCESS' THEN 1 ELSE 0 END) as successCount, " + " SUM(CASE WHEN dr.status = 'SUCCESS' THEN 1 ELSE 0 END) as successCount, " +
" SUM(CASE WHEN dr.status IN ('FAILED', 'CANCELLED', 'TERMINATED', 'PARTIAL_SUCCESS') " + " SUM(CASE WHEN dr.status IN ('FAILED', 'REJECTED', 'CANCELLED', 'TERMINATED', 'PARTIAL_SUCCESS') " +
" OR (dr.status = 'CREATED' AND TIMESTAMPDIFF(MINUTE, dr.create_time, NOW()) > 30) " + " OR (dr.status = 'CREATED' AND TIMESTAMPDIFF(MINUTE, dr.create_time, NOW()) > 30) " +
" THEN 1 ELSE 0 END) as failedCount, " + " THEN 1 ELSE 0 END) as failedCount, " +
" SUM(CASE WHEN dr.status = 'RUNNING' " + " SUM(CASE WHEN dr.status = 'RUNNING' " +

View File

@ -85,7 +85,7 @@ public class DeployRecordServiceImpl extends BaseServiceImpl<DeployRecord, Deplo
return; return;
} }
// 如果当前状态已经是终态特别是审批被拒绝的CANCELLED不应该被覆盖 // 如果当前状态已经是终态特别是审批被拒绝的REJECTED不应该被覆盖
if (isFinalState(record.getStatus())) { if (isFinalState(record.getStatus())) {
log.debug("部署记录已处于终态,跳过同步: id={}, workflowInstanceId={}, currentStatus={}, workflowStatus={}", log.debug("部署记录已处于终态,跳过同步: id={}, workflowInstanceId={}, currentStatus={}, workflowStatus={}",
record.getId(), instance.getId(), record.getStatus(), status); record.getId(), instance.getId(), record.getStatus(), status);
@ -172,9 +172,9 @@ public class DeployRecordServiceImpl extends BaseServiceImpl<DeployRecord, Deplo
log.info("部署记录状态已更新为运行中(审批通过): id={}, workflowInstanceId={}", log.info("部署记录状态已更新为运行中(审批通过): id={}, workflowInstanceId={}",
record.getId(), workflowInstanceId); record.getId(), workflowInstanceId);
} else { } else {
// 审批被拒绝更新为已取消 // 审批被拒绝更新为审批被拒绝
record.setStatus(DeployRecordStatusEnums.CANCELLED); record.setStatus(DeployRecordStatusEnums.REJECTED);
log.info("部署记录状态已更新为已取消(审批被拒): id={}, workflowInstanceId={}", log.info("部署记录状态已更新为审批被拒绝: id={}, workflowInstanceId={}",
record.getId(), workflowInstanceId); record.getId(), workflowInstanceId);
} }
@ -187,6 +187,7 @@ public class DeployRecordServiceImpl extends BaseServiceImpl<DeployRecord, Deplo
private boolean isFinalState(DeployRecordStatusEnums status) { private boolean isFinalState(DeployRecordStatusEnums status) {
return status == DeployRecordStatusEnums.SUCCESS return status == DeployRecordStatusEnums.SUCCESS
|| status == DeployRecordStatusEnums.FAILED || status == DeployRecordStatusEnums.FAILED
|| status == DeployRecordStatusEnums.REJECTED
|| status == DeployRecordStatusEnums.CANCELLED || status == DeployRecordStatusEnums.CANCELLED
|| status == DeployRecordStatusEnums.TERMINATED || status == DeployRecordStatusEnums.TERMINATED
|| status == DeployRecordStatusEnums.PARTIAL_SUCCESS; || status == DeployRecordStatusEnums.PARTIAL_SUCCESS;