打印了JENKINS节点日志
This commit is contained in:
parent
bb1faa1495
commit
e34248f3ea
@ -1,68 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.converter;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.ApproverDTO;
|
|
||||||
import com.qqchen.deploy.backend.system.entity.User;
|
|
||||||
import org.mapstruct.Mapper;
|
|
||||||
import org.mapstruct.Mapping;
|
|
||||||
import org.mapstruct.Mappings;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 审批人转换器
|
|
||||||
*
|
|
||||||
* <p>职责:User → ApproverDTO 的转换
|
|
||||||
* <p>遵循单一职责原则(SRP),只负责审批人相关的转换逻辑
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-11-06
|
|
||||||
*/
|
|
||||||
@Mapper(componentModel = "spring")
|
|
||||||
@Component
|
|
||||||
public interface ApproverConverter {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User 转换为 ApproverDTO
|
|
||||||
*
|
|
||||||
* @param user 用户实体
|
|
||||||
* @return 审批人DTO
|
|
||||||
*/
|
|
||||||
@Mappings({
|
|
||||||
@Mapping(source = "id", target = "userId"),
|
|
||||||
@Mapping(source = "username", target = "username"),
|
|
||||||
@Mapping(source = "nickname", target = "realName")
|
|
||||||
})
|
|
||||||
ApproverDTO toDTO(User user);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量转换 User 列表
|
|
||||||
*
|
|
||||||
* @param users 用户列表
|
|
||||||
* @return 审批人DTO列表
|
|
||||||
*/
|
|
||||||
List<ApproverDTO> toDTOList(List<User> users);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从用户ID列表和用户Map构建审批人列表
|
|
||||||
* <p>常用场景:从 TeamEnvironmentConfig.approverUserIds 构建审批人列表
|
|
||||||
*
|
|
||||||
* @param userIds 用户ID列表
|
|
||||||
* @param userMap 用户Map(key=userId, value=User)
|
|
||||||
* @return 审批人DTO列表(自动过滤 Map 中不存在的用户)
|
|
||||||
*/
|
|
||||||
default List<ApproverDTO> fromUserIds(List<Long> userIds, Map<Long, User> userMap) {
|
|
||||||
if (userIds == null || userIds.isEmpty() || userMap == null || userMap.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
return userIds.stream()
|
|
||||||
.map(userMap::get) // 从 Map 获取 User
|
|
||||||
.filter(user -> user != null) // 过滤 null(Map 中不存在的用户)
|
|
||||||
.map(this::toDTO) // 转换为 ApproverDTO
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,169 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.converter;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.DeployRecordSummaryDTO;
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.DeployStatisticsDTO;
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.DeployableApplicationDTO;
|
|
||||||
import com.qqchen.deploy.backend.deploy.entity.*;
|
|
||||||
import com.qqchen.deploy.backend.deploy.enums.DeployRecordStatusEnums;
|
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
|
||||||
import org.mapstruct.Mapper;
|
|
||||||
import org.mapstruct.Mapping;
|
|
||||||
import org.mapstruct.Mappings;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 可部署应用转换器
|
|
||||||
*
|
|
||||||
* <p>职责:多源数据 → DeployableApplicationDTO 的转换
|
|
||||||
* <p>遵循单一职责原则(SRP),只负责可部署应用相关的转换逻辑
|
|
||||||
*
|
|
||||||
* <p>数据源:
|
|
||||||
* <ul>
|
|
||||||
* <li>TeamApplication - 团队应用关联信息</li>
|
|
||||||
* <li>Application - 应用基础信息</li>
|
|
||||||
* <li>ExternalSystem - 部署系统信息(Jenkins)</li>
|
|
||||||
* <li>WorkflowDefinition - 工作流定义</li>
|
|
||||||
* <li>DeployStatistics - 部署统计</li>
|
|
||||||
* <li>DeployRecord - 部署记录</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-11-06
|
|
||||||
*/
|
|
||||||
@Mapper(componentModel = "spring")
|
|
||||||
@Component
|
|
||||||
public interface DeployableApplicationConverter {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 基础转换:TeamApplication + Application → DeployableApplicationDTO
|
|
||||||
* <p>只处理简单字段映射
|
|
||||||
*/
|
|
||||||
@Mappings({
|
|
||||||
@Mapping(source = "teamApplication.id", target = "teamApplicationId"),
|
|
||||||
@Mapping(source = "application.id", target = "applicationId"),
|
|
||||||
@Mapping(source = "application.appCode", target = "applicationCode"),
|
|
||||||
@Mapping(source = "application.appName", target = "applicationName"),
|
|
||||||
@Mapping(source = "application.appDesc", target = "applicationDesc"),
|
|
||||||
@Mapping(source = "teamApplication.branch", target = "branch"),
|
|
||||||
@Mapping(source = "teamApplication.deploySystemId", target = "deploySystemId"),
|
|
||||||
@Mapping(source = "teamApplication.deployJob", target = "deployJob"),
|
|
||||||
@Mapping(source = "teamApplication.workflowDefinitionId", target = "workflowDefinitionId"),
|
|
||||||
@Mapping(target = "deploySystemName", ignore = true),
|
|
||||||
@Mapping(target = "workflowDefinitionName", ignore = true),
|
|
||||||
@Mapping(target = "workflowDefinitionKey", ignore = true),
|
|
||||||
@Mapping(target = "deployStatistics", ignore = true),
|
|
||||||
@Mapping(target = "isDeploying", ignore = true),
|
|
||||||
@Mapping(target = "recentDeployRecords", ignore = true)
|
|
||||||
})
|
|
||||||
DeployableApplicationDTO toBaseDTO(TeamApplication teamApplication, Application application);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置部署系统信息
|
|
||||||
*
|
|
||||||
* @param dto 目标DTO
|
|
||||||
* @param system 部署系统(Jenkins)
|
|
||||||
*/
|
|
||||||
default void applyDeploySystem(DeployableApplicationDTO dto, ExternalSystem system) {
|
|
||||||
if (system != null) {
|
|
||||||
dto.setDeploySystemName(system.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置工作流定义信息
|
|
||||||
*
|
|
||||||
* @param dto 目标DTO
|
|
||||||
* @param workflow 工作流定义
|
|
||||||
*/
|
|
||||||
default void applyWorkflowDefinition(DeployableApplicationDTO dto, WorkflowDefinition workflow) {
|
|
||||||
if (workflow != null) {
|
|
||||||
dto.setWorkflowDefinitionName(workflow.getName());
|
|
||||||
dto.setWorkflowDefinitionKey(workflow.getKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置部署统计信息和部署状态
|
|
||||||
*
|
|
||||||
* @param dto 目标DTO
|
|
||||||
* @param statistics 部署统计
|
|
||||||
* @param latestRecord 最新部署记录(用于判断是否正在部署)
|
|
||||||
*/
|
|
||||||
default void applyDeployStatistics(DeployableApplicationDTO dto, DeployStatisticsDTO statistics, DeployRecord latestRecord) {
|
|
||||||
if (statistics != null) {
|
|
||||||
dto.setDeployStatistics(statistics);
|
|
||||||
|
|
||||||
// 判断是否正在部署中
|
|
||||||
if (latestRecord != null) {
|
|
||||||
DeployRecordStatusEnums status = latestRecord.getStatus();
|
|
||||||
dto.setIsDeploying(status == DeployRecordStatusEnums.CREATED || status == DeployRecordStatusEnums.RUNNING);
|
|
||||||
} else {
|
|
||||||
dto.setIsDeploying(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 没有部署记录,设置默认空统计
|
|
||||||
dto.setDeployStatistics(createEmptyStatistics());
|
|
||||||
dto.setIsDeploying(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置最近部署记录列表
|
|
||||||
*
|
|
||||||
* @param dto 目标DTO
|
|
||||||
* @param recentRecordSummaries 最近部署记录摘要列表
|
|
||||||
*/
|
|
||||||
default void setRecentDeployRecords(DeployableApplicationDTO dto, List<DeployRecordSummaryDTO> recentRecordSummaries) {
|
|
||||||
dto.setRecentDeployRecords(recentRecordSummaries != null ? recentRecordSummaries : List.of());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建空的部署统计(用于没有部署记录的应用)
|
|
||||||
*/
|
|
||||||
default DeployStatisticsDTO createEmptyStatistics() {
|
|
||||||
DeployStatisticsDTO statistics = new DeployStatisticsDTO();
|
|
||||||
statistics.setTotalCount(0L);
|
|
||||||
statistics.setSuccessCount(0L);
|
|
||||||
statistics.setFailedCount(0L);
|
|
||||||
statistics.setRunningCount(0L);
|
|
||||||
return statistics;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 完整转换:从上下文构建 DeployableApplicationDTO
|
|
||||||
* <p>封装所有转换逻辑,简化 Service 层调用
|
|
||||||
*
|
|
||||||
* @param teamApplication 团队应用关联
|
|
||||||
* @param application 应用实体
|
|
||||||
* @param deploySystem 部署系统(可选)
|
|
||||||
* @param workflow 工作流定义(可选)
|
|
||||||
* @param statistics 部署统计(可选)
|
|
||||||
* @param latestRecord 最新部署记录(可选)
|
|
||||||
* @param recentRecordSummaries 最近部署记录摘要列表(可选)
|
|
||||||
* @return 完整的可部署应用DTO
|
|
||||||
*/
|
|
||||||
default DeployableApplicationDTO toFullDTO(
|
|
||||||
TeamApplication teamApplication,
|
|
||||||
Application application,
|
|
||||||
ExternalSystem deploySystem,
|
|
||||||
WorkflowDefinition workflow,
|
|
||||||
DeployStatisticsDTO statistics,
|
|
||||||
DeployRecord latestRecord,
|
|
||||||
List<DeployRecordSummaryDTO> recentRecordSummaries
|
|
||||||
) {
|
|
||||||
// 1. 基础转换
|
|
||||||
DeployableApplicationDTO dto = toBaseDTO(teamApplication, application);
|
|
||||||
|
|
||||||
// 2. 应用扩展信息
|
|
||||||
applyDeploySystem(dto, deploySystem);
|
|
||||||
applyWorkflowDefinition(dto, workflow);
|
|
||||||
applyDeployStatistics(dto, statistics, latestRecord);
|
|
||||||
setRecentDeployRecords(dto, recentRecordSummaries);
|
|
||||||
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.converter;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.ApproverDTO;
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.DeployableApplicationDTO;
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.DeployableEnvironmentDTO;
|
|
||||||
import com.qqchen.deploy.backend.deploy.entity.Environment;
|
|
||||||
import com.qqchen.deploy.backend.deploy.entity.TeamEnvironmentConfig;
|
|
||||||
import org.mapstruct.Mapper;
|
|
||||||
import org.mapstruct.Mapping;
|
|
||||||
import org.mapstruct.Mappings;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 可部署环境转换器
|
|
||||||
*
|
|
||||||
* <p>采用 MapStruct + 自定义方法混合模式:
|
|
||||||
* <ul>
|
|
||||||
* <li>简单字段映射:交给 MapStruct 自动生成</li>
|
|
||||||
* <li>复杂业务逻辑:使用 @AfterMapping 或自定义方法</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-11-06
|
|
||||||
*/
|
|
||||||
@Mapper(componentModel = "spring")
|
|
||||||
@Component
|
|
||||||
public interface DeployableEnvironmentConverter {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 基础转换:Environment → DeployableEnvironmentDTO
|
|
||||||
* <p>仅处理简单字段映射
|
|
||||||
*/
|
|
||||||
@Mappings({
|
|
||||||
@Mapping(source = "id", target = "environmentId"),
|
|
||||||
@Mapping(source = "envCode", target = "environmentCode"),
|
|
||||||
@Mapping(source = "envName", target = "environmentName"),
|
|
||||||
@Mapping(source = "envDesc", target = "environmentDesc"),
|
|
||||||
@Mapping(target = "requiresApproval", ignore = true),
|
|
||||||
@Mapping(target = "approvers", ignore = true),
|
|
||||||
@Mapping(target = "notificationEnabled", ignore = true),
|
|
||||||
@Mapping(target = "notificationChannelId", ignore = true),
|
|
||||||
@Mapping(target = "requireCodeReview", ignore = true),
|
|
||||||
@Mapping(target = "remark", ignore = true),
|
|
||||||
@Mapping(target = "applications", ignore = true)
|
|
||||||
})
|
|
||||||
DeployableEnvironmentDTO toBaseDTO(Environment environment);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并配置信息到 DTO
|
|
||||||
* <p>从 TeamEnvironmentConfig 补充配置字段
|
|
||||||
*
|
|
||||||
* @param dto 目标 DTO
|
|
||||||
* @param config 团队环境配置
|
|
||||||
*/
|
|
||||||
default void applyConfig(DeployableEnvironmentDTO dto, TeamEnvironmentConfig config) {
|
|
||||||
if (config != null) {
|
|
||||||
dto.setRequiresApproval(config.getApprovalRequired() != null ? config.getApprovalRequired() : false);
|
|
||||||
dto.setRequireCodeReview(config.getRequireCodeReview() != null ? config.getRequireCodeReview() : false);
|
|
||||||
dto.setNotificationEnabled(config.getNotificationEnabled() != null ? config.getNotificationEnabled() : true);
|
|
||||||
dto.setNotificationChannelId(config.getNotificationChannelId());
|
|
||||||
dto.setRemark(config.getRemark());
|
|
||||||
} else {
|
|
||||||
// 默认值
|
|
||||||
dto.setRequiresApproval(false);
|
|
||||||
dto.setRequireCodeReview(false);
|
|
||||||
dto.setNotificationEnabled(true);
|
|
||||||
dto.setApprovers(Collections.emptyList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置审批人列表
|
|
||||||
*
|
|
||||||
* @param dto 目标 DTO
|
|
||||||
* @param approvers 审批人列表
|
|
||||||
*/
|
|
||||||
default void setApprovers(DeployableEnvironmentDTO dto, List<ApproverDTO> approvers) {
|
|
||||||
dto.setApprovers(approvers != null ? approvers : Collections.emptyList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置应用列表
|
|
||||||
*
|
|
||||||
* @param dto 目标 DTO
|
|
||||||
* @param applications 应用列表
|
|
||||||
*/
|
|
||||||
default void setApplications(DeployableEnvironmentDTO dto, List<DeployableApplicationDTO> applications) {
|
|
||||||
dto.setApplications(applications != null ? applications : Collections.emptyList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.dto;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 审批人DTO
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-11-02
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Schema(description = "审批人信息")
|
|
||||||
public class ApproverDTO {
|
|
||||||
|
|
||||||
@Schema(description = "用户ID")
|
|
||||||
private Long userId;
|
|
||||||
|
|
||||||
@Schema(description = "用户名")
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
@Schema(description = "真实姓名")
|
|
||||||
private String realName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -6,6 +6,8 @@ import jakarta.validation.constraints.NotBlank;
|
|||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部署执行请求
|
* 部署执行请求
|
||||||
*
|
*
|
||||||
@ -95,7 +97,7 @@ public class DeployExecuteRequest {
|
|||||||
private Boolean required;
|
private Boolean required;
|
||||||
|
|
||||||
@Schema(description = "审批人ID列表(逗号分隔)")
|
@Schema(description = "审批人ID列表(逗号分隔)")
|
||||||
private String userIds;
|
private List<String> userNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,64 +0,0 @@
|
|||||||
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
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-11-02
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Schema(description = "可部署应用信息")
|
|
||||||
public class DeployableApplicationDTO {
|
|
||||||
|
|
||||||
@Schema(description = "团队应用关联ID")
|
|
||||||
private Long teamApplicationId;
|
|
||||||
|
|
||||||
@Schema(description = "应用ID")
|
|
||||||
private Long applicationId;
|
|
||||||
|
|
||||||
@Schema(description = "应用编码")
|
|
||||||
private String applicationCode;
|
|
||||||
|
|
||||||
@Schema(description = "应用名称")
|
|
||||||
private String applicationName;
|
|
||||||
|
|
||||||
@Schema(description = "应用描述")
|
|
||||||
private String applicationDesc;
|
|
||||||
|
|
||||||
@Schema(description = "分支名称")
|
|
||||||
private String branch;
|
|
||||||
|
|
||||||
@Schema(description = "部署系统ID(Jenkins系统)")
|
|
||||||
private Long deploySystemId;
|
|
||||||
|
|
||||||
@Schema(description = "部署系统名称")
|
|
||||||
private String deploySystemName;
|
|
||||||
|
|
||||||
@Schema(description = "部署任务ID(Jenkins Job)")
|
|
||||||
private String deployJob;
|
|
||||||
|
|
||||||
@Schema(description = "工作流定义ID")
|
|
||||||
private Long workflowDefinitionId;
|
|
||||||
|
|
||||||
@Schema(description = "工作流定义名称")
|
|
||||||
private String workflowDefinitionName;
|
|
||||||
|
|
||||||
@Schema(description = "工作流流程标识(processKey)")
|
|
||||||
private String workflowDefinitionKey;
|
|
||||||
|
|
||||||
@Schema(description = "部署统计信息")
|
|
||||||
private DeployStatisticsDTO deployStatistics;
|
|
||||||
|
|
||||||
@Schema(description = "是否正在部署中")
|
|
||||||
private Boolean isDeploying;
|
|
||||||
|
|
||||||
@Schema(description = "最近部署记录列表(最多10条)")
|
|
||||||
private List<DeployRecordSummaryDTO> recentDeployRecords;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.dto;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 可部署环境DTO
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-11-02
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Schema(description = "可部署环境信息")
|
|
||||||
public class DeployableEnvironmentDTO {
|
|
||||||
|
|
||||||
@Schema(description = "环境ID")
|
|
||||||
private Long environmentId;
|
|
||||||
|
|
||||||
@Schema(description = "环境编码")
|
|
||||||
private String environmentCode;
|
|
||||||
|
|
||||||
@Schema(description = "环境名称")
|
|
||||||
private String environmentName;
|
|
||||||
|
|
||||||
@Schema(description = "环境描述")
|
|
||||||
private String environmentDesc;
|
|
||||||
|
|
||||||
@Schema(description = "是否启用")
|
|
||||||
private Boolean enabled;
|
|
||||||
|
|
||||||
@Schema(description = "排序号")
|
|
||||||
private Integer sort;
|
|
||||||
|
|
||||||
@Schema(description = "是否需要审批")
|
|
||||||
private Boolean requiresApproval;
|
|
||||||
|
|
||||||
@Schema(description = "审批人列表")
|
|
||||||
private List<ApproverDTO> approvers;
|
|
||||||
|
|
||||||
@Schema(description = "是否启用部署通知")
|
|
||||||
private Boolean notificationEnabled;
|
|
||||||
|
|
||||||
@Schema(description = "通知渠道ID")
|
|
||||||
private Long notificationChannelId;
|
|
||||||
|
|
||||||
@Schema(description = "是否要求代码审查")
|
|
||||||
private Boolean requireCodeReview;
|
|
||||||
|
|
||||||
@Schema(description = "备注信息")
|
|
||||||
private String remark;
|
|
||||||
|
|
||||||
@Schema(description = "可部署应用列表")
|
|
||||||
private List<DeployableApplicationDTO> applications;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.dto;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 团队可部署环境DTO
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-11-02
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Schema(description = "团队可部署环境信息")
|
|
||||||
public class TeamDeployableDTO {
|
|
||||||
|
|
||||||
@Schema(description = "团队ID")
|
|
||||||
private Long teamId;
|
|
||||||
|
|
||||||
@Schema(description = "团队编码")
|
|
||||||
private String teamCode;
|
|
||||||
|
|
||||||
@Schema(description = "团队名称")
|
|
||||||
private String teamName;
|
|
||||||
|
|
||||||
@Schema(description = "用户在团队中的角色")
|
|
||||||
private String teamRole;
|
|
||||||
|
|
||||||
@Schema(description = "团队描述")
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
@Schema(description = "可部署环境列表")
|
|
||||||
private List<DeployableEnvironmentDTO> environments;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -50,5 +50,13 @@ public class UserDeployableTeamEnvironmentDTO {
|
|||||||
|
|
||||||
@Schema(description = "可部署应用列表")
|
@Schema(description = "可部署应用列表")
|
||||||
private List<UserDeployableTeamEnvironmentApplicationDTO> applications;
|
private List<UserDeployableTeamEnvironmentApplicationDTO> applications;
|
||||||
|
|
||||||
|
// ==================== 当前用户权限 ====================
|
||||||
|
|
||||||
|
@Schema(description = "当前用户是否可以发起部署")
|
||||||
|
private Boolean canDeploy;
|
||||||
|
|
||||||
|
@Schema(description = "当前用户是否可以审批部署")
|
||||||
|
private Boolean canApprove;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.service.impl;
|
package com.qqchen.deploy.backend.deploy.service.impl;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.deploy.converter.ApproverConverter;
|
|
||||||
import com.qqchen.deploy.backend.deploy.converter.DeployableApplicationConverter;
|
|
||||||
import com.qqchen.deploy.backend.deploy.converter.DeployableEnvironmentConverter;
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.*;
|
import com.qqchen.deploy.backend.deploy.dto.*;
|
||||||
import com.qqchen.deploy.backend.deploy.entity.*;
|
import com.qqchen.deploy.backend.deploy.entity.*;
|
||||||
import com.qqchen.deploy.backend.deploy.repository.*;
|
import com.qqchen.deploy.backend.deploy.repository.*;
|
||||||
@ -11,6 +8,7 @@ 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.JsonUtils;
|
import com.qqchen.deploy.backend.framework.utils.JsonUtils;
|
||||||
|
import com.qqchen.deploy.backend.framework.utils.MapUtils;
|
||||||
import com.qqchen.deploy.backend.framework.utils.SpelExpressionResolver;
|
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;
|
||||||
@ -18,7 +16,6 @@ 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.ApprovalInputMapping;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.inputmapping.JenkinsBuildInputMapping;
|
|
||||||
import com.qqchen.deploy.backend.workflow.dto.outputs.ApprovalOutputs;
|
import com.qqchen.deploy.backend.workflow.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.model.NodeContext;
|
||||||
@ -30,11 +27,11 @@ import com.qqchen.deploy.backend.deploy.entity.DeployRecord;
|
|||||||
import com.qqchen.deploy.backend.deploy.enums.DeployRecordStatusEnums;
|
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.engine.RuntimeService;
|
import org.flowable.engine.RuntimeService;
|
||||||
import org.flowable.engine.TaskService;
|
import org.flowable.engine.TaskService;
|
||||||
import org.flowable.engine.runtime.ProcessInstance;
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
import org.flowable.task.api.Task;
|
import org.flowable.task.api.Task;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@ -102,17 +99,8 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
@Resource
|
@Resource
|
||||||
private RuntimeService runtimeService;
|
private RuntimeService runtimeService;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private DeployableEnvironmentConverter deployableEnvironmentConverter;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private ApproverConverter approverConverter;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private DeployableApplicationConverter deployableApplicationConverter;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public List<UserTeamDeployableDTO> getDeployableEnvironments() {
|
public List<UserTeamDeployableDTO> getDeployableEnvironments() {
|
||||||
Long currentUserId = SecurityUtils.getCurrentUserId();
|
Long currentUserId = SecurityUtils.getCurrentUserId();
|
||||||
|
|
||||||
@ -249,7 +237,7 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
List<UserTeamDeployableDTO> result = new ArrayList<>();
|
List<UserTeamDeployableDTO> result = new ArrayList<>();
|
||||||
for (Long teamId : teamIds) {
|
for (Long teamId : teamIds) {
|
||||||
UserTeamDeployableDTO teamDTO = buildUserTeamDeployableDTO(
|
UserTeamDeployableDTO teamDTO = buildUserTeamDeployableDTO(
|
||||||
teamId, teamMap, ownerMap, membersByTeam, memberUserMap,
|
currentUserId, teamId, teamMap, ownerMap, membersByTeam, memberUserMap,
|
||||||
teamAppsMap, envMap, appMap, systemMap, workflowMap,
|
teamAppsMap, envMap, appMap, systemMap, workflowMap,
|
||||||
teamEnvConfigMap, approverMap,
|
teamEnvConfigMap, approverMap,
|
||||||
statisticsMap, latestRecordMap, recentRecordsMap
|
statisticsMap, latestRecordMap, recentRecordsMap
|
||||||
@ -267,6 +255,7 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
* 构建单个团队的可部署数据DTO
|
* 构建单个团队的可部署数据DTO
|
||||||
*/
|
*/
|
||||||
private UserTeamDeployableDTO buildUserTeamDeployableDTO(
|
private UserTeamDeployableDTO buildUserTeamDeployableDTO(
|
||||||
|
Long currentUserId,
|
||||||
Long teamId,
|
Long teamId,
|
||||||
Map<Long, Team> teamMap,
|
Map<Long, Team> teamMap,
|
||||||
Map<Long, User> ownerMap,
|
Map<Long, User> ownerMap,
|
||||||
@ -342,6 +331,7 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UserDeployableTeamEnvironmentDTO envDTO = buildUserDeployableTeamEnvironmentDTO(
|
UserDeployableTeamEnvironmentDTO envDTO = buildUserDeployableTeamEnvironmentDTO(
|
||||||
|
currentUserId, team.getOwnerId(),
|
||||||
teamId, env, entry.getValue(),
|
teamId, env, entry.getValue(),
|
||||||
appMap, systemMap, workflowMap,
|
appMap, systemMap, workflowMap,
|
||||||
teamEnvConfigMap, approverMap,
|
teamEnvConfigMap, approverMap,
|
||||||
@ -364,6 +354,8 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
* 构建单个环境的可部署数据DTO
|
* 构建单个环境的可部署数据DTO
|
||||||
*/
|
*/
|
||||||
private UserDeployableTeamEnvironmentDTO buildUserDeployableTeamEnvironmentDTO(
|
private UserDeployableTeamEnvironmentDTO buildUserDeployableTeamEnvironmentDTO(
|
||||||
|
Long currentUserId,
|
||||||
|
Long teamOwnerId,
|
||||||
Long teamId,
|
Long teamId,
|
||||||
Environment env,
|
Environment env,
|
||||||
List<TeamApplication> teamApps,
|
List<TeamApplication> teamApps,
|
||||||
@ -430,6 +422,18 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
dto.setApplications(applications);
|
dto.setApplications(applications);
|
||||||
|
|
||||||
|
// ==================== 设置当前用户权限 ====================
|
||||||
|
|
||||||
|
// 1. 团队成员都可以发起部署
|
||||||
|
dto.setCanDeploy(true);
|
||||||
|
|
||||||
|
// 2. 判断是否可以审批(团队负责人 或 审批人列表中的任意一人)
|
||||||
|
List<Long> approverUserIds = config != null && config.getApproverUserIds() != null
|
||||||
|
? config.getApproverUserIds()
|
||||||
|
: Collections.emptyList();
|
||||||
|
boolean canApprove = hasApprovalPermission(currentUserId, teamOwnerId, approverUserIds);
|
||||||
|
dto.setCanApprove(canApprove);
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -696,9 +700,9 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
String currentUsername = SecurityUtils.getCurrentUsername();
|
String currentUsername = SecurityUtils.getCurrentUsername();
|
||||||
log.info("查询用户 {} 的部署审批任务", currentUsername);
|
log.info("查询用户 {} 的部署审批任务", currentUsername);
|
||||||
|
|
||||||
// 2. 查询用户的所有待办任务(模仿 ApprovalTaskService.getMyTasks)
|
// 2. 查询用户的所有待办任务(包括候选人和分配人,支持或签模式)
|
||||||
List<Task> tasks = taskService.createTaskQuery()
|
List<Task> tasks = taskService.createTaskQuery()
|
||||||
.taskAssignee(currentUsername)
|
.taskCandidateOrAssigned(currentUsername)
|
||||||
.orderByTaskCreateTime()
|
.orderByTaskCreateTime()
|
||||||
.desc()
|
.desc()
|
||||||
.list();
|
.list();
|
||||||
@ -721,6 +725,9 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. 批量查询团队信息(解决 N+1 查询问题)
|
||||||
|
enrichTeamInfo(result);
|
||||||
|
|
||||||
log.info("用户 {} 共有 {} 个部署审批任务", currentUsername, result.size());
|
log.info("用户 {} 共有 {} 个部署审批任务", currentUsername, result.size());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -740,19 +747,8 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
return null;
|
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 查询)
|
// 3. 获取 BusinessKey(需要通过 RuntimeService 查询)
|
||||||
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
|
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
|
||||||
.processInstanceId(task.getProcessInstanceId())
|
|
||||||
.singleResult();
|
|
||||||
|
|
||||||
if (processInstance == null) {
|
if (processInstance == null) {
|
||||||
log.debug("流程实例 {} 已结束,跳过", task.getProcessInstanceId());
|
log.debug("流程实例 {} 已结束,跳过", task.getProcessInstanceId());
|
||||||
@ -766,15 +762,9 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. 查询部署记录
|
// 4. 查询部署记录
|
||||||
Optional<DeployRecord> deployRecordOpt = deployRecordRepository
|
DeployRecord deployRecord = deployRecordRepository.findByBusinessKeyAndDeletedFalse(businessKey)
|
||||||
.findByBusinessKeyAndDeletedFalse(businessKey);
|
.orElseThrow(() -> new BusinessException(ResponseCode.DEPLOY_RECORD_NOT_FOUND,
|
||||||
|
new Object[]{businessKey}));
|
||||||
if (deployRecordOpt.isEmpty()) {
|
|
||||||
log.warn("找不到业务标识为 {} 的部署记录", businessKey);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
DeployRecord deployRecord = deployRecordOpt.get();
|
|
||||||
|
|
||||||
// 5. 构建 DTO
|
// 5. 构建 DTO
|
||||||
DeployApprovalTaskDTO dto = new DeployApprovalTaskDTO();
|
DeployApprovalTaskDTO dto = new DeployApprovalTaskDTO();
|
||||||
@ -799,37 +789,30 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
ZoneId.systemDefault()));
|
ZoneId.systemDefault()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5.2 审批配置信息(从流程变量获取)
|
// 5.2 审批配置信息(从审批节点的 NodeContext 中获取 ApprovalInputMapping)
|
||||||
dto.setApprovalTitle((String) variables.get("approvalTitle"));
|
String nodeId = task.getTaskDefinitionKey();
|
||||||
dto.setApprovalContent((String) variables.get("approvalContent"));
|
ApprovalInputMapping approvalInputs = extractApprovalInputMapping(variables, nodeId);
|
||||||
dto.setApprovalMode((String) variables.get("approvalMode"));
|
if (approvalInputs != null) {
|
||||||
dto.setAllowDelegate((Boolean) variables.get("allowDelegate"));
|
dto.setApprovalTitle(approvalInputs.getApprovalTitle());
|
||||||
dto.setAllowAddSign((Boolean) variables.get("allowAddSign"));
|
dto.setApprovalContent(approvalInputs.getApprovalContent());
|
||||||
dto.setRequireComment((Boolean) variables.get("requireComment"));
|
dto.setApprovalMode(approvalInputs.getApprovalMode() != null
|
||||||
|
? approvalInputs.getApprovalMode().name()
|
||||||
// 5.3 部署业务上下文信息
|
: null);
|
||||||
dto.setDeployRecordId(deployRecord.getId());
|
dto.setAllowDelegate(approvalInputs.getAllowDelegate());
|
||||||
dto.setBusinessKey(businessKey);
|
dto.setAllowAddSign(approvalInputs.getAllowAddSign());
|
||||||
dto.setTeamId(getLongValue(deployContext, "teamId"));
|
dto.setRequireComment(approvalInputs.getRequireComment());
|
||||||
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 计算待审批时长
|
// 5.3 部署业务上下文信息(从 variables 还原 DeployExecuteRequest)
|
||||||
|
DeployExecuteRequest deployRequest = JsonUtils.fromMap(variables, DeployExecuteRequest.class);
|
||||||
|
dto.setDeployRecordId(deployRecord.getId());
|
||||||
|
dto.setBusinessKey(businessKey);
|
||||||
|
dto.setDeployStartTime(deployRecord.getStartTime());
|
||||||
|
|
||||||
|
// 使用 BeanUtils 批量复制同名字段
|
||||||
|
BeanUtils.copyProperties(deployRequest, dto);
|
||||||
|
|
||||||
|
// 5.4 计算待审批时长(团队名称通过批量查询填充)
|
||||||
if (dto.getCreateTime() != null) {
|
if (dto.getCreateTime() != null) {
|
||||||
long pendingMillis = Duration.between(dto.getCreateTime(), LocalDateTime.now()).toMillis();
|
long pendingMillis = Duration.between(dto.getCreateTime(), LocalDateTime.now()).toMillis();
|
||||||
dto.setPendingDuration(pendingMillis);
|
dto.setPendingDuration(pendingMillis);
|
||||||
@ -841,30 +824,6 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
return dto;
|
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
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@ -873,31 +832,29 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
log.info("开始处理部署审批: taskId={}, result={}", taskId, request.getResult());
|
log.info("开始处理部署审批: taskId={}, result={}", taskId, request.getResult());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. 验证任务是否存在
|
// 1. 获取当前用户
|
||||||
|
String currentUsername = SecurityUtils.getCurrentUsername();
|
||||||
|
|
||||||
|
// 2. 验证任务是否存在 && 当前用户是否有权限审批
|
||||||
|
// taskCandidateOrAssigned 支持所有审批模式:
|
||||||
|
// - SINGLE 模式: 检查 assignee
|
||||||
|
// - ANY/ALL 模式: 检查 candidateUsers
|
||||||
Task task = taskService.createTaskQuery()
|
Task task = taskService.createTaskQuery()
|
||||||
.taskId(taskId)
|
.taskId(taskId)
|
||||||
|
.taskCandidateOrAssigned(currentUsername)
|
||||||
.singleResult();
|
.singleResult();
|
||||||
|
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
log.error("审批任务不存在: taskId={}", taskId);
|
log.error("审批任务不存在或无权审批: taskId={}, 当前用户={}", taskId, currentUsername);
|
||||||
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);
|
throw new BusinessException(ResponseCode.PERMISSION_DENIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 获取节点ID并更新 NodeContext(与 ApprovalTaskService 保持一致)
|
log.debug("审批人: {}, 任务负责人: {}", currentUsername, task.getAssignee());
|
||||||
|
|
||||||
|
// 3. 获取节点ID并更新 NodeContext(与 ApprovalTaskService 保持一致)
|
||||||
String nodeId = task.getTaskDefinitionKey();
|
String nodeId = task.getTaskDefinitionKey();
|
||||||
|
|
||||||
// 5. 读取现有 NodeContext
|
// 4. 读取现有 NodeContext
|
||||||
Object nodeDataObj = taskService.getVariable(task.getId(), nodeId);
|
Object nodeDataObj = taskService.getVariable(task.getId(), nodeId);
|
||||||
NodeContext<ApprovalInputMapping, ApprovalOutputs> nodeContext;
|
NodeContext<ApprovalInputMapping, ApprovalOutputs> nodeContext;
|
||||||
if (nodeDataObj instanceof Map) {
|
if (nodeDataObj instanceof Map) {
|
||||||
@ -909,7 +866,7 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
nodeContext = new NodeContext<>();
|
nodeContext = new NodeContext<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 创建审批结果(只设置核心字段,其他由 ApprovalEndExecutionListener 自动装配)
|
// 5. 创建审批结果(只设置核心字段,其他由 ApprovalEndExecutionListener 自动装配)
|
||||||
ApprovalOutputs outputs = new ApprovalOutputs();
|
ApprovalOutputs outputs = new ApprovalOutputs();
|
||||||
outputs.setApprovalResult(request.getResult());
|
outputs.setApprovalResult(request.getResult());
|
||||||
outputs.setApprover(currentUsername);
|
outputs.setApprover(currentUsername);
|
||||||
@ -917,18 +874,18 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
outputs.setApprovalComment(request.getComment());
|
outputs.setApprovalComment(request.getComment());
|
||||||
// ✅ 不需要手动设置 status 和 approvalDuration,监听器会自动装配
|
// ✅ 不需要手动设置 status 和 approvalDuration,监听器会自动装配
|
||||||
|
|
||||||
// 7. 设置到 NodeContext
|
// 6. 设置到 NodeContext
|
||||||
nodeContext.setOutputs(outputs);
|
nodeContext.setOutputs(outputs);
|
||||||
|
|
||||||
// 8. 保存回流程变量
|
// 7. 保存回流程变量
|
||||||
taskService.setVariable(task.getId(), nodeId, nodeContext.toMap(objectMapper));
|
taskService.setVariable(task.getId(), nodeId, nodeContext.toMap(objectMapper));
|
||||||
|
|
||||||
// 9. 添加任务评论(供历史查询)
|
// 8. 添加任务评论(供历史查询)
|
||||||
if (request.getComment() != null) {
|
if (request.getComment() != null) {
|
||||||
taskService.addComment(taskId, task.getProcessInstanceId(), request.getComment());
|
taskService.addComment(taskId, task.getProcessInstanceId(), request.getComment());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10. 完成任务(触发 ApprovalEndExecutionListener,监听器会自动装配其他字段)
|
// 9. 完成任务(触发 ApprovalEndExecutionListener,监听器会自动装配其他字段)
|
||||||
taskService.complete(taskId);
|
taskService.complete(taskId);
|
||||||
|
|
||||||
log.info("部署审批已提交,后续节点将异步执行: taskId={}, result={}, comment={}",
|
log.info("部署审批已提交,后续节点将异步执行: taskId={}, result={}, comment={}",
|
||||||
@ -942,5 +899,106 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
throw new BusinessException(ResponseCode.ERROR);
|
throw new BusinessException(ResponseCode.ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 工具方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询并填充团队信息(解决 N+1 查询问题)
|
||||||
|
*
|
||||||
|
* @param dtoList 待填充的 DTO 列表
|
||||||
|
*/
|
||||||
|
private void enrichTeamInfo(List<DeployApprovalTaskDTO> dtoList) {
|
||||||
|
if (dtoList == null || dtoList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 收集所有 teamIds
|
||||||
|
Set<Long> teamIds = dtoList.stream()
|
||||||
|
.map(DeployApprovalTaskDTO::getTeamId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
if (teamIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 批量查询团队信息
|
||||||
|
Map<Long, Team> teamMap = teamRepository.findAllById(teamIds).stream()
|
||||||
|
.collect(Collectors.toMap(Team::getId, team -> team));
|
||||||
|
|
||||||
|
// 3. 填充团队名称
|
||||||
|
for (DeployApprovalTaskDTO dto : dtoList) {
|
||||||
|
Long teamId = dto.getTeamId();
|
||||||
|
if (teamId != null) {
|
||||||
|
Team team = teamMap.get(teamId);
|
||||||
|
if (team != null) {
|
||||||
|
dto.setTeamName(team.getTeamName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从流程变量中提取审批节点的 ApprovalInputMapping
|
||||||
|
* <p>从 NodeContext 的 inputs 字段中解析审批配置
|
||||||
|
*
|
||||||
|
* @param variables 流程变量 Map
|
||||||
|
* @param nodeId 审批节点ID
|
||||||
|
* @return ApprovalInputMapping 对象,解析失败返回 null
|
||||||
|
*/
|
||||||
|
private ApprovalInputMapping extractApprovalInputMapping(Map<String, Object> variables, String nodeId) {
|
||||||
|
try {
|
||||||
|
// 1. 从 variables 中获取节点数据
|
||||||
|
Object nodeData = variables.get(nodeId);
|
||||||
|
if (nodeData == null) {
|
||||||
|
log.warn("流程变量中没有找到节点 {} 的数据", nodeId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 解析为 NodeContext
|
||||||
|
if (nodeData instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> nodeDataMap = (Map<String, Object>) nodeData;
|
||||||
|
NodeContext<ApprovalInputMapping, ApprovalOutputs> nodeContext =
|
||||||
|
NodeContext.fromMap(nodeDataMap, ApprovalInputMapping.class, ApprovalOutputs.class, objectMapper);
|
||||||
|
|
||||||
|
// 3. 返回 inputMapping
|
||||||
|
return nodeContext.getInputMapping();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn("节点 {} 的数据格式不正确,无法解析为 NodeContext", nodeId);
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("提取审批节点 {} 的 ApprovalInputMapping 失败", nodeId, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断用户是否有审批权限
|
||||||
|
* <p>权限规则:团队负责人 或 审批人列表中的任意一人
|
||||||
|
*
|
||||||
|
* @param currentUserId 当前用户ID
|
||||||
|
* @param teamOwnerId 团队负责人ID
|
||||||
|
* @param approverUserIds 审批人ID列表
|
||||||
|
* @return true-有权限, false-无权限
|
||||||
|
*/
|
||||||
|
private boolean hasApprovalPermission(Long currentUserId, Long teamOwnerId, List<Long> approverUserIds) {
|
||||||
|
if (currentUserId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 是团队负责人
|
||||||
|
if (currentUserId.equals(teamOwnerId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 在审批人列表中
|
||||||
|
if (approverUserIds != null && !approverUserIds.isEmpty()) {
|
||||||
|
return approverUserIds.contains(currentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,236 @@
|
|||||||
|
package com.qqchen.deploy.backend.framework.utils;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map 工具类
|
||||||
|
* <p>提供从 Map 中安全获取和转换各种类型值的工具方法
|
||||||
|
*
|
||||||
|
* @author qqchen
|
||||||
|
* @since 2025-11-06
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class MapUtils {
|
||||||
|
|
||||||
|
private MapUtils() {
|
||||||
|
throw new UnsupportedOperationException("Utility class cannot be instantiated");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Map 中安全获取 Long 值
|
||||||
|
* <p>支持类型转换:Long、Integer、String
|
||||||
|
*
|
||||||
|
* @param map 数据 Map
|
||||||
|
* @param key 键名
|
||||||
|
* @return Long 值,转换失败或不存在返回 null
|
||||||
|
*/
|
||||||
|
public static Long getLongValue(Map<String, Object> map, String key) {
|
||||||
|
if (map == null || key == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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("无法将 key={} 的值转换为 Long: {}", key, value);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn("不支持的 Long 类型转换,key={}, type={}", key, value.getClass().getName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Map 中安全获取 Integer 值
|
||||||
|
* <p>支持类型转换:Integer、Long、String
|
||||||
|
*
|
||||||
|
* @param map 数据 Map
|
||||||
|
* @param key 键名
|
||||||
|
* @return Integer 值,转换失败或不存在返回 null
|
||||||
|
*/
|
||||||
|
public static Integer getIntegerValue(Map<String, Object> map, String key) {
|
||||||
|
if (map == null || key == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object value = map.get(key);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof Integer) {
|
||||||
|
return (Integer) value;
|
||||||
|
}
|
||||||
|
if (value instanceof Long) {
|
||||||
|
return ((Long) value).intValue();
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt((String) value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn("无法将 key={} 的值转换为 Integer: {}", key, value);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn("不支持的 Integer 类型转换,key={}, type={}", key, value.getClass().getName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Map 中安全获取 String 值
|
||||||
|
*
|
||||||
|
* @param map 数据 Map
|
||||||
|
* @param key 键名
|
||||||
|
* @return String 值,不存在返回 null
|
||||||
|
*/
|
||||||
|
public static String getStringValue(Map<String, Object> map, String key) {
|
||||||
|
if (map == null || key == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object value = map.get(key);
|
||||||
|
return value != null ? value.toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Map 中安全获取 Boolean 值
|
||||||
|
* <p>支持类型转换:Boolean、String("true"/"false")
|
||||||
|
*
|
||||||
|
* @param map 数据 Map
|
||||||
|
* @param key 键名
|
||||||
|
* @return Boolean 值,转换失败或不存在返回 null
|
||||||
|
*/
|
||||||
|
public static Boolean getBooleanValue(Map<String, Object> map, String key) {
|
||||||
|
if (map == null || key == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object value = map.get(key);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof Boolean) {
|
||||||
|
return (Boolean) value;
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
return Boolean.parseBoolean((String) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn("不支持的 Boolean 类型转换,key={}, type={}", key, value.getClass().getName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Map 中安全获取 Double 值
|
||||||
|
* <p>支持类型转换:Double、Float、Integer、Long、String、BigDecimal
|
||||||
|
*
|
||||||
|
* @param map 数据 Map
|
||||||
|
* @param key 键名
|
||||||
|
* @return Double 值,转换失败或不存在返回 null
|
||||||
|
*/
|
||||||
|
public static Double getDoubleValue(Map<String, Object> map, String key) {
|
||||||
|
if (map == null || key == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object value = map.get(key);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof Double) {
|
||||||
|
return (Double) value;
|
||||||
|
}
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).doubleValue();
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
try {
|
||||||
|
return Double.parseDouble((String) value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn("无法将 key={} 的值转换为 Double: {}", key, value);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn("不支持的 Double 类型转换,key={}, type={}", key, value.getClass().getName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Map 中安全获取 Long 值(带默认值)
|
||||||
|
*
|
||||||
|
* @param map 数据 Map
|
||||||
|
* @param key 键名
|
||||||
|
* @param defaultValue 默认值
|
||||||
|
* @return Long 值,转换失败或不存在返回默认值
|
||||||
|
*/
|
||||||
|
public static Long getLongValue(Map<String, Object> map, String key, Long defaultValue) {
|
||||||
|
Long value = getLongValue(map, key);
|
||||||
|
return value != null ? value : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Map 中安全获取 Integer 值(带默认值)
|
||||||
|
*
|
||||||
|
* @param map 数据 Map
|
||||||
|
* @param key 键名
|
||||||
|
* @param defaultValue 默认值
|
||||||
|
* @return Integer 值,转换失败或不存在返回默认值
|
||||||
|
*/
|
||||||
|
public static Integer getIntegerValue(Map<String, Object> map, String key, Integer defaultValue) {
|
||||||
|
Integer value = getIntegerValue(map, key);
|
||||||
|
return value != null ? value : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Map 中安全获取 String 值(带默认值)
|
||||||
|
*
|
||||||
|
* @param map 数据 Map
|
||||||
|
* @param key 键名
|
||||||
|
* @param defaultValue 默认值
|
||||||
|
* @return String 值,不存在返回默认值
|
||||||
|
*/
|
||||||
|
public static String getStringValue(Map<String, Object> map, String key, String defaultValue) {
|
||||||
|
String value = getStringValue(map, key);
|
||||||
|
return value != null ? value : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Map 中安全获取 Boolean 值(带默认值)
|
||||||
|
*
|
||||||
|
* @param map 数据 Map
|
||||||
|
* @param key 键名
|
||||||
|
* @param defaultValue 默认值
|
||||||
|
* @return Boolean 值,转换失败或不存在返回默认值
|
||||||
|
*/
|
||||||
|
public static Boolean getBooleanValue(Map<String, Object> map, String key, Boolean defaultValue) {
|
||||||
|
Boolean value = getBooleanValue(map, key);
|
||||||
|
return value != null ? value : defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user