云文件管理系统上传组件优化

This commit is contained in:
2026-04-02 12:06:51 +08:00
parent 61a675b4de
commit 3710e98eb3
34 changed files with 1102 additions and 306 deletions

View File

@@ -307,4 +307,52 @@ public class FileController {
List<FileEntity> folders = fileService.getMovableFolders(principal.getUserId(), excludeIds, currentFolderId);
return ResponseEntity.ok(Map.of("data", folders));
}
@PostMapping("/batchCancelShare")
public ResponseEntity<?> batchCancelShare(
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, Object> request) {
Object idsObj = request.get("ids");
if (idsObj == null) {
return ResponseEntity.badRequest().body(Map.of("message", "请选择要取消共享的文件"));
}
List<Long> ids = new ArrayList<>();
if (idsObj instanceof List) {
for (Object id : (List<?>) idsObj) {
if (id instanceof Number) {
ids.add(((Number) id).longValue());
} else if (id instanceof String) {
ids.add(Long.parseLong((String) id));
}
}
}
for (Long fileId : ids) {
fileService.cancelShare(fileId, principal.getUserId());
}
return ResponseEntity.ok(Map.of("message", "批量取消共享成功"));
}
@PostMapping("/batchRestore")
public ResponseEntity<?> batchRestore(
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, Object> request) {
Object idsObj = request.get("ids");
if (idsObj == null) {
return ResponseEntity.badRequest().body(Map.of("message", "请选择要还原的文件"));
}
List<Long> ids = new ArrayList<>();
if (idsObj instanceof List) {
for (Object id : (List<?>) idsObj) {
if (id instanceof Number) {
ids.add(((Number) id).longValue());
} else if (id instanceof String) {
ids.add(Long.parseLong((String) id));
}
}
}
for (Long fileId : ids) {
fileService.restoreFile(fileId, principal.getUserId());
}
return ResponseEntity.ok(Map.of("message", "批量还原成功"));
}
}

View File

@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName(value = "sys_file", autoResultMap = true)
@@ -31,4 +33,10 @@ public class FileEntity extends BaseEntity {
@TableField("deleted_at")
private String deletedAt;
/**
* 子文件夹列表(非数据库字段,用于树形结构)
*/
@TableField(exist = false)
private List<FileEntity> children;
}

View File

