云文件系统初始化

This commit is contained in:
2026-04-01 22:39:11 +08:00
commit 3a20f6e7ed
74 changed files with 8693 additions and 0 deletions

View File

@@ -0,0 +1,502 @@
package com.filesystem.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.filesystem.entity.FileEntity;
import com.filesystem.entity.FileShare;
import com.filesystem.mapper.FileMapper;
import com.filesystem.mapper.FileShareMapper;
import com.filesystem.mapper.UserMapper;
import com.filesystem.entity.User;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
public class FileService {
@Resource
private FileMapper fileMapper;
@Resource
private FileShareMapper fileShareMapper;
@Resource
private UserMapper userMapper;
@Resource
private UserService userService;
@Value("${file.storage.path:./uploads}")
private String storagePath;
public List<FileEntity> getFiles(Long userId, Long folderId, String keyword) {
LambdaQueryWrapper<FileEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(FileEntity::getUserId, userId)
.eq(FileEntity::getIsDeleted, 0);
if (folderId != null) {
wrapper.eq(FileEntity::getFolderId, folderId);
} else {
wrapper.isNull(FileEntity::getFolderId);
}
if (keyword != null && !keyword.isEmpty()) {
wrapper.like(FileEntity::getName, keyword);
}
wrapper.orderByDesc(FileEntity::getIsFolder)
.orderByDesc(FileEntity::getCreateTime);
return fileMapper.selectList(wrapper);
}
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);
return fileMapper.selectList(wrapper);
}
public FileEntity getById(Long id) {
return fileMapper.selectById(id);
}
@Transactional
public void moveToTrash(Long id, Long userId) {
FileEntity file = fileMapper.selectById(id);
if (file != null && file.getUserId().equals(userId)) {
// 使用 LambdaUpdateWrapper 明确指定要更新的字段
LambdaUpdateWrapper<FileEntity> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FileEntity::getId, id)
.set(FileEntity::getIsDeleted, 1)
.set(FileEntity::getDeletedAt, LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
fileMapper.update(null, wrapper);
}
}
@Transactional
public void restoreFile(Long id, Long userId) {
FileEntity file = fileMapper.selectById(id);
if (file == null || !file.getUserId().equals(userId)) return;
LambdaUpdateWrapper<FileEntity> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FileEntity::getId, id)
.set(FileEntity::getIsDeleted, 0)
.set(FileEntity::getDeletedAt, null);
fileMapper.update(null, wrapper);
// 如果是文件夹,递归还原所有子文件
if (file.getIsFolder() != null && file.getIsFolder() == 1) {
restoreChildren(id, userId);
}
}
private void restoreChildren(Long parentFolderId, Long userId) {
List<FileEntity> children = fileMapper.selectList(
new LambdaQueryWrapper<FileEntity>()
.eq(FileEntity::getFolderId, parentFolderId)
.eq(FileEntity::getIsDeleted, 1)
.eq(FileEntity::getUserId, userId)
);
for (FileEntity child : children) {
LambdaUpdateWrapper<FileEntity> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FileEntity::getId, child.getId())
.set(FileEntity::getIsDeleted, 0)
.set(FileEntity::getDeletedAt, null);
fileMapper.update(null, wrapper);
if (child.getIsFolder() != null && child.getIsFolder() == 1) {
restoreChildren(child.getId(), userId);
}
}
}
@Transactional
public void deletePermanently(Long id, Long userId) {
FileEntity file = fileMapper.selectById(id);
if (file == null || !file.getUserId().equals(userId)) return;
// 如果是文件夹,先递归删除所有子文件
if (file.getIsFolder() != null && file.getIsFolder() == 1) {
deleteChildrenPermanently(id, userId);
}
// 删除当前文件的物理文件并扣减存储
if (file.getPath() != null && !file.getPath().isEmpty()) {
try {
Path filePath = Paths.get(storagePath).toAbsolutePath().resolve("files").resolve(file.getPath());
Files.deleteIfExists(filePath);
} catch (IOException e) {
// ignore
}
if (file.getSize() != null && file.getSize() > 0) {
userService.decreaseStorage(userId, file.getSize());
}
}
fileMapper.deleteById(id);
}
private void deleteChildrenPermanently(Long parentFolderId, Long userId) {
List<FileEntity> children = fileMapper.selectList(
new LambdaQueryWrapper<FileEntity>()
.eq(FileEntity::getFolderId, parentFolderId)
.eq(FileEntity::getIsDeleted, 1)
.eq(FileEntity::getUserId, userId)
);
for (FileEntity child : children) {
if (child.getIsFolder() != null && child.getIsFolder() == 1) {
deleteChildrenPermanently(child.getId(), userId);
}
if (child.getPath() != null && !child.getPath().isEmpty()) {
try {
Path filePath = Paths.get(storagePath).toAbsolutePath().resolve("files").resolve(child.getPath());
Files.deleteIfExists(filePath);
} catch (IOException e) {
// ignore
}
if (child.getSize() != null && child.getSize() > 0) {
userService.decreaseStorage(userId, child.getSize());
}
}
fileMapper.deleteById(child.getId());
}
}
@Transactional
public void emptyTrash(Long userId) {
List<FileEntity> trashFiles = getTrashFiles(userId, null);
for (FileEntity file : trashFiles) {
deletePermanently(file.getId(), userId);
}
}
@Transactional
public FileEntity createFolder(String name, Long userId, Long parentId) {
FileEntity folder = new FileEntity();
folder.setName(name);
folder.setType("folder");
folder.setIsFolder(1);
folder.setUserId(userId);
folder.setFolderId(parentId);
folder.setSize(0L);
folder.setIsShared(0);
folder.setIsDeleted(0);
fileMapper.insert(folder);
return folder;
}
@Transactional
public List<FileEntity> uploadFiles(List<MultipartFile> files, Long userId, Long folderId) throws IOException {
List<FileEntity> uploadedFiles = new ArrayList<>();
// 确保存储目录存在(使用配置文件中的路径 + files 子目录)
Path uploadDir = Paths.get(storagePath).toAbsolutePath().resolve("files");
if (!Files.exists(uploadDir)) {
Files.createDirectories(uploadDir);
}
for (MultipartFile file : files) {
if (file.isEmpty()) continue;
String originalName = file.getOriginalFilename();
String extension = originalName.contains(".") ? originalName.substring(originalName.lastIndexOf(".")) : "";
String storedName = UUID.randomUUID().toString() + extension;
String datePath = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM"));
Path targetDir = uploadDir.resolve(datePath);
if (!Files.exists(targetDir)) {
Files.createDirectories(targetDir);
}
Path targetPath = targetDir.resolve(storedName);
file.transferTo(targetPath.toFile());
FileEntity fileEntity = new FileEntity();
fileEntity.setName(originalName);
fileEntity.setType(getFileType(extension));
fileEntity.setSize(file.getSize());
fileEntity.setPath(datePath + "/" + storedName);
fileEntity.setUserId(userId);
fileEntity.setFolderId(folderId);
fileEntity.setIsFolder(0);
fileEntity.setIsShared(0);
fileEntity.setIsDeleted(0);
fileMapper.insert(fileEntity);
uploadedFiles.add(fileEntity);
// 更新用户存储空间
userService.updateStorage(userId, file.getSize());
}
return uploadedFiles;
}
private String getFileType(String extension) {
if (extension == null || extension.isEmpty()) return "file";
extension = extension.toLowerCase();
if (".jpg".equals(extension) || ".jpeg".equals(extension) || ".png".equals(extension)
|| ".gif".equals(extension) || ".webp".equals(extension) || ".svg".equals(extension)) {
return "image";
}
if (".mp4".equals(extension) || ".avi".equals(extension) || ".mov".equals(extension)) {
return "video";
}
if (".mp3".equals(extension) || ".wav".equals(extension) || ".flac".equals(extension)) {
return "audio";
}
if (".pdf".equals(extension)) {
return "pdf";
}
return "file";
}
public byte[] getFileContent(FileEntity file) throws IOException {
if (file == null || file.getPath() == null) return null;
Path filePath = Paths.get(storagePath).toAbsolutePath().resolve("files").resolve(file.getPath());
return Files.readAllBytes(filePath);
}
// 共享相关
public List<FileEntity> getSharedByMe(Long userId) {
List<FileShare> shares = fileShareMapper.selectList(
new LambdaQueryWrapper<FileShare>().eq(FileShare::getOwnerId, userId)
);
return shares.stream()
.map(share -> fileMapper.selectById(share.getFileId()))
.filter(f -> f != null && f.getIsDeleted() == 0)
.collect(Collectors.toList());
}
public List<FileEntity> getSharedByMeFolderFiles(Long userId, Long folderId) {
try {
// 检查该文件夹是否由当前用户共享
FileEntity folder = fileMapper.selectById(folderId);
if (folder == null || folder.getIsDeleted() == 1 || !folder.getUserId().equals(userId)) {
return new ArrayList<>();
}
// 检查是否有共享记录
List<FileShare> shares = fileShareMapper.selectList(
new LambdaQueryWrapper<FileShare>()
.eq(FileShare::getFileId, folderId)
.eq(FileShare::getOwnerId, userId)
);
if (shares.isEmpty()) {
return new ArrayList<>();
}
// 返回该文件夹内的文件
LambdaQueryWrapper<FileEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(FileEntity::getFolderId, folderId)
.eq(FileEntity::getIsDeleted, 0)
.isNull(FileEntity::getDeletedAt);
return fileMapper.selectList(wrapper);
} catch (Exception e) {
return new ArrayList<>();
}
}
public List<FileEntity> getSharedToMe(Long userId) {
List<FileShare> shares = fileShareMapper.selectList(
new LambdaQueryWrapper<FileShare>().eq(FileShare::getShareToUserId, userId)
);
return shares.stream()
.map(share -> fileMapper.selectById(share.getFileId()))
.filter(f -> f != null && f.getIsDeleted() == 0)
.collect(Collectors.toList());
}
public List<FileEntity> getSharedFolderFiles(Long userId, Long folderId) {
try {
// 检查该文件夹是否被共享给当前用户
FileEntity folder = fileMapper.selectById(folderId);
if (folder == null || folder.getIsDeleted() == 1) {
return new ArrayList<>();
}
// 检查是否有共享权限
List<FileShare> shares = fileShareMapper.selectList(
new LambdaQueryWrapper<FileShare>()
.eq(FileShare::getFileId, folderId)
.eq(FileShare::getShareToUserId, userId)
);
if (shares.isEmpty()) {
return new ArrayList<>();
}
// 返回该文件夹内的文件
LambdaQueryWrapper<FileEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(FileEntity::getFolderId, folderId)
.eq(FileEntity::getIsDeleted, 0)
.isNull(FileEntity::getDeletedAt);
return fileMapper.selectList(wrapper);
} catch (Exception e) {
return new ArrayList<>();
}
}
@Transactional
public FileShare shareFile(Long fileId, Long ownerId, Long shareToUserId, String permission) {
FileShare share = new FileShare();
share.setFileId(fileId);
share.setOwnerId(ownerId);
share.setShareToUserId(shareToUserId);
share.setPermission(permission != null ? permission : "view");
fileShareMapper.insert(share);
// 更新文件共享状态
FileEntity file = fileMapper.selectById(fileId);
if (file != null) {
file.setIsShared(1);
fileMapper.updateById(file);
}
return share;
}
@Transactional
public void cancelShare(Long fileId, Long ownerId) {
fileShareMapper.delete(
new LambdaQueryWrapper<FileShare>()
.eq(FileShare::getFileId, fileId)
.eq(FileShare::getOwnerId, ownerId)
);
// 检查是否还有其他共享
Long count = fileShareMapper.selectCount(
new LambdaQueryWrapper<FileShare>().eq(FileShare::getFileId, fileId)
);
if (count == 0) {
FileEntity file = fileMapper.selectById(fileId);
if (file != null) {
file.setIsShared(0);
fileMapper.updateById(file);
}
}
}
public String getOwnerName(Long userId) {
User user = userMapper.selectById(userId);
return user != null ? user.getNickname() : "未知用户";
}
public void renameFile(Long fileId, Long userId, String newName) {
FileEntity file = fileMapper.selectById(fileId);
if (file == null) {
throw new RuntimeException("文件不存在");
}
if (!file.getUserId().equals(userId)) {
throw new RuntimeException("无权操作此文件");
}
if (file.getIsDeleted() == 1) {
throw new RuntimeException("无法重命名回收站中的文件");
}
// 检查新名称是否已存在(同一目录下)
FileEntity existing = fileMapper.selectOne(
new LambdaQueryWrapper<FileEntity>()
.eq(FileEntity::getUserId, userId)
.eq(FileEntity::getFolderId, file.getFolderId())
.eq(FileEntity::getName, newName)
.eq(FileEntity::getIsDeleted, 0)
.ne(FileEntity::getId, fileId)
);
if (existing != null) {
throw new RuntimeException("该名称已存在");
}
file.setName(newName);
fileMapper.updateById(file);
}
public void moveFile(Long fileId, Long userId, Long targetFolderId) {
FileEntity file = fileMapper.selectById(fileId);
if (file == null) {
throw new RuntimeException("文件不存在");
}
if (!file.getUserId().equals(userId)) {
throw new RuntimeException("无权操作此文件");
}
if (file.getIsDeleted() == 1) {
throw new RuntimeException("无法移动回收站中的文件");
}
// 检查目标文件夹是否存在(如果不是根目录)
if (targetFolderId != null) {
FileEntity targetFolder = fileMapper.selectById(targetFolderId);
if (targetFolder == null) {
throw new RuntimeException("目标文件夹不存在");
}
if (!targetFolder.getUserId().equals(userId)) {
throw new RuntimeException("无权访问目标文件夹");
}
if (targetFolder.getIsDeleted() == 1) {
throw new RuntimeException("目标文件夹在回收站中");
}
// 检查是否移动到自己里面(如果是文件夹)
if (file.getIsFolder() == 1) {
if (file.getId().equals(targetFolderId)) {
throw new RuntimeException("不能移动到自己里面");
}
// 检查目标是否是自己子文件夹
Long parentId = targetFolder.getFolderId();
while (parentId != null) {
if (parentId.equals(file.getId())) {
throw new RuntimeException("不能移动到自己的子文件夹中");
}
FileEntity parent = fileMapper.selectById(parentId);
if (parent == null) break;
parentId = parent.getFolderId();
}
}
}
// 检查目标位置是否已有同名文件
FileEntity existing = fileMapper.selectOne(
new LambdaQueryWrapper<FileEntity>()
.eq(FileEntity::getUserId, userId)
.eq(FileEntity::getFolderId, targetFolderId)
.eq(FileEntity::getName, file.getName())
.eq(FileEntity::getIsDeleted, 0)
.ne(FileEntity::getId, fileId)
);
if (existing != null) {
throw new RuntimeException("目标位置已存在同名文件");
}
file.setFolderId(targetFolderId);
fileMapper.updateById(file);
}
}

