fix:批量同步节点下的普通文件夹,保存uuid

This commit is contained in:
2026-01-26 10:05:31 +08:00
parent 4fa8cd3f1e
commit 59650e5151
3 changed files with 211 additions and 97 deletions

View File

@@ -18,7 +18,7 @@ public class BatchCreateNormalDirReq {
@Schema(description = "父节点对应的文件夹ID", requiredMode = Schema.RequiredMode.REQUIRED)
private Long parentId;
@NotEmpty(message = "文件夹名称列表不能为空")
@Schema(description = "待创建的文件夹名称列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<String> folderNames;
@NotEmpty(message = "文件夹列表不能为空")
@Schema(description = "待创建的文件夹列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<FolderItemReq> folderItems;
}

View File

@@ -0,0 +1,24 @@
package com.sdm.common.entity.req.data;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 文件夹项请求对象
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "文件夹项请求对象")
public class FolderItemReq {
@NotBlank(message = "文件夹名称不能为空")
@Schema(description = "文件夹名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String folderName;
@Schema(description = "文件夹UUID可选用于关联业务资源")
private String folderUuid;
}

View File

@@ -3827,118 +3827,208 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
@Override
@Transactional(rollbackFor = Exception.class)
public SdmResponse<BatchCreateNormalDirResp> batchCreateNormalDirs(BatchCreateNormalDirReq req) {
// 1. 基础校验
if (req.getParentUUId() == null) {
return SdmResponse.failed("父文件夹ID不能为空");
log.info("开始执行批量创建普通文件夹父目UUID: {}, folderItems数量: {}",
req.getParentUUId(), req.getFolderItems() == null ? 0 : req.getFolderItems().size());
long startTime = System.currentTimeMillis();
// 1. 参数校验
SdmResponse<Void> validateResult = validateBatchCreateNormalDirRequest(req);
if (!validateResult.isSuccess()) {
log.error("批量创建普通文件夹参数校验失败: {}", validateResult.getMessage());
return SdmResponse.failed(validateResult.getMessage());
}
if (CollectionUtils.isEmpty(req.getFolderNames())) {
return SdmResponse.failed("文件夹名称列表不能为空");
}
// 2. 父目录校验与权限检查
FileMetadataInfo parentDir = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getRelatedResourceUuid, req.getParentUUId())
.one();
if (parentDir == null) {
return SdmResponse.failed("父文件夹不存在");
SdmResponse<FileMetadataInfo> parentDirResult = validateParentDirAndPermission(req.getParentUUId());
if (!parentDirResult.isSuccess()) {
log.error("父目录校验失败: {}", parentDirResult.getMessage());
return SdmResponse.failed(parentDirResult.getMessage());
}
// 权限检查(需要写入权限)
boolean hasWritePermission = fileUserPermissionService.hasFilePermission(parentDir.getId(), ThreadLocalContext.getUserId(), FilePermissionEnum.WRITE);
if (!hasWritePermission) {
return SdmResponse.failed("没有写入权限");
}
FileMetadataInfo parentDir = parentDirResult.getData();
BatchCreateNormalDirResp resp = new BatchCreateNormalDirResp();
// 去重并过滤空名
List<String> validFolderNames = req.getFolderNames().stream()
.filter(name -> name != null && !name.trim().isEmpty())
.distinct()
.collect(Collectors.toList());
if (validFolderNames.isEmpty()) {
return SdmResponse.failed("有效文件夹名称为空");
// 3. 过滤并验证文件夹项
List<FolderItemReq> validFolderItems = filterAndValidateFolderItems(req.getFolderItems());
if (validFolderItems.isEmpty()) {
log.error("有效文件夹项为空");
return SdmResponse.failed("有效文件夹项为空");
}
// 3. 检查数据库中是否已存在同名目录
List<FileMetadataInfo> existingFiles = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getParentId, parentDir.getId())
.eq(FileMetadataInfo::getTenantId, ThreadLocalContext.getTenantId())
.eq(FileMetadataInfo::getDataType, DataTypeEnum.DIRECTORY.getValue())
.in(FileMetadataInfo::getOriginalName, validFolderNames)
.list();
Set<String> existingNames = existingFiles.stream()
.map(FileMetadataInfo::getOriginalName)
.collect(Collectors.toSet());
List<String> toCreateNames = new ArrayList<>();
for (String name : validFolderNames) {
if (existingNames.contains(name)) {
resp.addFailure(name, "文件夹已存在");
} else {
toCreateNames.add(name);
}
}
if (toCreateNames.isEmpty()) {
// 4. 检查已存在的文件夹,分离出需要创建的项
List<FolderItemReq> toCreateItems = checkExistingFoldersAndFilter(
validFolderItems, parentDir, resp);
if (toCreateItems.isEmpty()) {
log.info("所有文件夹均已存在,无需创建");
return SdmResponse.success(resp);
}
// 4. 准备创建数据
// 5. 准备创建数据(元数据对象 + MinIO Key
List<FileMetadataInfo> newDirs = new ArrayList<>();
List<String> minioKeys = new ArrayList<>();
String parentObjectKey = parentDir.getObjectKey();
// 确保parentObjectKey以/结尾
if (!parentObjectKey.endsWith("/")) {
parentObjectKey += "/";
}
for (String name : toCreateNames) {
// 构造对象Key
String objectKey = getDirMinioObjectKey(parentObjectKey + name);
// 创建元数据对象 (复用现有的createDirectoryMetadata方法)
FileMetadataInfo dirInfo = createDirectoryMetadata(
objectKey, name, false, parentDir.getId(),
null, null, parentDir.getDirType());
newDirs.add(dirInfo);
minioKeys.add(objectKey);
}
prepareBatchCreateData(toCreateItems, parentDir, newDirs, minioKeys);
try {
// 5. MinIO批量创建
// 6. 批量MinIO创建
minioService.batchCreateDirectories(minioKeys, minioService.getCurrentTenantBucketName());
// 6. 数据库批量插入
fileMetadataInfoService.saveBatch(newDirs);
// 7. 批量添加权限
List<FileUserPermission> permissions = newDirs.stream().map(dir -> {
FileUserPermission p = new FileUserPermission();
p.setTFilemetaId(dir.getId());
p.setPermission(FilePermissionEnum.ALL.getValue());
p.setUserId(ThreadLocalContext.getUserId());
p.setTenantId(ThreadLocalContext.getTenantId());
return p;
}).collect(Collectors.toList());
fileUserPermissionService.saveBatch(permissions, 500);
// 填充成功列表
for (FileMetadataInfo dir : newDirs) {
resp.addSuccess(dir.getId(), dir.getOriginalName());
}
log.info("批量MinIO创建完成数量: {}", minioKeys.size());
// 7. 数据库批量插入
fileMetadataInfoService.saveBatch(newDirs, 500);
log.info("数据库批量插入完成,数量: {}", newDirs.size());
// 8. 批量添加权限
createBatchPermissions(newDirs, ThreadLocalContext.getTenantId());
// 9. 填充成功列表
fillSuccessResponse(newDirs, resp);
long duration = System.currentTimeMillis() - startTime;
log.info("批量创建普通文件夹成功,耗时: {}ms, 共创建{}个目录", duration, newDirs.size());
} catch (Exception e) {
log.error("批量创建普通文件夹失败", e);
// 补偿删除MinIO目录
compensateDeleteMinioKeys(minioKeys);
throw new RuntimeException("批量创建失败: " + e.getMessage(), e);
}
return SdmResponse.success(resp);
}
/**
* 校验批量创建普通目录请求参数
*/
private SdmResponse<Void> validateBatchCreateNormalDirRequest(BatchCreateNormalDirReq req) {
if (req == null) {
return SdmResponse.failed("请求参数不能为空");
}
if (req.getParentUUId() == null) {
return SdmResponse.failed("父文件夹UUID不能为空");
}
if (CollectionUtils.isEmpty(req.getFolderItems())) {
return SdmResponse.failed("文件夹项列表不能为空");
}
return SdmResponse.success(null);
}
/**
* 验证父目录并检查权限
*/
private SdmResponse<FileMetadataInfo> validateParentDirAndPermission(String parentUuid) {
FileMetadataInfo parentDir = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getRelatedResourceUuid, parentUuid)
.one();
if (parentDir == null) {
return SdmResponse.failed("父文件夹不存在");
}
// 权限检查(需要写入权限)
boolean hasWritePermission = fileUserPermissionService.hasFilePermission(
parentDir.getId(), ThreadLocalContext.getUserId(), FilePermissionEnum.WRITE);
if (!hasWritePermission) {
return SdmResponse.failed("没有写入权限");
}
return SdmResponse.success(parentDir);
}
/**
* 过滤并验证文件夹项:去重、过滤空名
*/
private List<FolderItemReq> filterAndValidateFolderItems(List<FolderItemReq> folderItems) {
// 使用 folderName 作为唯一标识去重
Map<String, FolderItemReq> uniqueMap = new LinkedHashMap<>();
for (FolderItemReq item : folderItems) {
if (item != null && item.getFolderName() != null && !item.getFolderName().trim().isEmpty()) {
// 后面的同名项会覆盖前面的,保留最后一个
uniqueMap.put(item.getFolderName().trim(), item);
}
}
return new ArrayList<>(uniqueMap.values());
}
/**
* 检查已存在的文件夹,过滤出需要创建的项
*/
private List<FolderItemReq> checkExistingFoldersAndFilter(
List<FolderItemReq> validFolderItems,
FileMetadataInfo parentDir,
BatchCreateNormalDirResp resp) {
// 提取所有文件夹名称
List<String> folderNames = validFolderItems.stream()
.map(FolderItemReq::getFolderName)
.collect(Collectors.toList());
// 查询数据库中是否已存在同名目录
List<FileMetadataInfo> existingFiles = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getParentId, parentDir.getId())
.eq(FileMetadataInfo::getTenantId, ThreadLocalContext.getTenantId())
.eq(FileMetadataInfo::getDataType, DataTypeEnum.DIRECTORY.getValue())
.in(FileMetadataInfo::getOriginalName, folderNames)
.list();
// 构建已存在的文件夹名称集合
Set<String> existingNames = existingFiles.stream()
.map(FileMetadataInfo::getOriginalName)
.collect(Collectors.toSet());
// 过滤出需要创建的项
List<FolderItemReq> toCreateItems = new ArrayList<>();
for (FolderItemReq item : validFolderItems) {
if (existingNames.contains(item.getFolderName())) {
resp.addFailure(item.getFolderName(), "文件夹已存在");
} else {
toCreateItems.add(item);
}
}
return toCreateItems;
}
/**
* 准备批量创建数据构建元数据对象和MinIO Key
*/
private void prepareBatchCreateData(
List<FolderItemReq> toCreateItems,
FileMetadataInfo parentDir,
List<FileMetadataInfo> newDirs,
List<String> minioKeys) {
String parentObjectKey = parentDir.getObjectKey();
// 确保 parentObjectKey 以 / 结尾
if (!parentObjectKey.endsWith("/")) {
parentObjectKey += "/";
}
for (FolderItemReq item : toCreateItems) {
// 构造对象Key
String objectKey = getDirMinioObjectKey(parentObjectKey + item.getFolderName());
// 创建元数据对象(复用现有的 createDirectoryMetadata 方法)
// 传入 folderUuid 作为 relatedResourceUuid
FileMetadataInfo dirInfo = createDirectoryMetadata(
objectKey,
item.getFolderName(),
false,
parentDir.getId(),
item.getFolderUuid(), // 保存 UUID
null,
parentDir.getDirType());
newDirs.add(dirInfo);
minioKeys.add(objectKey);
}
}
/**
* 填充成功响应结果
*/
private void fillSuccessResponse(List<FileMetadataInfo> newDirs, BatchCreateNormalDirResp resp) {
for (FileMetadataInfo dir : newDirs) {
resp.addSuccess(dir.getId(), dir.getOriginalName());
}
}
}