@@ -67,18 +67,8 @@ public class FileService {
public List<FileEntity> getTrashFiles(Long userId, Long folderId) {
LambdaQueryWrapper<FileEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(FileEntity::getUserId, userId)
.eq(FileEntity::getIsDeleted, 1);
if (folderId != null) {
// 查询指定文件夹下的已删除文件
wrapper.eq(FileEntity::getFolderId, folderId);
} else {
// 查询根目录下已删除的文件和文件夹
wrapper.isNull(FileEntity::getFolderId)
.or(w -> w.eq(FileEntity::getFolderId, 0));
}
wrapper.orderByDesc(FileEntity::getDeletedAt);
.eq(FileEntity::getIsDeleted, 1)
.orderByDesc(FileEntity::getDeletedAt);
return fileMapper.selectList(wrapper);
}
@@ -90,6 +80,19 @@ public class FileService {
public void moveToTrash(Long id, Long userId) {
FileEntity file = fileMapper.selectById(id);
if (file != null && file.getUserId().equals(userId)) {
// 如果是文件夹,检查是否有子文件
if (file.getIsFolder() != null && file.getIsFolder() == 1) {
Long childCount = fileMapper.selectCount(
new LambdaQueryWrapper<FileEntity>()
.eq(FileEntity::getFolderId, id)
.eq(FileEntity::getIsDeleted, 0)
.eq(FileEntity::getUserId, userId)
);
if (childCount != null && childCount > 0) {
throw new RuntimeException("请删除该目录下文件后重试");
}
}
// 使用 LambdaUpdateWrapper 明确指定要更新的字段
LambdaUpdateWrapper<FileEntity> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FileEntity::getId, id)
@@ -509,18 +512,30 @@ public class FileService {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(baos);
// 用于跟踪已使用的文件名,避免重复
java.util.Set<String> usedNames = new java.util.HashSet<>();
for (Long fileId : fileIds) {
FileEntity file = fileMapper.selectById(fileId);
if (file == null || !file.getUserId().equals(userId) || file.getIsDeleted() == 1) {
if (file == null || file.getIsDeleted() == 1) {
continue;
}
// 检查权限:文件所有者 或 文件被共享给当前用户(包括文件夹内的子文件)
boolean hasPermission = file.getUserId().equals(userId) || canAccessFile(fileId, userId);
if (!hasPermission) {
continue;
}
// 确定文件夹内容的查询用户ID如果是共享文件用文件所有者的ID查询子文件
Long ownerUserId = file.getUserId();
if (file.getIsFolder() == 1) {
// 递归添加文件夹内容
addFolderToZip(zos, file, "", userId);
addFolderToZip(zos, file, "", ownerUserId, usedNames);
} else {
// 添加单个文件
addFileToZip(zos, file, "");
addFileToZip(zos, file, "", usedNames);
}
}
@@ -528,7 +543,44 @@ public class FileService {
return baos.toByteArray();
}
private void addFolderToZip(java.util.zip.ZipOutputStream zos, FileEntity folder, String parentPath, Long userId) throws IOException {
/**
* 检查用户是否有权限访问文件(包括共享文件夹内的子文件)
*/
private boolean canAccessFile(Long fileId, Long userId) {
FileEntity file = fileMapper.selectById(fileId);
if (file == null) return false;
// 直接共享给用户
if (isFileSharedToUser(fileId, userId)) {
return true;
}
// 检查父文件夹是否被共享
Long parentId = file.getFolderId();
while (parentId != null) {
if (isFileSharedToUser(parentId, userId)) {
return true;
}
FileEntity parent = fileMapper.selectById(parentId);
parentId = parent != null ? parent.getFolderId() : null;
}
return false;
}
/**
* 检查文件是否被共享给指定用户
*/
private boolean isFileSharedToUser(Long fileId, Long userId) {
Long count = fileShareMapper.selectCount(
new LambdaQueryWrapper<FileShare>()
.eq(FileShare::getFileId, fileId)
.eq(FileShare::getShareToUserId, userId)
);
return count != null && count > 0;
}
private void addFolderToZip(java.util.zip.ZipOutputStream zos, FileEntity folder, String parentPath, Long userId, java.util.Set<String> usedNames) throws IOException {
String folderPath = parentPath + folder.getName() + "/";
// 获取文件夹下的所有内容
@@ -541,21 +593,41 @@ public class FileService {
for (FileEntity child : children) {
if (child.getIsFolder() == 1) {
addFolderToZip(zos, child, folderPath, userId);
addFolderToZip(zos, child, folderPath, userId, usedNames);
} else {
addFileToZip(zos, child, folderPath);
addFileToZip(zos, child, folderPath, usedNames);
}
}
}
private void addFileToZip(java.util.zip.ZipOutputStream zos, FileEntity file, String parentPath) throws IOException {
private void addFileToZip(java.util.zip.ZipOutputStream zos, FileEntity file, String parentPath, java.util.Set<String> usedNames) throws IOException {
if (file.getPath() == null) return;
Path filePath = Paths.get(storagePath).toAbsolutePath().resolve("files").resolve(file.getPath());
if (!Files.exists(filePath)) return;
// 生成唯一的 ZIP 条目名称
String zipEntryName = parentPath + file.getName();
java.util.zip.ZipEntry zipEntry = new java.util.zip.ZipEntry(zipEntryName);
String finalName = zipEntryName;
int counter = 1;
// 如果名称已存在,添加序号
while (usedNames.contains(finalName)) {
// 处理文件名和扩展名
String baseName = zipEntryName;
String extension = "";
int dotIndex = zipEntryName.lastIndexOf(".");
if (dotIndex > 0 && dotIndex > parentPath.length()) {
baseName = zipEntryName.substring(0, dotIndex);
extension = zipEntryName.substring(dotIndex);
}
finalName = baseName + " (" + counter + ")" + extension;
counter++;
}
usedNames.add(finalName);
java.util.zip.ZipEntry zipEntry = new java.util.zip.ZipEntry(finalName);
zos.putNextEntry(zipEntry);
byte[] fileBytes = Files.readAllBytes(filePath);
@@ -569,17 +641,35 @@ public class FileService {
.eq(FileEntity::getIsDeleted, 0)
.eq(FileEntity::getIsFolder, 1);
// 只查询当前目录下的文件夹(同级)
if (currentFolderId == null) {
wrapper.isNull(FileEntity::getFolderId);
} else {
wrapper.eq(FileEntity::getFolderId, currentFolderId);
}
if (excludeIds != null && !excludeIds.isEmpty()) {
wrapper.notIn(FileEntity::getId, excludeIds);
}
return fileMapper.selectList(wrapper);
// 查询用户所有文件夹
List<FileEntity> allFolders = fileMapper.selectList(wrapper);
// 构建树形结构
return buildFolderTree(allFolders, null, excludeIds);
}
/**
* 构建文件夹树形结构
*/
private List<FileEntity> buildFolderTree(List<FileEntity> allFolders, Long parentId, List<Long> excludeIds) {
List<FileEntity> tree = new ArrayList<>();
for (FileEntity folder : allFolders) {
// 匹配父级关系
boolean isChildOfParent = (parentId == null && folder.getFolderId() == null)
|| (parentId != null && parentId.equals(folder.getFolderId()));
if (isChildOfParent) {
// 递归构建子文件夹
List<FileEntity> children = buildFolderTree(allFolders, folder.getId(), excludeIds);
// 设置子文件夹列表(通过 transient 字段)
folder.setChildren(children.isEmpty() ? null : children);
tree.add(folder);
}
}
return tree;
}
}