增加部署日志

This commit is contained in:
dengqichen 2025-11-02 22:26:31 +08:00
parent b6b23bfd34
commit 990889858c
14 changed files with 904 additions and 10 deletions

View File

@ -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> {
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
};
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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

View File

@ -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='部署记录表';