文件上传在项目开发中再常见不过了,大多项目都会涉及到图片、音频、视频、文件的上传,通常简单的一个Form表单就可以上传小文件了,但是遇到大文件时比如1GB以上,或者用户网络比较慢时,简单的文件上传就不能适用了,会出现以下隐患或问题
1、网络传输速度慢
上传时间长,大文件完整上传需要占用持续稳定的上行带宽,如果网络条件不好,上传会非常慢,损耗用户体验。
2、中间失败需重新上传
上传过程中如果由于网络等原因发生中断,整个传输会失败。这就需要用户重新再上传一遍完整文件,重复劳动。
3、服务器压力大
服务端需要占用较多资源持续处理一个大文件,对服务器性能压力较大,可能影响到其他服务。
4、流量资源浪费
一次完整上传大文件,如果遇到已经存在相同文件,会重复消耗大量网络流量,是数据浪费。
5、难以实现上传进度提示
用户无法感知上传进度,如果上传失败也不知道已经上传了多少数据。
所以为了解决以上这些问题,使用分片、断点续传等技术就非常重要。它可以分批次上传数据块,避免一次性全量上传的弊端。同时结合校验、记录已上传分片等手段,可以使整个上传过程可控、可恢复、节省流量,大幅提升传输效率。
大文件上传主要是借助于分治思想(任务分解法)实现,将大文件分解为多个易处理的小文件,并循环递归的上传小文件,最后把子问题的结果合并为原始问题的结果。
文件小传非常的简单,本项目后端我们使用SrpingBoot 3.1.2 + JDK17,前端我们使用原生的JavaScript+spark-md5.min.js实现。
org.springframework.boot spring-boot-starter-parent 2.4.3 UTF-8 UTF-8 1.8 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-configuration-processor true org.projectlombok lombok true
public static final String UPLOAD_PATH = "D:\\upload\\"; /** * 小文件上传 * @param file * @return * @throws IOException */ @PostMapping(value = "/upload", consumes = "multipart/form-data") public ResponseEntity
上传结果upload upload
在上传过程会报文件大小限制错误,主要有三个参数需要设置:
server: port: 8080 servlet: context-path: /demo spring: servlet: multipart: max-file-size: 1024MB max-request-size: 1024MB
1、分片上传的目的是将大文件切割为多个小块,实现并发上传以提高传输速度。
2、分片上传可以按配置的分片大小(例如1M一个分片)将大文件分割。
3、前端项目将切割的每个分片按顺序上传至后端服务器。
4、后端收到分片后可以暂存于本地,并记录这个分片的特征信息,如分片序号、文件MD5等,写入到数据库。
5、全部分片上传完成后,后端按序号顺序重新组装成原完整文件。
大文件分片上传前端主要有三步:
前端上传代码计算文件MD5值用了spark-md5这个库,使用也是比较简单的。这里为什么要计算MD5简单说一下,因为文件在传输写入过程中可能会出现错误,导致最终合成的文件可能和原文件不一样,所以要对比一下前端计算的MD5和后端计算的MD5是不是一样,保证上传数据的一致性。
upload upload 上传结果 分片上传
文件MD5:
上传结果:
检测文件完整性:
前端注意事项
后端就两个接口/uploadBig用于每一片文件的上传和/checkFile检测文件的MD5。
这里需要注意的:
@RestController public class UploadController { public static final String UPLOAD_PATH = "D:\\upload\\"; /** * @param chunkSize 每个分片大小 * @param chunkNumber 当前分片 * @param md5 文件总MD5 * @param file 当前分片文件数据 * @return * @throws IOException */ @RequestMapping("/uploadBig") public ResponseEntity
配合前端上传演示分片上传,依次按如下流程点击按钮:
效果
如何实现断点续传?
1、前端在上传文件时,将文件切成多个小块,每次上传一个小块。
2、每上传一个小块,后端会记录这个小块的信息,比如该小块的序号、文件MD5、内容Hash等。可以保存在MySQL数据库中。
3、如果上传中断了,前端可以向后端询问已经上传了哪些小块。
4、后端从数据库中查询,返回已上传小块的信息给前端。
5、前端就可以接着只上传中间中断的那一部分小块。
6、后端会根据小块的序号、文件MD5来把这些小块重新拼接成完整的文件。
7、以后如果这个文件再次上传,通过MD5值就可以知道该文件已经存在了,则直接返回上传成功,无需上传实际内容。这样通过切片上传和持久化记录已上传切片的信息,就可以实现断点续传。
有了上面的设计做断点续传就比较简单的,后端代码不需要改变,只要修改前端上传流程就好了:
用/checkFile接口,文件里如果有未完成上传的分片,接口返回chunks字段对就的位置值为0,前端将未上传的分片继续上传,完成后再调用/checkFile就完成了断点续传
如何实现秒传?
1、前端在上传文件时,先计算该文件的MD5值,然后将MD5值发送到后端服务器。
2、后端服务器收到MD5值后,查询MySQL数据库,检查是否已存在相同MD5值的文件。
3、如果存在,表示该文件已上传过,服务器直接从数据库查询到该文件存在哪些分片,并返回给客户端。
4、客户端拿到文件分片信息后,会直接组装完整的文件,而不再上传实际文件内容。
5、如果数据库不存在该MD5值,表示文件未上传过,服务器会返回需要客户端上传整个文件。
6、客户端上传完文件后,服务器才会在MySQL数据库中新增该文件与MD5的对应关系,以及存储文件分片信息。
7、下次再上传同样文件时,通过MD5值就可以实现秒传了
秒传也是比较简单的,只要修改前端代码流程就好了,比如张三上传了一个文件,然后李四又上传了同样内容的文件,同一文件的MD5值可以认为是一样的(虽然会存在不同文件的MD5一样,不过概率很小,可以认为MD5一样文件就是一样),10万不同文件MD5相同概率为110000000000000000000000000000\frac{1}{10000000000000000000000000000}100000000000000000000000000001,福利彩票的中头奖的概率一般为11000000\frac{1}{1000000}10000001 ,具体计算方法可以参考走近消息摘要–Md5产生重复的概率,,所以MD5冲突的概率可以忽略不计。当李四调用/checkFile接口后,后端直接返回了李四上传的文件路径,李四就完成了秒传。大部分云盘秒传的思路应该也是这样,只不过计算文件HASH算法更为复杂,返回给用户文件路径也更为安全,要防止被别人算出文件路径了。
秒传前端代码流程:
页面访问 http://localhost:8080/demo/index.html 即可