View File

@@ -0,0 +1,85 @@
package com.filesystem.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.filesystem.entity.Message;
import com.filesystem.mapper.MessageMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class MessageService {
@Resource
private MessageMapper messageMapper;
public List<Message> getMessages(Long userId1, Long userId2) {
return messageMapper.selectList(
new LambdaQueryWrapper<Message>()
.and(wrapper -> wrapper
.eq(Message::getFromUserId, userId1).eq(Message::getToUserId, userId2)
.or()
.eq(Message::getFromUserId, userId2).eq(Message::getToUserId, userId1)
)
.orderByAsc(Message::getCreateTime)
);
}
public Message sendMessage(Long fromUserId, Long toUserId, String content, String type) {
Message message = new Message();
message.setFromUserId(fromUserId);
message.setToUserId(toUserId);
message.setContent(content);
message.setType(type);
message.setIsRead(0);
messageMapper.insert(message);
return message;
}
public void updateMessage(Message message) {
messageMapper.updateById(message);
}
public Message sendFileMessage(Long fromUserId, Long toUserId, String fileName, Long fileSize) {
Message message = new Message();
message.setFromUserId(fromUserId);
message.setToUserId(toUserId);
message.setContent(fileName);
message.setType("file");
message.setFileName(fileName);
message.setFileSize(fileSize);
message.setIsRead(0);
messageMapper.insert(message);
return message;
}
public int getUnreadCount(Long userId) {
return Math.toIntExact(messageMapper.selectCount(
new LambdaQueryWrapper<Message>()
.eq(Message::getToUserId, userId)
.eq(Message::getIsRead, 0)
));
}
public void markAsRead(Long messageId) {
Message message = messageMapper.selectById(messageId);
if (message != null) {
message.setIsRead(1);
messageMapper.updateById(message);
}
}
/**
* 获取未读消息列表:按发送人分组,返回每个联系人的未读数和最后一条消息
*/
public List<Message> getUnreadMessages(Long userId) {
return messageMapper.selectList(
new LambdaQueryWrapper<Message>()
.eq(Message::getToUserId, userId)
.eq(Message::getIsRead, 0)
.orderByDesc(Message::getCreateTime)
);
}
}

