diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/BaseTreeNode.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/BaseTreeNode.java index 5bfd9f1175..7f647303c7 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/BaseTreeNode.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/BaseTreeNode.java @@ -20,15 +20,15 @@ public class BaseTreeNode { @Schema(description = "节点类型") private String type; - @Schema(description = "是否是叶子节点") - private boolean leafNode = false; - @Schema(description = "父节点ID") private String parentId; @Schema(description = "子节点") private List children = new ArrayList<>(); + @Schema(description = "节点资源数量(多数情况下不会随着节点信息返回,视接口而定)") + private long count = 0; + public BaseTreeNode(String id, String name, String type) { this.id = id; this.name = name; @@ -43,7 +43,6 @@ public class BaseTreeNode { } public void addChild(BaseTreeNode node) { - this.leafNode = false; children.add(node); } } diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilePreviewUtils.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilePreviewUtils.java deleted file mode 100644 index 6365a23ac6..0000000000 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilePreviewUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -package io.metersphere.sdk.util; - -import org.apache.commons.lang3.StringUtils; - -import java.io.*; - -public class FilePreviewUtils { - private static final String BASE_FILE_FOLDER = File.separator + "opt" + File.separator + "metersphere" + File.separator + "data" + File.separator + "file" + File.separator + "preview" + File.separator; - - public static boolean isImage(String type) { - return StringUtils.equalsAnyIgnoreCase(type, "jpg", "jpeg", "png", "gif", "bmp", "svg", "ico"); - } - - //生成执行文件的绝对路径 - public static String getFileAbsolutePath(String fileName) { - return BASE_FILE_FOLDER + fileName; - } - - public static File getFile(String fileName) { - File file = new File(getFileAbsolutePath(fileName)); - if (file.exists()) { - return file; - } else { - return null; - } - } - - public static String catchFileIfNotExists(String fileName, byte[] fileBytes) { - if (getFile(fileName) == null && fileBytes != null) { - createFile(getFileAbsolutePath(fileName), fileBytes); - } - return getFileAbsolutePath(fileName); - } - - public static void deleteFile(String deleteFileName) { - File file = new File(getFileAbsolutePath(deleteFileName)); - if (file.exists()) { - file.delete(); - } - } - - private static void createFile(String filePath, byte[] fileBytes) { - File file = new File(filePath); - if (file.exists()) { - file.delete(); - } - try { - File dir = file.getParentFile(); - if (!dir.exists()) { - dir.mkdirs(); - } - file.createNewFile(); - } catch (Exception e) { - LogUtils.error(e); - } - - try (InputStream in = new ByteArrayInputStream(fileBytes); OutputStream out = new FileOutputStream(file)) { - final int MAX = 4096; - byte[] buf = new byte[MAX]; - for (int bytesRead = in.read(buf, 0, MAX); bytesRead != -1; bytesRead = in.read(buf, 0, MAX)) { - out.write(buf, 0, bytesRead); - } - } catch (IOException e) { - LogUtils.error(e); - } - } -} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/TempFileUtils.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/TempFileUtils.java new file mode 100644 index 0000000000..42fd5a59ea --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/TempFileUtils.java @@ -0,0 +1,102 @@ +package io.metersphere.sdk.util; + +import org.apache.commons.lang3.StringUtils; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; + +public class TempFileUtils { + private static final String TEMP_FILE_FOLDER = "/tmp/metersphere/file/"; + + private TempFileUtils() { + } + + public static boolean isImage(String type) { + return StringUtils.equalsAnyIgnoreCase(type, "jpg", "jpeg", "png", "gif", "bmp", "svg", "ico"); + } + + + public static String getFileTmpPath(String fileId) { + return TEMP_FILE_FOLDER + fileId; + } + + public static void deleteTmpFile(String fileId) { + File file = new File(getFileTmpPath(fileId)); + if (file.exists()) { + file.delete(); + } + } + + public static String catchCompressFileIfNotExists(String fileId, byte[] fileBytes) { + try { + String previewPath = getFileTmpPath(fileId); + compressPic(fileBytes, previewPath); + return previewPath; + } catch (Exception ignore) { + } + return null; + } + + public static void compressPic(byte[] fileBytes, String compressPicAbsolutePath) throws Exception { + // 读取原始图像 + BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(fileBytes)); + if (originalImage == null) { + //如果将一个不是图片的文件强行转换为BufferedImage对象,那么得到的就是null + createFile(compressPicAbsolutePath, fileBytes); + } else { + File file = new File(compressPicAbsolutePath); + File dir = file.getParentFile(); + if (!dir.exists()) { + dir.mkdirs(); + } + + // 指定预览图像的宽度和高度 + int previewWidth = originalImage.getWidth() / 10; + int previewHeight = originalImage.getHeight() / 10; + // 创建一个缩小后的图像 + BufferedImage previewImage = new BufferedImage(previewWidth, previewHeight, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = previewImage.createGraphics(); + + // 绘制缩小后的图像 + g2d.drawImage(originalImage, 0, 0, previewWidth, previewHeight, null); + g2d.dispose(); + ImageIO.setUseCache(false); + // 保存预览图像到文件 + ImageIO.write(previewImage, "JPEG", new File(compressPicAbsolutePath)); + } + } + + + private static void createFile(String filePath, byte[] fileBytes) { + File file = new File(filePath); + if (file.exists()) { + file.delete(); + } + try { + File dir = file.getParentFile(); + if (!dir.exists()) { + dir.mkdirs(); + } + file.createNewFile(); + } catch (Exception e) { + LogUtils.error(e); + } + + try (InputStream in = new ByteArrayInputStream(fileBytes); OutputStream out = new FileOutputStream(file)) { + final int MAX = 4096; + byte[] buf = new byte[MAX]; + for (int bytesRead = in.read(buf, 0, MAX); bytesRead != -1; bytesRead = in.read(buf, 0, MAX)) { + out.write(buf, 0, bytesRead); + } + } catch (IOException e) { + LogUtils.error(e); + } + } + + public static boolean isFileExists(String fileId) { + File file = new File(getFileTmpPath(fileId)); + return file.exists(); + } +} diff --git a/backend/services/case-management/src/test/java/io/metersphere/functional/utils/FileBaseUtils.java b/backend/services/case-management/src/test/java/io/metersphere/functional/utils/FileBaseUtils.java index 7b596254de..63a2be36f1 100644 --- a/backend/services/case-management/src/test/java/io/metersphere/functional/utils/FileBaseUtils.java +++ b/backend/services/case-management/src/test/java/io/metersphere/functional/utils/FileBaseUtils.java @@ -3,9 +3,9 @@ package io.metersphere.functional.utils; import io.metersphere.project.dto.FileInformationDTO; import io.metersphere.project.request.filemanagement.FileMetadataTableRequest; import io.metersphere.sdk.dto.BaseTreeNode; -import io.metersphere.sdk.util.FilePreviewUtils; import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.Pager; +import io.metersphere.sdk.util.TempFileUtils; import org.apache.commons.collections4.CollectionUtils; import org.junit.jupiter.api.Assertions; import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils; @@ -104,7 +104,7 @@ public class FileBaseUtils { Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(tableData.getList())).size() <= request.getPageSize()); List fileInformationDTOList = JSON.parseArray(JSON.toJSONString(tableData.getList()), FileInformationDTO.class); for (FileInformationDTO fileInformationDTO : fileInformationDTOList) { - if (FilePreviewUtils.isImage(fileInformationDTO.getFileType())) { + if (TempFileUtils.isImage(fileInformationDTO.getFileType())) { //检查是否有预览文件 String previewPath = fileInformationDTO.getPreviewSrc(); File file = new File(previewPath); diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/controller/FileManagementController.java b/backend/services/project-management/src/main/java/io/metersphere/project/controller/FileManagementController.java index a659c36770..147326c61d 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/controller/FileManagementController.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/controller/FileManagementController.java @@ -41,8 +41,8 @@ public class FileManagementController { @PostMapping("/module/count") @Operation(summary = "项目管理-文件管理-表格分页查询文件") @RequiresPermissions(PermissionConstants.PROJECT_FILE_MANAGEMENT_READ) - public Map moduleCount(@Validated @RequestBody FileMetadataTableRequest request) { - return fileMetadataService.moduleCount(request); + public Map moduleCount(@Validated @RequestBody FileMetadataTableRequest request) { + return fileMetadataService.moduleCount(request, SessionUtils.getUserId()); } diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileMetadataMapper.java b/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileMetadataMapper.java index 4ec62093ec..87b23e87d1 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileMetadataMapper.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileMetadataMapper.java @@ -7,9 +7,14 @@ import org.apache.ibatis.annotations.Param; import java.util.List; public interface ExtFileMetadataMapper { - List selectByKeywordAndFileType(@Param("projectId") String projectId, @Param("keyword") String keyword, @Param("moduleIds") List moduleIds, @Param("fileType") String fileType, @Param("isRefId") boolean isRefId); + List selectByKeywordAndFileType(@Param("projectId") String projectId, @Param("keyword") String keyword, + @Param("moduleIds") List moduleIds, @Param("fileType") String fileType, @Param("isRefId") boolean isRefId); - List countModuleIdByKeywordAndFileType(@Param("projectId") String projectId, @Param("keyword") String keyword, @Param("moduleIds") List moduleIds, @Param("fileType") String fileType); + List countModuleIdByKeywordAndFileType(@Param("projectId") String projectId, @Param("keyword") String keyword, + @Param("moduleIds") List moduleIds, @Param("fileType") String fileType); + + long countMyFile(@Param("projectId") String projectId, @Param("keyword") String keyword, + @Param("moduleIds") List moduleIds, @Param("fileType") String fileType, @Param("operator") String operator); List selectIdByRefIdList(@Param("refIdList") List refIdList); @@ -20,4 +25,5 @@ public interface ExtFileMetadataMapper { List selectRefIdByIds(@Param("fileIds") List processIds); List selectRefIdByModuleIds(@Param("moduleIds") List moduleIds); + } diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileMetadataMapper.xml b/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileMetadataMapper.xml index d481971498..8ab03d60ef 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileMetadataMapper.xml +++ b/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileMetadataMapper.xml @@ -32,6 +32,13 @@ GROUP BY f.module_id + f.latest = true diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileModuleMapper.java b/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileModuleMapper.java index 71759601b7..3f80fbdb86 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileModuleMapper.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileModuleMapper.java @@ -1,12 +1,14 @@ package io.metersphere.project.mapper; -import io.metersphere.project.domain.FileModule; +import io.metersphere.sdk.dto.BaseTreeNode; import org.apache.ibatis.annotations.Param; import java.util.List; public interface ExtFileModuleMapper { - List selectBaseByProjectId(String projectId); + List selectBaseByProjectId(String projectId); + + List selectIdAndParentIdByProjectId(String projectId); List selectChildrenIdsByParentIds(@Param("ids") List deleteIds); diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileModuleMapper.xml b/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileModuleMapper.xml index 1485b22e6a..b06a362cd9 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileModuleMapper.xml +++ b/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileModuleMapper.xml @@ -1,12 +1,17 @@ - + SELECT id, name, parent_id AS parentId, 'module' AS type FROM file_module WHERE project_id = #{0} ORDER BY pos + + \ No newline at end of file diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/service/FileManagementService.java b/backend/services/project-management/src/main/java/io/metersphere/project/service/FileManagementService.java index 0793b9b384..b0742c626e 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/service/FileManagementService.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/service/FileManagementService.java @@ -11,6 +11,7 @@ import io.metersphere.sdk.constants.ModuleConstants; import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.file.FileRequest; import io.metersphere.sdk.util.LogUtils; +import io.metersphere.sdk.util.TempFileUtils; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -64,7 +65,10 @@ public class FileManagementService { fileRequest.setProjectId(fileMetadata.getProjectId()); fileRequest.setStorage(fileMetadata.getStorage()); try { + //删除存储容器中的文件 fileService.deleteFile(fileRequest); + //删除临时文件 + TempFileUtils.deleteTmpFile(fileMetadata.getId()); } catch (Exception e) { LogUtils.error("删除文件失败", e); } diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/service/FileMetadataService.java b/backend/services/project-management/src/main/java/io/metersphere/project/service/FileMetadataService.java index 02331d9206..984b391e03 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/service/FileMetadataService.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/service/FileMetadataService.java @@ -33,7 +33,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.Collectors; @Service @Transactional(rollbackFor = Exception.class) @@ -47,6 +46,8 @@ public class FileMetadataService { @Resource private FileManagementService fileManagementService; @Resource + private FileModuleService fileModuleService; + @Resource private FileService fileService; @Value("${metersphere.file.batch-download-max:600MB}") @@ -57,8 +58,13 @@ public class FileMetadataService { List fileMetadataList = extFileMetadataMapper.selectByKeywordAndFileType(request.getProjectId(), request.getKeyword(), request.getModuleIds(), request.getFileType(), false); fileMetadataList.forEach(fileMetadata -> { FileInformationDTO fileInformationDTO = new FileInformationDTO(fileMetadata); - if (FilePreviewUtils.isImage(fileMetadata.getType())) { - fileInformationDTO.setPreviewSrc(FilePreviewUtils.catchFileIfNotExists(fileMetadata.getPath() + "." + fileMetadata.getType(), this.getFile(fileMetadata))); + if (TempFileUtils.isImage(fileMetadata.getType())) { + if (TempFileUtils.isFileExists(fileMetadata.getId())) { + fileInformationDTO.setPreviewSrc(TempFileUtils.getFileTmpPath(fileMetadata.getId())); + } else { + fileInformationDTO.setPreviewSrc(TempFileUtils.catchCompressFileIfNotExists(fileMetadata.getId(), this.getFile(fileMetadata))); + } + } returnList.add(fileInformationDTO); }); @@ -125,48 +131,7 @@ public class FileMetadataService { } } - /** - * 重新上传 - */ - public String reUpload(FileReUploadRequest request, String operator, MultipartFile uploadFile) throws Exception { - //检查模块的合法性 - FileMetadata oldFile = fileMetadataMapper.selectByPrimaryKey(request.getFileId()); - if (oldFile == null) { - throw new MSException(Translator.get("old.file.not.exist")); - } - oldFile.setLatest(false); - fileMetadataMapper.updateByPrimaryKeySelective(oldFile); - - long operationTime = System.currentTimeMillis(); - FileMetadata fileMetadata = new FileMetadata(); - fileMetadata.setId(IDGenerator.nextStr()); - fileMetadata.setStorage(oldFile.getStorage()); - fileMetadata.setProjectId(oldFile.getProjectId()); - fileMetadata.setModuleId(oldFile.getModuleId()); - fileMetadata.setName(oldFile.getName()); - fileMetadata.setType(oldFile.getType()); - fileMetadata.setCreateTime(operationTime); - fileMetadata.setCreateUser(operator); - fileMetadata.setUpdateTime(operationTime); - fileMetadata.setUpdateUser(operator); - fileMetadata.setSize(uploadFile.getSize()); - fileMetadata.setRefId(oldFile.getRefId()); - fileMetadata.setLatest(true); - fileMetadataMapper.insert(fileMetadata); - - //记录日志 - fileMetadataLogService.saveReUploadLog(fileMetadata, operator); - - // 上传文件 - String filePath = this.uploadFile(fileMetadata, uploadFile); - FileMetadata updateFileMetadata = new FileMetadata(); - updateFileMetadata.setId(fileMetadata.getId()); - updateFileMetadata.setPath(filePath); - updateFileMetadata.setFileVersion(fileMetadata.getId()); - fileMetadataMapper.updateByPrimaryKeySelective(updateFileMetadata); - - return fileMetadata.getId(); - } + private static final String FILE_MODULE_COUNT_ALL = "all"; private String uploadFile(FileMetadata fileMetadata, MultipartFile file) throws Exception { FileRequest uploadFileRequest = new FileRequest(); @@ -206,7 +171,7 @@ public class FileMetadataService { } catch (Exception e) { LogUtils.error("获取文件失败", e); } - return null; + return new byte[0]; } public void update(FileUpdateRequest request, String operator) { @@ -285,10 +250,63 @@ public class FileMetadataService { throw new MSException(Translator.get("file.size.is.too.large")); } } + private static final String FILE_MODULE_COUNT_MY = "my"; + + /** + * 重新上传 + */ + public String reUpload(FileReUploadRequest request, String operator, MultipartFile uploadFile) throws Exception { + //检查模块的合法性 + FileMetadata oldFile = fileMetadataMapper.selectByPrimaryKey(request.getFileId()); + if (oldFile == null) { + throw new MSException(Translator.get("old.file.not.exist")); + } + oldFile.setLatest(false); + fileMetadataMapper.updateByPrimaryKeySelective(oldFile); + + //删除旧的预览文件 + TempFileUtils.deleteTmpFile(oldFile.getId()); + + long operationTime = System.currentTimeMillis(); + FileMetadata fileMetadata = new FileMetadata(); + fileMetadata.setId(IDGenerator.nextStr()); + fileMetadata.setStorage(oldFile.getStorage()); + fileMetadata.setProjectId(oldFile.getProjectId()); + fileMetadata.setModuleId(oldFile.getModuleId()); + fileMetadata.setName(oldFile.getName()); + fileMetadata.setType(oldFile.getType()); + fileMetadata.setCreateTime(operationTime); + fileMetadata.setCreateUser(oldFile.getCreateUser()); + fileMetadata.setUpdateTime(operationTime); + fileMetadata.setUpdateUser(operator); + fileMetadata.setSize(uploadFile.getSize()); + fileMetadata.setRefId(oldFile.getRefId()); + fileMetadata.setLatest(true); + fileMetadataMapper.insert(fileMetadata); + + //记录日志 + fileMetadataLogService.saveReUploadLog(fileMetadata, operator); + + // 上传文件 + String filePath = this.uploadFile(fileMetadata, uploadFile); + FileMetadata updateFileMetadata = new FileMetadata(); + updateFileMetadata.setId(fileMetadata.getId()); + updateFileMetadata.setPath(filePath); + updateFileMetadata.setFileVersion(fileMetadata.getId()); + fileMetadataMapper.updateByPrimaryKeySelective(updateFileMetadata); + + return fileMetadata.getId(); + } //获取模块统计 - public Map moduleCount(FileMetadataTableRequest request) { - List moduleCountDTOList = extFileMetadataMapper.countModuleIdByKeywordAndFileType(request.getProjectId(), request.getKeyword(), request.getModuleIds(), request.getFileType()); - return moduleCountDTOList.stream().collect(Collectors.toMap(ModuleCountDTO::getModuleId, ModuleCountDTO::getDataCount)); + public Map moduleCount(FileMetadataTableRequest request, String operator) { + //查出每个模块节点下的资源数量 + List moduleCountDTOList = extFileMetadataMapper.countModuleIdByKeywordAndFileType(request.getProjectId(), request.getKeyword(), null, request.getFileType()); + long allCount = fileModuleService.getAllCount(moduleCountDTOList); + long myFileCount = extFileMetadataMapper.countMyFile(request.getProjectId(), request.getKeyword(), null, request.getFileType(), operator); + Map moduleCountMap = fileModuleService.getModuleCountMap(request.getProjectId(), moduleCountDTOList); + moduleCountMap.put(FILE_MODULE_COUNT_MY, myFileCount); + moduleCountMap.put(FILE_MODULE_COUNT_ALL, allCount); + return moduleCountMap; } } diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/service/FileModuleService.java b/backend/services/project-management/src/main/java/io/metersphere/project/service/FileModuleService.java index 9c5669ec9f..365d8860a6 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/service/FileModuleService.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/service/FileModuleService.java @@ -2,6 +2,7 @@ package io.metersphere.project.service; import io.metersphere.project.domain.FileModule; import io.metersphere.project.domain.FileModuleExample; +import io.metersphere.project.dto.ModuleCountDTO; import io.metersphere.project.mapper.ExtFileModuleMapper; import io.metersphere.project.mapper.FileModuleMapper; import io.metersphere.project.request.filemanagement.FileModuleCreateRequest; @@ -41,32 +42,14 @@ public class FileModuleService extends ModuleTreeService implements CleanupProje private FileManagementService fileManagementService; public List getTree(String projectId) { - BaseTreeNode defaultNode = this.getDefaultModule(); - List baseTreeNodeList = new ArrayList<>(); - baseTreeNodeList.add(defaultNode); - List fileModuleList = extFileModuleMapper.selectBaseByProjectId(projectId); - int lastSize = 0; - Map baseTreeNodeMap = new HashMap<>(); - while (CollectionUtils.isNotEmpty(fileModuleList) && fileModuleList.size() != lastSize) { - List notMatchedList = new ArrayList<>(); - for (FileModule fileModule : fileModuleList) { - if (StringUtils.equals(fileModule.getParentId(), ModuleConstants.ROOT_NODE_PARENT_ID)) { - BaseTreeNode node = new BaseTreeNode(fileModule.getId(), fileModule.getName(), ModuleConstants.NODE_TYPE_DEFAULT, fileModule.getParentId()); - baseTreeNodeList.add(node); - baseTreeNodeMap.put(fileModule.getId(), node); - } else { - if (baseTreeNodeMap.containsKey(fileModule.getParentId())) { - BaseTreeNode node = new BaseTreeNode(fileModule.getId(), fileModule.getName(), ModuleConstants.NODE_TYPE_DEFAULT, fileModule.getParentId()); - baseTreeNodeMap.get(fileModule.getParentId()).addChild(node); - baseTreeNodeMap.put(fileModule.getId(), node); - } else { - notMatchedList.add(fileModule); - } - } - } - fileModuleList = notMatchedList; - } - return baseTreeNodeList; + List fileModuleList = extFileModuleMapper.selectBaseByProjectId(projectId); + return this.traverseToBuildTree(fileModuleList, true); + } + + //节点内容只有Id和parentId + public List getTreeOnlyIdsAndResourceCount(String projectId, List moduleCountDTOList) { + List fileModuleList = extFileModuleMapper.selectIdAndParentIdByProjectId(projectId); + return this.traverseToBuildTree(fileModuleList, moduleCountDTOList, true); } public String add(FileModuleCreateRequest request, String operator) { @@ -182,6 +165,24 @@ public class FileModuleService extends ModuleTreeService implements CleanupProje fileModuleLogService.saveMoveLog(request, currentUser); } + public Map getModuleCountMap(String projectId, List moduleCountDTOList) { + //构建模块树,然后统计每个节点下的所有数量(包含子节点) + List treeNodeList = this.getTreeOnlyIdsAndResourceCount(projectId, moduleCountDTOList); + Map returnMap = new HashMap<>(); + + List whileList = new ArrayList<>(treeNodeList); + //通过广度遍历的方式构建返回值 + while (CollectionUtils.isNotEmpty(whileList)) { + List childList = new ArrayList<>(); + for (BaseTreeNode treeNode : whileList) { + returnMap.put(treeNode.getId(), treeNode.getCount()); + childList.addAll(treeNode.getChildren()); + } + whileList = childList; + } + return returnMap; + } + @Override public BaseModule getNode(String id) { FileModule module = fileModuleMapper.selectByPrimaryKey(id); @@ -228,5 +229,4 @@ public class FileModuleService extends ModuleTreeService implements CleanupProje public void cleanReportResources(String projectId) { // nothing to do } - } diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/service/ModuleTreeService.java b/backend/services/project-management/src/main/java/io/metersphere/project/service/ModuleTreeService.java index 860fb2de1f..a8af40e2a4 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/service/ModuleTreeService.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/service/ModuleTreeService.java @@ -1,10 +1,20 @@ package io.metersphere.project.service; +import io.metersphere.project.dto.ModuleCountDTO; import io.metersphere.sdk.constants.ModuleConstants; import io.metersphere.sdk.dto.BaseModule; import io.metersphere.sdk.dto.BaseTreeNode; import io.metersphere.sdk.dto.request.NodeMoveRequest; import io.metersphere.sdk.util.Translator; +import jakarta.validation.constraints.NotNull; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; public abstract class ModuleTreeService { @@ -12,10 +22,59 @@ public abstract class ModuleTreeService { public BaseTreeNode getDefaultModule() { //默认模块下不允许创建子模块。 它本身也就是叶子节点。 - return new BaseTreeNode(ModuleConstants.DEFAULT_NODE_ID, Translator.get("default.module"), ModuleConstants.NODE_TYPE_DEFAULT); + return new BaseTreeNode(ModuleConstants.DEFAULT_NODE_ID, Translator.get("default.module"), ModuleConstants.NODE_TYPE_DEFAULT, ModuleConstants.ROOT_NODE_PARENT_ID); } + public List traverseToBuildTree(List traverseList, @NotNull List moduleCountDTOList, boolean haveVirtualRootNode) { + List baseTreeNodeList = this.traverseToBuildTree(traverseList, haveVirtualRootNode); + Map resourceCountMap = moduleCountDTOList.stream().collect(Collectors.toMap(ModuleCountDTO::getModuleId, ModuleCountDTO::getDataCount)); + this.sumModuleResourceCount(baseTreeNodeList, resourceCountMap); + return baseTreeNodeList; + } + + /** + * 遍历构建节点树 + * + * @param traverseList 要遍历的节点集合(会被清空) + * @param haveVirtualRootNode 是否包含虚拟跟节点 + * @return + */ + public List traverseToBuildTree(List traverseList, boolean haveVirtualRootNode) { + List baseTreeNodeList = new ArrayList<>(); + if (haveVirtualRootNode) { + BaseTreeNode defaultNode = this.getDefaultModule(); + baseTreeNodeList.add(defaultNode); + } + int lastSize = 0; + Map baseTreeNodeMap = new HashMap<>(); + while (CollectionUtils.isNotEmpty(traverseList) && traverseList.size() != lastSize) { + List notMatchedList = new ArrayList<>(); + for (BaseTreeNode treeNode : traverseList) { + if (StringUtils.equals(treeNode.getParentId(), ModuleConstants.ROOT_NODE_PARENT_ID)) { + BaseTreeNode node = new BaseTreeNode(treeNode.getId(), treeNode.getName(), treeNode.getType(), treeNode.getParentId()); + baseTreeNodeList.add(node); + baseTreeNodeMap.put(treeNode.getId(), node); + } else { + if (baseTreeNodeMap.containsKey(treeNode.getParentId())) { + BaseTreeNode node = new BaseTreeNode(treeNode.getId(), treeNode.getName(), treeNode.getType(), treeNode.getParentId()); + baseTreeNodeMap.get(treeNode.getParentId()).addChild(node); + baseTreeNodeMap.put(treeNode.getId(), node); + } else { + notMatchedList.add(treeNode); + } + } + } + traverseList = notMatchedList; + } + //剩余的节点没有匹配上,直接放到根节点下 + traverseList.forEach(treeNode -> { + BaseTreeNode node = new BaseTreeNode(treeNode.getId(), treeNode.getName(), ModuleConstants.NODE_TYPE_DEFAULT, treeNode.getParentId()); + baseTreeNodeMap.get(treeNode.getParentId()).addChild(node); + }); + return baseTreeNodeList; + } + /** * 模块树排序 */ @@ -56,4 +115,37 @@ public abstract class ModuleTreeService { public abstract void updatePos(String id, int pos); public abstract void refreshPos(String parentId); + + /** + * 通过深度遍历的方式,将资源数量复制到节点上,并将子节点上的资源数量统计到父节点上 + * + * @param baseTreeNodeList + * @param resourceCountMap + */ + private void sumModuleResourceCount(List baseTreeNodeList, Map resourceCountMap) { + for (BaseTreeNode node : baseTreeNodeList) { + //赋值子节点的资源数量 + this.sumModuleResourceCount(node.getChildren(), resourceCountMap); + //当前节点的资源数量(包含子节点) + long childResourceCount = 0; + for (BaseTreeNode childNode : node.getChildren()) { + childResourceCount += childNode.getCount(); + } + + if (resourceCountMap.containsKey(node.getId())) { + node.setCount(childResourceCount + resourceCountMap.get(node.getId())); + } else { + node.setCount(childResourceCount); + } + + } + } + + public long getAllCount(List moduleCountDTOList) { + long count = 0; + for (ModuleCountDTO countDTO : moduleCountDTOList) { + count += countDTO.getDataCount(); + } + return count; + } } diff --git a/backend/services/project-management/src/test/java/io/metersphere/project/controller/filemanagement/FileManagementControllerTests.java b/backend/services/project-management/src/test/java/io/metersphere/project/controller/filemanagement/FileManagementControllerTests.java index b55918ecbc..b16d625561 100644 --- a/backend/services/project-management/src/test/java/io/metersphere/project/controller/filemanagement/FileManagementControllerTests.java +++ b/backend/services/project-management/src/test/java/io/metersphere/project/controller/filemanagement/FileManagementControllerTests.java @@ -93,6 +93,7 @@ public class FileManagementControllerTests extends BaseTest { if (StringUtils.equals(baseTreeNode.getId(), ModuleConstants.DEFAULT_NODE_ID)) { hasNode = true; } + Assertions.assertNotNull(baseTreeNode.getParentId()); } Assertions.assertTrue(hasNode); @@ -128,6 +129,7 @@ public class FileManagementControllerTests extends BaseTest { if (StringUtils.equals(baseTreeNode.getName(), request.getName())) { a1Node = baseTreeNode; } + Assertions.assertNotNull(baseTreeNode.getParentId()); } Assertions.assertNotNull(a1Node); checkLog(a1Node.getId(), OperationLogType.ADD, FileManagementRequestUtils.URL_MODULE_ADD); @@ -151,11 +153,13 @@ public class FileManagementControllerTests extends BaseTest { BaseTreeNode a1b1Node = null; BaseTreeNode a2Node = null; for (BaseTreeNode baseTreeNode : treeNodes) { + Assertions.assertNotNull(baseTreeNode.getParentId()); if (StringUtils.equals(baseTreeNode.getName(), "a1") && CollectionUtils.isNotEmpty(baseTreeNode.getChildren())) { for (BaseTreeNode childNode : baseTreeNode.getChildren()) { if (StringUtils.equals(childNode.getName(), "a1-b1")) { a1b1Node = childNode; } + Assertions.assertNotNull(childNode.getParentId()); } } else if (StringUtils.equals(baseTreeNode.getName(), "a2")) { a2Node = baseTreeNode; @@ -177,8 +181,10 @@ public class FileManagementControllerTests extends BaseTest { treeNodes = this.getFileModuleTreeNode(); BaseTreeNode a1ChildNode = null; for (BaseTreeNode baseTreeNode : treeNodes) { + Assertions.assertNotNull(baseTreeNode.getParentId()); if (StringUtils.equals(baseTreeNode.getName(), "a1") && CollectionUtils.isNotEmpty(baseTreeNode.getChildren())) { for (BaseTreeNode childNode : baseTreeNode.getChildren()) { + Assertions.assertNotNull(childNode.getParentId()); if (StringUtils.equals(childNode.getName(), "a1")) { a1ChildNode = childNode; } @@ -197,10 +203,13 @@ public class FileManagementControllerTests extends BaseTest { treeNodes = this.getFileModuleTreeNode(); BaseTreeNode a1a1c1Node = null; for (BaseTreeNode baseTreeNode : treeNodes) { + Assertions.assertNotNull(baseTreeNode.getParentId()); if (StringUtils.equals(baseTreeNode.getName(), "a1") && CollectionUtils.isNotEmpty(baseTreeNode.getChildren())) { for (BaseTreeNode secondNode : baseTreeNode.getChildren()) { + Assertions.assertNotNull(secondNode.getParentId()); if (StringUtils.equals(secondNode.getName(), "a1") && CollectionUtils.isNotEmpty(secondNode.getChildren())) { for (BaseTreeNode thirdNode : secondNode.getChildren()) { + Assertions.assertNotNull(thirdNode.getParentId()); if (StringUtils.equals(thirdNode.getName(), "a1-a1-c1")) { a1a1c1Node = thirdNode; } @@ -532,7 +541,7 @@ public class FileManagementControllerTests extends BaseTest { this.setPageSize(10); this.setProjectId(project.getId()); }}; - this.filePageRequestAndCheck(request, true); + this.filePageRequestAndCheck(request); //查找默认模块 request = new FileMetadataTableRequest() {{ @@ -543,7 +552,7 @@ public class FileManagementControllerTests extends BaseTest { this.add(ModuleConstants.DEFAULT_NODE_ID); }}); }}; - this.filePageRequestAndCheck(request, true); + this.filePageRequestAndCheck(request); //查找有数据的a1-a1模块 BaseTreeNode a1a1Node = FileManagementBaseUtils.getNodeByName(this.getFileModuleTreeNode(), "a1-a1"); @@ -555,7 +564,7 @@ public class FileManagementControllerTests extends BaseTest { this.add(a1a1Node.getId()); }}); }}; - this.filePageRequestAndCheck(request, true); + this.filePageRequestAndCheck(request); //查找没有数据的a1-b1模块 BaseTreeNode a1b2Node = FileManagementBaseUtils.getNodeByName(this.getFileModuleTreeNode(), "a1-b1"); @@ -567,7 +576,7 @@ public class FileManagementControllerTests extends BaseTest { this.add(a1b2Node.getId()); }}); }}; - this.filePageRequestAndCheck(request, false); + this.filePageRequestAndCheck(request); //查找不存在的模块 request = new FileMetadataTableRequest() {{ @@ -578,7 +587,7 @@ public class FileManagementControllerTests extends BaseTest { this.add(IDGenerator.nextStr()); }}); }}; - this.filePageRequestAndCheck(request, false); + this.filePageRequestAndCheck(request); //使用已存在的文件类型过滤 区分大小写 request = new FileMetadataTableRequest() {{ @@ -587,7 +596,7 @@ public class FileManagementControllerTests extends BaseTest { this.setProjectId(project.getId()); this.setFileType("JPG"); }}; - this.filePageRequestAndCheck(request, true); + this.filePageRequestAndCheck(request); //使用已存在的文件类型过滤 不区分大小写 request = new FileMetadataTableRequest() {{ @@ -596,7 +605,7 @@ public class FileManagementControllerTests extends BaseTest { this.setProjectId(project.getId()); this.setFileType("JpG"); }}; - this.filePageRequestAndCheck(request, true); + this.filePageRequestAndCheck(request); //使用不存在的文件类型过滤 request = new FileMetadataTableRequest() {{ @@ -605,7 +614,7 @@ public class FileManagementControllerTests extends BaseTest { this.setProjectId(project.getId()); this.setFileType("fire"); }}; - this.filePageRequestAndCheck(request, false); + this.filePageRequestAndCheck(request); } @Test @@ -1104,7 +1113,7 @@ public class FileManagementControllerTests extends BaseTest { return JSON.parseArray(JSON.toJSONString(resultHolder.getData()), BaseTreeNode.class); } - private void filePageRequestAndCheck(FileMetadataTableRequest request, Boolean hasData) throws Exception { + private void filePageRequestAndCheck(FileMetadataTableRequest request) throws Exception { MvcResult mvcResult = this.requestPostWithOkAndReturn(FileManagementRequestUtils.URL_FILE_PAGE, request); Pager> pageResult = JSON.parseObject(JSON.toJSONString( JSON.parseObject(mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class).getData()), @@ -1113,7 +1122,7 @@ public class FileManagementControllerTests extends BaseTest { Map moduleCountResult = JSON.parseObject(JSON.toJSONString( JSON.parseObject(moduleCountMvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class).getData()), Map.class); - FileManagementBaseUtils.checkFilePage(pageResult, moduleCountResult, request, hasData); + FileManagementBaseUtils.checkFilePage(pageResult, moduleCountResult, request); } private void preliminaryData() throws Exception { diff --git a/backend/services/project-management/src/test/java/io/metersphere/project/utils/FileManagementBaseUtils.java b/backend/services/project-management/src/test/java/io/metersphere/project/utils/FileManagementBaseUtils.java index ad235155e5..aaab914751 100644 --- a/backend/services/project-management/src/test/java/io/metersphere/project/utils/FileManagementBaseUtils.java +++ b/backend/services/project-management/src/test/java/io/metersphere/project/utils/FileManagementBaseUtils.java @@ -3,9 +3,9 @@ package io.metersphere.project.utils; import io.metersphere.project.dto.FileInformationDTO; import io.metersphere.project.request.filemanagement.FileMetadataTableRequest; import io.metersphere.sdk.dto.BaseTreeNode; -import io.metersphere.sdk.util.FilePreviewUtils; import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.Pager; +import io.metersphere.sdk.util.TempFileUtils; import org.apache.commons.collections4.CollectionUtils; import org.junit.jupiter.api.Assertions; import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils; @@ -97,14 +97,14 @@ public class FileManagementBaseUtils { } } - public static void checkFilePage(Pager> tableData, Map moduleCount, FileMetadataTableRequest request, boolean hasData) { + public static void checkFilePage(Pager> tableData, Map moduleCount, FileMetadataTableRequest request) { //返回值的页码和当前页码相同 Assertions.assertEquals(tableData.getCurrent(), request.getCurrent()); //返回的数据量不超过规定要返回的数据量相同 Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(tableData.getList())).size() <= request.getPageSize()); List fileInformationDTOList = JSON.parseArray(JSON.toJSONString(tableData.getList()), FileInformationDTO.class); for (FileInformationDTO fileInformationDTO : fileInformationDTOList) { - if (FilePreviewUtils.isImage(fileInformationDTO.getFileType())) { + if (TempFileUtils.isImage(fileInformationDTO.getFileType())) { //检查是否有预览文件 String previewPath = fileInformationDTO.getPreviewSrc(); File file = new File(previewPath); @@ -112,17 +112,19 @@ public class FileManagementBaseUtils { } } - //判断返回的节点统计总量是否和表格总量匹配 - long allResult = 0; + //如果没有数据,则返回的模块节点也不应该有数据 + boolean moduleHaveResource = false; for (int countByModuleId : moduleCount.values()) { - allResult += countByModuleId; + if (countByModuleId > 0) { + moduleHaveResource = true; + } } - Assertions.assertEquals(allResult, tableData.getTotal()); Assertions.assertEquals(request.getPageSize(), tableData.getPageSize()); - if (hasData) { - Assertions.assertTrue(allResult > 0); - } else { - Assertions.assertTrue(allResult == 0); + if (tableData.getTotal() > 0) { + Assertions.assertTrue(moduleHaveResource); } + + Assertions.assertTrue(moduleCount.containsKey("all")); + Assertions.assertTrue(moduleCount.containsKey("my")); } } diff --git a/backend/services/system-setting/src/test/java/io/metersphere/system/base/BaseTest.java b/backend/services/system-setting/src/test/java/io/metersphere/system/base/BaseTest.java index 9f792d767e..102454417e 100644 --- a/backend/services/system-setting/src/test/java/io/metersphere/system/base/BaseTest.java +++ b/backend/services/system-setting/src/test/java/io/metersphere/system/base/BaseTest.java @@ -53,6 +53,7 @@ import java.util.List; import java.util.Map; import java.util.function.Supplier; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) @@ -174,6 +175,7 @@ public abstract class BaseTest { MockHttpServletRequestBuilder requestBuilder = getMultipartRequestBuilder(url, paramMap, uriVariables); return mockMvc.perform(requestBuilder) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) .andExpect(status().isOk()); }