
前言
再次重申,本项目非原创,视频来源于:
Springboot项目实战 easypan 仿百度网盘 计算机毕业设计 简历项目 项目经验(后端)
再次重申:本人不分享项目源码,支持项目付费!!
回顾
之前已经完成了分片文件的合并、视频文件切割、生成缩略图,以及文件预览等业务,接下来关于文件还有创建目录,文件重命名,移动文件等操作。本次笔记记录这些后续操作的实现过程。
完成任务
1. 创建目录
controller 层实现:
创建目录需要传入文件父id,因为有的目录直接在根目录下创建,而有的目录则是在已存在的目录下创建的。还需要传入文件名 fileName。session 用户获取当前登录用户的信息。
service 层实现:
正式创建目录前,需要对创建的目录文件名进行校验,检查当前用户当前父级目录下是否有同名的目录文件:
2. 获取目录路径
在点击更深层目录下时,应当在页面上方显示当前的目录路径:
请求时的负载中会传入走过的所有目录id,中间用 “/” 隔开
controller 层实现:
将这个功能的具体实现过程写在了当前 controller 类的父类 CommonFileController 类中,所以调用时使用 super:
- 为什么要将查询的数据转换为 FileInfoVO 对象再返回?
VO 代表视图对象(View Object),通常用于与前端交互,封装需要展示给用户的简要信息。直接返回内部数据结构 FileInfo,可能会暴露一些不必要的或敏感的信息。
3. 文件重命名
controller 层代码实现:
对文件进行重命名需要从前端获取重命名文件的 id,以及文件要重名为什么名称的 fileName。
service 层代码实现:
- 这里我一开始比较疑惑,为什么在更新数据库之前,已经对传入的文件名进行校验,检验当前用户在同一目录下是否有与之同名的文件,在更新数据库之后,还要再次检查?
这与并发操作有关。在高并发或多用户的环境中,可能有操作出现重叠。其次,有些场景中,文件夹可能是共享的,这种情况下多个用户访问同一个文件夹的可能性会增加。此时,例如,用户 A 正在重命名文件时,而在数据库更新前,用户 B 可能已经在同一目录下创建了同名文件。这种情况下,如果不进行第二次检验,就会导致在同一目录下出现同名文件。
4. 获取所有目录
当我们选择文件要将它进行移动时,在点击移动文件后,应显示当前父目录下的所有目录。
controller 层代码实现:
注意
:如果要移动的文件中包含目录,需要在展示的目录中排除要移动的目录本身,因为一个目录移动到自身,这是没有任何意义的,不符合常规做法。
xml 文件中针对要排除的目录,需要增加下面的约束:
5. 移动文件
controller 层代码实现:
service 层实现:
这里的实现过程较为复杂:
(1)检查要移动到的目标目录是否是自身目录下,目标目录是否处于正常使用状态。
(2)查询当前用户目标目录下的所有文件。并将其以文件名为 key,文件对象为 value 存入 Map 中。
(3)根据文件 id 和用户 id 查询所选中的文件,遍历每个文件,查看目标目录下的文件 Map 中是否存在与之同名的文件,若存在,则要重命名所选的文件。再更新数据库中的文件信息。
6. 创建下载链接
在点击下载文件时,会首先创建一个下载链接,实际是生成一个下载链接的唯一标识码。那么,为什么在文件正式下载前创建下载链接?
- 首先,是针对系统的安全性,直接提供文件路径可能会暴露文件的实际位置,有安全风险,使用下载链接可以限制用户对系统的直接访问。
- 其次,生成下载连接可以设置有效期,是链接在一定时间内有效,过期后自动失效,这有助于控制文件的访问。
controller 层实现:
父层的 createDownloadUrl() 方法:
需要将下载链接的代码和对应的文件信息保存在 Redis 中,以便在下载文件时从 Redis 中或文件信息。
DownloadFileDto类:(其实就是一个文件下载时的数据传输对象)
7. 下载文件
controller 层实现:
父层的 download() 方法:
根据下载链接码从 Redis 中获取对应的下载文件信息,获取该文件在服务器中的存放路径以及文件名,将其作为响应返回给客户端。
这段代码我直接粘贴在下面,以便后续使用:
/**
* 下载文件
*/
protected void download(HttpServletRequest request, HttpServletResponse response, String code) throws Exception{
DownloadFileDto downloadFileDto = redisComponent.getDownloadCode(code);
if (null == downloadFileDto){
return;
}
String filePath = appConfig.getProjectFolder() + Constants.FILE_FOLDER_FILE + downloadFileDto.getFilePath();
String fileName = downloadFileDto.getFileName();
// 设置HTTP响应内容类型为application/octet-stream,表示文件下载
response.setContentType("application/x-msdownload; charset=UTF-8");
// 处理不同浏览器对文件名的兼容性
// request.getHeader("User-Agent")获取到请求头的用户代理,并转换为小写
// 检查用户代理字符串中是否包含"msie",包含则说明是 IE 浏览器
if (request.getHeader("User-Agent").toLowerCase().indexOf("msie") > 0) { // IE浏览器
// IE浏览器对文件名进行了URL编码,确保文件名中的特殊字符(空格、中文等)在URL中是安全的
fileName = URLEncoder.encode(fileName, "UTF-8");
} else {
// 其他浏览器使用ISO8859-1编码
fileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1");
}
// 设置响应头Content-Disposition,告知浏览器以附件形式下载,并指定下载时的文件名
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
readFile(response, filePath);
}