fix:修正重命名文件和文件夹bug

This commit is contained in:
2026-01-21 16:56:25 +08:00
parent 5a30c79f02
commit 89077fedf6
3 changed files with 204 additions and 26 deletions

View File

@@ -78,6 +78,15 @@ public interface IMinioService {
void copyFile(String oldObjectName, String newObjectName, String bucketName);
/**
* 递归重命名目录(将目录下所有对象从旧前缀迁移到新前缀)
*
* @param oldDirPath 旧目录路径objectKey前缀
* @param newDirPath 新目录路径objectKey前缀
* @param bucketName 桶名称
*/
void renameDirectoryRecursively(String oldDirPath, String newDirPath, String bucketName);
/**
* 从MinIO下载文件
* @param objectName 文件名objectKey

View File

@@ -835,29 +835,55 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
Long fileId = req.getFileId();
try {
FileMetadataInfo oldFile = fileMetadataInfoService.lambdaQuery().eq(FileMetadataInfo::getId, fileId).one();
bucketName = oldFile.getBucketName();
if (ObjectUtils.isEmpty(oldFile)) {
return SdmResponse.failed("文件不存在");
}
/*boolean hasDeletePermission = fileUserPermissionService.hasFilePermission(fileId, ThreadLocalContext.getUserId(), FilePermissionEnum.WRITE);
if (!hasDeletePermission){
return SdmResponse.failed("没有修改权限");
}*/
bucketName = oldFile.getBucketName();
String newName = req.getNewName();
// 1. 校验名称是否发生变化,如果未变化则直接返回成功,避免 MinIO 原地拷贝报错
if (Objects.equals(newName, oldFile.getOriginalName())) {
return SdmResponse.success("名称未改变");
}
// 2. 校验同级目录下是否存在同名文件,防止覆盖或数据库记录冲突
boolean exists = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getParentId, oldFile.getParentId())
.eq(FileMetadataInfo::getOriginalName, newName)
.eq(FileMetadataInfo::getTenantId, ThreadLocalContext.getTenantId())
.exists();
if (exists) {
return SdmResponse.failed("当前目录下已存在同名文件");
}
oldObjectKey = oldFile.getObjectKey();
newObjectKey = oldObjectKey.substring(0, oldObjectKey.lastIndexOf("/") + 1) + newName;
minioService.renameFile(oldObjectKey, newObjectKey,bucketName);
minioService.renameFile(oldObjectKey, newObjectKey, bucketName);
fileMetadataInfoService.lambdaUpdate()
.set(FileMetadataInfo::getObjectKey, newObjectKey)
.set(FileMetadataInfo::getOriginalName, newName)
.set(FileMetadataInfo::getUpdateTime, new Date())
.set(FileMetadataInfo::getUpdateTime, LocalDateTime.now())
.set(FileMetadataInfo::getUpdaterId, ThreadLocalContext.getUserId())
.eq(FileMetadataInfo::getId, fileId).update();
// 更新关联表中的名称或路径
fileStorageService.lambdaUpdate()
.set(FileStorage::getFileName, newName)
.eq(FileStorage::getFileId, fileId)
.update();
return SdmResponse.success("重命名成功");
} catch (Exception e) {
log.error("重命名文件失败", e);
minioService.renameFile(newObjectKey, oldObjectKey,bucketName);
// 只有当 MinIO 操作成功但后续数据库更新失败时,才尝试回滚 MinIO 路径
if (oldObjectKey != null && newObjectKey != null) {
try {
minioService.renameFile(newObjectKey, oldObjectKey, bucketName);
} catch (Exception re) {
log.error("重命名失败回滚 MinIO 路径失败", re);
}
}
throw new RuntimeException("重命名文件失败: " + e.getMessage(), e);
}
}
@@ -992,43 +1018,120 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public SdmResponse renameDirNew(RenameDirReq req) {
FileMetadataInfo dirMetadataInfo = null;
if (ObjectUtils.isNotEmpty(req.getUuid()) ) {
dirMetadataInfo = fileMetadataInfoService.lambdaQuery().eq(FileMetadataInfo::getRelatedResourceUuid, req.getUuid()).one();
} else {
dirMetadataInfo = fileMetadataInfoService.lambdaQuery().eq(FileMetadataInfo::getId, req.getDirId()).one();
}
// 1. 获取目录元数据
FileMetadataInfo dirMetadataInfo = queryDirMetadataInfo(req);
if (ObjectUtils.isEmpty(dirMetadataInfo)) {
return SdmResponse.failed("文件夹不存在");
}
String oldDirMinioObjectKey = dirMetadataInfo.getObjectKey();
boolean hasWritePermission = fileUserPermissionService.hasFilePermission(dirMetadataInfo.getId(), ThreadLocalContext.getUserId(), FilePermissionEnum.WRITE);
// 2. 权限校验
boolean hasWritePermission = fileUserPermissionService.hasFilePermission(
dirMetadataInfo.getId(), ThreadLocalContext.getUserId(), FilePermissionEnum.WRITE);
if (!hasWritePermission) {
return SdmResponse.failed("没有写入权限");
}
// 修正路径处理逻辑
String newDirMinioObjectKey = getDirMinioObjectKey(oldDirMinioObjectKey.substring(0, oldDirMinioObjectKey.lastIndexOf(dirMetadataInfo.getOriginalName())) + req.getNewName());
String newName = req.getNewName();
String oldName = dirMetadataInfo.getOriginalName();
// 3. 名称变化校验:若名称未改变,直接返回,避免 MinIO 原地拷贝报错
if (Objects.equals(newName, oldName)) {
return SdmResponse.success("名称未改变");
}
// 4. 同名冲突校验:检查同级目录下是否存在同名文件夹
boolean exists = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getParentId, dirMetadataInfo.getParentId())
.eq(FileMetadataInfo::getOriginalName, newName)
.eq(FileMetadataInfo::getTenantId, ThreadLocalContext.getTenantId())
.exists();
if (exists) {
return SdmResponse.failed("当前目录下已存在同名文件夹");
}
String oldDirMinioObjectKey = dirMetadataInfo.getObjectKey();
String bucketName = dirMetadataInfo.getBucketName();
// 计算新的目录 objectKey
String parentPath = oldDirMinioObjectKey.substring(0, oldDirMinioObjectKey.lastIndexOf(oldName));
String newDirMinioObjectKey = getDirMinioObjectKey(parentPath + newName);
try {
minioService.renameFile(oldDirMinioObjectKey, newDirMinioObjectKey,dirMetadataInfo.getBucketName());
// 5. MinIO 递归重命名:将目录下所有对象从旧前缀迁移到新前缀
minioService.renameDirectoryRecursively(oldDirMinioObjectKey, newDirMinioObjectKey, bucketName);
// 6. 数据库递归更新:更新所有子文件/子目录的 objectKey
updateChildrenObjectKeys(oldDirMinioObjectKey, newDirMinioObjectKey, bucketName);
// 7. 更新当前目录记录
fileMetadataInfoService.lambdaUpdate()
.set(FileMetadataInfo::getObjectKey, newDirMinioObjectKey)
.set(FileMetadataInfo::getOriginalName, req.getNewName())
.set(FileMetadataInfo::getUpdateTime, new Date())
.set(FileMetadataInfo::getOriginalName, newName)
.set(FileMetadataInfo::getUpdateTime, LocalDateTime.now())
.set(FileMetadataInfo::getUpdaterId, ThreadLocalContext.getUserId())
.eq(FileMetadataInfo::getId, dirMetadataInfo.getId()).update();
.eq(FileMetadataInfo::getId, dirMetadataInfo.getId())
.update();
// 8. 更新 file_storage 表中当前目录的 fileName
fileStorageService.lambdaUpdate()
.set(FileStorage::getFileName, newName)
.eq(FileStorage::getDirId, dirMetadataInfo.getId())
.update();
return SdmResponse.success("重命名目录成功");
} catch (Exception e) {
log.error("重命名目录失败", e);
minioService.renameFile(newDirMinioObjectKey, oldDirMinioObjectKey,dirMetadataInfo.getBucketName());
log.error("重命名目录失败, oldKey={}, newKey={}", oldDirMinioObjectKey, newDirMinioObjectKey, e);
// 尝试回滚 MinIO若部分成功部分失败回滚可能不完整需人工介入
try {
minioService.renameDirectoryRecursively(newDirMinioObjectKey, oldDirMinioObjectKey, bucketName);
} catch (Exception re) {
log.error("重命名失败后回滚 MinIO 路径失败", re);
}
throw new RuntimeException("重命名目录失败: " + e.getMessage(), e);
}
}
/**
* 查询目录元数据信息
*/
private FileMetadataInfo queryDirMetadataInfo(RenameDirReq req) {
if (ObjectUtils.isNotEmpty(req.getUuid())) {
return fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getRelatedResourceUuid, req.getUuid())
.one();
}
return fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getId, req.getDirId())
.one();
}
/**
* 递归更新数据库中所有子项的 objectKey
*
* @param oldPrefix 旧的 objectKey 前缀
* @param newPrefix 新的 objectKey 前缀
* @param bucketName 桶名称
*/
private void updateChildrenObjectKeys(String oldPrefix, String newPrefix, String bucketName) {
// 使用 SQL 的 REPLACE 函数批量更新所有以 oldPrefix 开头的 objectKey
fileMetadataInfoService.lambdaQuery()
.likeRight(FileMetadataInfo::getObjectKey, oldPrefix)
.eq(FileMetadataInfo::getBucketName, bucketName)
.eq(FileMetadataInfo::getTenantId, ThreadLocalContext.getTenantId())
.list()
.forEach(child -> {
String oldKey = child.getObjectKey();
String newKey = newPrefix + oldKey.substring(oldPrefix.length());
fileMetadataInfoService.lambdaUpdate()
.set(FileMetadataInfo::getObjectKey, newKey)
.set(FileMetadataInfo::getUpdateTime, LocalDateTime.now())
.set(FileMetadataInfo::getUpdaterId, ThreadLocalContext.getUserId())
.eq(FileMetadataInfo::getId, child.getId())
.update();
});
}
@Override
public SdmResponse copyFileToTask(CopyFileToTaskReq req) {
// 源文件

View File

@@ -416,6 +416,72 @@ public class MinioService implements IMinioService {
}
}
/**
* 递归重命名目录(将目录下所有对象从旧前缀迁移到新前缀)
*
* @param oldDirPath 旧目录路径objectKey前缀
* @param newDirPath 新目录路径objectKey前缀
* @param bucketName 桶名称
*/
@Override
public void renameDirectoryRecursively(String oldDirPath, String newDirPath, String bucketName) {
try {
bucketName = getBucketName(bucketName);
// 确保目录路径以 "/" 结尾
oldDirPath = dealDirPath(oldDirPath);
newDirPath = dealDirPath(newDirPath);
// 列出旧目录下的所有对象
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder()
.bucket(bucketName)
.prefix(oldDirPath)
.recursive(true)
.build());
List<String> oldObjectKeys = new ArrayList<>();
for (Result<Item> result : results) {
Item item = result.get();
oldObjectKeys.add(item.objectName());
}
// 逐个拷贝到新路径
for (String oldKey : oldObjectKeys) {
String newKey = newDirPath + oldKey.substring(oldDirPath.length());
minioClient.copyObject(
CopyObjectArgs.builder()
.bucket(bucketName)
.object(newKey)
.source(CopySource.builder()
.bucket(bucketName)
.object(oldKey)
.build())
.build());
}
// 批量删除旧对象
if (!oldObjectKeys.isEmpty()) {
List<DeleteObject> deleteObjects = oldObjectKeys.stream()
.map(DeleteObject::new)
.collect(Collectors.toList());
Iterable<Result<DeleteError>> deleteErrors = minioClient.removeObjects(
RemoveObjectsArgs.builder()
.bucket(bucketName)
.objects(deleteObjects)
.build());
for (Result<DeleteError> deleteErrorResult : deleteErrors) {
DeleteError deleteError = deleteErrorResult.get();
log.error("删除旧对象失败: {}, 错误: {}", deleteError.objectName(), deleteError.message());
}
}
} catch (Exception e) {
log.error("递归重命名目录失败: 从 {} 到 {}", oldDirPath, newDirPath, e);
throw new RuntimeException("递归重命名目录失败: " + e.getMessage(), e);
}
}
/**
* 从MinIO下载文件