diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/DeployApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/DeployApiController.java index a3c613a3..da77f359 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/DeployApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/DeployApiController.java @@ -40,7 +40,7 @@ public class DeployApiController { @Operation(summary = "获取可部署的环境", description = "获取当前登录用户在各团队中可部署的环境和应用列表") @GetMapping("/environments") @PreAuthorize("isAuthenticated()") - public Response getDeployableEnvironments() { + public Response> getDeployableEnvironments() { return Response.success(deployService.getDeployableEnvironments()); } @@ -50,7 +50,7 @@ public class DeployApiController { @Operation(summary = "执行部署", description = "根据团队应用配置启动部署工作流") @PostMapping("/execute") @PreAuthorize("isAuthenticated()") - public Response executeDeploy(@Validated @RequestBody DeployRequestDTO request) { + public Response executeDeploy(@Validated @RequestBody DeployExecuteRequest request) { return Response.success(deployService.executeDeploy(request)); } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/ApproverConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/ApproverConverter.java new file mode 100644 index 00000000..6a09bd80 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/ApproverConverter.java @@ -0,0 +1,68 @@ +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; + +/** + * 审批人转换器 + * + *

职责:User → ApproverDTO 的转换 + *

遵循单一职责原则(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 toDTOList(List users); + + /** + * 从用户ID列表和用户Map构建审批人列表 + *

常用场景:从 TeamEnvironmentConfig.approverUserIds 构建审批人列表 + * + * @param userIds 用户ID列表 + * @param userMap 用户Map(key=userId, value=User) + * @return 审批人DTO列表(自动过滤 Map 中不存在的用户) + */ + default List fromUserIds(List userIds, Map 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()); + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/DeployableApplicationConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/DeployableApplicationConverter.java new file mode 100644 index 00000000..6f36f92c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/DeployableApplicationConverter.java @@ -0,0 +1,169 @@ +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; + +/** + * 可部署应用转换器 + * + *

职责:多源数据 → DeployableApplicationDTO 的转换 + *

遵循单一职责原则(SRP),只负责可部署应用相关的转换逻辑 + * + *

数据源: + *

    + *
  • TeamApplication - 团队应用关联信息
  • + *
  • Application - 应用基础信息
  • + *
  • ExternalSystem - 部署系统信息(Jenkins)
  • + *
  • WorkflowDefinition - 工作流定义
  • + *
  • DeployStatistics - 部署统计
  • + *
  • DeployRecord - 部署记录
  • + *
+ * + * @author qqchen + * @since 2025-11-06 + */ +@Mapper(componentModel = "spring") +@Component +public interface DeployableApplicationConverter { + + /** + * 基础转换:TeamApplication + Application → DeployableApplicationDTO + *

只处理简单字段映射 + */ + @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 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 + *

封装所有转换逻辑,简化 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 recentRecordSummaries + ) { + // 1. 基础转换 + DeployableApplicationDTO dto = toBaseDTO(teamApplication, application); + + // 2. 应用扩展信息 + applyDeploySystem(dto, deploySystem); + applyWorkflowDefinition(dto, workflow); + applyDeployStatistics(dto, statistics, latestRecord); + setRecentDeployRecords(dto, recentRecordSummaries); + + return dto; + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/DeployableEnvironmentConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/DeployableEnvironmentConverter.java new file mode 100644 index 00000000..0972a7f3 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/DeployableEnvironmentConverter.java @@ -0,0 +1,94 @@ +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; + +/** + * 可部署环境转换器 + * + *

采用 MapStruct + 自定义方法混合模式: + *

    + *
  • 简单字段映射:交给 MapStruct 自动生成
  • + *
  • 复杂业务逻辑:使用 @AfterMapping 或自定义方法
  • + *
+ * + * @author qqchen + * @since 2025-11-06 + */ +@Mapper(componentModel = "spring") +@Component +public interface DeployableEnvironmentConverter { + + /** + * 基础转换:Environment → DeployableEnvironmentDTO + *

仅处理简单字段映射 + */ + @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 + *

从 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 approvers) { + dto.setApprovers(approvers != null ? approvers : Collections.emptyList()); + } + + /** + * 设置应用列表 + * + * @param dto 目标 DTO + * @param applications 应用列表 + */ + default void setApplications(DeployableEnvironmentDTO dto, List applications) { + dto.setApplications(applications != null ? applications : Collections.emptyList()); + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployAppBuildDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployAppBuildDTO.java deleted file mode 100644 index 8a984399..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployAppBuildDTO.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.qqchen.deploy.backend.deploy.dto; - -import com.fasterxml.jackson.databind.JsonNode; -import com.qqchen.deploy.backend.deploy.enums.BuildTypeEnum; -import com.qqchen.deploy.backend.deploy.enums.DevelopmentLanguageTypeEnum; -import com.qqchen.deploy.backend.framework.dto.BaseDTO; -import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 应用配置DTO - */ -@Data -@Schema(description = "应用配置") -@EqualsAndHashCode(callSuper = true) -public class DeployAppBuildDTO extends BaseDTO { - - @Schema(description = "构建类型") - @NotNull(message = "构建类型不能为空") - private BuildTypeEnum buildType; - - @NotNull(message = "应用语言不能为空") - private DevelopmentLanguageTypeEnum languageType; - - @Schema(description = "表单配置") - private JsonNode formVariables; - - @Schema(description = "构建配置") - @NotNull(message = "构建配置不能为空") - private JsonNode buildVariables; - - @Schema(description = "环境ID") - @NotNull(message = "环境ID不能为空") - private Long environmentId; - - @Schema(description = "应用ID") - @NotNull(message = "应用ID不能为空") - private Long applicationId; - - @NotNull(message = "已发布的流程定义ID") - private Long workflowDefinitionId; - -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployExecuteRequest.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployExecuteRequest.java new file mode 100644 index 00000000..869eb60c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployExecuteRequest.java @@ -0,0 +1,115 @@ +package com.qqchen.deploy.backend.deploy.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 部署执行请求 + * + * @author qqchen + * @since 2025-11-02 + */ +@Data +@Schema(description = "部署执行请求") +public class DeployExecuteRequest { + + @Schema(description = "Jenkins配置", required = true) + @NotNull(message = "Jenkins配置不能为空") + @Valid + private JenkinsConfig jenkins; + + @Schema(description = "团队ID", required = true) + @NotNull(message = "团队ID不能为空") + private Long teamId; + + @Schema(description = "团队应用关联ID", required = true) + @NotNull(message = "团队应用关联ID不能为空") + private Long teamApplicationId; + + @Schema(description = "应用ID", required = true) + @NotNull(message = "应用ID不能为空") + private Long applicationId; + + @Schema(description = "应用编码", required = true) + @NotBlank(message = "应用编码不能为空") + private String applicationCode; + + @Schema(description = "应用名称", required = true) + @NotBlank(message = "应用名称不能为空") + private String applicationName; + + @Schema(description = "环境ID", required = true) + @NotNull(message = "环境ID不能为空") + private Long environmentId; + + @Schema(description = "环境编码", required = true) + @NotBlank(message = "环境编码不能为空") + private String environmentCode; + + @Schema(description = "环境名称", required = true) + @NotBlank(message = "环境名称不能为空") + private String environmentName; + + @Schema(description = "审批配置", required = true) + @NotNull(message = "审批配置不能为空") + @Valid + private ApprovalConfig approval; + + @Schema(description = "通知配置", required = true) + @NotNull(message = "通知配置不能为空") + @Valid + private NotificationConfig notification; + + @Schema(description = "部署备注") + private String remark; + + /** + * Jenkins配置 + */ + @Data + @Schema(description = "Jenkins配置") + public static class JenkinsConfig { + @Schema(description = "Jenkins服务器ID", required = true) + @NotNull(message = "Jenkins服务器ID不能为空") + private Long serverId; + + @Schema(description = "Jenkins任务名称", required = true) + @NotBlank(message = "Jenkins任务名称不能为空") + private String jobName; + + @Schema(description = "Git分支") + private String branch; + } + + /** + * 审批配置 + */ + @Data + @Schema(description = "审批配置") + public static class ApprovalConfig { + @Schema(description = "是否需要审批", required = true) + @NotNull(message = "是否需要审批不能为空") + private Boolean required; + + @Schema(description = "审批人ID列表(逗号分隔)") + private String userIds; + } + + /** + * 通知配置 + */ + @Data + @Schema(description = "通知配置") + public static class NotificationConfig { + @Schema(description = "是否需要通知", required = true) + @NotNull(message = "是否需要通知不能为空") + private Boolean required; + + @Schema(description = "通知渠道ID") + private Long channelId; + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployRequestDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployRequestDTO.java deleted file mode 100644 index e5220f5d..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployRequestDTO.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.qqchen.deploy.backend.deploy.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * 部署请求DTO - * - * @author qqchen - * @since 2025-11-02 - */ -@Data -@Schema(description = "部署请求") -public class DeployRequestDTO { - - @Schema(description = "团队应用关联ID", required = true) - @NotNull(message = "团队应用关联ID不能为空") - private Long teamApplicationId; - - @Schema(description = "部署备注") - private String remark; -} - diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployableEnvironmentDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployableEnvironmentDTO.java index 9ae98937..43e269b3 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployableEnvironmentDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployableEnvironmentDTO.java @@ -39,6 +39,12 @@ public class DeployableEnvironmentDTO { @Schema(description = "审批人列表") private List approvers; + @Schema(description = "是否启用部署通知") + private Boolean notificationEnabled; + + @Schema(description = "通知渠道ID") + private Long notificationChannelId; + @Schema(description = "是否要求代码审查") private Boolean requireCodeReview; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableTeamEnvironmentApplicationDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableTeamEnvironmentApplicationDTO.java new file mode 100644 index 00000000..e61a23de --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableTeamEnvironmentApplicationDTO.java @@ -0,0 +1,68 @@ +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-06 + */ +@Data +@Schema(description = "可部署应用信息") +public class UserDeployableTeamEnvironmentApplicationDTO { + + @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 = "部署任务分支") + private String deployBranch; + + @Schema(description = "工作流定义ID") + private Long workflowDefinitionId; + + @Schema(description = "工作流绑定的FORM表单ID") + private Long workflowDefinitionFormId; + + @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 recentDeployRecords; +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableTeamEnvironmentApproverDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableTeamEnvironmentApproverDTO.java new file mode 100644 index 00000000..e9f72447 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableTeamEnvironmentApproverDTO.java @@ -0,0 +1,25 @@ +package com.qqchen.deploy.backend.deploy.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 用户可部署团队环境审批人DTO + * + * @author qqchen + * @since 2025-11-06 + */ +@Data +@Schema(description = "审批人信息") +public class UserDeployableTeamEnvironmentApproverDTO { + + @Schema(description = "用户ID") + private Long userId; + + @Schema(description = "用户名") + private String username; + + @Schema(description = "真实姓名") + private String realName; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableTeamEnvironmentDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableTeamEnvironmentDTO.java new file mode 100644 index 00000000..d3ca39f3 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableTeamEnvironmentDTO.java @@ -0,0 +1,54 @@ +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-06 + */ +@Data +@Schema(description = "用户可部署团队环境信息") +public class UserDeployableTeamEnvironmentDTO { + + @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 approvers; + + @Schema(description = "是否启用部署通知") + private Boolean notificationEnabled; + + @Schema(description = "通知渠道ID") + private Long notificationChannelId; + + @Schema(description = "是否要求代码审查") + private Boolean requireCodeReview; + + @Schema(description = "可部署应用列表") + private List applications; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableTeamMemberDTO.java similarity index 58% rename from backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableDTO.java rename to backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableTeamMemberDTO.java index bc1dae55..18f45bbc 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserDeployableTeamMemberDTO.java @@ -3,17 +3,15 @@ package com.qqchen.deploy.backend.deploy.dto; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import java.util.List; - /** - * 用户可部署环境DTO + * 用户可部署团队成员DTO * * @author qqchen - * @since 2025-11-02 + * @since 2025-11-06 */ @Data -@Schema(description = "用户可部署环境信息") -public class UserDeployableDTO { +@Schema(description = "团队成员信息") +public class UserDeployableTeamMemberDTO { @Schema(description = "用户ID") private Long userId; @@ -24,7 +22,7 @@ public class UserDeployableDTO { @Schema(description = "真实姓名") private String realName; - @Schema(description = "用户所属团队列表") - private List teams; + @Schema(description = "团队角色") + private String roleInTeam; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserTeamDeployableDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserTeamDeployableDTO.java new file mode 100644 index 00000000..dba4be09 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/UserTeamDeployableDTO.java @@ -0,0 +1,42 @@ +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-06 + */ +@Data +@Schema(description = "用户团队可部署数据") +public class UserTeamDeployableDTO { + + @Schema(description = "团队ID") + private Long teamId; + + @Schema(description = "团队编码") + private String teamCode; + + @Schema(description = "团队名称") + private String teamName; + + @Schema(description = "团队描述") + private String description; + + @Schema(description = "团队负责人ID") + private Long ownerId; + + @Schema(description = "团队负责人名称") + private String ownerName; + + @Schema(description = "团队成员列表") + private List members; + + @Schema(description = "可部署环境列表") + private List environments; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/ITeamMemberRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/ITeamMemberRepository.java index c0d242a0..b08f18e8 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/ITeamMemberRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/ITeamMemberRepository.java @@ -29,5 +29,14 @@ public interface ITeamMemberRepository extends IBaseRepository * 根据用户ID查询所有团队成员记录 */ List findByUserIdAndDeletedFalse(Long userId); -} + /** + * 批量查询用户在指定团队列表中的成员记录 + */ + List findByUserIdAndTeamIdInAndDeletedFalse(Long userId, List teamIds); + + /** + * 批量查询指定团队列表的所有成员 + */ + List findByTeamIdInAndDeletedFalse(List teamIds); +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IDeployRecordService.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IDeployRecordService.java index 9c638c79..520b6a11 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IDeployRecordService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IDeployRecordService.java @@ -27,16 +27,7 @@ public interface IDeployRecordService { * @param deployRemark 部署备注 * @return 部署记录DTO */ - DeployRecordDTO createDeployRecord( - Long workflowInstanceId, - String businessKey, - Long teamApplicationId, - Long teamId, - Long applicationId, - Long environmentId, - String deployBy, - String deployRemark - ); + DeployRecordDTO createDeployRecord(Long workflowInstanceId, String businessKey, Long teamApplicationId, Long teamId, Long applicationId, Long environmentId, String deployBy, String deployRemark); /** * 根据工作流实例同步部署记录状态 diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IDeployService.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IDeployService.java index 2f580c9a..ff2a653f 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IDeployService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IDeployService.java @@ -15,9 +15,9 @@ public interface IDeployService { /** * 获取当前用户可部署的环境和应用 * - * @return 用户可部署环境信息 + * @return 用户所属团队的可部署环境列表 */ - UserDeployableDTO getDeployableEnvironments(); + List getDeployableEnvironments(); /** * 执行部署 @@ -25,7 +25,7 @@ public interface IDeployService { * @param request 部署请求 * @return 部署结果 */ - DeployResultDTO executeDeploy(DeployRequestDTO request); + DeployResultDTO executeDeploy(DeployExecuteRequest request); /** * 获取当前用户的部署审批任务列表 diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/TeamDeployConfigCacheService.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/TeamDeployConfigCacheService.java new file mode 100644 index 00000000..e69de29b diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/DeployRecordServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/DeployRecordServiceImpl.java index 3d513139..048a9992 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/DeployRecordServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/DeployRecordServiceImpl.java @@ -56,7 +56,7 @@ import java.util.stream.Collectors; @Slf4j @Service public class DeployRecordServiceImpl extends BaseServiceImpl - implements IDeployRecordService { + implements IDeployRecordService { @Resource private IDeployRecordRepository deployRecordRepository; @@ -84,16 +84,7 @@ public class DeployRecordServiceImpl extends BaseServiceImpl new BusinessException(ResponseCode.NOT_FOUND, new Object[]{"部署记录"})); + .orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[] {"部署记录"})); // 1.2 查询工作流实例 WorkflowInstance workflowInstance = workflowInstanceRepository.findById(deployRecord.getWorkflowInstanceId()) - .orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[]{"工作流实例"})); + .orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[] {"工作流实例"})); // 1.3 获取流程图快照 WorkflowDefinitionGraph graphSnapshot = workflowInstance.getGraphSnapshot(); @@ -257,13 +248,13 @@ public class DeployRecordServiceImpl extends BaseServiceImpl nodeInstances = workflowNodeInstanceRepository.findByWorkflowInstanceId(workflowInstance.getId()); // 2.2 构建已执行节点的映射(按nodeId索引) Map nodeInstanceMap = nodeInstances.stream() - .collect(Collectors.toMap(WorkflowNodeInstance::getNodeId, instance -> instance, (a, b) -> a)); + .collect(Collectors.toMap(WorkflowNodeInstance::getNodeId, instance -> instance, (a, b) -> a)); // 2.3 从BPMN模型中获取流程元素顺序(用于排序) BpmnModel bpmnModel = repositoryService.getBpmnModel(workflowInstance.getProcessDefinitionId()); @@ -278,30 +269,30 @@ public class DeployRecordServiceImpl extends BaseServiceImpl nodeInstanceDTOs = graphSnapshot.getNodes().stream() - .sorted(Comparator.comparingInt(node -> nodeOrderMap.getOrDefault(node.getId(), Integer.MAX_VALUE))) - .map(graphNode -> buildNodeInstanceDTO(graphNode, nodeInstanceMap.get(graphNode.getId()), workflowInstance)) - .collect(Collectors.toList()); + .sorted(Comparator.comparingInt(node -> nodeOrderMap.getOrDefault(node.getId(), Integer.MAX_VALUE))) + .map(graphNode -> buildNodeInstanceDTO(graphNode, nodeInstanceMap.get(graphNode.getId()), workflowInstance)) + .collect(Collectors.toList()); // ============ 3. 计算执行统计 ============ - + int totalNodeCount = nodeInstanceDTOs.size(); int executedNodeCount = (int) nodeInstanceDTOs.stream() - .filter(node -> node.getStatus() != WorkflowNodeInstanceStatusEnums.NOT_STARTED) - .count(); + .filter(node -> node.getStatus() != WorkflowNodeInstanceStatusEnums.NOT_STARTED) + .count(); int successNodeCount = (int) nodeInstanceDTOs.stream() - .filter(node -> node.getStatus() == WorkflowNodeInstanceStatusEnums.COMPLETED) - .count(); + .filter(node -> node.getStatus() == WorkflowNodeInstanceStatusEnums.COMPLETED) + .count(); int failedNodeCount = (int) nodeInstanceDTOs.stream() - .filter(node -> node.getStatus() == WorkflowNodeInstanceStatusEnums.FAILED) - .count(); + .filter(node -> node.getStatus() == WorkflowNodeInstanceStatusEnums.FAILED) + .count(); int runningNodeCount = (int) nodeInstanceDTOs.stream() - .filter(node -> node.getStatus() == WorkflowNodeInstanceStatusEnums.RUNNING) - .count(); + .filter(node -> node.getStatus() == WorkflowNodeInstanceStatusEnums.RUNNING) + .count(); // ============ 4. 组装完整的DTO ============ - + DeployRecordFlowGraphDTO dto = new DeployRecordFlowGraphDTO(); - + // 4.1 部署记录基本信息 dto.setDeployRecordId(deployRecord.getId()); dto.setBusinessKey(deployRecord.getBusinessKey()); @@ -337,27 +328,27 @@ public class DeployRecordServiceImpl extends BaseServiceImpl()); - + return dto; } @@ -448,12 +439,12 @@ public class DeployRecordServiceImpl extends BaseServiceImpl teamMap; - Map teamMemberMap; - Map teamEnvConfigMap; - Map envMap; - Map> teamAppsMap; - Map appMap; - Map systemMap; - Map workflowMap; - Map approverMap; - Map statisticsMap; - Map latestRecordMap; - Map> recentRecordsMap; - } + @Resource + private DeployableEnvironmentConverter deployableEnvironmentConverter; + + @Resource + private ApproverConverter approverConverter; + + @Resource + private DeployableApplicationConverter deployableApplicationConverter; @Override @Transactional(readOnly = true) - public UserDeployableDTO getDeployableEnvironments() { - // 1. 获取当前登录用户 + public List getDeployableEnvironments() { Long currentUserId = SecurityUtils.getCurrentUserId(); - User user = userRepository.findById(currentUserId).orElseThrow(() -> new RuntimeException("用户不存在")); - // 2. 查询用户作为成员的团队 - List teamMembers = teamMemberRepository.findByUserIdAndDeletedFalse(currentUserId); + // 一次性查询并构建完整数据(避免重复查询) + return batchQueryUserTeamDeployableData(currentUserId); + } - // 3. 查询用户作为负责人的团队 + /** + * 批量查询用户团队可部署数据(完整版,直接构建新DTO) + *

优化:一次性完成所有查询,避免重复查询团队信息 + * + * @param currentUserId 当前用户ID + * @return 用户团队可部署数据列表 + */ + private List batchQueryUserTeamDeployableData(Long currentUserId) { + // 1. 查询用户所属团队(作为成员 + 作为负责人) + List userTeamMembers = teamMemberRepository.findByUserIdAndDeletedFalse(currentUserId); List ownedTeams = teamRepository.findByOwnerIdAndDeletedFalse(currentUserId); - // 4. 合并团队ID并构建团队Map(优化:直接复用ownedTeams数据) + // 2. 合并团队ID并去重 Set teamIdSet = new HashSet<>(); - Map teamMap = new HashMap<>(); - - // 4.1 从负责人团队中提取并直接放入teamMap(避免重复查询) - for (Team team : ownedTeams) { - teamIdSet.add(team.getId()); - teamMap.put(team.getId(), team); - } - - // 4.2 从成员关系中提取teamId - Set memberTeamIds = teamMembers.stream() - .map(TeamMember::getTeamId) - .collect(Collectors.toSet()); - teamIdSet.addAll(memberTeamIds); + teamIdSet.addAll(userTeamMembers.stream().map(TeamMember::getTeamId).collect(Collectors.toList())); + teamIdSet.addAll(ownedTeams.stream().map(Team::getId).collect(Collectors.toList())); if (teamIdSet.isEmpty()) { - log.info("用户 {} 未加入任何团队且不是任何团队的负责人", user.getUsername()); - return buildEmptyResult(user); + log.info("用户 {} 未加入任何团队且不是任何团队的负责人", currentUserId); + return Collections.emptyList(); } - // 4.3 只查询成员团队中不在teamMap里的团队(减少查询量) - Set needQueryTeamIds = memberTeamIds.stream() - .filter(id -> !teamMap.containsKey(id)) - .collect(Collectors.toSet()); - - if (!needQueryTeamIds.isEmpty()) { - teamRepository.findAllById(needQueryTeamIds).forEach(team -> - teamMap.put(team.getId(), team) - ); - } - List teamIds = new ArrayList<>(teamIdSet); - // 5. 批量查询所有团队的应用配置 - List allTeamApps = teamApplicationRepository.findByTeamIdIn(teamIds); - - // 5.1 提前判断:如果没有应用配置,直接返回 - if (allTeamApps.isEmpty()) { - log.info("用户 {} 所属团队未配置任何应用", user.getUsername()); - return buildEmptyResult(user); + // 3. 批量查询团队基础信息(复用已查询的数据) + Map teamMap = new HashMap<>(); + ownedTeams.forEach(team -> teamMap.put(team.getId(), team)); + // 补充查询作为成员但不是负责人的团队 + Set missingTeamIds = teamIds.stream() + .filter(id -> !teamMap.containsKey(id)) + .collect(Collectors.toSet()); + if (!missingTeamIds.isEmpty()) { + teamRepository.findAllById(missingTeamIds).forEach(team -> teamMap.put(team.getId(), team)); } - - // 6. 一次遍历提取所有需要的ID集合(优化:减少4次额外遍历) + + // 4. 批量查询团队负责人信息 + Set ownerIds = teamMap.values().stream() + .map(Team::getOwnerId) + .filter(id -> id != null) + .collect(Collectors.toSet()); + Map ownerMap = !ownerIds.isEmpty() + ? userRepository.findAllById(ownerIds).stream().collect(toMap(User::getId, u -> u)) + : Collections.emptyMap(); + + // 5. 批量查询团队成员 + List allMembers = teamMemberRepository.findByTeamIdInAndDeletedFalse(teamIds); + Map> membersByTeam = allMembers.stream() + .collect(Collectors.groupingBy(TeamMember::getTeamId)); + + // 6. 批量查询成员用户信息 + Set memberUserIds = allMembers.stream() + .map(TeamMember::getUserId) + .collect(Collectors.toSet()); + Map memberUserMap = !memberUserIds.isEmpty() + ? userRepository.findAllById(memberUserIds).stream().collect(toMap(User::getId, u -> u)) + : Collections.emptyMap(); + + // 7. 批量查询所有团队的应用配置 + List allTeamApps = teamApplicationRepository.findByTeamIdIn(teamIds); + if (allTeamApps.isEmpty()) { + log.debug("团队 {} 未配置任何应用", teamIds); + return Collections.emptyList(); + } + + // 8. 提取所有需要的ID集合 Set allEnvIds = new HashSet<>(); Set appIds = new HashSet<>(); Set systemIds = new HashSet<>(); Set workflowIds = new HashSet<>(); - List teamApplicationIds = new ArrayList<>(allTeamApps.size()); - + List teamApplicationIds = new ArrayList<>(); + for (TeamApplication ta : allTeamApps) { teamApplicationIds.add(ta.getId()); allEnvIds.add(ta.getEnvironmentId()); @@ -191,329 +203,298 @@ public class DeployServiceImpl implements IDeployService { } } - if (allEnvIds.isEmpty()) { - log.info("用户 {} 所属团队未配置任何环境", user.getUsername()); - return buildEmptyResult(user); - } - - // 7. 应用配置按团队分组 + // 9. 应用配置按团队分组 Map> teamAppsMap = allTeamApps.stream() - .collect(groupingBy(TeamApplication::getTeamId)); + .collect(groupingBy(TeamApplication::getTeamId)); - // 8. 批量查询环境信息 + // 10. 批量查询环境信息 Map envMap = environmentRepository.findAllById(allEnvIds).stream() - .collect(toMap(Environment::getId, e -> e)); + .collect(toMap(Environment::getId, e -> e)); - // 9. 批量查询应用信息 - final Map appMap; - if (!appIds.isEmpty()) { - appMap = applicationRepository.findAllById(appIds).stream() - .collect(toMap(Application::getId, a -> a)); - } else { - appMap = Collections.emptyMap(); - } + // 11. 批量查询应用信息 + Map appMap = !appIds.isEmpty() + ? applicationRepository.findAllById(appIds).stream().collect(toMap(Application::getId, a -> a)) + : Collections.emptyMap(); - // 10. 批量查询部署系统 - final Map systemMap; - if (!systemIds.isEmpty()) { - systemMap = externalSystemRepository.findAllById(systemIds).stream() - .collect(toMap(ExternalSystem::getId, s -> s)); - } else { - systemMap = Collections.emptyMap(); - } + // 12. 批量查询部署系统 + Map systemMap = !systemIds.isEmpty() + ? externalSystemRepository.findAllById(systemIds).stream().collect(toMap(ExternalSystem::getId, s -> s)) + : Collections.emptyMap(); - // 11. 批量查询工作流定义 - final Map workflowMap; - if (!workflowIds.isEmpty()) { - workflowMap = workflowDefinitionRepository.findAllById(workflowIds).stream() - .collect(toMap(WorkflowDefinition::getId, w -> w)); - } else { - workflowMap = Collections.emptyMap(); - } + // 13. 批量查询工作流定义 + Map workflowMap = !workflowIds.isEmpty() + ? workflowDefinitionRepository.findAllById(workflowIds).stream().collect(toMap(WorkflowDefinition::getId, w -> w)) + : Collections.emptyMap(); - // 11. 批量查询团队环境配置 + // 14. 批量查询团队环境配置 List teamEnvConfigs = teamEnvironmentConfigRepository.findByTeamIdIn(teamIds); - // 按 (teamId, environmentId) 组织配置: key = teamId + "_" + environmentId Map teamEnvConfigMap = teamEnvConfigs.stream() - .collect(toMap(c -> c.getTeamId() + "_" + c.getEnvironmentId(), c -> c)); - - // 12. 批量查询审批人信息 + .collect(toMap(c -> c.getTeamId() + "_" + c.getEnvironmentId(), c -> c)); + + // 15. 批量查询审批人信息 Set approverUserIds = teamEnvConfigs.stream() - .filter(c -> c.getApproverUserIds() != null) - .flatMap(c -> c.getApproverUserIds().stream()) - .collect(Collectors.toSet()); - final Map approverMap; - if (!approverUserIds.isEmpty()) { - approverMap = userRepository.findAllById(approverUserIds).stream().collect(toMap(User::getId, u -> u)); - } else { - approverMap = Collections.emptyMap(); - } + .filter(c -> c.getApproverUserIds() != null) + .flatMap(c -> c.getApproverUserIds().stream()) + .collect(Collectors.toSet()); + Map approverMap = !approverUserIds.isEmpty() + ? userRepository.findAllById(approverUserIds).stream().collect(toMap(User::getId, u -> u)) + : Collections.emptyMap(); - // 13. 批量查询部署记录信息(teamApplicationIds 已在第6步提取) - // 12.1 批量查询部署统计信息 - final Map statisticsMap = new HashMap<>(); - final Map latestRecordMap = new HashMap<>(); - final Map> recentRecordsMap = new HashMap<>(); + // 16. 批量查询动态数据(部署统计、最近记录) + Map statisticsMap = queryDeployStatistics(teamApplicationIds); + Map latestRecordMap = queryLatestRecords(teamApplicationIds); + Map> recentRecordsMap = queryRecentRecords(teamApplicationIds); - if (!teamApplicationIds.isEmpty()) { - // 查询统计信息 - List 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(); - // 处理原生SQL返回的Timestamp类型 - LocalDateTime lastDeployTime = null; - if (row[5] != null) { - if (row[5] instanceof Timestamp) { - lastDeployTime = ((Timestamp) row[5]).toLocalDateTime(); - } else if (row[5] instanceof 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); + // 17. 为每个团队组装完整数据 + List result = new ArrayList<>(); + for (Long teamId : teamIds) { + UserTeamDeployableDTO teamDTO = buildUserTeamDeployableDTO( + teamId, teamMap, ownerMap, membersByTeam, memberUserMap, + teamAppsMap, envMap, appMap, systemMap, workflowMap, + teamEnvConfigMap, approverMap, + statisticsMap, latestRecordMap, recentRecordsMap + ); + if (teamDTO != null) { + result.add(teamDTO); } - - // 查询最新部署记录(用于获取最新状态和部署人) - List 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 recentRecords = deployRecordRepository.findRecentDeployRecordsByTeamApplicationIds(teamApplicationIds, 10); - recentRecordsMap.putAll(recentRecords.stream().collect(groupingBy(DeployRecord::getTeamApplicationId))); } - // 13. 组装团队数据(优化:处理潜在的Key冲突,保留第一个成员记录) - Map teamMemberMap = teamMembers.stream() - .collect(toMap( - TeamMember::getTeamId, - tm -> tm, - (existing, replacement) -> existing // 如果有重复,保留第一个 - )); - - // 14. 构建上下文对象(避免方法参数过多) - BuildContext context = new BuildContext(); - context.teamMap = teamMap; - context.teamMemberMap = teamMemberMap; - context.teamEnvConfigMap = teamEnvConfigMap; - context.envMap = envMap; - context.teamAppsMap = teamAppsMap; - context.appMap = appMap; - context.systemMap = systemMap; - context.workflowMap = workflowMap; - context.approverMap = approverMap; - context.statisticsMap = statisticsMap; - context.latestRecordMap = latestRecordMap; - context.recentRecordsMap = recentRecordsMap; - - List teamDTOs = teamIds.stream() - .map(teamId -> buildTeamDTO(teamId, currentUserId, context)) - .filter(Objects::nonNull) - .collect(toList()); - - // 14. 组装最终结果 - UserDeployableDTO result = new UserDeployableDTO(); - result.setUserId(user.getId()); - result.setUsername(user.getUsername()); - result.setRealName(user.getNickname()); - result.setTeams(teamDTOs); - - log.info("用户 {} 可部署环境数据查询完成,包含 {} 个团队", user.getUsername(), teamDTOs.size()); + log.debug("批量查询用户团队可部署数据完成: 团队数={}", result.size()); return result; } /** - * 构建空结果 + * 构建单个团队的可部署数据DTO */ - private UserDeployableDTO buildEmptyResult(User user) { - UserDeployableDTO result = new UserDeployableDTO(); - result.setUserId(user.getId()); - result.setUsername(user.getUsername()); - result.setRealName(user.getNickname()); - result.setTeams(Collections.emptyList()); - return result; - } - - /** - * 构建团队DTO - */ - private TeamDeployableDTO buildTeamDTO(Long teamId, Long currentUserId, BuildContext ctx) { - Team team = ctx.teamMap.get(teamId); + private UserTeamDeployableDTO buildUserTeamDeployableDTO( + Long teamId, + Map teamMap, + Map ownerMap, + Map> membersByTeam, + Map memberUserMap, + Map> teamAppsMap, + Map envMap, + Map appMap, + Map systemMap, + Map workflowMap, + Map teamEnvConfigMap, + Map approverMap, + Map statisticsMap, + Map latestRecordMap, + Map> recentRecordsMap + ) { + Team team = teamMap.get(teamId); if (team == null) { return null; } - TeamMember member = ctx.teamMemberMap.get(teamId); + UserTeamDeployableDTO dto = new UserTeamDeployableDTO(); + dto.setTeamId(team.getId()); + dto.setTeamCode(team.getTeamCode()); + dto.setTeamName(team.getTeamName()); + dto.setDescription(team.getDescription()); - TeamDeployableDTO teamDTO = new TeamDeployableDTO(); - teamDTO.setTeamId(team.getId()); - teamDTO.setTeamCode(team.getTeamCode()); - teamDTO.setTeamName(team.getTeamName()); - teamDTO.setDescription(team.getDescription()); - - // 设置团队角色:如果是成员则使用成员角色,如果是负责人但不在成员表中则设置为 "OWNER" - if (member != null) { - teamDTO.setTeamRole(member.getRoleInTeam()); - } else if (team.getOwnerId() != null && team.getOwnerId().equals(currentUserId)) { - teamDTO.setTeamRole("OWNER"); - } else { - teamDTO.setTeamRole(null); + // 设置团队负责人 + if (team.getOwnerId() != null) { + dto.setOwnerId(team.getOwnerId()); + User owner = ownerMap.get(team.getOwnerId()); + if (owner != null) { + dto.setOwnerName(owner.getNickname()); + } } - // 组装环境数据(从 TeamApplication 中提取该团队的所有环境) - List teamApps = ctx.teamAppsMap.getOrDefault(teamId, Collections.emptyList()); - - // 按环境分组应用 - Map> appsByEnv = teamApps.stream() + // 设置团队成员 + List members = membersByTeam.get(teamId); + if (members != null && !members.isEmpty()) { + List memberDTOs = members.stream() + .map(member -> { + User user = memberUserMap.get(member.getUserId()); + if (user == null) { + return null; + } + UserDeployableTeamMemberDTO memberDTO = new UserDeployableTeamMemberDTO(); + memberDTO.setUserId(user.getId()); + memberDTO.setUsername(user.getUsername()); + memberDTO.setRealName(user.getNickname()); + memberDTO.setRoleInTeam(member.getRoleInTeam()); + return memberDTO; + }) + .filter(m -> m != null) + .collect(Collectors.toList()); + dto.setMembers(memberDTOs); + } else { + dto.setMembers(Collections.emptyList()); + } + + // 构建环境列表 + List teamApps = teamAppsMap.get(teamId); + if (teamApps != null && !teamApps.isEmpty()) { + // 按环境分组 + Map> appsByEnv = teamApps.stream() .collect(groupingBy(TeamApplication::getEnvironmentId)); - List envDTOs = new ArrayList<>(); - for (Long envId : appsByEnv.keySet()) { - Environment env = ctx.envMap.get(envId); - if (env == null) { - continue; + List environments = new ArrayList<>(); + for (Map.Entry> entry : appsByEnv.entrySet()) { + Long envId = entry.getKey(); + Environment env = envMap.get(envId); + if (env == null) { + continue; + } + + UserDeployableTeamEnvironmentDTO envDTO = buildUserDeployableTeamEnvironmentDTO( + teamId, env, entry.getValue(), + appMap, systemMap, workflowMap, + teamEnvConfigMap, approverMap, + statisticsMap, latestRecordMap, recentRecordsMap + ); + environments.add(envDTO); } - // 查找该团队+环境的配置 - String configKey = teamId + "_" + envId; - TeamEnvironmentConfig envConfig = ctx.teamEnvConfigMap.get(configKey); - - // 只显示在 deploy_team_environment_config 表中配置过的环境 - if (envConfig == null) { - log.debug("团队 {} 的环境 {} 未在 team_environment_config 中配置,跳过", teamId, envId); - continue; - } - - DeployableEnvironmentDTO envDTO = buildEnvironmentDTO(env, envConfig, appsByEnv.get(envId), ctx); - - envDTOs.add(envDTO); - } - - envDTOs.sort(Comparator.comparing(DeployableEnvironmentDTO::getSort)); - teamDTO.setEnvironments(envDTOs); - - return teamDTO; - } - - /** - * 构建环境DTO - */ - private DeployableEnvironmentDTO buildEnvironmentDTO(Environment env, TeamEnvironmentConfig envConfig, List teamApps, BuildContext ctx) { - DeployableEnvironmentDTO envDTO = new DeployableEnvironmentDTO(); - envDTO.setEnvironmentId(env.getId()); - envDTO.setEnvironmentCode(env.getEnvCode()); - envDTO.setEnvironmentName(env.getEnvName()); - envDTO.setEnvironmentDesc(env.getEnvDesc()); - envDTO.setEnabled(env.getEnabled()); - envDTO.setSort(env.getSort()); - - // 从 TeamEnvironmentConfig 读取配置信息 - envDTO.setRequiresApproval(envConfig != null ? envConfig.getApprovalRequired() : false); - envDTO.setRequireCodeReview(envConfig != null ? envConfig.getRequireCodeReview() : false); - envDTO.setRemark(envConfig != null ? envConfig.getRemark() : null); - - // 审批人列表(需要从 List 转换为 List 以包含用户详细信息) - if (envConfig != null && envConfig.getApproverUserIds() != null && !envConfig.getApproverUserIds().isEmpty()) { - List approverDTOs = envConfig.getApproverUserIds().stream() - .map(id -> buildApproverDTO(id, ctx.approverMap)) - .filter(Objects::nonNull) - .collect(toList()); - envDTO.setApprovers(approverDTOs); + // 按sort排序 + environments.sort(Comparator.comparingInt(e -> e.getSort() != null ? e.getSort() : Integer.MAX_VALUE)); + dto.setEnvironments(environments); } else { - envDTO.setApprovers(Collections.emptyList()); + dto.setEnvironments(Collections.emptyList()); } - // 应用列表 - List appDTOs = teamApps.stream() - .map(ta -> buildApplicationDTO(ta, ctx)) - .filter(Objects::nonNull) - .collect(toList()); - - envDTO.setApplications(appDTOs); - - return envDTO; - } - - /** - * 构建审批人DTO - */ - private ApproverDTO buildApproverDTO(Long userId, Map approverMap) { - User user = approverMap.get(userId); - if (user == null) { - return null; - } - - ApproverDTO dto = new ApproverDTO(); - dto.setUserId(user.getId()); - dto.setUsername(user.getUsername()); - dto.setRealName(user.getNickname()); return dto; } /** - * 构建应用DTO + * 构建单个环境的可部署数据DTO */ - private DeployableApplicationDTO buildApplicationDTO(TeamApplication ta, BuildContext ctx) { - Application app = ctx.appMap.get(ta.getApplicationId()); + private UserDeployableTeamEnvironmentDTO buildUserDeployableTeamEnvironmentDTO( + Long teamId, + Environment env, + List teamApps, + Map appMap, + Map systemMap, + Map workflowMap, + Map teamEnvConfigMap, + Map approverMap, + Map statisticsMap, + Map latestRecordMap, + Map> recentRecordsMap + ) { + UserDeployableTeamEnvironmentDTO dto = new UserDeployableTeamEnvironmentDTO(); + dto.setEnvironmentId(env.getId()); + dto.setEnvironmentCode(env.getEnvCode()); + dto.setEnvironmentName(env.getEnvName()); + dto.setEnvironmentDesc(env.getEnvDesc()); + dto.setEnabled(env.getEnabled()); + dto.setSort(env.getSort()); + + // 获取团队环境配置 + String configKey = teamId + "_" + env.getId(); + TeamEnvironmentConfig config = teamEnvConfigMap.get(configKey); + 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()); + + // 设置审批人列表 + if (config.getApproverUserIds() != null && !config.getApproverUserIds().isEmpty()) { + List approvers = config.getApproverUserIds().stream() + .map(userId -> { + User user = approverMap.get(userId); + if (user == null) { + return null; + } + UserDeployableTeamEnvironmentApproverDTO approverDTO = new UserDeployableTeamEnvironmentApproverDTO(); + approverDTO.setUserId(user.getId()); + approverDTO.setUsername(user.getUsername()); + approverDTO.setRealName(user.getNickname()); + return approverDTO; + }) + .filter(a -> a != null) + .collect(Collectors.toList()); + dto.setApprovers(approvers); + } else { + dto.setApprovers(Collections.emptyList()); + } + } else { + dto.setRequiresApproval(false); + dto.setRequireCodeReview(false); + dto.setNotificationEnabled(true); + dto.setApprovers(Collections.emptyList()); + } + + // 构建应用列表 + List applications = teamApps.stream() + .map(teamApp -> buildUserDeployableTeamEnvironmentApplicationDTO( + teamApp, appMap, systemMap, workflowMap, + statisticsMap, latestRecordMap, recentRecordsMap + )) + .filter(app -> app != null) + .collect(Collectors.toList()); + dto.setApplications(applications); + + return dto; + } + + /** + * 构建单个应用的可部署数据DTO + */ + private UserDeployableTeamEnvironmentApplicationDTO buildUserDeployableTeamEnvironmentApplicationDTO( + TeamApplication teamApp, + Map appMap, + Map systemMap, + Map workflowMap, + Map statisticsMap, + Map latestRecordMap, + Map> recentRecordsMap + ) { + Application app = appMap.get(teamApp.getApplicationId()); if (app == null) { return null; } - DeployableApplicationDTO dto = new DeployableApplicationDTO(); - dto.setTeamApplicationId(ta.getId()); + UserDeployableTeamEnvironmentApplicationDTO dto = new UserDeployableTeamEnvironmentApplicationDTO(); + dto.setTeamApplicationId(teamApp.getId()); dto.setApplicationId(app.getId()); dto.setApplicationCode(app.getAppCode()); dto.setApplicationName(app.getAppName()); dto.setApplicationDesc(app.getAppDesc()); - dto.setBranch(ta.getBranch()); + dto.setBranch(teamApp.getBranch()); - // 部署系统 - dto.setDeploySystemId(ta.getDeploySystemId()); - if (ta.getDeploySystemId() != null) { - ExternalSystem system = ctx.systemMap.get(ta.getDeploySystemId()); - dto.setDeploySystemName(system != null ? system.getName() : null); + // 设置部署系统信息 + if (teamApp.getDeploySystemId() != null) { + dto.setDeploySystemId(teamApp.getDeploySystemId()); + ExternalSystem system = systemMap.get(teamApp.getDeploySystemId()); + if (system != null) { + dto.setDeploySystemName(system.getName()); + } + dto.setDeployJob(teamApp.getDeployJob()); + dto.setDeployBranch(teamApp.getBranch()); } - // 部署任务 - dto.setDeployJob(ta.getDeployJob()); - - // 工作流定义 - dto.setWorkflowDefinitionId(ta.getWorkflowDefinitionId()); - if (ta.getWorkflowDefinitionId() != null) { - WorkflowDefinition workflow = ctx.workflowMap.get(ta.getWorkflowDefinitionId()); + // 设置工作流定义信息 + if (teamApp.getWorkflowDefinitionId() != null) { + dto.setWorkflowDefinitionId(teamApp.getWorkflowDefinitionId()); + WorkflowDefinition workflow = workflowMap.get(teamApp.getWorkflowDefinitionId()); if (workflow != null) { dto.setWorkflowDefinitionName(workflow.getName()); dto.setWorkflowDefinitionKey(workflow.getKey()); + if (workflow.getFormDefinitionId() != null) { + dto.setWorkflowDefinitionFormId(workflow.getFormDefinitionId()); + } } } - // 部署统计信息和记录 - DeployStatisticsDTO statistics = ctx.statisticsMap.get(ta.getId()); - if (statistics != null) { - dto.setDeployStatistics(statistics); + // 设置部署统计信息(动态数据) + Long teamAppId = teamApp.getId(); + DeployRecord latestRecord = latestRecordMap.get(teamAppId); + DeployStatisticsDTO stats = statisticsMap.get(teamAppId); + if (stats != null) { + // 从最新记录中补充 lastDeployBy 和 latestStatus + if (latestRecord != null) { + stats.setLastDeployBy(latestRecord.getDeployBy()); + stats.setLatestStatus(latestRecord.getStatus()); + } + dto.setDeployStatistics(stats); - // 判断是否正在部署中 - DeployRecord latestRecord = ctx.latestRecordMap.get(ta.getId()); + // 设置是否正在部署 if (latestRecord != null) { DeployRecordStatusEnums status = latestRecord.getStatus(); dto.setIsDeploying(status == DeployRecordStatusEnums.CREATED || status == DeployRecordStatusEnums.RUNNING); @@ -522,23 +503,97 @@ public class DeployServiceImpl implements IDeployService { } } else { // 没有部署记录,设置默认值 - DeployStatisticsDTO emptyStats = new DeployStatisticsDTO(); - emptyStats.setTotalCount(0L); - emptyStats.setSuccessCount(0L); - emptyStats.setFailedCount(0L); - emptyStats.setRunningCount(0L); - dto.setDeployStatistics(emptyStats); + dto.setDeployStatistics(createEmptyStatistics()); dto.setIsDeploying(false); } - // 最近部署记录列表 - List recentRecords = ctx.recentRecordsMap.getOrDefault(ta.getId(), Collections.emptyList()); - List recordSummaryList = recentRecords.stream().map(this::buildDeployRecordSummary).collect(toList()); - dto.setRecentDeployRecords(recordSummaryList); + // 设置最近部署记录 + List recentRecords = recentRecordsMap.get(teamAppId); + dto.setRecentDeployRecords(recentRecords != null ? recentRecords : Collections.emptyList()); return dto; } + /** + * 创建空的部署统计 + */ + private DeployStatisticsDTO createEmptyStatistics() { + DeployStatisticsDTO statistics = new DeployStatisticsDTO(); + statistics.setTotalCount(0L); + statistics.setSuccessCount(0L); + statistics.setFailedCount(0L); + statistics.setRunningCount(0L); + return statistics; + } + + /** + * 批量查询部署统计 + */ + private Map queryDeployStatistics(List teamApplicationIds) { + Map statisticsMap = new HashMap<>(); + List 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 = null; + if (row[5] != null) { + if (row[5] instanceof Timestamp) { + lastDeployTime = ((Timestamp) row[5]).toLocalDateTime(); + } else if (row[5] instanceof 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); + } + + return statisticsMap; + } + + /** + * 批量查询最新部署记录 + */ + private Map queryLatestRecords(List teamApplicationIds) { + List latestRecords = deployRecordRepository.findLatestDeployRecordsByTeamApplicationIds(teamApplicationIds); + Map latestRecordMap = latestRecords.stream() + .collect(toMap(DeployRecord::getTeamApplicationId, r -> r)); + + // 更新统计信息中的最新状态和部署人 + latestRecordMap.forEach((teamAppId, record) -> { + // 这里可以添加额外的处理逻辑 + }); + + return latestRecordMap; + } + + /** + * 批量查询最近部署记录 + */ + private Map> queryRecentRecords(List teamApplicationIds) { + List recentRecords = deployRecordRepository.findRecentDeployRecordsByTeamApplicationIds(teamApplicationIds, 10); + + return recentRecords.stream() + .collect(groupingBy( + DeployRecord::getTeamApplicationId, + Collectors.mapping( + this::buildDeployRecordSummary, + Collectors.toList() + ) + )); + } + + /** * 构建部署记录摘要DTO */ @@ -553,245 +608,88 @@ public class DeployServiceImpl implements IDeployService { // 计算持续时间(毫秒) 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) { - // 如果正在运行,计算到目前为止的持续时间 + } else if (record.getStartTime() != null && isRunningStatus(record.getStatus())) { + // 正在运行的记录:计算到目前为止的持续时间 long duration = java.time.Duration.between(record.getStartTime(), LocalDateTime.now()).toMillis(); summary.setDuration(duration); } + // 等待审批或其他未开始的状态:不设置 duration(保持为 null) return summary; } + /** + * 判断是否为运行中状态 + */ + private boolean isRunningStatus(DeployRecordStatusEnums status) { + return status == DeployRecordStatusEnums.RUNNING + || status == DeployRecordStatusEnums.CREATED; + } + @Override @Transactional - public DeployResultDTO executeDeploy(DeployRequestDTO request) { - log.info("开始执行部署: teamApplicationId={}, remark={}", request.getTeamApplicationId(), request.getRemark()); - - // 1. 加载部署上下文数据 - DeployContext context = loadDeployContext(request.getTeamApplicationId()); - - // 2. 生成业务标识(UUID) + public DeployResultDTO executeDeploy(DeployExecuteRequest request) { + log.info("开始执行部署: teamApplicationId={}, applicationCode={}, environmentCode={}", request.getTeamApplicationId(), request.getApplicationCode(), request.getEnvironmentCode()); + + // 1. 查询工作流定义 + TeamApplication teamApp = teamApplicationRepository.findById(request.getTeamApplicationId()).orElseThrow(() -> new BusinessException(ResponseCode.TEAM_APPLICATION_NOT_FOUND)); + + WorkflowDefinition workflowDefinition = workflowDefinitionRepository.findById(teamApp.getWorkflowDefinitionId()).orElseThrow(() -> new BusinessException(ResponseCode.DEPLOY_WORKFLOW_NOT_CONFIGURED)); + + // 2. 生成业务标识(使用前端传入的 executionNo) String businessKey = UUID.randomUUID().toString(); - - // 3. 构造流程变量 - Map variables = buildDeployVariables(context, request, businessKey); - - // 4. 启动工作流并创建记录 - WorkflowInstanceDTO workflowInstance = startWorkflowAndCreateRecord(context, businessKey, variables, request.getRemark()); - - // 5. 返回结果 - return buildDeployResult(workflowInstance, businessKey, context); + + log.debug("工作流变量: jenkins={}, approval={}, notification={}", request.getJenkins(), request.getApproval(), request.getNotification()); + + // 4. 启动工作流 + WorkflowInstanceDTO workflowInstance = startWorkflow(workflowDefinition, businessKey, JsonUtils.toMap(request)); + + // 5. 创建部署记录 + createDeployRecord(request, workflowInstance, businessKey); + + // 6. 返回结果 + return buildDeployResult(workflowInstance, businessKey, request); } /** - * 加载部署上下文数据 + * 启动工作流 */ - private DeployContext loadDeployContext(Long teamApplicationId) { - // 查询团队应用配置 - TeamApplication teamApp = teamApplicationRepository.findById(teamApplicationId) - .orElseThrow(() -> new BusinessException(ResponseCode.TEAM_APPLICATION_NOT_FOUND)); - - // 查询工作流定义 - WorkflowDefinition workflowDefinition = workflowDefinitionRepository.findById(teamApp.getWorkflowDefinitionId()) - .orElseThrow(() -> new BusinessException(ResponseCode.DEPLOY_WORKFLOW_NOT_CONFIGURED)); - - // 查询应用信息 - Application application = applicationRepository.findById(teamApp.getApplicationId()) - .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND, new Object[]{"应用"})); - - // 查询环境信息 - Environment environment = environmentRepository.findById(teamApp.getEnvironmentId()) - .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND, new Object[]{"环境"})); - - // 查询团队环境配置 - TeamEnvironmentConfig teamEnvConfig = teamEnvironmentConfigRepository - .findByTeamIdAndEnvironmentId(teamApp.getTeamId(), teamApp.getEnvironmentId()) - .orElseThrow(() -> new BusinessException(ResponseCode.DEPLOY_ENVIRONMENT_CONFIG_MISSING, - new Object[]{teamApp.getTeamId(), teamApp.getEnvironmentId()})); - - return new DeployContext(teamApp, workflowDefinition, application, environment, teamEnvConfig); - } - - /** - * 构建部署流程变量 - */ - private Map buildDeployVariables(DeployContext context, DeployRequestDTO request, String businessKey) { - Map variables = new HashMap<>(); - - // 部署上下文 - variables.put("deploy", buildDeployContextMap(context, request)); - - // Jenkins 配置 - Map jenkinsConfig = buildJenkinsConfig(context.teamApp); - if (jenkinsConfig != null) { - variables.put("jenkins", jenkinsConfig); - } - - // 审批配置 - variables.put("approval", buildApprovalConfig(context.teamEnvConfig)); - - // 通知配置 - variables.put("notification", buildNotificationConfig(context.teamEnvConfig)); - - return variables; - } - - /** - * 构建部署上下文Map - */ - private Map buildDeployContextMap(DeployContext context, DeployRequestDTO request) { - Map deployContext = new HashMap<>(); - deployContext.put("teamApplicationId", context.teamApp.getId()); - deployContext.put("teamId", context.teamApp.getTeamId()); - deployContext.put("applicationId", context.teamApp.getApplicationId()); - deployContext.put("applicationCode", context.application.getAppCode()); - deployContext.put("applicationName", context.application.getAppName()); - deployContext.put("environmentId", context.teamApp.getEnvironmentId()); - deployContext.put("environmentCode", context.environment.getEnvCode()); - deployContext.put("environmentName", context.environment.getEnvName()); - deployContext.put("by", SecurityUtils.getCurrentUsername()); - deployContext.put("remark", request.getRemark()); - return deployContext; - } - - /** - * 构建Jenkins配置 - */ - private Map buildJenkinsConfig(TeamApplication teamApp) { - if (teamApp.getDeploySystemId() == null || teamApp.getDeployJob() == null) { - log.warn("未配置Jenkins构建任务: teamApplicationId={}", teamApp.getId()); - return null; - } - - JenkinsBuildInputMapping jenkinsInput = new JenkinsBuildInputMapping(); - jenkinsInput.setServerId(teamApp.getDeploySystemId()); - jenkinsInput.setJobName(teamApp.getDeployJob()); - if (teamApp.getBranch() != null) { - jenkinsInput.setBranch(teamApp.getBranch()); - } - - // 转换为 Map(Flowable 只支持基本类型) - return objectMapper.convertValue(jenkinsInput, Map.class); - } - - /** - * 构建审批配置 - */ - private Map buildApprovalConfig(TeamEnvironmentConfig teamEnvConfig) { - Map approvalConfig = new HashMap<>(); - Boolean approvalRequired = teamEnvConfig.getApprovalRequired() != null ? teamEnvConfig.getApprovalRequired() : false; - approvalConfig.put("required", approvalRequired); - - // 处理审批人ID列表:转换为逗号分隔的用户名字符串 - if (teamEnvConfig.getApproverUserIds() != null && !teamEnvConfig.getApproverUserIds().isEmpty()) { - List approvers = userRepository.findAllById(teamEnvConfig.getApproverUserIds()); - if (approvers.isEmpty()) { - throw new BusinessException(ResponseCode.USER_NOT_FOUND); - } - - String userIds = approvers.stream() - .map(User::getUsername) - .collect(Collectors.joining(",")); - approvalConfig.put("userIds", userIds); - log.debug("审批配置: required={}, userIds={}", approvalRequired, userIds); - } else { - // 如果要求审批但未配置审批人,抛出专属异常 - if (Boolean.TRUE.equals(approvalRequired)) { - throw new BusinessException(ResponseCode.DEPLOY_APPROVER_NOT_CONFIGURED); - } - approvalConfig.put("userIds", ""); - } - - return approvalConfig; - } - - /** - * 构建通知配置 - */ - private Map buildNotificationConfig(TeamEnvironmentConfig teamEnvConfig) { - Map notificationConfig = new HashMap<>(); - - if (teamEnvConfig.getNotificationChannelId() == null) { - throw new BusinessException(ResponseCode.DEPLOY_NOTIFICATION_CONFIG_MISSING); - } - - notificationConfig.put("channelId", teamEnvConfig.getNotificationChannelId()); - log.debug("通知配置: channelId={}", teamEnvConfig.getNotificationChannelId()); - - return notificationConfig; - } - - /** - * 启动工作流并创建部署记录 - */ - private WorkflowInstanceDTO startWorkflowAndCreateRecord(DeployContext context, String businessKey, - Map variables, String remark) { - // 构造工作流启动请求 + private WorkflowInstanceDTO startWorkflow(WorkflowDefinition workflowDefinition, String businessKey, Map variables) { WorkflowInstanceStartRequest workflowRequest = new WorkflowInstanceStartRequest(); - workflowRequest.setProcessKey(context.workflowDefinition.getKey()); + workflowRequest.setProcessKey(workflowDefinition.getKey()); workflowRequest.setBusinessKey(businessKey); workflowRequest.setVariables(variables); - - // 启动工作流 WorkflowInstanceDTO workflowInstance = workflowInstanceService.startWorkflow(workflowRequest); - - log.info("部署流程已启动: businessKey={}, workflowInstanceId={}, application={}, environment={}", - businessKey, workflowInstance.getId(), context.application.getAppCode(), context.environment.getEnvCode()); - - // 创建部署记录 - deployRecordService.createDeployRecord( - workflowInstance.getId(), - businessKey, - context.teamApp.getId(), - context.teamApp.getTeamId(), - context.teamApp.getApplicationId(), - context.teamApp.getEnvironmentId(), - SecurityUtils.getCurrentUsername(), - remark - ); - + log.info("部署流程已启动: businessKey={}, processInstanceId={}", businessKey, workflowInstance.getProcessInstanceId()); return workflowInstance; } + /** + * 创建部署记录 + */ + private void createDeployRecord(DeployExecuteRequest request, WorkflowInstanceDTO workflowInstance, String businessKey) { + String currentUsername = SecurityUtils.getCurrentUsername(); + deployRecordService.createDeployRecord(workflowInstance.getId(), businessKey, request.getTeamApplicationId(), request.getTeamId(), request.getApplicationId(), request.getEnvironmentId(), currentUsername, request.getRemark()); + + log.info("部署记录已创建: businessKey={}, workflowInstanceId={}", businessKey, workflowInstance.getId()); + } + /** * 构建部署结果DTO */ - private DeployResultDTO buildDeployResult(WorkflowInstanceDTO workflowInstance, String businessKey, DeployContext context) { + private DeployResultDTO buildDeployResult(WorkflowInstanceDTO workflowInstance, String businessKey, DeployExecuteRequest request) { DeployResultDTO result = new DeployResultDTO(); result.setWorkflowInstanceId(workflowInstance.getId()); result.setBusinessKey(businessKey); result.setProcessInstanceId(workflowInstance.getProcessInstanceId()); result.setStatus(workflowInstance.getStatus().name()); - result.setMessage("部署流程已启动"); - - log.info("部署请求处理完成: businessKey={}, workflowInstanceId={}, application={}, environment={}", - businessKey, workflowInstance.getId(), context.application.getAppCode(), context.environment.getEnvCode()); - + log.info("部署启动成功: businessKey={}, application={}, environment={}", businessKey, request.getApplicationCode(), request.getEnvironmentCode()); return result; } - /** - * 部署上下文内部类 - */ - private static class DeployContext { - final TeamApplication teamApp; - final WorkflowDefinition workflowDefinition; - final Application application; - final Environment environment; - final TeamEnvironmentConfig teamEnvConfig; - - DeployContext(TeamApplication teamApp, WorkflowDefinition workflowDefinition, - Application application, Environment environment, TeamEnvironmentConfig teamEnvConfig) { - this.teamApp = teamApp; - this.workflowDefinition = workflowDefinition; - this.application = application; - this.environment = environment; - this.teamEnvConfig = teamEnvConfig; - } - } - @Override public List getMyApprovalTasks() { // 1. 获取当前登录用户 @@ -800,10 +698,10 @@ public class DeployServiceImpl implements IDeployService { // 2. 查询用户的所有待办任务(模仿 ApprovalTaskService.getMyTasks) List tasks = taskService.createTaskQuery() - .taskAssignee(currentUsername) - .orderByTaskCreateTime() - .desc() - .list(); + .taskAssignee(currentUsername) + .orderByTaskCreateTime() + .desc() + .list(); if (tasks.isEmpty()) { log.info("用户 {} 当前没有待办审批任务", currentUsername); @@ -830,7 +728,7 @@ public class DeployServiceImpl implements IDeployService { /** * 转换 Flowable Task 为部署审批 DTO *

模仿 ApprovalTaskService.convertToDTO 方法 - * + * * @param task Flowable 任务 * @return 部署审批DTO,如果不是部署任务则返回 null */ @@ -845,7 +743,7 @@ public class DeployServiceImpl implements IDeployService { // 2. 检查是否为部署流程(通过 deploy 变量判断) @SuppressWarnings("unchecked") Map deployContext = (Map) variables.get("deploy"); - + if (deployContext == null) { log.debug("任务 {} 不包含部署上下文,跳过", task.getId()); return null; @@ -853,14 +751,14 @@ public class DeployServiceImpl implements IDeployService { // 3. 获取 BusinessKey(需要通过 RuntimeService 查询) ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() - .processInstanceId(task.getProcessInstanceId()) - .singleResult(); - + .processInstanceId(task.getProcessInstanceId()) + .singleResult(); + if (processInstance == null) { log.debug("流程实例 {} 已结束,跳过", task.getProcessInstanceId()); return null; } - + String businessKey = processInstance.getBusinessKey(); if (businessKey == null) { log.debug("任务 {} 的流程实例没有 businessKey,跳过", task.getId()); @@ -869,13 +767,13 @@ public class DeployServiceImpl implements IDeployService { // 4. 查询部署记录 Optional deployRecordOpt = deployRecordRepository - .findByBusinessKeyAndDeletedFalse(businessKey); - + .findByBusinessKeyAndDeletedFalse(businessKey); + if (deployRecordOpt.isEmpty()) { log.warn("找不到业务标识为 {} 的部署记录", businessKey); return null; } - + DeployRecord deployRecord = deployRecordOpt.get(); // 5. 构建 DTO @@ -891,14 +789,14 @@ public class DeployServiceImpl implements IDeployService { if (task.getCreateTime() != null) { dto.setCreateTime(LocalDateTime.ofInstant( - task.getCreateTime().toInstant(), - ZoneId.systemDefault())); + task.getCreateTime().toInstant(), + ZoneId.systemDefault())); } if (task.getDueDate() != null) { dto.setDueDate(LocalDateTime.ofInstant( - task.getDueDate().toInstant(), - ZoneId.systemDefault())); + task.getDueDate().toInstant(), + ZoneId.systemDefault())); } // 5.2 审批配置信息(从流程变量获取) @@ -926,7 +824,7 @@ public class DeployServiceImpl implements IDeployService { // 5.4 查询团队名称 Long teamId = dto.getTeamId(); if (teamId != null) { - teamRepository.findById(teamId).ifPresent(team -> + teamRepository.findById(teamId).ifPresent(team -> dto.setTeamName(team.getTeamName()) ); } @@ -977,8 +875,8 @@ public class DeployServiceImpl implements IDeployService { try { // 1. 验证任务是否存在 Task task = taskService.createTaskQuery() - .taskId(taskId) - .singleResult(); + .taskId(taskId) + .singleResult(); if (task == null) { log.error("审批任务不存在: taskId={}", taskId); @@ -992,7 +890,7 @@ public class DeployServiceImpl implements IDeployService { // 3. 验证审批权限 if (!currentUsername.equals(task.getAssignee())) { log.error("无权审批该任务: taskId={}, 当前用户={}, 任务负责人={}", - taskId, currentUsername, task.getAssignee()); + taskId, currentUsername, task.getAssignee()); throw new BusinessException(ResponseCode.PERMISSION_DENIED); } @@ -1034,7 +932,7 @@ public class DeployServiceImpl implements IDeployService { taskService.complete(taskId); log.info("部署审批已提交,后续节点将异步执行: taskId={}, result={}, comment={}", - taskId, request.getResult(), request.getComment()); + taskId, request.getResult(), request.getComment()); } catch (BusinessException e) { log.error("审批失败: taskId={}, error={}", taskId, e.getMessage()); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/JsonUtils.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/JsonUtils.java new file mode 100644 index 00000000..e3fefaf1 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/JsonUtils.java @@ -0,0 +1,439 @@ +package com.qqchen.deploy.backend.framework.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import java.util.Collections; +import java.util.Map; + +/** + * JSON 工具类 + *

提供对象与 JSON、Map 之间的转换功能 + *

封装 ObjectMapper,提供统一的异常处理和类型安全的 API + *

基于 Jackson,性能优于 FastJSON2(大型数据集场景下快 1.6 倍) + * + * @author qqchen + * @since 2025-11-06 + */ +@Slf4j +@Component +public class JsonUtils { + + @Resource + private ObjectMapper objectMapper; + + private static ObjectMapper MAPPER; + + /** + * 性能监控阈值(纳秒):超过 10ms 记录警告日志 + */ + private static final long SLOW_CONVERSION_THRESHOLD_NS = 10_000_000L; + + @PostConstruct + public void init() { + MAPPER = this.objectMapper; + optimizePerformance(); + } + + /** + * 优化 Jackson 性能配置 + */ + private void optimizePerformance() { + // 1. 反序列化优化:遇到未知属性不抛异常(提升容错性和性能) + MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + // 2. 序列化优化:允许空对象 + MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + + // 3. 序列化优化:不序列化 null 值字段(减少 JSON 大小,提升性能) + // 注意:如果业务需要保留 null 字段,可以注释掉这行 + // MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + // 4. 日期格式优化:使用 ISO-8601 格式而非时间戳(可读性更好) + MAPPER.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + // 5. 数字格式优化:BigDecimal 作为字符串输出(避免精度丢失) + MAPPER.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); + + log.info("JsonUtils 性能优化配置已加载"); + } + + /** + * 将对象转换为 Map + *

常用于将 DTO 转换为工作流变量 + * + * @param obj 源对象 + * @return Map,转换失败返回空 Map + */ + @SuppressWarnings("unchecked") + public static Map toMap(Object obj) { + if (obj == null) { + return Collections.emptyMap(); + } + + try { + long start = System.nanoTime(); + Map result = MAPPER.convertValue(obj, Map.class); + logSlowConversion(start, "toMap", obj.getClass().getName()); + return result; + } catch (Exception e) { + log.error("对象转换为 Map 失败: {}", obj.getClass().getName(), e); + return Collections.emptyMap(); + } + } + + /** + * 将对象转换为 Map(不带性能监控,适用于高频调用场景) + *

性能略优于 toMap(),但不记录慢转换日志 + * + * @param obj 源对象 + * @return Map,转换失败返回空 Map + */ + @SuppressWarnings("unchecked") + public static Map toMapFast(Object obj) { + if (obj == null) { + return Collections.emptyMap(); + } + + try { + return MAPPER.convertValue(obj, Map.class); + } catch (Exception e) { + log.error("对象转换为 Map 失败: {}", obj.getClass().getName(), e); + return Collections.emptyMap(); + } + } + + /** + * 将 Map 转换为指定类型的对象 + *

常用于将配置 Map 转换为强类型配置对象 + * + * @param map 源 Map + * @param clazz 目标类型 + * @param 目标类型 + * @return 转换后的对象,转换失败返回 null + */ + public static T fromMap(Map map, Class clazz) { + if (map == null || map.isEmpty()) { + return null; + } + + try { + return MAPPER.convertValue(map, clazz); + } catch (Exception e) { + log.error("Map 转换为对象失败: {}", clazz.getName(), e); + return null; + } + } + + /** + * 对象类型转换 + *

将一个对象转换为另一个类型(通过 JSON 序列化/反序列化) + * + * @param obj 源对象 + * @param clazz 目标类型 + * @param 目标类型 + * @return 转换后的对象,转换失败返回 null + */ + public static T convert(Object obj, Class clazz) { + if (obj == null) { + return null; + } + + try { + return MAPPER.convertValue(obj, clazz); + } catch (Exception e) { + log.error("对象转换失败: {} -> {}", obj.getClass().getName(), clazz.getName(), e); + return null; + } + } + + /** + * 对象类型转换(支持泛型) + *

用于复杂类型转换,如 List、Map 等 + * + * @param obj 源对象 + * @param typeReference 目标类型引用 + * @param 目标类型 + * @return 转换后的对象,转换失败返回 null + */ + public static T convert(Object obj, TypeReference typeReference) { + if (obj == null) { + return null; + } + + try { + return MAPPER.convertValue(obj, typeReference); + } catch (Exception e) { + log.error("对象转换失败: {}", obj.getClass().getName(), e); + return null; + } + } + + /** + * 将对象转换为 JSON 字符串 + * + * @param obj 源对象 + * @return JSON 字符串,转换失败返回 null + */ + public static String toJson(Object obj) { + if (obj == null) { + return null; + } + + try { + return MAPPER.writeValueAsString(obj); + } catch (JsonProcessingException e) { + log.error("对象转换为 JSON 失败: {}", obj.getClass().getName(), e); + return null; + } + } + + /** + * 将对象转换为格式化的 JSON 字符串(带缩进) + * + * @param obj 源对象 + * @return 格式化的 JSON 字符串,转换失败返回 null + */ + public static String toPrettyJson(Object obj) { + if (obj == null) { + return null; + } + + try { + return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj); + } catch (JsonProcessingException e) { + log.error("对象转换为格式化 JSON 失败: {}", obj.getClass().getName(), e); + return null; + } + } + + /** + * 将 JSON 字符串转换为对象 + * + * @param json JSON 字符串 + * @param clazz 目标类型 + * @param 目标类型 + * @return 转换后的对象,转换失败返回 null + */ + public static T fromJson(String json, Class clazz) { + if (json == null || json.isEmpty()) { + return null; + } + + try { + return MAPPER.readValue(json, clazz); + } catch (JsonProcessingException e) { + log.error("JSON 转换为对象失败: {}", clazz.getName(), e); + return null; + } + } + + /** + * 将 JSON 字符串转换为对象(支持泛型) + * + * @param json JSON 字符串 + * @param typeReference 目标类型引用 + * @param 目标类型 + * @return 转换后的对象,转换失败返回 null + */ + public static T fromJson(String json, TypeReference typeReference) { + if (json == null || json.isEmpty()) { + return null; + } + + try { + return MAPPER.readValue(json, typeReference); + } catch (JsonProcessingException e) { + log.error("JSON 转换为对象失败", e); + return null; + } + } + + /** + * 将 JSON 字符串解析为 JsonNode + * + * @param json JSON 字符串 + * @return JsonNode,转换失败返回 null + */ + public static JsonNode parseJson(String json) { + if (json == null || json.isEmpty()) { + return null; + } + + try { + return MAPPER.readTree(json); + } catch (JsonProcessingException e) { + log.error("JSON 解析失败", e); + return null; + } + } + + /** + * 深拷贝对象 + *

通过 JSON 序列化/反序列化实现深拷贝 + * + * @param obj 源对象 + * @param clazz 目标类型 + * @param 目标类型 + * @return 深拷贝后的对象,转换失败返回 null + */ + public static T deepCopy(T obj, Class clazz) { + if (obj == null) { + return null; + } + + try { + String json = MAPPER.writeValueAsString(obj); + return MAPPER.readValue(json, clazz); + } catch (JsonProcessingException e) { + log.error("对象深拷贝失败: {}", clazz.getName(), e); + return null; + } + } + + /** + * 判断字符串是否为有效的 JSON + * + * @param json JSON 字符串 + * @return true-有效的 JSON,false-无效 + */ + public static boolean isValidJson(String json) { + if (json == null || json.isEmpty()) { + return false; + } + + try { + MAPPER.readTree(json); + return true; + } catch (JsonProcessingException e) { + return false; + } + } + + /** + * 合并两个对象(使用后者覆盖前者的非空字段) + * + * @param original 原始对象 + * @param updates 更新对象 + * @param clazz 目标类型 + * @param 目标类型 + * @return 合并后的对象,转换失败返回原始对象 + */ + public static T merge(T original, T updates, Class clazz) { + if (original == null) { + return updates; + } + if (updates == null) { + return original; + } + + try { + return MAPPER.updateValue(original, updates); + } catch (JsonProcessingException e) { + log.error("对象合并失败: {}", clazz.getName(), e); + return original; + } + } + + // ==================== 性能监控辅助方法 ==================== + + /** + * 记录慢转换日志 + *

当转换耗时超过阈值时记录警告日志 + * + * @param startTimeNs 开始时间(纳秒) + * @param operation 操作名称 + * @param target 目标类型 + */ + private static void logSlowConversion(long startTimeNs, String operation, String target) { + long duration = System.nanoTime() - startTimeNs; + if (duration > SLOW_CONVERSION_THRESHOLD_NS) { + double durationMs = duration / 1_000_000.0; + log.warn("JSON 转换耗时过长: operation={}, target={}, duration={}ms", + operation, target, String.format("%.2f", durationMs)); + } + } + + // ==================== 便捷工具方法 ==================== + + /** + * 获取 ObjectMapper 实例(用于高级定制场景) + *

注意:直接使用 ObjectMapper 会绕过工具类的统一异常处理和性能监控 + * + * @return ObjectMapper 实例 + */ + public static ObjectMapper getMapper() { + return MAPPER; + } + + /** + * 判断对象是否可以被 JSON 序列化 + * + * @param obj 源对象 + * @return true-可序列化,false-不可序列化 + */ + public static boolean isSerializable(Object obj) { + if (obj == null) { + return true; + } + + try { + MAPPER.writeValueAsString(obj); + return true; + } catch (Exception e) { + log.debug("对象不可序列化: {}", obj.getClass().getName()); + return false; + } + } + + /** + * 格式化 JSON 字符串(美化输出) + *

适用于日志输出、调试等场景 + * + * @param json JSON 字符串 + * @return 格式化后的 JSON 字符串,格式化失败返回原字符串 + */ + public static String prettyFormat(String json) { + if (json == null || json.isEmpty()) { + return json; + } + + try { + Object obj = MAPPER.readValue(json, Object.class); + return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj); + } catch (JsonProcessingException e) { + log.debug("JSON 格式化失败,返回原字符串", e); + return json; + } + } + + /** + * 压缩 JSON 字符串(移除所有空白字符) + *

适用于网络传输、存储等场景 + * + * @param json JSON 字符串 + * @return 压缩后的 JSON 字符串,压缩失败返回原字符串 + */ + public static String compactFormat(String json) { + if (json == null || json.isEmpty()) { + return json; + } + + try { + Object obj = MAPPER.readValue(json, Object.class); + return MAPPER.writeValueAsString(obj); + } catch (JsonProcessingException e) { + log.debug("JSON 压缩失败,返回原字符串", e); + return json; + } + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/RedisUtil.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/RedisUtil.java index d5d9ef38..1e81d42f 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/RedisUtil.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/RedisUtil.java @@ -533,5 +533,20 @@ public class RedisUtil { return 0; } } + + /** + * 根据pattern查找所有匹配的key + * + * @param pattern 匹配模式(支持通配符*) + * @return 匹配的key集合 + */ + public Set keys(String pattern) { + try { + return redisTemplate.keys(pattern); + } catch (Exception e) { + log.error("Redis keys error: pattern={}", pattern, e); + return Collections.emptySet(); + } + } } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/adapter/impl/EmailChannelAdapter.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/adapter/impl/EmailChannelAdapter.java index b27d9261..f9711c69 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/adapter/impl/EmailChannelAdapter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/adapter/impl/EmailChannelAdapter.java @@ -1,6 +1,7 @@ package com.qqchen.deploy.backend.notification.adapter.impl; import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.framework.utils.JsonUtils; import com.qqchen.deploy.backend.notification.adapter.INotificationChannelAdapter; import com.qqchen.deploy.backend.notification.dto.EmailNotificationConfig; import com.qqchen.deploy.backend.notification.dto.NotificationRequest; @@ -33,7 +34,7 @@ public class EmailChannelAdapter implements INotificationChannelAdapter { @Override public void send(Map config, NotificationRequest request) throws Exception { // 1. 解析配置 - EmailNotificationConfig emailConfig = objectMapper.convertValue(config, EmailNotificationConfig.class); + EmailNotificationConfig emailConfig = JsonUtils.fromMap(config, EmailNotificationConfig.class); validateEmailConfig(emailConfig); @@ -89,7 +90,7 @@ public class EmailChannelAdapter implements INotificationChannelAdapter { @Override public String validateConfig(Map config) { try { - EmailNotificationConfig emailConfig = objectMapper.convertValue(config, EmailNotificationConfig.class); + EmailNotificationConfig emailConfig = JsonUtils.fromMap(config, EmailNotificationConfig.class); validateEmailConfig(emailConfig); return "配置有效"; } catch (Exception e) { @@ -100,7 +101,7 @@ public class EmailChannelAdapter implements INotificationChannelAdapter { @Override public void testConnection(Map config) throws Exception { // 1. 解析配置 - EmailNotificationConfig emailConfig = objectMapper.convertValue(config, EmailNotificationConfig.class); + EmailNotificationConfig emailConfig = JsonUtils.fromMap(config, EmailNotificationConfig.class); // 2. 校验配置 validateEmailConfig(emailConfig); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/adapter/impl/WeworkChannelAdapter.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/adapter/impl/WeworkChannelAdapter.java index c3777a3c..0f4a7469 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/adapter/impl/WeworkChannelAdapter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/adapter/impl/WeworkChannelAdapter.java @@ -1,6 +1,7 @@ package com.qqchen.deploy.backend.notification.adapter.impl; import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.framework.utils.JsonUtils; import com.qqchen.deploy.backend.notification.adapter.INotificationChannelAdapter; import com.qqchen.deploy.backend.notification.dto.NotificationRequest; import com.qqchen.deploy.backend.notification.dto.WeworkNotificationConfig; @@ -35,7 +36,7 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter { @Override public void send(Map config, NotificationRequest request) throws Exception { // 1. 解析配置 - WeworkNotificationConfig weworkConfig = objectMapper.convertValue(config, WeworkNotificationConfig.class); + WeworkNotificationConfig weworkConfig = JsonUtils.fromMap(config, WeworkNotificationConfig.class); if (weworkConfig.getWebhookUrl() == null || weworkConfig.getWebhookUrl().isEmpty()) { throw new IllegalArgumentException("企业微信Webhook URL未配置"); @@ -104,7 +105,7 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter { @Override public String validateConfig(Map config) { try { - WeworkNotificationConfig weworkConfig = objectMapper.convertValue(config, WeworkNotificationConfig.class); + WeworkNotificationConfig weworkConfig = JsonUtils.fromMap(config, WeworkNotificationConfig.class); if (weworkConfig.getWebhookUrl() == null || weworkConfig.getWebhookUrl().isEmpty()) { return "Webhook URL不能为空"; @@ -123,7 +124,7 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter { @Override public void testConnection(Map config) throws Exception { // 1. 解析配置 - WeworkNotificationConfig weworkConfig = objectMapper.convertValue(config, WeworkNotificationConfig.class); + WeworkNotificationConfig weworkConfig = JsonUtils.fromMap(config, WeworkNotificationConfig.class); if (weworkConfig.getWebhookUrl() == null || weworkConfig.getWebhookUrl().isEmpty()) { throw new IllegalArgumentException("企业微信Webhook URL未配置"); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/BaseNodeDelegate.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/BaseNodeDelegate.java index d94a9925..047efec0 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/BaseNodeDelegate.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/BaseNodeDelegate.java @@ -2,6 +2,7 @@ package com.qqchen.deploy.backend.workflow.delegate; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.framework.utils.JsonUtils; import com.qqchen.deploy.backend.framework.utils.SpelExpressionResolver; import com.qqchen.deploy.backend.workflow.dto.outputs.BaseNodeOutputs; import com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum; @@ -165,7 +166,7 @@ public abstract class BaseNodeDelegate implements JavaDelegate { Map resolvedMap = resolveExpressions(inputMap, execution); // 转换为强类型对象 - return objectMapper.convertValue(resolvedMap, inputClass); + return JsonUtils.convert(resolvedMap, inputClass); } catch (Exception e) { log.error("Failed to parse input mapping", e); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/model/NodeContext.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/model/NodeContext.java index cc21d38f..3dbe8c8a 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/model/NodeContext.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/model/NodeContext.java @@ -2,6 +2,7 @@ package com.qqchen.deploy.backend.workflow.model; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.framework.utils.JsonUtils; import lombok.Data; import java.util.HashMap; @@ -68,14 +69,12 @@ public class NodeContext { } if (inputMapping != null) { - Map inputMap = objectMapper.convertValue(inputMapping, - new TypeReference>() {}); + Map inputMap = JsonUtils.toMapFast(inputMapping); map.put("inputMapping", inputMap); } if (outputs != null) { - Map outputsMap = objectMapper.convertValue(outputs, - new TypeReference>() {}); + Map outputsMap = JsonUtils.toMapFast(outputs); // 只保存嵌套的 outputs(保持数据结构规范) map.put("outputs", outputsMap); @@ -102,13 +101,13 @@ public class NodeContext { if (map.containsKey("inputMapping") && inputClass != null) { Object inputObj = map.get("inputMapping"); - I input = objectMapper.convertValue(inputObj, inputClass); + I input = JsonUtils.convert(inputObj, inputClass); context.setInputMapping(input); } if (map.containsKey("outputs") && outputClass != null) { Object outputsObj = map.get("outputs"); - O output = objectMapper.convertValue(outputsObj, outputClass); + O output = JsonUtils.convert(outputsObj, outputClass); context.setOutputs(output); }