数据存储优化

This commit is contained in:
2025-12-30 17:19:08 +08:00
parent f06fd5c883
commit cd7567cc16
9 changed files with 159 additions and 84 deletions

View File

@@ -0,0 +1,3 @@
ALTER TABLE file_storage
MODIFY COLUMN createYearMonth VARCHAR(7)
GENERATED ALWAYS AS (DATE_FORMAT(createTime, '%Y-%m')) STORED;

View File

@@ -37,7 +37,13 @@ public enum DirTypeEnum {
TRAIN_MODEL_DIR("trainModel", 5),
@Schema(description = "脚本文件夹", example = "6")
SCRIPT_DIR("script", 6);
SCRIPT_DIR("script", 6),
/**
* 视频库
*/
@Schema(description = "视频库文件夹", example = "7")
VIDEO_DIR("video", 7);
@@ -70,7 +76,7 @@ public enum DirTypeEnum {
// 初始化用户业务库目录
private static final List<DirTypeEnum> INIT_SPMD_DIR = List.of(
DirTypeEnum.KNOWLEDGE_BASE_DIR, DirTypeEnum.PROJECT_NODE_DIR,
DirTypeEnum.AVATAR_DIR, DirTypeEnum.SIMULATION_PARAMETER_DIR, DirTypeEnum.TRAIN_MODEL_DIR,DirTypeEnum.SCRIPT_DIR);
DirTypeEnum.AVATAR_DIR, DirTypeEnum.SIMULATION_PARAMETER_DIR, DirTypeEnum.TRAIN_MODEL_DIR,DirTypeEnum.SCRIPT_DIR, DirTypeEnum.VIDEO_DIR);
public static final List<DirTypeEnum> getInitSpmdDir() {
return INIT_SPMD_DIR;
}

View File

@@ -52,7 +52,7 @@ public class FileSizeUtils {
public static String formatFileSizeToGB(BigDecimal bytes) {
// 处理null值和非正值的情况
if (bytes == null || bytes.compareTo(BigDecimal.ZERO) <= 0) {
return "0.00000000 GB";
return "0.00000000";
}
// 1 GB = 1024^3 字节

View File

@@ -51,6 +51,13 @@ public class FileSearchReq extends BaseReq {
@Schema(description = "文件后缀")
private String fileSuffix;
/**
* 目录类型 DirTypeEnum
*/
@Schema(description = "目录类型")
private Integer dirType;
/**
* 文件大小
*/

View File

@@ -15,6 +15,11 @@ public class QueryBigFileReq extends BaseReq {
*/
private Long dirId;
/**
* 目录类型 DirTypeEnum
*/
private Integer dirType;
/**
* 文件名称
*/

View File

@@ -53,8 +53,6 @@ public interface DataStorageAnalysis {
SdmResponse batchUpdateUserQuota(List<AddUserQuotaEntity> addUserQuota);
List<Long> getListBigFileId(QueryBigFileReq queryBigFileReq);
SdmResponse<List<FileStorageQuota>> listAllUserQuotaForJob();
}

View File

@@ -74,7 +74,7 @@ public class DataStorageAnalysisImpl implements DataStorageAnalysis {
if (ObjectUtils.isEmpty(nodeList)) {
log.error("获取节点信息失败,节点类型: {}, 标识符: {}", queryNodeType, queryNodeName);
return getEmptyNodeSize(queryNodeName);
return getEmptyNodeSize(queryNodeName,intervalMonths,targetYm);
}
Long tenantId = ThreadLocalContext.getTenantId();
@@ -98,7 +98,7 @@ public class DataStorageAnalysisImpl implements DataStorageAnalysis {
.list().stream().collect(Collectors.toMap(FileMetadataInfo::getRelatedResourceUuid, FileMetadataInfo::getId));
if (uuidToDirIdMap.isEmpty()) {
log.error("获取节点ID映射失败节点类型: {}, 标识符: {}", queryNodeType, queryNodeName);
return getEmptyNodeSize(queryNodeName);
return getEmptyNodeSize(queryNodeName,intervalMonths,targetYm);
}
// fileMetadIds: uuid对应的fileid结合
@@ -109,45 +109,72 @@ public class DataStorageAnalysisImpl implements DataStorageAnalysis {
if (ObjectUtils.isNotEmpty(intervalMonths)) {
// 近几个月
nodeSizeDTOS = fileStorageService.selectNodeSizeByNodeType(fileMetadIds, intervalMonths,tenantId );
}
if (ObjectUtils.isNotEmpty(targetYm)) {
if (ObjectUtils.isEmpty(nodeSizeDTOS)) {
// 空间为空 也要返回nodeName
return getEmptyNodeSize(queryNodeName,intervalMonths,targetYm);
}
// nodeSizeDTOMaps: 节点的fileid --> filesize
Map<Long, Long> nodeSizeDTOMaps = nodeSizeDTOS.stream().collect(Collectors.toMap(NodeSizeDTO::getDirId, NodeSizeDTO::getTotalSize));
nodeNameToUuidListMap.forEach((nodeName, uuidList) -> {
AtomicLong totalSize = new AtomicLong();
uuidList.forEach(uuidItem -> {
Long dirId = uuidToDirIdMap.get(uuidItem);
Long filesize = nodeSizeDTOMaps.get(dirId);
if (ObjectUtils.isNotEmpty(filesize)) {
totalSize.addAndGet(filesize);
}
});
JSONObject jsonObject = new JSONObject();
jsonObject.put("nodeName", nodeName);
jsonObject.put("totalSize", FileSizeUtils.formatFileSizeToGB(new BigDecimal(totalSize.toString())));
result.add(jsonObject);
});
}else if (ObjectUtils.isNotEmpty(targetYm)) {
//查询增量的
nodeSizeDTOS = fileStorageService.statDirStorageByTargetYm(fileMetadIds, targetYm,tenantId);
}
if (ObjectUtils.isEmpty(nodeSizeDTOS)) {
// 空间为空 也要返回nodeName
return getEmptyNodeSize(queryNodeName);
}
if (ObjectUtils.isEmpty(nodeSizeDTOS)) {
// 空间为空 也要返回nodeName
return getEmptyNodeSize(queryNodeName,intervalMonths,targetYm);
}
Map<Long, Map<String, Long>> nodeSizeDTOMaps = nodeSizeDTOS.stream().collect(Collectors.groupingBy(NodeSizeDTO::getDirId,
Collectors.toMap(NodeSizeDTO::getStatDimension, NodeSizeDTO::getTotalSize)));
nodeNameToUuidListMap.forEach((nodeName, uuidList) -> {
Map<String, Long> totalSizeMap = new HashMap<>();
uuidList.forEach(uuidItem -> {
Long dirId = uuidToDirIdMap.get(uuidItem);
Map<String, Long> statDimensionSizeMap = nodeSizeDTOMaps.get(dirId);
totalSizeMap.put("BEFORE", totalSizeMap.getOrDefault("BEFORE",0L) + statDimensionSizeMap.getOrDefault("BEFORE",0L));
totalSizeMap.put("INCREMENT", totalSizeMap.getOrDefault("INCREMENT",0L) + statDimensionSizeMap.getOrDefault("INCREMENT",0L));
});
// nodeSizeDTOMaps: 节点的fileid --> filesize
Map<Long, Long> nodeSizeDTOMaps = nodeSizeDTOS.stream().collect(Collectors.toMap(NodeSizeDTO::getDirId, NodeSizeDTO::getTotalSize));
nodeNameToUuidListMap.forEach((nodeName, uuidList) -> {
AtomicLong totalSize = new AtomicLong();
uuidList.forEach(uuidItem -> {
Long dirId = uuidToDirIdMap.get(uuidItem);
Long filesize = nodeSizeDTOMaps.get(dirId);
if (ObjectUtils.isNotEmpty(filesize)) {
totalSize.addAndGet(filesize);
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("nodeName", nodeName);
jsonObject.put("BEFORE", FileSizeUtils.formatFileSizeToGB(new BigDecimal(totalSizeMap.get("BEFORE").toString())));
jsonObject.put("INCREMENT", FileSizeUtils.formatFileSizeToGB(new BigDecimal(totalSizeMap.get("INCREMENT").toString())));
result.add(jsonObject);
});
JSONObject jsonObject = new JSONObject();
jsonObject.put("nodeName", nodeName);
jsonObject.put("totalSize", FileSizeUtils.formatFileSizeToGB(new BigDecimal(totalSize.toString())));
result.add(jsonObject);
});
}
}
return SdmResponse.success(result);
}
@NotNull
private static SdmResponse<List<JSONObject>> getEmptyNodeSize(String actualNodeName) {
private static SdmResponse<List<JSONObject>> getEmptyNodeSize(String actualNodeName,Integer intervalMonths, String targetYm) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("nodeName", actualNodeName);
jsonObject.put("totalSize", 0);
return SdmResponse.success(Arrays.asList(jsonObject));
if (ObjectUtils.isNotEmpty(intervalMonths)) {
jsonObject.put("nodeName", actualNodeName);
jsonObject.put("totalSize", 0);
}else if (ObjectUtils.isNotEmpty(targetYm)) {
jsonObject.put("nodeName", actualNodeName);
jsonObject.put("BEFORE", 0);
jsonObject.put("INCREMENT", 0);
}
return SdmResponse.success(List.of(jsonObject));
}
@Override
@@ -169,8 +196,6 @@ public class DataStorageAnalysisImpl implements DataStorageAnalysis {
return SdmResponse.success(new ArrayList<>());
}
Map<Long, Long> userIdToTotalSizeMap = totalFileSizeByCreator.stream().collect(Collectors.toMap(UserTotalFileSizeDTO::getUserId, UserTotalFileSizeDTO::getTotalSize));
// 2. 批量获取用户信息 (性能优化核心)
// 提取所有涉及到的 userId
List<Long> targetUserIds = new ArrayList<>();
@@ -183,17 +208,28 @@ public class DataStorageAnalysisImpl implements DataStorageAnalysis {
targetUserIds = userIds;
}
Map<Long, String> userIdToNicknameMap = userNameCacheService.batchGetUserNames(new HashSet<>(targetUserIds));
// 3. 组装结果
List<JSONObject> result = new ArrayList<>();
for (Map.Entry<Long, String> entry : userIdToNicknameMap.entrySet()){
JSONObject jsonObject = new JSONObject();
jsonObject.put("userName", entry.getValue());
Long totalSize = userIdToTotalSizeMap.getOrDefault(entry.getKey(), 0L);
jsonObject.put("totalFileSize", FileSizeUtils.formatFileSizeToGB(new BigDecimal(totalSize)));
result.add(jsonObject);
if (ObjectUtils.isNotEmpty(intervalMonths)) {
Map<Long, Long> userIdToTotalSizeMap = totalFileSizeByCreator.stream().collect(Collectors.toMap(UserTotalFileSizeDTO::getUserId, UserTotalFileSizeDTO::getTotalSize));
for (Map.Entry<Long, String> entry : userIdToNicknameMap.entrySet()) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("userName", entry.getValue());
Long totalSize = userIdToTotalSizeMap.getOrDefault(entry.getKey(), 0L);
jsonObject.put("totalFileSize", FileSizeUtils.formatFileSizeToGB(new BigDecimal(totalSize)));
result.add(jsonObject);
}
} else if (ObjectUtils.isNotEmpty(targetYm)) {
Map<Long, Map<String, Long>> userSizeDTOMaps = totalFileSizeByCreator.stream().collect(Collectors.groupingBy(UserTotalFileSizeDTO::getUserId, Collectors.toMap(UserTotalFileSizeDTO::getStatDimension, UserTotalFileSizeDTO::getTotalSize)));
userSizeDTOMaps.forEach((userId, sizeMap) -> {
JSONObject jsonObject = new JSONObject();
jsonObject.put("userName", userIdToNicknameMap.getOrDefault(userId,"未知用户"));
jsonObject.put("BEFORE", FileSizeUtils.formatFileSizeToGB(new BigDecimal(sizeMap.getOrDefault("BEFORE", 0L))));
jsonObject.put("INCREMENT", FileSizeUtils.formatFileSizeToGB(new BigDecimal(sizeMap.getOrDefault("INCREMENT", 0L))));
result.add(jsonObject);
});
}
return SdmResponse.success(result);
@@ -293,12 +329,11 @@ public class DataStorageAnalysisImpl implements DataStorageAnalysis {
@Override
public SdmResponse<PageDataResp<List<FileStorage>>> listBigFile(QueryBigFileReq queryBigFileReq) {
List<FileStorage> list = getFileStorages(queryBigFileReq);
PageInfo<FileStorage> page = new PageInfo<>(list);
return PageUtils.getJsonObjectSdmResponse(list, page);
PageInfo<FileStorage> fileStorages = getFileStorages(queryBigFileReq);
return PageUtils.getJsonObjectSdmResponse(fileStorages.getList(), fileStorages);
}
private List<FileStorage> getFileStorages(QueryBigFileReq queryBigFileReq) {
private PageInfo<FileStorage> getFileStorages(QueryBigFileReq queryBigFileReq) {
// 将前端传入的 fileSize 和 fileSizeUnit 转换为字节(B)
Long fileSizeInBytes = null;
if (queryBigFileReq.getFileSize() != null && queryBigFileReq.getFileSizeUnit() != null) {
@@ -307,7 +342,7 @@ public class DataStorageAnalysisImpl implements DataStorageAnalysis {
Long tenantId = ThreadLocalContext.getTenantId();
PageHelper.startPage(queryBigFileReq.getCurrent(), queryBigFileReq.getSize());
List<FileStorage> list = fileStorageService.selectBigFiles(queryBigFileReq, fileSizeInBytes, tenantId);
return list;
return new PageInfo<>(list);
}
/**
@@ -348,7 +383,7 @@ public class DataStorageAnalysisImpl implements DataStorageAnalysis {
delFileReq.setDelFileId(fileId);
dataFileService.delFile(delFileReq);
}
return null;
return SdmResponse.success("删除成功");
}
@Override
@@ -492,16 +527,6 @@ public class DataStorageAnalysisImpl implements DataStorageAnalysis {
return SdmResponse.success("更新成功");
}
/*
* 根据条件查询文件存储表的文件id
* */
@Override
public List<Long> getListBigFileId(QueryBigFileReq queryBigFileReq) {
return getFileStorages(queryBigFileReq).stream()
.map(FileStorage::getFileId)
.collect(Collectors.toList());
}
@Override
public SdmResponse<List<FileStorageQuota>> listAllUserQuotaForJob() {
List<FileStorageQuota> quotaList = fileStorageQuotaService.list();

View File

@@ -601,24 +601,48 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
public SdmResponse fileSearch(FileSearchReq minioFileSearchReq) {
QueryBigFileReq queryBigFileReq = new QueryBigFileReq();
FileMetadataInfo fileMetadataInfo = null;
Long dirId;
Integer dirType;
if (ObjectUtils.isNotEmpty(minioFileSearchReq.getParentUuid())) {
// 项目节点下搜索文件
fileMetadataInfo = fileMetadataInfoService.lambdaQuery().eq(FileMetadataInfo::getRelatedResourceUuid, minioFileSearchReq.getParentUuid()).one();
FileMetadataInfo fileMetadataInfo = fileMetadataInfoService.lambdaQuery().eq(FileMetadataInfo::getRelatedResourceUuid, minioFileSearchReq.getParentUuid()).one();
dirId = fileMetadataInfo.getId();
dirType = fileMetadataInfo.getDirType();
} else if (ObjectUtils.isNotEmpty(minioFileSearchReq.getParentDirId())) {
// 知识库的文件查询
fileMetadataInfo = fileMetadataInfoService.getById(minioFileSearchReq.getParentDirId());
FileMetadataInfo fileMetadataInfo = fileMetadataInfoService.getById(minioFileSearchReq.getParentDirId());
dirId = fileMetadataInfo.getId();
dirType = fileMetadataInfo.getDirType();
} else if (ObjectUtils.isNotEmpty(minioFileSearchReq.getDirType())) {
dirType = minioFileSearchReq.getDirType();
DirTypeEnum dirTypeByValue = DirTypeEnum.getDirTypeByValue(dirType);
if (dirTypeByValue == null) {
return SdmResponse.failed("请选择正确的目录类型:1 知识库文件夹2 项目节点文件夹3 头像库文件夹4 仿真参数库文件夹,5 训练模型文件夹");
}
// 先检查根目录是否已存在
String rootDirMinioObjectKey = getDirMinioObjectKey(dirTypeByValue.getDirName());
Optional<FileMetadataInfo> fileMetadataInfoByObjectKey = getFileMetadataInfoByObjectKey(rootDirMinioObjectKey, null);
// 检查目录是否已存在
if (!fileMetadataInfoByObjectKey.isPresent()) {
return SdmResponse.failed("知识库、项目根目录不存在,等待initSystemDirectory 初始化完成");
}
// 获取根目录的 id
dirId = fileMetadataInfoByObjectKey.get().getId();
}else {
return SdmResponse.failed("请选择目录类型:1 知识库文件夹2 项目节点文件夹3 头像库文件夹4 仿真参数库文件夹,5 训练模型文件夹");
}
BeanUtils.copyProperties(minioFileSearchReq, queryBigFileReq);
if (ObjectUtils.isNotEmpty(fileMetadataInfo)) {
queryBigFileReq.setDirId(fileMetadataInfo.getId());
queryBigFileReq.setCurrent(minioFileSearchReq.getCurrent());
queryBigFileReq.setSize(minioFileSearchReq.getSize());
}
// 这里是知识库文件:排除新增在审批的文件
queryBigFileReq.setApproveTypeList(fileDatdList);
queryBigFileReq.setIsLatest(true);
queryBigFileReq.setCurrent(minioFileSearchReq.getCurrent());
queryBigFileReq.setSize(minioFileSearchReq.getSize());
queryBigFileReq.setDirId(dirId);
if (Objects.equals(DirTypeEnum.KNOWLEDGE_BASE_DIR.getValue(), dirType)) {
// 知识库文件:排除新增在审批的文件
queryBigFileReq.setApproveTypeList(fileDatdList);
}
List<Long> creatorIds = org.apache.commons.lang3.StringUtils.isBlank(minioFileSearchReq.getUploadUserId())
? new ArrayList<>()
@@ -628,7 +652,10 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
.collect(Collectors.toList());
queryBigFileReq.setUploadUserId(creatorIds);
List<Long> fileIdList =dataStorageAnalysis.getListBigFileId(queryBigFileReq);
SdmResponse<PageDataResp<List<FileStorage>>> searchResult = dataStorageAnalysis.listBigFile(queryBigFileReq);
List<Long> fileIdList =searchResult.getData().getData().stream().map(FileStorage::getFileId).collect(Collectors.toList());
if(CollectionUtils.isEmpty(fileIdList)){
return SdmResponse.success();
}
@@ -641,9 +668,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
setProjectName(files);
setAnalysisDirectionName(files);
setSimulationPoolAndTaskInfo(files);
PageInfo<FileMetadataInfo> page = new PageInfo<>(files);
long total = page.getTotal();
List<FileMetadataInfoResp> dtoList = files.stream().map(entity -> {
FileMetadataInfoResp dto = new FileMetadataInfoResp();
BeanUtils.copyProperties(entity, dto);
@@ -653,9 +678,12 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
dto.setPermissionValue(fileUserPermissionService.getMergedPermission(entity.getId(), ThreadLocalContext.getUserId()));
return dto;
}).collect(Collectors.toList());
PageInfo<FileMetadataInfoResp> page1 = new PageInfo<>(dtoList);
page1.setTotal(total);
return PageUtils.getJsonObjectSdmResponse(dtoList, page1);
PageDataResp<List<FileStorage>> pageDataResp = searchResult.getData();
PageInfo page = new PageInfo();
page.setPageNum(pageDataResp.getCurrentPage());
page.setPageSize(pageDataResp.getPageSize());
page.setTotal(pageDataResp.getTotal());
return PageUtils.getJsonObjectSdmResponse(dtoList, page);
}
@Override
@@ -887,6 +915,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
String bucketName = minioService.getCurrentTenantBucketName(); // 这里会触发 MinioService 的懒加载创建桶
for (DirTypeEnum dirType : DirTypeEnum.getInitSpmdDir()) {
log.info("租户:{},开始初始化目录: {}",tenantId, dirType.getDirName());
// 目录路径,如 "knowledge/"
String dirPath = getDirMinioObjectKey(dirType.getDirName());
@@ -897,6 +926,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
.exists();
if (exists) {
log.info("目录已存在: {},跳过", dirType.getDirName());
continue; // 已初始化过,跳过
}

View File

@@ -54,7 +54,7 @@
<!-- 1. 统计:目标年月之前的历史累计存储(不包含目标年月) -->
SELECT
dirId,
BEFORE AS statDimension, -- 示例BEFORE_202410
'BEFORE' AS statDimension, -- 示例BEFORE_202410
SUM(fileSize) AS totalSize -- 总占用字节数(原始单位)
FROM file_storage
WHERE
@@ -72,7 +72,7 @@
<!-- 2. 统计:目标年月的当月增量存储(仅包含目标年月) -->
SELECT
dirId,
INCREMENT AS statDimension, -- 示例INCREMENT_202410
'INCREMENT' AS statDimension, -- 示例INCREMENT_202410
SUM(fileSize) AS totalSize
FROM file_storage
WHERE
@@ -135,30 +135,31 @@
SELECT
t.userId,
s.statDimension,
SUM(s.totalSize) as totalSize
s.totalSize
FROM TargetUsers t
INNER JOIN (
-- 历史累计
SELECT userId, 'BEFORE' as statDimension, fileSize
-- 1. 历史累计 (内部先 SUM)
SELECT userId, 'BEFORE' as statDimension, SUM(fileSize) as totalSize
FROM file_storage
WHERE tenantId = #{tenantId} AND createYearMonth &lt; #{targetYm}
GROUP BY userId
UNION ALL
-- 当月增量
SELECT userId, 'INCREMENT' as statDimension, fileSize
-- 2. 当月增量 (内部先 SUM)
SELECT userId, 'INCREMENT' as statDimension, SUM(fileSize) as totalSize
FROM file_storage
WHERE tenantId = #{tenantId} AND createYearMonth = #{targetYm}
GROUP BY userId
) s ON t.userId = s.userId
GROUP BY t.userId, s.statDimension
</select>
<select id="selectBigFiles" resultType="com.sdm.data.model.entity.FileStorage">
SELECT
distinct
file_storage.fileName,file_storage.fileId,file_storage.userGroupId,file_storage.userId,file_storage.fileBizType,file_storage.fileSuffix,file_storage.updateTime
FROM file_storage
left join file_metadata_info on file_storage.fileId = file_metadata_info.id
FROM file_metadata_info
inner join file_storage on file_storage.fileId = file_metadata_info.id
<where>
file_metadata_info.id is not null
and file_metadata_info.isLatest = #{queryBigFileReq.isLatest}