View File

@@ -0,0 +1,150 @@
package com.filesystem.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.filesystem.entity.FileEntity;
import com.filesystem.entity.User;
import com.filesystem.mapper.FileMapper;
import com.filesystem.mapper.UserMapper;
import com.filesystem.security.JwtUtil;
import jakarta.annotation.Resource;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Resource
private FileMapper fileMapper;
@Resource
private JwtUtil jwtUtil;
@Resource
private BCryptPasswordEncoder passwordEncoder;
public User findByUsername(String username) {
return userMapper.selectOne(
new LambdaQueryWrapper<User>().eq(User::getUsername, username)
);
}
public User findById(Long id) {
return userMapper.selectById(id);
}
public String login(String username, String password) {
User user = findByUsername(username);
if (user == null) {
throw new RuntimeException("用户不存在");
}
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new RuntimeException("密码错误");
}
return jwtUtil.generateToken(username, user.getId());
}
public void logout(Long userId) {
jwtUtil.invalidateToken(userId);
}
public User createUser(String username, String password, String nickname) {
User user = new User();
user.setUsername(username);
user.setPassword(passwordEncoder.encode(password));
user.setNickname(nickname);
user.setStatus(1);
user.setStorageUsed(0L);
user.setStorageLimit(20L * 1024 * 1024 * 1024); // 20GB
userMapper.insert(user);
return user;
}
/**
* 精确重算用户存储空间:统计该用户所有未删除(非回收站)文件的总大小
*/
public long recalculateStorage(Long userId) {
List<FileEntity> userFiles = fileMapper.selectList(
new LambdaQueryWrapper<FileEntity>()
.eq(FileEntity::getUserId, userId)
.eq(FileEntity::getIsDeleted, 0)
.ne(FileEntity::getIsFolder, 1)
);
long total = 0L;
for (FileEntity f : userFiles) {
if (f.getSize() != null) {
total += f.getSize();
}
}
// 更新到用户表
User user = userMapper.selectById(userId);
if (user != null) {
user.setStorageUsed(total);
// 同步存储上限为 20GB兼容老用户
if (user.getStorageLimit() == null || user.getStorageLimit() < 20L * 1024 * 1024 * 1024) {
user.setStorageLimit(20L * 1024 * 1024 * 1024);
}
userMapper.updateById(user);
}
return total;
}
public void updateStorage(Long userId, Long size) {
User user = userMapper.selectById(userId);
if (user != null) {
user.setStorageUsed(user.getStorageUsed() + size);
userMapper.updateById(user);
}
}
/**
* 扣减存储空间(用于彻底删除文件时)
*/
public void decreaseStorage(Long userId, Long size) {
User user = userMapper.selectById(userId);
if (user != null) {
long newValue = user.getStorageUsed() - size;
user.setStorageUsed(Math.max(0L, newValue));
userMapper.updateById(user);
}
}
public List<User> getAllUsersExcept(Long excludeId) {
return userMapper.selectList(
new LambdaQueryWrapper<User>()
.ne(User::getId, excludeId)
.eq(User::getStatus, 1)
);
}
public void updateAvatar(Long userId, String avatarUrl) {
User user = userMapper.selectById(userId);
if (user != null) {
user.setAvatar(avatarUrl);
userMapper.updateById(user);
}
}
public void updateProfile(Long userId, String nickname, String signature, String phone, String email) {
User user = userMapper.selectById(userId);
if (user != null) {
if (nickname != null) {
user.setNickname(nickname);
}
if (signature != null) {
user.setSignature(signature);
}
if (phone != null) {
user.setPhone(phone);
}
if (email != null) {
user.setEmail(email);
}
userMapper.updateById(user);
}
}
}