700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 大文件分片上传前后端实现

大文件分片上传前后端实现

时间:2022-10-31 06:07:55

相关推荐

大文件分片上传前后端实现

最近在做公司的视频业务,涉及到大视频的上传。

之前的图片、Excel等上传做的很简单,直接表单提交后端用MultipartFile接收保存到磁盘就行了。

但是针对大文件的上传,需要做额外的处理,否则可能会遇到如下问题:

文件过大,超出服务端的请求大小限制(如SpringMVC,默认文件上传最大1MB)。请求的时间过长,请求超时。客户端网络不好的话,容易传输中断,必须整个文件重传。

为了解决这些问题,笔者研究了一下,发现可以用分片上传的方式来解决。

前端处理

大文件分片上传,是需要前端和后端配合操作的。

整体流程是:前端将大文件进行分片,例如一个50MB的文件,分成10片,每个片5MB。然后发10个HTTP请求,将这10个分片数据发送给后端,后端根据分片的下标和Size来往磁盘文件的不同位置写入分片数据,10个分片全部写完后即得到一个完整的文件。

前端的处理流程如下:

前端实战代码如下:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><script src="/jquery/2.0.0/jquery.min.js"></script><script src="/blueimp-md5/2.10.0/js/md5.js"></script></head><body><input type="file" name="file" id="file"><button id="upload" onClick="upload()">upload</button></body></html><script>const sliceSize = 5 * 1024 * 1024; // 每个文件切片大小定为5MB//发送请求function upload() {const blob = document.getElementById("file").files[0];const fileSize = blob.size;// 文件大小const fileName = blob.name;// 文件名//计算文件切片总数const totalSlice = Math.ceil(fileSize / sliceSize);// 循环上传for (let i = 1; i <= totalSlice; i++) {let chunk;if (i == totalSlice) {// 最后一片chunk = blob.slice((i - 1) * sliceSize, fileSize - 1);//切割文件} else {chunk = blob.slice((i - 1) * sliceSize, i * sliceSize);}const formData = new FormData();formData.append("file", chunk);formData.append("md5", md5(blob));formData.append("name", fileName);formData.append("size", fileSize);formData.append("chunks", totalSlice);formData.append("chunk", i);$.ajax({url: 'http://localhost:8080/chunk/upload',type: 'POST',cache: false,data: formData,processData: false,contentType: false,async: false});}}</script>

笔者这里写了一个比较粗糙的前端例子,市面上有很多优秀的分片上传插件,例如:webuploader。

后端处理

后端接收到分片数据后,要根据分片的下标和分片的大小来往文件的指定位置写入分片数据。

例如:分片大小为1MB,第一个分片就要往文件的第0个字节开始,写入1048576字节的数据。第二个分片就要往文件的第1048576个字节开始,写入1048576字节的数据,以此类推。待所有的分片数据全部写入完成后,即得到一个完整的文件。

后端处理流程如下:

RandomAccessFile

分片数据的写入,需要对文件进行定位,移动访问指针。

JDK提供了java.io.RandomAccessFile类,支持对文件进行随机的读写操作。

在Linux平台上,所有打开的文件都有一个文件描述符(FD),文件描述符自身维护了一个文件偏移量(current file offset),简称cfo,通过lseek函数可以移动文件的读写指针,RandomAccessFile的seek()方法就是调用了Linux的lseek系统函数来实现的。

通过RandomAccessFile.seek()移动访问指针,然后写入分片数据。

后端处理代码如下:

@RestControllerpublic class FileController {// 存放文件的临时目录private static final String DATA_DIR = System.getProperty("user.dir") + "/temp/";// 文件MD5的缓存容器private static final ConcurrentMap<String, File> MD5_CACHE = new ConcurrentHashMap<>();/*** 大文件分片上传* @param name 文件名* @param md5 文件MD5值* @param size 文件大小* @param chunks 总的分片数* @param chunk 当前分片数* @param multipartFile 分片流* @throws IOException*/@PostMapping("/chunk/upload")public void chunkUpload(String name,String md5,Long size,Integer chunks,Integer chunk,@RequestParam("file") MultipartFile multipartFile) throws IOException {// 是否生成了文件???File targetFile = MD5_CACHE.get(md5);if (targetFile == null) {// 没有生成的话就生成一个新的文件,没有做并发控制,多线程上传会出问题targetFile = new File(DATA_DIR, UUID.randomUUID().toString(true) + "." + FileNameUtil.extName(name));targetFile.getParentFile().mkdirs();MD5_CACHE.put(md5, targetFile);}// 可以对文件的任意位置进行读写RandomAccessFile accessFile = new RandomAccessFile(targetFile, "rw");boolean finished = chunk == chunks;//是否最后一片if (finished) {// 移动指针到指定位置accessFile.seek(size - multipartFile.getSize());}else {accessFile.seek((chunk - 1) * multipartFile.getSize());}// 写入分片的数据accessFile.write(multipartFile.getBytes());accessFile.close();if (finished) {System.out.println("success.");// 上传成功MD5_CACHE.remove(md5);}}}

文件分片上传的所有请求中,必须有一个唯一标识可以将所有的分片数据关联起来,笔者这里用的是文件的MD5值。

你可能感兴趣的文章:

AQS源码导读摊牌了,我要手写一个RPCJava锁的膨胀过程以及一致性哈希对锁膨胀的影响ThreadLocal源码解析CMS与三色标记算法大白话理解可达性分析算法

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。