增加部署日志
This commit is contained in:
parent
b6b23bfd34
commit
990889858c
@ -0,0 +1,17 @@
|
||||
package com.qqchen.deploy.backend.deploy.converter;
|
||||
|
||||
import com.qqchen.deploy.backend.deploy.dto.DeployRecordDTO;
|
||||
import com.qqchen.deploy.backend.deploy.entity.DeployRecord;
|
||||
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
/**
|
||||
* 部署记录转换器
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Mapper(config = BaseConverter.class)
|
||||
public interface DeployRecordConverter extends BaseConverter<DeployRecord, DeployRecordDTO> {
|
||||
}
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
package com.qqchen.deploy.backend.deploy.dto;
|
||||
|
||||
import com.qqchen.deploy.backend.deploy.enums.DeployRecordStatusEnums;
|
||||
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 部署记录DTO
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "部署记录信息")
|
||||
public class DeployRecordDTO extends BaseDTO {
|
||||
|
||||
@Schema(description = "工作流实例ID")
|
||||
private Long workflowInstanceId;
|
||||
|
||||
@Schema(description = "业务标识")
|
||||
private String businessKey;
|
||||
|
||||
@Schema(description = "团队应用配置ID")
|
||||
private Long teamApplicationId;
|
||||
|
||||
@Schema(description = "团队ID")
|
||||
private Long teamId;
|
||||
|
||||
@Schema(description = "应用ID")
|
||||
private Long applicationId;
|
||||
|
||||
@Schema(description = "环境ID")
|
||||
private Long environmentId;
|
||||
|
||||
@Schema(description = "部署人")
|
||||
private String deployBy;
|
||||
|
||||
@Schema(description = "部署备注")
|
||||
private String deployRemark;
|
||||
|
||||
@Schema(description = "部署状态")
|
||||
private DeployRecordStatusEnums status;
|
||||
|
||||
@Schema(description = "开始时间")
|
||||
private LocalDateTime startTime;
|
||||
|
||||
@Schema(description = "结束时间")
|
||||
private LocalDateTime endTime;
|
||||
}
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
package com.qqchen.deploy.backend.deploy.dto;
|
||||
|
||||
import com.qqchen.deploy.backend.deploy.enums.DeployRecordStatusEnums;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 部署记录摘要DTO
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "部署记录摘要")
|
||||
public class DeployRecordSummaryDTO {
|
||||
|
||||
@Schema(description = "部署记录ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "部署状态")
|
||||
private DeployRecordStatusEnums status;
|
||||
|
||||
@Schema(description = "部署人")
|
||||
private String deployBy;
|
||||
|
||||
@Schema(description = "开始时间")
|
||||
private LocalDateTime startTime;
|
||||
|
||||
@Schema(description = "结束时间")
|
||||
private LocalDateTime endTime;
|
||||
|
||||
@Schema(description = "部署备注")
|
||||
private String deployRemark;
|
||||
|
||||
@Schema(description = "持续时间(毫秒)")
|
||||
private Long duration;
|
||||
}
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
package com.qqchen.deploy.backend.deploy.dto;
|
||||
|
||||
import com.qqchen.deploy.backend.deploy.enums.DeployRecordStatusEnums;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 部署统计信息DTO
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "部署统计信息")
|
||||
public class DeployStatisticsDTO {
|
||||
|
||||
@Schema(description = "总部署次数")
|
||||
private Long totalCount;
|
||||
|
||||
@Schema(description = "成功次数")
|
||||
private Long successCount;
|
||||
|
||||
@Schema(description = "失败次数")
|
||||
private Long failedCount;
|
||||
|
||||
@Schema(description = "运行中次数")
|
||||
private Long runningCount;
|
||||
|
||||
@Schema(description = "最近部署时间")
|
||||
private LocalDateTime lastDeployTime;
|
||||
|
||||
@Schema(description = "最近部署人")
|
||||
private String lastDeployBy;
|
||||
|
||||
@Schema(description = "最新部署状态")
|
||||
private DeployRecordStatusEnums latestStatus;
|
||||
}
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package com.qqchen.deploy.backend.deploy.dto;
|
||||
|
||||
import com.qqchen.deploy.backend.deploy.enums.DeployRecordStatusEnums;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 可部署应用DTO
|
||||
*
|
||||
@ -48,5 +51,14 @@ public class DeployableApplicationDTO {
|
||||
|
||||
@Schema(description = "工作流流程标识(processKey)")
|
||||
private String workflowDefinitionKey;
|
||||
|
||||
@Schema(description = "部署统计信息")
|
||||
private DeployStatisticsDTO deployStatistics;
|
||||
|
||||
@Schema(description = "是否正在部署中")
|
||||
private Boolean isDeploying;
|
||||
|
||||
@Schema(description = "最近部署记录列表(最多10条)")
|
||||
private List<DeployRecordSummaryDTO> recentDeployRecords;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,95 @@
|
||||
package com.qqchen.deploy.backend.deploy.entity;
|
||||
|
||||
import com.qqchen.deploy.backend.deploy.enums.DeployRecordStatusEnums;
|
||||
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
|
||||
import com.qqchen.deploy.backend.framework.domain.Entity;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 部署记录实体
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@jakarta.persistence.Entity
|
||||
@Table(name = "deploy_record")
|
||||
@LogicDelete
|
||||
public class DeployRecord extends Entity<Long> {
|
||||
|
||||
/**
|
||||
* 工作流实例ID
|
||||
*/
|
||||
@Column(name = "workflow_instance_id", nullable = false)
|
||||
private Long workflowInstanceId;
|
||||
|
||||
/**
|
||||
* 业务标识(UUID)
|
||||
*/
|
||||
@Column(name = "business_key", nullable = false, length = 64)
|
||||
private String businessKey;
|
||||
|
||||
/**
|
||||
* 团队应用配置ID
|
||||
*/
|
||||
@Column(name = "team_application_id", nullable = false)
|
||||
private Long teamApplicationId;
|
||||
|
||||
/**
|
||||
* 团队ID
|
||||
*/
|
||||
@Column(name = "team_id", nullable = false)
|
||||
private Long teamId;
|
||||
|
||||
/**
|
||||
* 应用ID
|
||||
*/
|
||||
@Column(name = "application_id", nullable = false)
|
||||
private Long applicationId;
|
||||
|
||||
/**
|
||||
* 环境ID
|
||||
*/
|
||||
@Column(name = "environment_id", nullable = false)
|
||||
private Long environmentId;
|
||||
|
||||
/**
|
||||
* 部署人
|
||||
*/
|
||||
@Column(name = "deploy_by", length = 50)
|
||||
private String deployBy;
|
||||
|
||||
/**
|
||||
* 部署备注
|
||||
*/
|
||||
@Column(name = "deploy_remark", length = 500)
|
||||
private String deployRemark;
|
||||
|
||||
/**
|
||||
* 部署状态
|
||||
*/
|
||||
@Column(name = "status", nullable = false, length = 20)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private DeployRecordStatusEnums status;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
@Column(name = "start_time")
|
||||
private LocalDateTime startTime;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
@Column(name = "end_time")
|
||||
private LocalDateTime endTime;
|
||||
}
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
package com.qqchen.deploy.backend.deploy.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 部署记录状态枚举
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Getter
|
||||
public enum DeployRecordStatusEnums {
|
||||
|
||||
/**
|
||||
* 已创建
|
||||
*/
|
||||
CREATED("CREATED", "已创建"),
|
||||
|
||||
/**
|
||||
* 运行中
|
||||
*/
|
||||
RUNNING("RUNNING", "运行中"),
|
||||
|
||||
/**
|
||||
* 部署成功
|
||||
*/
|
||||
SUCCESS("SUCCESS", "部署成功"),
|
||||
|
||||
/**
|
||||
* 部署失败
|
||||
*/
|
||||
FAILED("FAILED", "部署失败"),
|
||||
|
||||
/**
|
||||
* 部分成功(工作流完成但存在失败的节点)
|
||||
*/
|
||||
PARTIAL_SUCCESS("PARTIAL_SUCCESS", "部分成功"),
|
||||
|
||||
/**
|
||||
* 已取消
|
||||
*/
|
||||
CANCELLED("CANCELLED", "已取消"),
|
||||
|
||||
/**
|
||||
* 已终止
|
||||
*/
|
||||
TERMINATED("TERMINATED", "已终止"),
|
||||
|
||||
/**
|
||||
* 已暂停
|
||||
*/
|
||||
SUSPENDED("SUSPENDED", "已暂停");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
DeployRecordStatusEnums(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将工作流状态转换为部署记录状态
|
||||
*/
|
||||
public static DeployRecordStatusEnums fromWorkflowStatus(String workflowStatus) {
|
||||
return switch (workflowStatus) {
|
||||
case "CREATED" -> CREATED;
|
||||
case "RUNNING" -> RUNNING;
|
||||
case "COMPLETED" -> SUCCESS;
|
||||
case "COMPLETED_WITH_ERRORS" -> PARTIAL_SUCCESS;
|
||||
case "FAILED" -> FAILED;
|
||||
case "TERMINATED" -> TERMINATED;
|
||||
case "SUSPENDED" -> SUSPENDED;
|
||||
default -> CREATED;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
package com.qqchen.deploy.backend.deploy.query;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
||||
import com.qqchen.deploy.backend.framework.enums.QueryType;
|
||||
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 部署记录查询条件
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DeployRecordQuery extends BaseQuery {
|
||||
|
||||
@QueryField(field = "teamApplicationId", type = QueryType.EQUAL)
|
||||
private Long teamApplicationId;
|
||||
|
||||
@QueryField(field = "applicationId", type = QueryType.EQUAL)
|
||||
private Long applicationId;
|
||||
|
||||
@QueryField(field = "environmentId", type = QueryType.EQUAL)
|
||||
private Long environmentId;
|
||||
|
||||
@QueryField(field = "teamId", type = QueryType.EQUAL)
|
||||
private Long teamId;
|
||||
|
||||
@QueryField(field = "status", type = QueryType.EQUAL)
|
||||
private String status;
|
||||
|
||||
@QueryField(field = "deployBy", type = QueryType.LIKE)
|
||||
private String deployBy;
|
||||
}
|
||||
|
||||
@ -0,0 +1,102 @@
|
||||
package com.qqchen.deploy.backend.deploy.repository;
|
||||
|
||||
import com.qqchen.deploy.backend.deploy.entity.DeployRecord;
|
||||
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 部署记录仓库
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Repository
|
||||
public interface IDeployRecordRepository extends IBaseRepository<DeployRecord, Long> {
|
||||
|
||||
/**
|
||||
* 根据工作流实例ID查询部署记录
|
||||
*/
|
||||
Optional<DeployRecord> findByWorkflowInstanceIdAndDeletedFalse(Long workflowInstanceId);
|
||||
|
||||
/**
|
||||
* 根据团队应用ID查询最新部署记录
|
||||
*/
|
||||
Optional<DeployRecord> findFirstByTeamApplicationIdAndDeletedFalseOrderByCreateTimeDesc(Long teamApplicationId);
|
||||
|
||||
/**
|
||||
* 根据应用ID和环境ID查询最新部署记录
|
||||
*/
|
||||
Optional<DeployRecord> findFirstByApplicationIdAndEnvironmentIdAndDeletedFalseOrderByCreateTimeDesc(
|
||||
Long applicationId, Long environmentId);
|
||||
|
||||
/**
|
||||
* 根据团队应用ID分页查询部署记录
|
||||
*/
|
||||
Page<DeployRecord> findByTeamApplicationIdAndDeletedFalseOrderByCreateTimeDesc(
|
||||
Long teamApplicationId, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 根据应用ID和环境ID查询部署记录列表
|
||||
*/
|
||||
List<DeployRecord> findByApplicationIdAndEnvironmentIdAndDeletedFalseOrderByCreateTimeDesc(
|
||||
Long applicationId, Long environmentId);
|
||||
|
||||
/**
|
||||
* 批量查询部署统计信息(按团队应用ID分组)
|
||||
*/
|
||||
@Query("SELECT dr.teamApplicationId, " +
|
||||
"COUNT(dr.id) as totalCount, " +
|
||||
"SUM(CASE WHEN dr.status = 'SUCCESS' THEN 1 ELSE 0 END) as successCount, " +
|
||||
"SUM(CASE WHEN dr.status IN ('FAILED', 'CANCELLED', 'TERMINATED') THEN 1 ELSE 0 END) as failedCount, " +
|
||||
"SUM(CASE WHEN dr.status IN ('CREATED', 'RUNNING') THEN 1 ELSE 0 END) as runningCount, " +
|
||||
"MAX(dr.startTime) as lastDeployTime " +
|
||||
"FROM DeployRecord dr " +
|
||||
"WHERE dr.teamApplicationId IN :teamApplicationIds AND dr.deleted = false " +
|
||||
"GROUP BY dr.teamApplicationId")
|
||||
List<Object[]> findDeployStatisticsByTeamApplicationIds(
|
||||
@Param("teamApplicationIds") List<Long> teamApplicationIds);
|
||||
|
||||
/**
|
||||
* 批量查询每个团队应用的最新部署记录(用于获取最新状态和部署人)
|
||||
* 使用原生SQL避免JPQL类型问题
|
||||
*/
|
||||
@Query(value = "SELECT dr.* FROM deploy_record dr " +
|
||||
"INNER JOIN (" +
|
||||
" SELECT team_application_id, MAX(id) as max_id " +
|
||||
" FROM deploy_record " +
|
||||
" WHERE team_application_id IN (:teamApplicationIds) " +
|
||||
" AND deleted = false " +
|
||||
" GROUP BY team_application_id" +
|
||||
") latest ON dr.id = latest.max_id " +
|
||||
"WHERE dr.deleted = false " +
|
||||
"ORDER BY dr.create_time DESC",
|
||||
nativeQuery = true)
|
||||
List<DeployRecord> findLatestDeployRecordsByTeamApplicationIds(
|
||||
@Param("teamApplicationIds") List<Long> teamApplicationIds);
|
||||
|
||||
/**
|
||||
* 批量查询每个团队应用的最近N条部署记录
|
||||
* 使用原生SQL实现(MySQL支持窗口函数)
|
||||
*/
|
||||
@Query(value = "SELECT * FROM (" +
|
||||
" SELECT dr.*, " +
|
||||
" ROW_NUMBER() OVER (PARTITION BY dr.team_application_id ORDER BY dr.create_time DESC) as rn " +
|
||||
" FROM deploy_record dr " +
|
||||
" WHERE dr.team_application_id IN :teamApplicationIds " +
|
||||
" AND dr.deleted = false" +
|
||||
") ranked " +
|
||||
"WHERE ranked.rn <= :limit " +
|
||||
"ORDER BY ranked.team_application_id, ranked.create_time DESC",
|
||||
nativeQuery = true)
|
||||
List<DeployRecord> findRecentDeployRecordsByTeamApplicationIds(
|
||||
@Param("teamApplicationIds") List<Long> teamApplicationIds,
|
||||
@Param("limit") int limit);
|
||||
}
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
package com.qqchen.deploy.backend.deploy.service;
|
||||
|
||||
import com.qqchen.deploy.backend.deploy.dto.DeployRecordDTO;
|
||||
import com.qqchen.deploy.backend.deploy.entity.DeployRecord;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums;
|
||||
|
||||
/**
|
||||
* 部署记录服务接口
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
public interface IDeployRecordService {
|
||||
|
||||
/**
|
||||
* 创建部署记录
|
||||
*
|
||||
* @param workflowInstanceId 工作流实例ID
|
||||
* @param businessKey 业务标识
|
||||
* @param teamApplicationId 团队应用配置ID
|
||||
* @param teamId 团队ID
|
||||
* @param applicationId 应用ID
|
||||
* @param environmentId 环境ID
|
||||
* @param deployBy 部署人
|
||||
* @param deployRemark 部署备注
|
||||
* @return 部署记录DTO
|
||||
*/
|
||||
DeployRecordDTO createDeployRecord(
|
||||
Long workflowInstanceId,
|
||||
String businessKey,
|
||||
Long teamApplicationId,
|
||||
Long teamId,
|
||||
Long applicationId,
|
||||
Long environmentId,
|
||||
String deployBy,
|
||||
String deployRemark
|
||||
);
|
||||
|
||||
/**
|
||||
* 根据工作流实例同步部署记录状态
|
||||
*
|
||||
* @param instance 工作流实例
|
||||
* @param status 工作流状态
|
||||
*/
|
||||
void syncStatusFromWorkflowInstance(WorkflowInstance instance, WorkflowInstanceStatusEnums status);
|
||||
|
||||
/**
|
||||
* 根据工作流实例ID查询部署记录
|
||||
*
|
||||
* @param workflowInstanceId 工作流实例ID
|
||||
* @return 部署记录DTO
|
||||
*/
|
||||
DeployRecordDTO findByWorkflowInstanceId(Long workflowInstanceId);
|
||||
}
|
||||
|
||||
@ -0,0 +1,116 @@
|
||||
package com.qqchen.deploy.backend.deploy.service.impl;
|
||||
|
||||
import com.qqchen.deploy.backend.deploy.converter.DeployRecordConverter;
|
||||
import com.qqchen.deploy.backend.deploy.dto.DeployRecordDTO;
|
||||
import com.qqchen.deploy.backend.deploy.entity.DeployRecord;
|
||||
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.service.IDeployRecordService;
|
||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 部署记录服务实现
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-02
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DeployRecordServiceImpl extends BaseServiceImpl<DeployRecord, DeployRecordDTO, DeployRecordQuery, Long>
|
||||
implements IDeployRecordService {
|
||||
|
||||
@Resource
|
||||
private IDeployRecordRepository deployRecordRepository;
|
||||
|
||||
@Resource
|
||||
private DeployRecordConverter deployRecordConverter;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public DeployRecordDTO createDeployRecord(
|
||||
Long workflowInstanceId,
|
||||
String businessKey,
|
||||
Long teamApplicationId,
|
||||
Long teamId,
|
||||
Long applicationId,
|
||||
Long environmentId,
|
||||
String deployBy,
|
||||
String deployRemark
|
||||
) {
|
||||
// 检查是否已存在
|
||||
DeployRecord existing = deployRecordRepository
|
||||
.findByWorkflowInstanceIdAndDeletedFalse(workflowInstanceId)
|
||||
.orElse(null);
|
||||
|
||||
if (existing != null) {
|
||||
log.warn("部署记录已存在: workflowInstanceId={}", workflowInstanceId);
|
||||
return deployRecordConverter.toDto(existing);
|
||||
}
|
||||
|
||||
// 创建新记录
|
||||
DeployRecord record = new DeployRecord();
|
||||
record.setWorkflowInstanceId(workflowInstanceId);
|
||||
record.setBusinessKey(businessKey);
|
||||
record.setTeamApplicationId(teamApplicationId);
|
||||
record.setTeamId(teamId);
|
||||
record.setApplicationId(applicationId);
|
||||
record.setEnvironmentId(environmentId);
|
||||
record.setDeployBy(deployBy);
|
||||
record.setDeployRemark(deployRemark);
|
||||
record.setStatus(DeployRecordStatusEnums.CREATED);
|
||||
record.setStartTime(LocalDateTime.now());
|
||||
|
||||
DeployRecord saved = deployRecordRepository.save(record);
|
||||
log.info("创建部署记录成功: id={}, workflowInstanceId={}, applicationId={}, environmentId={}",
|
||||
saved.getId(), workflowInstanceId, applicationId, environmentId);
|
||||
|
||||
return deployRecordConverter.toDto(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void syncStatusFromWorkflowInstance(WorkflowInstance instance, WorkflowInstanceStatusEnums status) {
|
||||
DeployRecord record = deployRecordRepository
|
||||
.findByWorkflowInstanceIdAndDeletedFalse(instance.getId())
|
||||
.orElse(null);
|
||||
|
||||
if (record == null) {
|
||||
log.warn("部署记录不存在,无法同步状态: workflowInstanceId={}", instance.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 转换状态
|
||||
DeployRecordStatusEnums deployStatus = DeployRecordStatusEnums.fromWorkflowStatus(status.name());
|
||||
|
||||
// 更新状态和时间
|
||||
record.setStatus(deployStatus);
|
||||
if (instance.getStartTime() != null && record.getStartTime() == null) {
|
||||
record.setStartTime(instance.getStartTime());
|
||||
}
|
||||
if (instance.getEndTime() != null) {
|
||||
record.setEndTime(instance.getEndTime());
|
||||
}
|
||||
|
||||
deployRecordRepository.save(record);
|
||||
log.debug("同步部署记录状态: id={}, workflowInstanceId={}, status={}",
|
||||
record.getId(), instance.getId(), deployStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeployRecordDTO findByWorkflowInstanceId(Long workflowInstanceId) {
|
||||
return deployRecordRepository
|
||||
.findByWorkflowInstanceIdAndDeletedFalse(workflowInstanceId)
|
||||
.map(deployRecordConverter::toDto)
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,11 +16,16 @@ import com.qqchen.deploy.backend.workflow.dto.inputmapping.JenkinsBuildInputMapp
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
|
||||
import com.qqchen.deploy.backend.deploy.service.IDeployRecordService;
|
||||
import com.qqchen.deploy.backend.deploy.repository.IDeployRecordRepository;
|
||||
import com.qqchen.deploy.backend.deploy.entity.DeployRecord;
|
||||
import com.qqchen.deploy.backend.deploy.enums.DeployRecordStatusEnums;
|
||||
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.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -66,6 +71,12 @@ public class DeployServiceImpl implements IDeployService {
|
||||
@Resource
|
||||
private IWorkflowInstanceService workflowInstanceService;
|
||||
|
||||
@Resource
|
||||
private IDeployRecordService deployRecordService;
|
||||
|
||||
@Resource
|
||||
private IDeployRecordRepository deployRecordRepository;
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@ -178,7 +189,62 @@ public class DeployServiceImpl implements IDeployService {
|
||||
approverMap = Collections.emptyMap();
|
||||
}
|
||||
|
||||
// 12. 组装团队数据
|
||||
// 12. 批量查询部署记录信息
|
||||
List<Long> teamApplicationIds = allTeamApps.stream()
|
||||
.map(TeamApplication::getId)
|
||||
.collect(toList());
|
||||
|
||||
// 12.1 批量查询部署统计信息
|
||||
final Map<Long, DeployStatisticsDTO> statisticsMap = new HashMap<>();
|
||||
final Map<Long, DeployRecord> latestRecordMap = new HashMap<>();
|
||||
final Map<Long, List<DeployRecord>> recentRecordsMap = new HashMap<>();
|
||||
|
||||
if (!teamApplicationIds.isEmpty()) {
|
||||
// 查询统计信息
|
||||
List<Object[]> statisticsList = deployRecordRepository
|
||||
.findDeployStatisticsByTeamApplicationIds(teamApplicationIds);
|
||||
for (Object[] row : statisticsList) {
|
||||
Long teamApplicationId = (Long) row[0];
|
||||
Long totalCount = ((Number) row[1]).longValue();
|
||||
Long successCount = ((Number) row[2]).longValue();
|
||||
Long failedCount = ((Number) row[3]).longValue();
|
||||
Long runningCount = ((Number) row[4]).longValue();
|
||||
LocalDateTime lastDeployTime = (LocalDateTime) row[5];
|
||||
|
||||
DeployStatisticsDTO stats = new DeployStatisticsDTO();
|
||||
stats.setTotalCount(totalCount);
|
||||
stats.setSuccessCount(successCount);
|
||||
stats.setFailedCount(failedCount);
|
||||
stats.setRunningCount(runningCount);
|
||||
stats.setLastDeployTime(lastDeployTime);
|
||||
statisticsMap.put(teamApplicationId, stats);
|
||||
}
|
||||
|
||||
// 查询最新部署记录(用于获取最新状态和部署人)
|
||||
List<DeployRecord> latestRecords = deployRecordRepository
|
||||
.findLatestDeployRecordsByTeamApplicationIds(teamApplicationIds);
|
||||
latestRecordMap.putAll(latestRecords.stream()
|
||||
.collect(toMap(DeployRecord::getTeamApplicationId, r -> r)));
|
||||
|
||||
// 更新统计信息中的最新状态和部署人
|
||||
latestRecordMap.forEach((teamAppId, record) -> {
|
||||
DeployStatisticsDTO stats = statisticsMap.get(teamAppId);
|
||||
if (stats == null) {
|
||||
stats = new DeployStatisticsDTO();
|
||||
statisticsMap.put(teamAppId, stats);
|
||||
}
|
||||
stats.setLatestStatus(record.getStatus());
|
||||
stats.setLastDeployBy(record.getDeployBy());
|
||||
});
|
||||
|
||||
// 查询最近10条部署记录
|
||||
List<DeployRecord> recentRecords = deployRecordRepository
|
||||
.findRecentDeployRecordsByTeamApplicationIds(teamApplicationIds, 10);
|
||||
recentRecordsMap.putAll(recentRecords.stream()
|
||||
.collect(groupingBy(DeployRecord::getTeamApplicationId)));
|
||||
}
|
||||
|
||||
// 13. 组装团队数据
|
||||
Map<Long, TeamMember> teamMemberMap = teamMembers.stream()
|
||||
.collect(toMap(TeamMember::getTeamId, tm -> tm));
|
||||
|
||||
@ -194,12 +260,15 @@ public class DeployServiceImpl implements IDeployService {
|
||||
appMap,
|
||||
systemMap,
|
||||
workflowMap,
|
||||
approverMap
|
||||
approverMap,
|
||||
statisticsMap,
|
||||
latestRecordMap,
|
||||
recentRecordsMap
|
||||
))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(toList());
|
||||
|
||||
// 13. 组装最终结果
|
||||
// 14. 组装最终结果
|
||||
UserDeployableDTO result = new UserDeployableDTO();
|
||||
result.setUserId(user.getId());
|
||||
result.setUsername(user.getUsername());
|
||||
@ -236,7 +305,10 @@ public class DeployServiceImpl implements IDeployService {
|
||||
Map<Long, Application> appMap,
|
||||
Map<Long, ExternalSystem> systemMap,
|
||||
Map<Long, WorkflowDefinition> workflowMap,
|
||||
Map<Long, User> approverMap
|
||||
Map<Long, User> approverMap,
|
||||
Map<Long, DeployStatisticsDTO> statisticsMap,
|
||||
Map<Long, DeployRecord> latestRecordMap,
|
||||
Map<Long, List<DeployRecord>> recentRecordsMap
|
||||
) {
|
||||
Team team = teamMap.get(teamId);
|
||||
if (team == null) {
|
||||
@ -286,7 +358,10 @@ public class DeployServiceImpl implements IDeployService {
|
||||
appMap,
|
||||
systemMap,
|
||||
workflowMap,
|
||||
approverMap
|
||||
approverMap,
|
||||
statisticsMap,
|
||||
latestRecordMap,
|
||||
recentRecordsMap
|
||||
);
|
||||
|
||||
envDTOs.add(envDTO);
|
||||
@ -312,7 +387,10 @@ public class DeployServiceImpl implements IDeployService {
|
||||
Map<Long, Application> appMap,
|
||||
Map<Long, ExternalSystem> systemMap,
|
||||
Map<Long, WorkflowDefinition> workflowMap,
|
||||
Map<Long, User> approverMap
|
||||
Map<Long, User> approverMap,
|
||||
Map<Long, DeployStatisticsDTO> statisticsMap,
|
||||
Map<Long, DeployRecord> latestRecordMap,
|
||||
Map<Long, List<DeployRecord>> recentRecordsMap
|
||||
) {
|
||||
DeployableEnvironmentDTO envDTO = new DeployableEnvironmentDTO();
|
||||
envDTO.setEnvironmentId(env.getId());
|
||||
@ -349,7 +427,15 @@ public class DeployServiceImpl implements IDeployService {
|
||||
|
||||
// 应用列表
|
||||
List<DeployableApplicationDTO> appDTOs = teamApps.stream()
|
||||
.map(ta -> buildApplicationDTO(ta, appMap, systemMap, workflowMap))
|
||||
.map(ta -> buildApplicationDTO(
|
||||
ta,
|
||||
appMap,
|
||||
systemMap,
|
||||
workflowMap,
|
||||
statisticsMap,
|
||||
latestRecordMap,
|
||||
recentRecordsMap
|
||||
))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(toList());
|
||||
|
||||
@ -381,7 +467,10 @@ public class DeployServiceImpl implements IDeployService {
|
||||
TeamApplication ta,
|
||||
Map<Long, Application> appMap,
|
||||
Map<Long, ExternalSystem> systemMap,
|
||||
Map<Long, WorkflowDefinition> workflowMap
|
||||
Map<Long, WorkflowDefinition> workflowMap,
|
||||
Map<Long, DeployStatisticsDTO> statisticsMap,
|
||||
Map<Long, DeployRecord> latestRecordMap,
|
||||
Map<Long, List<DeployRecord>> recentRecordsMap
|
||||
) {
|
||||
Application app = appMap.get(ta.getApplicationId());
|
||||
if (app == null) {
|
||||
@ -416,9 +505,66 @@ public class DeployServiceImpl implements IDeployService {
|
||||
}
|
||||
}
|
||||
|
||||
// 部署统计信息和记录
|
||||
DeployStatisticsDTO statistics = statisticsMap.get(ta.getId());
|
||||
if (statistics != null) {
|
||||
dto.setDeployStatistics(statistics);
|
||||
|
||||
// 判断是否正在部署中
|
||||
DeployRecord latestRecord = latestRecordMap.get(ta.getId());
|
||||
if (latestRecord != null) {
|
||||
DeployRecordStatusEnums status = latestRecord.getStatus();
|
||||
dto.setIsDeploying(status == DeployRecordStatusEnums.CREATED ||
|
||||
status == DeployRecordStatusEnums.RUNNING);
|
||||
} else {
|
||||
dto.setIsDeploying(false);
|
||||
}
|
||||
} else {
|
||||
// 没有部署记录,设置默认值
|
||||
DeployStatisticsDTO emptyStats = new DeployStatisticsDTO();
|
||||
emptyStats.setTotalCount(0L);
|
||||
emptyStats.setSuccessCount(0L);
|
||||
emptyStats.setFailedCount(0L);
|
||||
emptyStats.setRunningCount(0L);
|
||||
dto.setDeployStatistics(emptyStats);
|
||||
dto.setIsDeploying(false);
|
||||
}
|
||||
|
||||
// 最近部署记录列表
|
||||
List<DeployRecord> recentRecords = recentRecordsMap.getOrDefault(ta.getId(), Collections.emptyList());
|
||||
List<DeployRecordSummaryDTO> recordSummaryList = recentRecords.stream()
|
||||
.map(this::buildDeployRecordSummary)
|
||||
.collect(toList());
|
||||
dto.setRecentDeployRecords(recordSummaryList);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建部署记录摘要DTO
|
||||
*/
|
||||
private DeployRecordSummaryDTO buildDeployRecordSummary(DeployRecord record) {
|
||||
DeployRecordSummaryDTO summary = new DeployRecordSummaryDTO();
|
||||
summary.setId(record.getId());
|
||||
summary.setStatus(record.getStatus());
|
||||
summary.setDeployBy(record.getDeployBy());
|
||||
summary.setStartTime(record.getStartTime());
|
||||
summary.setEndTime(record.getEndTime());
|
||||
summary.setDeployRemark(record.getDeployRemark());
|
||||
|
||||
// 计算持续时间(毫秒)
|
||||
if (record.getStartTime() != null && record.getEndTime() != null) {
|
||||
long duration = java.time.Duration.between(record.getStartTime(), record.getEndTime()).toMillis();
|
||||
summary.setDuration(duration);
|
||||
} else if (record.getStartTime() != null) {
|
||||
// 如果正在运行,计算到目前为止的持续时间
|
||||
long duration = java.time.Duration.between(record.getStartTime(), LocalDateTime.now()).toMillis();
|
||||
summary.setDuration(duration);
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public DeployResultDTO executeDeploy(DeployRequestDTO request) {
|
||||
@ -483,7 +629,19 @@ public class DeployServiceImpl implements IDeployService {
|
||||
log.info("部署流程已启动: businessKey={}, workflowInstanceId={}, application={}, environment={}",
|
||||
businessKey, workflowInstance.getId(), application.getAppCode(), environment.getEnvCode());
|
||||
|
||||
// 9. 返回结果
|
||||
// 9. 创建部署记录(此时已有实例ID)
|
||||
deployRecordService.createDeployRecord(
|
||||
workflowInstance.getId(),
|
||||
businessKey,
|
||||
teamApp.getId(),
|
||||
teamApp.getTeamId(),
|
||||
teamApp.getApplicationId(),
|
||||
teamApp.getEnvironmentId(),
|
||||
SecurityUtils.getCurrentUsername(),
|
||||
request.getRemark()
|
||||
);
|
||||
|
||||
// 10. 返回结果
|
||||
DeployResultDTO result = new DeployResultDTO();
|
||||
result.setWorkflowInstanceId(workflowInstance.getId());
|
||||
result.setBusinessKey(businessKey);
|
||||
|
||||
@ -23,6 +23,9 @@ import com.qqchen.deploy.backend.workflow.dto.query.WorkflowHistoricalInstancesQ
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowNodeInstanceRepository;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowCategoryRepository;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowCategory;
|
||||
import com.qqchen.deploy.backend.deploy.service.IDeployRecordService;
|
||||
import com.qqchen.deploy.backend.workflow.service.IFormDataService;
|
||||
import com.qqchen.deploy.backend.workflow.service.IFormDefinitionService;
|
||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
|
||||
@ -86,13 +89,65 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
|
||||
@Resource
|
||||
private WorkflowNodeInstanceConverter workflowNodeInstanceConverter;
|
||||
|
||||
@Resource
|
||||
private IDeployRecordService deployRecordService;
|
||||
|
||||
@Resource
|
||||
private IWorkflowCategoryRepository workflowCategoryRepository;
|
||||
|
||||
/**
|
||||
* 部署分类编码常量
|
||||
*/
|
||||
private static final String DEPLOYMENT_CATEGORY_CODE = "DEPLOYMENT";
|
||||
|
||||
@Override
|
||||
public WorkflowInstance updateInstanceStatus(String processInstanceId, WorkflowInstanceStatusEnums status, LocalDateTime endTime) {
|
||||
WorkflowInstance instance = workflowInstanceRepository.findByProcessInstanceId(processInstanceId)
|
||||
.orElseThrow(() -> new RuntimeException("Workflow instance not found: " + processInstanceId));
|
||||
instance.setStatus(status);
|
||||
instance.setEndTime(endTime);
|
||||
return workflowInstanceRepository.save(instance);
|
||||
WorkflowInstance saved = workflowInstanceRepository.save(instance);
|
||||
|
||||
// 同步部署记录状态(如果是部署类型的工作流)
|
||||
try {
|
||||
syncDeployRecordIfNeeded(saved, status);
|
||||
} catch (Exception e) {
|
||||
log.error("同步部署记录状态失败: workflowInstanceId={}, status={}",
|
||||
saved.getId(), status, e);
|
||||
// 不影响主流程,只记录错误
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果需要,同步部署记录状态
|
||||
*/
|
||||
private void syncDeployRecordIfNeeded(WorkflowInstance instance, WorkflowInstanceStatusEnums status) {
|
||||
// 1. 查询工作流定义
|
||||
WorkflowDefinition definition = workflowDefinitionRepository.findById(instance.getWorkflowDefinitionId())
|
||||
.orElse(null);
|
||||
|
||||
if (definition == null || definition.getCategoryId() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 查询分类
|
||||
WorkflowCategory category = workflowCategoryRepository.findById(definition.getCategoryId())
|
||||
.orElse(null);
|
||||
|
||||
if (category == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 判断是否是部署类型(通过分类code)
|
||||
if (!DEPLOYMENT_CATEGORY_CODE.equals(category.getCode())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 同步部署记录状态
|
||||
deployRecordService.syncStatusFromWorkflowInstance(instance, status);
|
||||
log.debug("部署记录状态同步成功: workflowInstanceId={}, status={}", instance.getId(), status);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -1070,3 +1070,36 @@ CREATE TABLE deploy_server
|
||||
CONSTRAINT fk_server_category FOREIGN KEY (category_id) REFERENCES deploy_server_category (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='服务器管理表';
|
||||
|
||||
-- 部署记录表
|
||||
CREATE TABLE deploy_record
|
||||
(
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
create_by VARCHAR(100) NULL COMMENT '创建人',
|
||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||
update_by VARCHAR(100) NULL COMMENT '更新人',
|
||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||
version INT NOT NULL DEFAULT 1 COMMENT '版本号',
|
||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
|
||||
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID(关联workflow_instance)',
|
||||
business_key VARCHAR(64) NOT NULL COMMENT '业务标识(UUID)',
|
||||
team_application_id BIGINT NOT NULL COMMENT '团队应用配置ID(关联deploy_team_application)',
|
||||
team_id BIGINT NOT NULL COMMENT '团队ID',
|
||||
application_id BIGINT NOT NULL COMMENT '应用ID',
|
||||
environment_id BIGINT NOT NULL COMMENT '环境ID',
|
||||
deploy_by VARCHAR(50) NULL COMMENT '部署人',
|
||||
deploy_remark VARCHAR(500) NULL COMMENT '部署备注',
|
||||
status VARCHAR(20) NOT NULL COMMENT '部署状态(CREATED/RUNNING/SUCCESS/FAILED等)',
|
||||
start_time DATETIME(6) NULL COMMENT '开始时间',
|
||||
end_time DATETIME(6) NULL COMMENT '结束时间',
|
||||
|
||||
UNIQUE INDEX uk_workflow_instance (workflow_instance_id),
|
||||
INDEX idx_team_application (team_application_id),
|
||||
INDEX idx_application (application_id),
|
||||
INDEX idx_environment (environment_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_start_time (start_time),
|
||||
CONSTRAINT fk_deploy_record_instance FOREIGN KEY (workflow_instance_id) REFERENCES workflow_instance (id),
|
||||
CONSTRAINT fk_deploy_record_team_app FOREIGN KEY (team_application_id) REFERENCES deploy_team_application (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部署记录表';
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user