From 88f5ec1d5cd98dd4174c8c0f9b2bcc571a694d50 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Sat, 15 Nov 2025 11:24:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9E=84=E5=BB=BA=E9=80=9A?= =?UTF-8?q?=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../deploy/dto/DeployApprovalTaskDTO.java | 197 ++++++++++++------ .../deploy/dto/DeployExecuteRequest.java | 2 + .../service/impl/DeployServiceImpl.java | 172 +++++++++++---- .../system/repository/IUserRepository.java | 7 + 4 files changed, 277 insertions(+), 101 deletions(-) diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployApprovalTaskDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployApprovalTaskDTO.java index 610d24cb..78a24021 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployApprovalTaskDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/DeployApprovalTaskDTO.java @@ -8,7 +8,7 @@ import java.time.LocalDateTime; /** * 部署审批任务DTO * - *

扩展了审批任务的基本信息,增加部署相关的业务上下文 + *

结构化的审批任务信息,包含审批任务、部署记录、关联实体等信息 *

用于部署审批列表,让审批人员快速了解审批内容 * * @author qqchen @@ -18,92 +18,171 @@ import java.time.LocalDateTime; @Schema(description = "部署审批任务信息") public class DeployApprovalTaskDTO { - // ============ 审批任务基本信息 ============ + // ========== 审批任务信息 ========== + @Schema(description = "审批任务信息") + private ApprovalTaskInfo approvalTask; - @Schema(description = "任务ID") - private String taskId; + // ========== 部署记录信息 ========== + @Schema(description = "部署记录信息") + private DeployRecordInfo deployRecord; - @Schema(description = "任务名称") - private String taskName; + // ========== 关联实体信息 ========== + @Schema(description = "团队信息") + private TeamInfo team; - @Schema(description = "任务描述") - private String taskDescription; + @Schema(description = "应用信息") + private ApplicationInfo application; - @Schema(description = "流程实例ID") - private String processInstanceId; + @Schema(description = "环境信息") + private EnvironmentInfo environment; - @Schema(description = "流程定义ID") - private String processDefinitionId; + @Schema(description = "发起人信息") + private UserSimpleInfo deployUser; - @Schema(description = "审批人") - private String assignee; + // ========== 嵌套类定义 ========== - @Schema(description = "创建时间") - private LocalDateTime createTime; + /** + * 审批任务信息 + */ + @Data + @Schema(description = "审批任务信息") + public static class ApprovalTaskInfo { + + @Schema(description = "任务ID") + private String taskId; - @Schema(description = "到期时间") - private LocalDateTime dueDate; + @Schema(description = "任务名称") + private String taskName; - @Schema(description = "审批标题") - private String approvalTitle; + @Schema(description = "任务描述") + private String taskDescription; - @Schema(description = "审批内容") - private String approvalContent; + @Schema(description = "流程实例ID") + private String processInstanceId; - @Schema(description = "审批模式: SINGLE-单人审批, MULTI-多人会签, OR-多人或签") - private String approvalMode; + @Schema(description = "流程定义ID") + private String processDefinitionId; - @Schema(description = "是否允许转交") - private Boolean allowDelegate; + @Schema(description = "审批人用户名") + private String assignee; - @Schema(description = "是否允许加签") - private Boolean allowAddSign; + @Schema(description = "创建时间") + private LocalDateTime createTime; - @Schema(description = "是否必须填写意见") - private Boolean requireComment; + @Schema(description = "到期时间") + private LocalDateTime dueDate; - // ============ 部署业务上下文信息 ============ + @Schema(description = "待审批时长(毫秒)- 从任务创建到现在的时长") + private Long pendingDuration; - @Schema(description = "部署记录ID") - private Long deployRecordId; + @Schema(description = "审批标题") + private String approvalTitle; - @Schema(description = "业务标识(UUID)") - private String businessKey; + @Schema(description = "审批内容") + private String approvalContent; - @Schema(description = "团队ID") - private Long teamId; + @Schema(description = "审批模式: SINGLE-单人审批, MULTI-多人会签, OR-多人或签") + private String approvalMode; - @Schema(description = "团队名称") - private String teamName; + @Schema(description = "是否允许转交") + private Boolean allowDelegate; - @Schema(description = "应用ID") - private Long applicationId; + @Schema(description = "是否允许加签") + private Boolean allowAddSign; - @Schema(description = "应用编码") - private String applicationCode; + @Schema(description = "是否必须填写意见") + private Boolean requireComment; + } - @Schema(description = "应用名称") - private String applicationName; + /** + * 部署记录信息 + */ + @Data + @Schema(description = "部署记录信息") + public static class DeployRecordInfo { + + @Schema(description = "部署记录ID") + private Long id; - @Schema(description = "环境ID") - private Long environmentId; + @Schema(description = "业务标识(UUID)") + private String businessKey; - @Schema(description = "环境编码") - private String environmentCode; + @Schema(description = "部署备注") + private String remark; - @Schema(description = "环境名称") - private String environmentName; + @Schema(description = "部署开始时间") + private LocalDateTime startTime; + } - @Schema(description = "发起人") - private String deployBy; + /** + * 团队信息 + */ + @Data + @Schema(description = "团队信息") + public static class TeamInfo { + + @Schema(description = "团队ID") + private Long id; - @Schema(description = "部署备注") - private String deployRemark; + @Schema(description = "团队名称") + private String name; + } - @Schema(description = "部署开始时间") - private LocalDateTime deployStartTime; + /** + * 应用信息 + */ + @Data + @Schema(description = "应用信息") + public static class ApplicationInfo { + + @Schema(description = "应用ID") + private Long id; - @Schema(description = "待审批时长(毫秒)- 从任务创建到现在的时长") - private Long pendingDuration; + @Schema(description = "应用编码") + private String code; + + @Schema(description = "应用名称") + private String name; + } + + /** + * 环境信息 + */ + @Data + @Schema(description = "环境信息") + public static class EnvironmentInfo { + + @Schema(description = "环境ID") + private Long id; + + @Schema(description = "环境编码") + private String code; + + @Schema(description = "环境名称") + private String name; + } + + /** + * 用户简要信息 + */ + @Data + @Schema(description = "用户简要信息") + public static class UserSimpleInfo { + + @Schema(description = "用户名") + private String username; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "邮箱") + private String email; + + @Schema(description = "手机号") + private String phone; + + @Schema(description = "部门名称") + private String departmentName; + } } 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 index f2b9cbe2..5513f4cc 100644 --- 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 @@ -72,6 +72,8 @@ public class DeployExecuteRequest { @Schema(description = "部署备注") private String deployRemark; + private String deployUser; + /** * Jenkins配置 */ diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/DeployServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/DeployServiceImpl.java index c67cb1e6..7efc7ea8 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/DeployServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/DeployServiceImpl.java @@ -755,9 +755,7 @@ public class DeployServiceImpl implements IDeployService { * 创建部署记录 */ 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.getDeployRemark()); - + deployRecordService.createDeployRecord(workflowInstance.getId(), businessKey, request.getTeamApplicationId(), request.getTeamId(), request.getApplicationId(), request.getEnvironmentId(), request.getDeployUser(), request.getDeployRemark()); log.info("部署记录已创建: businessKey={}, workflowInstanceId={}", businessKey, workflowInstance.getId()); } @@ -820,20 +818,21 @@ public class DeployServiceImpl implements IDeployService { } } - // 4. 批量查询团队信息(解决 N+1 查询问题) + // 4. 批量查询团队信息和用户信息(解决 N+1 查询问题) enrichTeamInfo(result); + enrichDeployUserInfo(result); // 5. 后置筛选:按团队和环境过滤 if (teamId != null) { result = result.stream() - .filter(task -> teamId.equals(task.getTeamId())) + .filter(task -> task.getTeam() != null && teamId.equals(task.getTeam().getId())) .collect(Collectors.toList()); log.debug("按团队ID筛选后剩余 {} 个任务", result.size()); } if (environmentId != null) { result = result.stream() - .filter(task -> environmentId.equals(task.getEnvironmentId())) + .filter(task -> task.getEnvironment() != null && environmentId.equals(task.getEnvironment().getId())) .collect(Collectors.toList()); log.debug("按环境ID筛选后剩余 {} 个任务", result.size()); } @@ -879,56 +878,79 @@ public class DeployServiceImpl implements IDeployService { // 5. 构建 DTO DeployApprovalTaskDTO dto = new DeployApprovalTaskDTO(); - // 5.1 审批任务基本信息 - dto.setTaskId(task.getId()); - dto.setTaskName(task.getName()); - dto.setTaskDescription(task.getDescription()); - dto.setProcessInstanceId(task.getProcessInstanceId()); - dto.setProcessDefinitionId(task.getProcessDefinitionId()); - dto.setAssignee(task.getAssignee()); + // 5.1 构建审批任务信息 + DeployApprovalTaskDTO.ApprovalTaskInfo approvalTask = new DeployApprovalTaskDTO.ApprovalTaskInfo(); + approvalTask.setTaskId(task.getId()); + approvalTask.setTaskName(task.getName()); + approvalTask.setTaskDescription(task.getDescription()); + approvalTask.setProcessInstanceId(task.getProcessInstanceId()); + approvalTask.setProcessDefinitionId(task.getProcessDefinitionId()); + approvalTask.setAssignee(task.getAssignee()); if (task.getCreateTime() != null) { - dto.setCreateTime(LocalDateTime.ofInstant( + LocalDateTime createTime = LocalDateTime.ofInstant( task.getCreateTime().toInstant(), - ZoneId.systemDefault())); + ZoneId.systemDefault()); + approvalTask.setCreateTime(createTime); + + // 计算待审批时长 + long pendingMillis = Duration.between(createTime, LocalDateTime.now()).toMillis(); + approvalTask.setPendingDuration(pendingMillis); } if (task.getDueDate() != null) { - dto.setDueDate(LocalDateTime.ofInstant( + approvalTask.setDueDate(LocalDateTime.ofInstant( task.getDueDate().toInstant(), ZoneId.systemDefault())); } - // 5.2 审批配置信息(从审批节点的 NodeContext 中获取 ApprovalInputMapping) + // 5.2 填充审批配置信息(从审批节点的 NodeContext 中获取 ApprovalInputMapping) String nodeId = task.getTaskDefinitionKey(); ApprovalInputMapping approvalInputs = extractApprovalInputMapping(variables, nodeId); if (approvalInputs != null) { - dto.setApprovalTitle(approvalInputs.getApprovalTitle()); - dto.setApprovalContent(approvalInputs.getApprovalContent()); - dto.setApprovalMode(approvalInputs.getApprovalMode() != null + approvalTask.setApprovalTitle(approvalInputs.getApprovalTitle()); + approvalTask.setApprovalContent(approvalInputs.getApprovalContent()); + approvalTask.setApprovalMode(approvalInputs.getApprovalMode() != null ? approvalInputs.getApprovalMode().name() : null); - dto.setAllowDelegate(approvalInputs.getAllowDelegate()); - dto.setAllowAddSign(approvalInputs.getAllowAddSign()); - dto.setRequireComment(approvalInputs.getRequireComment()); + approvalTask.setAllowDelegate(approvalInputs.getAllowDelegate()); + approvalTask.setAllowAddSign(approvalInputs.getAllowAddSign()); + approvalTask.setRequireComment(approvalInputs.getRequireComment()); } + dto.setApprovalTask(approvalTask); - // 5.3 部署业务上下文信息(从 variables 还原 DeployExecuteRequest) + // 5.3 构建部署记录信息 + DeployApprovalTaskDTO.DeployRecordInfo deployRecordInfo = new DeployApprovalTaskDTO.DeployRecordInfo(); + deployRecordInfo.setId(deployRecord.getId()); + deployRecordInfo.setBusinessKey(businessKey); + deployRecordInfo.setRemark(deployRecord.getDeployRemark()); + deployRecordInfo.setStartTime(deployRecord.getStartTime()); + dto.setDeployRecord(deployRecordInfo); + + // 5.4 构建关联实体信息(从 variables 还原 DeployExecuteRequest) DeployExecuteRequest deployRequest = JsonUtils.fromMap(variables, DeployExecuteRequest.class); - dto.setDeployRecordId(deployRecord.getId()); - dto.setBusinessKey(businessKey); - dto.setDeployStartTime(deployRecord.getStartTime()); + + // 团队信息 + DeployApprovalTaskDTO.TeamInfo team = new DeployApprovalTaskDTO.TeamInfo(); + team.setId(deployRequest.getTeamId()); + // 团队名称通过批量查询填充(见 enrichTeamInfo 方法) + dto.setTeam(team); + + // 应用信息 + DeployApprovalTaskDTO.ApplicationInfo application = new DeployApprovalTaskDTO.ApplicationInfo(); + application.setId(deployRequest.getApplicationId()); + application.setCode(deployRequest.getApplicationCode()); + application.setName(deployRequest.getApplicationName()); + dto.setApplication(application); + + // 环境信息 + DeployApprovalTaskDTO.EnvironmentInfo environment = new DeployApprovalTaskDTO.EnvironmentInfo(); + environment.setId(deployRequest.getEnvironmentId()); + environment.setCode(deployRequest.getEnvironmentCode()); + environment.setName(deployRequest.getEnvironmentName()); + dto.setEnvironment(environment); - // 使用 BeanUtils 批量复制同名字段 - BeanUtils.copyProperties(deployRequest, dto); - - // 5.4 计算待审批时长(团队名称通过批量查询填充) - if (dto.getCreateTime() != null) { - long pendingMillis = Duration.between(dto.getCreateTime(), LocalDateTime.now()).toMillis(); - dto.setPendingDuration(pendingMillis); - } - - // 5.6 自动解析 EL 表达式(如 ${deploy.applicationName}) + // 5.5 自动解析 EL 表达式(如 ${deploy.applicationName}) SpelExpressionResolver.resolveObject(dto, variables); return dto; @@ -1017,6 +1039,11 @@ public class DeployServiceImpl implements IDeployService { * * @param dtoList 待填充的 DTO 列表 */ + /** + * 批量查询并填充团队信息 + * + * @param dtoList 待填充的 DTO 列表 + */ private void enrichTeamInfo(List dtoList) { if (dtoList == null || dtoList.isEmpty()) { return; @@ -1024,7 +1051,9 @@ public class DeployServiceImpl implements IDeployService { // 1. 收集所有 teamIds Set teamIds = dtoList.stream() - .map(DeployApprovalTaskDTO::getTeamId) + .map(DeployApprovalTaskDTO::getTeam) + .filter(Objects::nonNull) + .map(DeployApprovalTaskDTO.TeamInfo::getId) .filter(Objects::nonNull) .collect(Collectors.toSet()); @@ -1038,16 +1067,75 @@ public class DeployServiceImpl implements IDeployService { // 3. 填充团队名称 for (DeployApprovalTaskDTO dto : dtoList) { - Long teamId = dto.getTeamId(); - if (teamId != null) { - Team team = teamMap.get(teamId); + if (dto.getTeam() != null && dto.getTeam().getId() != null) { + Team team = teamMap.get(dto.getTeam().getId()); if (team != null) { - dto.setTeamName(team.getTeamName()); + dto.getTeam().setName(team.getTeamName()); } } } } + /** + * 批量查询并填充部署发起人信息 + * + * @param dtoList 待填充的 DTO 列表 + */ + private void enrichDeployUserInfo(List dtoList) { + if (dtoList == null || dtoList.isEmpty()) { + return; + } + + // 1. 收集所有 deployBy 用户名 + Set usernames = dtoList.stream() + .map(DeployApprovalTaskDTO::getDeployRecord) + .filter(Objects::nonNull) + .map(record -> { + // 从原始 DeployRecord 获取 deployBy(需要通过 deployRecordId 查询) + if (record.getId() != null) { + return deployRecordRepository.findById(record.getId()) + .map(DeployRecord::getDeployBy) + .orElse(null); + } + return null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + if (usernames.isEmpty()) { + return; + } + + // 2. 批量查询用户信息 + List users = userRepository.findByUsernameInAndDeletedFalse(usernames); + Map userMap = users.stream() + .collect(Collectors.toMap(User::getUsername, u -> u)); + + // 3. 填充用户信息 + for (DeployApprovalTaskDTO dto : dtoList) { + if (dto.getDeployRecord() != null && dto.getDeployRecord().getId() != null) { + deployRecordRepository.findById(dto.getDeployRecord().getId()) + .ifPresent(deployRecord -> { + String username = deployRecord.getDeployBy(); + if (username != null) { + User user = userMap.get(username); + if (user != null) { + DeployApprovalTaskDTO.UserSimpleInfo userInfo = new DeployApprovalTaskDTO.UserSimpleInfo(); + userInfo.setUsername(user.getUsername()); + userInfo.setNickname(user.getNickname()); + userInfo.setEmail(user.getEmail()); + userInfo.setPhone(user.getPhone()); + if (user.getDepartment() != null) { + userInfo.setDepartmentName(user.getDepartment().getName()); + } + dto.setDeployUser(userInfo); + } + } + }); + } + } + } + /** * 从流程变量中提取审批节点的 ApprovalInputMapping *

从 NodeContext 的 inputs 字段中解析审批配置 diff --git a/backend/src/main/java/com/qqchen/deploy/backend/system/repository/IUserRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/system/repository/IUserRepository.java index 28911a95..7ee9c74e 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/system/repository/IUserRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/system/repository/IUserRepository.java @@ -4,6 +4,8 @@ import com.qqchen.deploy.backend.system.entity.User; import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import org.springframework.stereotype.Repository; +import java.util.Collection; +import java.util.List; import java.util.Optional; @Repository @@ -14,6 +16,11 @@ public interface IUserRepository extends IBaseRepository { */ Optional findByUsernameAndDeletedFalse(String username); + /** + * 批量根据用户名查找未删除的用户(用于批量查询) + */ + List findByUsernameInAndDeletedFalse(Collection usernames); + /** * 检查用户名是否存在(包括已删除的记录) */