refactor(项目设置): 文件管理一揽子优化

优化了文件模块树资源统计的方式、图片预览的生成方法和资源清除逻辑等一揽子功能
This commit is contained in:
song-tianyang 2023-10-24 18:04:13 +08:00 committed by 刘瑞斌
parent e9a7ebe733
commit 128e3691ec
16 changed files with 361 additions and 179 deletions

View File

@ -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<BaseTreeNode> 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);
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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<FileInformationDTO> 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);

View File

@ -41,8 +41,8 @@ public class FileManagementController {
@PostMapping("/module/count")
@Operation(summary = "项目管理-文件管理-表格分页查询文件")
@RequiresPermissions(PermissionConstants.PROJECT_FILE_MANAGEMENT_READ)
public Map<String, Integer> moduleCount(@Validated @RequestBody FileMetadataTableRequest request) {
return fileMetadataService.moduleCount(request);
public Map<String, Long> moduleCount(@Validated @RequestBody FileMetadataTableRequest request) {
return fileMetadataService.moduleCount(request, SessionUtils.getUserId());
}

View File

@ -7,9 +7,14 @@ import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ExtFileMetadataMapper {
List<FileMetadata> selectByKeywordAndFileType(@Param("projectId") String projectId, @Param("keyword") String keyword, @Param("moduleIds") List<String> moduleIds, @Param("fileType") String fileType, @Param("isRefId") boolean isRefId);
List<FileMetadata> selectByKeywordAndFileType(@Param("projectId") String projectId, @Param("keyword") String keyword,
@Param("moduleIds") List<String> moduleIds, @Param("fileType") String fileType, @Param("isRefId") boolean isRefId);
List<ModuleCountDTO> countModuleIdByKeywordAndFileType(@Param("projectId") String projectId, @Param("keyword") String keyword, @Param("moduleIds") List<String> moduleIds, @Param("fileType") String fileType);
List<ModuleCountDTO> countModuleIdByKeywordAndFileType(@Param("projectId") String projectId, @Param("keyword") String keyword,
@Param("moduleIds") List<String> moduleIds, @Param("fileType") String fileType);
long countMyFile(@Param("projectId") String projectId, @Param("keyword") String keyword,
@Param("moduleIds") List<String> moduleIds, @Param("fileType") String fileType, @Param("operator") String operator);
List<String> selectIdByRefIdList(@Param("refIdList") List<String> refIdList);
@ -20,4 +25,5 @@ public interface ExtFileMetadataMapper {
List<FileMetadata> selectRefIdByIds(@Param("fileIds") List<String> processIds);
List<FileMetadata> selectRefIdByModuleIds(@Param("moduleIds") List<String> moduleIds);
}

View File

@ -32,6 +32,13 @@
GROUP BY f.module_id
</select>
<select id="countMyFile" resultType="java.lang.Long">
SELECT count(f.id)
FROM file_metadata f
INNER JOIN user u ON f.update_user = u.id
<include refid="file_page_request"/>
AND f.create_user = #{operator}
</select>
<sql id="file_page_request">
<where>
f.latest = true

View File

@ -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<FileModule> selectBaseByProjectId(String projectId);
List<BaseTreeNode> selectBaseByProjectId(String projectId);
List<BaseTreeNode> selectIdAndParentIdByProjectId(String projectId);
List<String> selectChildrenIdsByParentIds(@Param("ids") List<String> deleteIds);

View File

@ -1,12 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.project.mapper.ExtFileModuleMapper">
<select id="selectBaseByProjectId" resultType="io.metersphere.project.domain.FileModule">
SELECT id, name, parent_id, pos
<select id="selectBaseByProjectId" resultType="io.metersphere.sdk.dto.BaseTreeNode">
SELECT id, name, parent_id AS parentId, 'module' AS type
FROM file_module
WHERE project_id = #{0}
ORDER BY pos
</select>
<select id="selectIdAndParentIdByProjectId" resultType="io.metersphere.sdk.dto.BaseTreeNode">
SELECT id, parent_id AS parentId
FROM file_module
WHERE project_id = #{0}
</select>
<select id="selectIdsByProjectId" resultType="java.lang.String">
SELECT id
FROM file_module
@ -32,4 +37,5 @@
SELECT id FROM file_module WHERE parent_id = #{0}
ORDER BY pos ASC
</select>
</mapper>

View File

@ -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);
}

View File

@ -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<FileMetadata> 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<String, Integer> moduleCount(FileMetadataTableRequest request) {
List<ModuleCountDTO> moduleCountDTOList = extFileMetadataMapper.countModuleIdByKeywordAndFileType(request.getProjectId(), request.getKeyword(), request.getModuleIds(), request.getFileType());
return moduleCountDTOList.stream().collect(Collectors.toMap(ModuleCountDTO::getModuleId, ModuleCountDTO::getDataCount));
public Map<String, Long> moduleCount(FileMetadataTableRequest request, String operator) {
//查出每个模块节点下的资源数量
List<ModuleCountDTO> 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<String, Long> moduleCountMap = fileModuleService.getModuleCountMap(request.getProjectId(), moduleCountDTOList);
moduleCountMap.put(FILE_MODULE_COUNT_MY, myFileCount);
moduleCountMap.put(FILE_MODULE_COUNT_ALL, allCount);
return moduleCountMap;
}
}

View File

@ -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<BaseTreeNode> getTree(String projectId) {
BaseTreeNode defaultNode = this.getDefaultModule();
List<BaseTreeNode> baseTreeNodeList = new ArrayList<>();
baseTreeNodeList.add(defaultNode);
List<FileModule> fileModuleList = extFileModuleMapper.selectBaseByProjectId(projectId);
int lastSize = 0;
Map<String, BaseTreeNode> baseTreeNodeMap = new HashMap<>();
while (CollectionUtils.isNotEmpty(fileModuleList) && fileModuleList.size() != lastSize) {
List<FileModule> 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<BaseTreeNode> fileModuleList = extFileModuleMapper.selectBaseByProjectId(projectId);
return this.traverseToBuildTree(fileModuleList, true);
}
//节点内容只有Id和parentId
public List<BaseTreeNode> getTreeOnlyIdsAndResourceCount(String projectId, List<ModuleCountDTO> moduleCountDTOList) {
List<BaseTreeNode> 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<String, Long> getModuleCountMap(String projectId, List<ModuleCountDTO> moduleCountDTOList) {
//构建模块树然后统计每个节点下的所有数量包含子节点
List<BaseTreeNode> treeNodeList = this.getTreeOnlyIdsAndResourceCount(projectId, moduleCountDTOList);
Map<String, Long> returnMap = new HashMap<>();
List<BaseTreeNode> whileList = new ArrayList<>(treeNodeList);
//通过广度遍历的方式构建返回值
while (CollectionUtils.isNotEmpty(whileList)) {
List<BaseTreeNode> 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
}
}

View File

@ -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<BaseTreeNode> traverseToBuildTree(List<BaseTreeNode> traverseList, @NotNull List<ModuleCountDTO> moduleCountDTOList, boolean haveVirtualRootNode) {
List<BaseTreeNode> baseTreeNodeList = this.traverseToBuildTree(traverseList, haveVirtualRootNode);
Map<String, Integer> resourceCountMap = moduleCountDTOList.stream().collect(Collectors.toMap(ModuleCountDTO::getModuleId, ModuleCountDTO::getDataCount));
this.sumModuleResourceCount(baseTreeNodeList, resourceCountMap);
return baseTreeNodeList;
}
/**
* 遍历构建节点树
*
* @param traverseList 要遍历的节点集合会被清空
* @param haveVirtualRootNode 是否包含虚拟跟节点
* @return
*/
public List<BaseTreeNode> traverseToBuildTree(List<BaseTreeNode> traverseList, boolean haveVirtualRootNode) {
List<BaseTreeNode> baseTreeNodeList = new ArrayList<>();
if (haveVirtualRootNode) {
BaseTreeNode defaultNode = this.getDefaultModule();
baseTreeNodeList.add(defaultNode);
}
int lastSize = 0;
Map<String, BaseTreeNode> baseTreeNodeMap = new HashMap<>();
while (CollectionUtils.isNotEmpty(traverseList) && traverseList.size() != lastSize) {
List<BaseTreeNode> 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<BaseTreeNode> baseTreeNodeList, Map<String, Integer> 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<ModuleCountDTO> moduleCountDTOList) {
long count = 0;
for (ModuleCountDTO countDTO : moduleCountDTOList) {
count += countDTO.getDataCount();
}
return count;
}
}

View File

@ -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<List<FileInformationDTO>> 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<String, Integer> 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 {

View File

@ -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<List<FileInformationDTO>> tableData, Map<String, Integer> moduleCount, FileMetadataTableRequest request, boolean hasData) {
public static void checkFilePage(Pager<List<FileInformationDTO>> tableData, Map<String, Integer> moduleCount, FileMetadataTableRequest request) {
//返回值的页码和当前页码相同
Assertions.assertEquals(tableData.getCurrent(), request.getCurrent());
//返回的数据量不超过规定要返回的数据量相同
Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(tableData.getList())).size() <= request.getPageSize());
List<FileInformationDTO> 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"));
}
}

View File

@ -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());
}