打印了JENKINS节点日志
This commit is contained in:
parent
69a41c8aaf
commit
6318ca6241
@ -40,7 +40,7 @@ public class DeployApiController {
|
||||
@Operation(summary = "获取可部署的环境", description = "获取当前登录用户在各团队中可部署的环境和应用列表")
|
||||
@GetMapping("/environments")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public Response<UserDeployableDTO> getDeployableEnvironments() {
|
||||
public Response<List<UserTeamDeployableDTO>> getDeployableEnvironments() {
|
||||
return Response.success(deployService.getDeployableEnvironments());
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ public class DeployApiController {
|
||||
@Operation(summary = "执行部署", description = "根据团队应用配置启动部署工作流")
|
||||
@PostMapping("/execute")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public Response<DeployResultDTO> executeDeploy(@Validated @RequestBody DeployRequestDTO request) {
|
||||
public Response<DeployResultDTO> executeDeploy(@Validated @RequestBody DeployExecuteRequest request) {
|
||||
return Response.success(deployService.executeDeploy(request));
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
* 审批人转换器
|
||||
*
|
||||
* <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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
* 可部署应用转换器
|
||||
*
|
||||
* <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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
* 可部署环境转换器
|
||||
*
|
||||
* <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,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;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -39,6 +39,12 @@ public class DeployableEnvironmentDTO {
|
||||
@Schema(description = "审批人列表")
|
||||
private List<ApproverDTO> approvers;
|
||||
|
||||
@Schema(description = "是否启用部署通知")
|
||||
private Boolean notificationEnabled;
|
||||
|
||||
@Schema(description = "通知渠道ID")
|
||||
private Long notificationChannelId;
|
||||
|
||||
@Schema(description = "是否要求代码审查")
|
||||
private Boolean requireCodeReview;
|
||||
|
||||
|
||||
@ -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<DeployRecordSummaryDTO> recentDeployRecords;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<UserDeployableTeamEnvironmentApproverDTO> approvers;
|
||||
|
||||
@Schema(description = "是否启用部署通知")
|
||||
private Boolean notificationEnabled;
|
||||
|
||||
@Schema(description = "通知渠道ID")
|
||||
private Long notificationChannelId;
|
||||
|
||||
@Schema(description = "是否要求代码审查")
|
||||
private Boolean requireCodeReview;
|
||||
|
||||
@Schema(description = "可部署应用列表")
|
||||
private List<UserDeployableTeamEnvironmentApplicationDTO> applications;
|
||||
}
|
||||
|
||||
@ -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<TeamDeployableDTO> teams;
|
||||
@Schema(description = "团队角色")
|
||||
private String roleInTeam;
|
||||
}
|
||||
|
||||
@ -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<UserDeployableTeamMemberDTO> members;
|
||||
|
||||
@Schema(description = "可部署环境列表")
|
||||
private List<UserDeployableTeamEnvironmentDTO> environments;
|
||||
}
|
||||
|
||||
@ -29,5 +29,14 @@ public interface ITeamMemberRepository extends IBaseRepository<TeamMember, Long>
|
||||
* 根据用户ID查询所有团队成员记录
|
||||
*/
|
||||
List<TeamMember> findByUserIdAndDeletedFalse(Long userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查询用户在指定团队列表中的成员记录
|
||||
*/
|
||||
List<TeamMember> findByUserIdAndTeamIdInAndDeletedFalse(Long userId, List<Long> teamIds);
|
||||
|
||||
/**
|
||||
* 批量查询指定团队列表的所有成员
|
||||
*/
|
||||
List<TeamMember> findByTeamIdInAndDeletedFalse(List<Long> teamIds);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
/**
|
||||
* 根据工作流实例同步部署记录状态
|
||||
|
||||
@ -15,9 +15,9 @@ public interface IDeployService {
|
||||
/**
|
||||
* 获取当前用户可部署的环境和应用
|
||||
*
|
||||
* @return 用户可部署环境信息
|
||||
* @return 用户所属团队的可部署环境列表
|
||||
*/
|
||||
UserDeployableDTO getDeployableEnvironments();
|
||||
List<UserTeamDeployableDTO> getDeployableEnvironments();
|
||||
|
||||
/**
|
||||
* 执行部署
|
||||
@ -25,7 +25,7 @@ public interface IDeployService {
|
||||
* @param request 部署请求
|
||||
* @return 部署结果
|
||||
*/
|
||||
DeployResultDTO executeDeploy(DeployRequestDTO request);
|
||||
DeployResultDTO executeDeploy(DeployExecuteRequest request);
|
||||
|
||||
/**
|
||||
* 获取当前用户的部署审批任务列表
|
||||
|
||||
@ -84,16 +84,7 @@ public class DeployRecordServiceImpl extends BaseServiceImpl<DeployRecord, Deplo
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public DeployRecordDTO createDeployRecord(
|
||||
Long workflowInstanceId,
|
||||
String businessKey,
|
||||
Long teamApplicationId,
|
||||
Long teamId,
|
||||
Long applicationId,
|
||||
Long environmentId,
|
||||
String deployBy,
|
||||
String deployRemark
|
||||
) {
|
||||
public DeployRecordDTO createDeployRecord(Long workflowInstanceId, String businessKey, Long teamApplicationId, Long teamId, Long applicationId, Long environmentId, String deployBy, String deployRemark) {
|
||||
// 检查是否已存在
|
||||
DeployRecord existing = deployRecordRepository.findByWorkflowInstanceIdAndDeletedFalse(workflowInstanceId).orElse(null);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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 工具类
|
||||
* <p>提供对象与 JSON、Map 之间的转换功能
|
||||
* <p>封装 ObjectMapper,提供统一的异常处理和类型安全的 API
|
||||
* <p>基于 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
|
||||
* <p>常用于将 DTO 转换为工作流变量
|
||||
*
|
||||
* @param obj 源对象
|
||||
* @return Map,转换失败返回空 Map
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Map<String, Object> toMap(Object obj) {
|
||||
if (obj == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
try {
|
||||
long start = System.nanoTime();
|
||||
Map<String, Object> 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(不带性能监控,适用于高频调用场景)
|
||||
* <p>性能略优于 toMap(),但不记录慢转换日志
|
||||
*
|
||||
* @param obj 源对象
|
||||
* @return Map,转换失败返回空 Map
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Map<String, Object> 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 转换为指定类型的对象
|
||||
* <p>常用于将配置 Map 转换为强类型配置对象
|
||||
*
|
||||
* @param map 源 Map
|
||||
* @param clazz 目标类型
|
||||
* @param <T> 目标类型
|
||||
* @return 转换后的对象,转换失败返回 null
|
||||
*/
|
||||
public static <T> T fromMap(Map<String, Object> map, Class<T> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象类型转换
|
||||
* <p>将一个对象转换为另一个类型(通过 JSON 序列化/反序列化)
|
||||
*
|
||||
* @param obj 源对象
|
||||
* @param clazz 目标类型
|
||||
* @param <T> 目标类型
|
||||
* @return 转换后的对象,转换失败返回 null
|
||||
*/
|
||||
public static <T> T convert(Object obj, Class<T> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象类型转换(支持泛型)
|
||||
* <p>用于复杂类型转换,如 List、Map 等
|
||||
*
|
||||
* @param obj 源对象
|
||||
* @param typeReference 目标类型引用
|
||||
* @param <T> 目标类型
|
||||
* @return 转换后的对象,转换失败返回 null
|
||||
*/
|
||||
public static <T> T convert(Object obj, TypeReference<T> 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 <T> 目标类型
|
||||
* @return 转换后的对象,转换失败返回 null
|
||||
*/
|
||||
public static <T> T fromJson(String json, Class<T> 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 <T> 目标类型
|
||||
* @return 转换后的对象,转换失败返回 null
|
||||
*/
|
||||
public static <T> T fromJson(String json, TypeReference<T> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 深拷贝对象
|
||||
* <p>通过 JSON 序列化/反序列化实现深拷贝
|
||||
*
|
||||
* @param obj 源对象
|
||||
* @param clazz 目标类型
|
||||
* @param <T> 目标类型
|
||||
* @return 深拷贝后的对象,转换失败返回 null
|
||||
*/
|
||||
public static <T> T deepCopy(T obj, Class<T> 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 <T> 目标类型
|
||||
* @return 合并后的对象,转换失败返回原始对象
|
||||
*/
|
||||
public static <T> T merge(T original, T updates, Class<T> 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;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 性能监控辅助方法 ====================
|
||||
|
||||
/**
|
||||
* 记录慢转换日志
|
||||
* <p>当转换耗时超过阈值时记录警告日志
|
||||
*
|
||||
* @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 实例(用于高级定制场景)
|
||||
* <p>注意:直接使用 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 字符串(美化输出)
|
||||
* <p>适用于日志输出、调试等场景
|
||||
*
|
||||
* @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 字符串(移除所有空白字符)
|
||||
* <p>适用于网络传输、存储等场景
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -533,5 +533,20 @@ public class RedisUtil {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据pattern查找所有匹配的key
|
||||
*
|
||||
* @param pattern 匹配模式(支持通配符*)
|
||||
* @return 匹配的key集合
|
||||
*/
|
||||
public Set<String> keys(String pattern) {
|
||||
try {
|
||||
return redisTemplate.keys(pattern);
|
||||
} catch (Exception e) {
|
||||
log.error("Redis keys error: pattern={}", pattern, e);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<String, Object> 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<String, Object> 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<String, Object> config) throws Exception {
|
||||
// 1. 解析配置
|
||||
EmailNotificationConfig emailConfig = objectMapper.convertValue(config, EmailNotificationConfig.class);
|
||||
EmailNotificationConfig emailConfig = JsonUtils.fromMap(config, EmailNotificationConfig.class);
|
||||
|
||||
// 2. 校验配置
|
||||
validateEmailConfig(emailConfig);
|
||||
|
||||
@ -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<String, Object> 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<String, Object> 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<String, Object> 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未配置");
|
||||
|
||||
@ -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<I, O> implements JavaDelegate {
|
||||
Map<String, Object> 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);
|
||||
|
||||
@ -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<I, O> {
|
||||
}
|
||||
|
||||
if (inputMapping != null) {
|
||||
Map<String, Object> inputMap = objectMapper.convertValue(inputMapping,
|
||||
new TypeReference<Map<String, Object>>() {});
|
||||
Map<String, Object> inputMap = JsonUtils.toMapFast(inputMapping);
|
||||
map.put("inputMapping", inputMap);
|
||||
}
|
||||
|
||||
if (outputs != null) {
|
||||
Map<String, Object> outputsMap = objectMapper.convertValue(outputs,
|
||||
new TypeReference<Map<String, Object>>() {});
|
||||
Map<String, Object> outputsMap = JsonUtils.toMapFast(outputs);
|
||||
|
||||
// 只保存嵌套的 outputs(保持数据结构规范)
|
||||
map.put("outputs", outputsMap);
|
||||
@ -102,13 +101,13 @@ public class NodeContext<I, O> {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user