fix:修正重命名文件和文件夹bug
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
// 源文件
|
||||
|
||||
@@ -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下载文件
|
||||
|
||||
Reference in New Issue
Block a user