【EasyPan】项目常见问题解答(自用&持续更新中…)汇总版
保存分享文件到个人网盘代码分析
一、代码结构概览
该代码实现了一个将他人分享的文件保存到自己网盘的功能,主要分为三个部分:
- 控制器层(Controller):处理HTTP请求和响应
- 服务层(Service):实现核心业务逻辑
- 递归方法:处理目录结构的深度复制
二、控制器层分析
saveShare
方法
@RequestMapping("/saveShare")
@GlobalInterceptor(checkParams = true, checkLogin = false)
public ResponseVO saveShare(HttpSession session,
@VerifyParam(required = true) String shareId,
@VerifyParam(required = true) String shareFileIds,
@VerifyParam(required = true) String myFolderId) {
// 验证分享有效性
SessionShareDto shareSessionDto = checkShare(session, shareId);
// 获取当前用户信息
SessionWebUserDto webUserDto = getUserInfoFromSession(session);
// 防止自分享操作
if (shareSessionDto.getShareUserId().equals(webUserDto.getUserId())) {
throw new BusinessException("自己分享的文件无法保存到自己的网盘");
}
// 调用服务层保存逻辑
fileInfoService.saveShare(shareSessionDto.getFileId(), shareFileIds, myFolderId,
shareSessionDto.getShareUserId(), webUserDto.getUserId());
return getSuccessResponseVO(null);
}
三、服务层分析
saveShare
方法
@Override
public void saveShare(String shareRootFilePid, String shareFileIds, String myFolderId,
String shareUserId, String currentUserId) {
// 分割文件ID列表
String[] shareFileIdArray = shareFileIds.split(",");
// 查询目标文件夹现有文件(用于重名检查)
FileInfoQuery fileInfoQuery = new FileInfoQuery();
fileInfoQuery.setUserId(currentUserId);
fileInfoQuery.setFilePid(myFolderId);
List<FileInfo> currentFileList = this.fileInfoMapper.selectList(fileInfoQuery);
// 构建文件名映射表
Map<String, FileInfo> currentFileMap = currentFileList.stream()
.collect(Collectors.toMap(FileInfo::getFileName, Function.identity(), (data1,data2)->data2));
// 查询待保存的分享文件列表
fileInfoQuery = new FileInfoQuery();
fileInfoQuery.setUserId(shareUserId);
fileInfoQuery.setFileIdArray(shareFileIdArray);
List<FileInfo> shareFileList = this.fileInfoMapper.selectList(fileInfoQuery);
// 准备拷贝文件列表
List<FileInfo> copyFileList = new ArrayList<>();
Date curDate = new Date();
// 处理每个待保存文件
for (FileInfo item : shareFileList) {
// 处理重名文件
FileInfo haveFile = currentFileMap.get(item.getFileName());
if (haveFile != null) {
item.setFileName(StringTools.rename(item.getFileName()));
}
// 递归处理文件/目录
findAllSubFile(copyFileList, item, shareUserId, currentUserId, curDate, myFolderId);
}
// 批量插入数据库
this.fileInfoMapper.insertBatch(copyFileList);
}
四、递归方法分析
findAllSubFile
方法
private void findAllSubFile(List<FileInfo> copyFileList, FileInfo fileInfo,
String sourceUserId, String currentUserId,
Date curDate, String newFilePid) {
// 保存原始文件ID(用于目录查询)
String sourceFileId = fileInfo.getFileId();
// 更新文件元信息
fileInfo.setCreateTime(curDate);
fileInfo.setLastUpdateTime(curDate);
fileInfo.setFilePid(newFilePid);
fileInfo.setUserId(currentUserId);
// 生成新文件ID
String newFileId = StringTools.getRandomString(Constants.LENGTH_10);
fileInfo.setFileId(newFileId);
// 添加到拷贝列表
copyFileList.add(fileInfo);
// 如果是目录则递归处理子文件
if (FileFolderTypeEnums.FOLDER.getType().equals(fileInfo.getFolderType())) {
FileInfoQuery query = new FileInfoQuery();
query.setFilePid(sourceFileId);
query.setUserId(sourceUserId);
List<FileInfo> sourceFileList = this.fileInfoMapper.selectList(query);
for (FileInfo item : sourceFileList) {
findAllSubFile(copyFileList, item, sourceUserId, currentUserId, curDate, newFileId);
}
}
}
五、安全性和可靠性设计
-
参数校验:
- 使用
@VerifyParam
确保必要参数非空 - 通过
checkShare
验证分享有效性
- 使用
-
数据隔离:
- 严格区分
shareUserId
和currentUserId
- 所有查询都带有用户ID条件
- 严格区分
-
异常处理:
- 自分享操作抛出明确业务异常
- 潜在的数据库异常由全局异常处理器捕获
-
幂等性设计:
- 重名文件自动重命名
- 使用新ID避免冲突