断点续传

1、 什么是断点续传

通常视频文件都比较大,所以对于媒资系统上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大小没有限制,但是客户的网络环境质量、电脑硬件环境等参差不齐,如果一个大文件快上传完了网断了没有上传完成,需要客户重新上传,用户体验非常差,所以对于大文件上传的要求最基本的是断点续传。

什么是断点续传:

        引用百度百科:断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载,断点续传可以提高节省操作时间,提高用户体验性。

断点续传流程如下图:

流程如下:

1、前端上传前先把文件分成块

2、一块一块的上传,上传中断后重新上传,已上传的分块则不用再上传

3、各分块上传完成最后在服务端合并文件

2、断点续传实现

1.前端对文件进行分块

2.前端使用多线程上传分片,上传前给服务器发送消息验证当前分片是否已经上传。

3.所有分片上传完毕后,发送合并分片请求,校验文件的完整性。 (上传的分片应该具备顺序标记)

4.前端给服务器传一个MD5值,服务器合并文件后,利用MD5值计算是否与源文件一致。如果不一致,说明文件需要重新上传。

分片文件清理问题:

在数据库中有一张文件表记录minIo中存储的文件信息文件开始上传时会写入文件表,状态为上传中,上传完成会更新状态为上传完成当一个文件传了一半不再上传了,说明该文件没有上传完成,通过定时任务去查询文件表中的记录,如果文件距离上次上传结束超过24小时,则可以考虑清除MinIo中相关的分片数据

3、断点续传是怎么做的?

我们是基于分块上传的模式实现断点续传的需求,当文件. 上传-部分断网后前边已经上传过的不再上传。

1、前端对文件分块。

2、前端使用多线程-块一块上传,上传前给服务端发-个消息校验该分块是否.上传,如果已上传则不再上传。

3、等所有分块上传完毕,服务端合并所有分块,校验文件的完整性。

因为分块全部上传到了服务器,服务器将所有分块按顺序进行合并,就是写每个分块文件内容按顺序依次写入一个文件中。使用字节流去读写文件。

4、前端给服务传了一个md5值,服务端合并文件后计算合并后文件的md5是否和前端传的一样,如果一样则说文件完整,如果不一样说明可能由于网络丢包导致文件不完整,这时上传失败需要重新上传。

4、分块文件清理问题?

上传一个文件进行分块上传,上传一半不传了, 之前上传到minio的分块文件要清理吗?怎么做的?

1、在数据库中有一-张文件表记录minio中存 储的文件信息。

2、文件开始上传时会写入文件表,状态为上传中,上传完成会更新状态为 上传完成。

3、当一个文件传了一半不再上传了说明该文件没有上传完成,会有定时任务去查询文件表中的记录,如果文件未上传完成则删除minio中没有.上传成功的文件日录。

5、分块与合并测试

为了更好的理解文件分块上传的原理,下边用java代码测试文件的分块与合并。

文件分块的流程如下:

1、获取源文件长度

2、根据设定的分块文件的大小计算出块数

3、从源文件读数据依次向每一个块文件写数据。

测试代码如下:

package com.xuecheng.media;

import org.apache.commons.codec.digest.DigestUtils;

import org.junit.jupiter.api.Test;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.RandomAccessFile;

import java.util.*;

/**

* @program: xuecheng-plus-project148

* @description: TODO大文件分块合并

* @author: Mr.Zhang

* @create: 2023-02-27 08:54

**/

public class BigFileTest {

//分块测试

@Test

public void testChunk() throws IOException {

//源文件

File sourceFile = new File("D:\\software\\Test\\xuecheng\\video\\1.mp4");

//分块文件存储路径

File chunkFolderPath = new File("D:\\software\\Test\\xuecheng\\chunk\\");

if (!chunkFolderPath.exists()) {

chunkFolderPath.mkdir();

}

//分块的大小 1mb

int chunkSize = 1024 * 1024 * 1;

//分块数量

long chunkNum = (long) Math.ceil(sourceFile.length() * 1.0 / chunkSize);

//思路,使用流对象读取文件,向分块文件写数据,达到分块大小不再写

RandomAccessFile raf_read = new RandomAccessFile(sourceFile, "r");

//缓冲区

byte[] b = new byte[1024];

for (long i = 0; i < chunkNum; i++) {

//分块文件

File file = new File("D:\\software\\Test\\xuecheng\\chunk\\" + i);

//如果分块文件存在了,则删除

if (file.exists()) {

file.delete();

}

//创建文件

boolean newFile = file.createNewFile();

if (newFile) {

//向分块文件写数据流对象

RandomAccessFile raf_write = new RandomAccessFile(file, "rw");

int len = -1;

while ((len = raf_read.read(b)) != -1) {

//向文件中写数据

raf_write.write(b, 0, len);

//达到分块大小不在写了

if (file.length() >= chunkSize) {

break;

}

}

raf_write.close();

}

}

raf_read.close();

}

//测试合并

@Test

public void testMerge() throws IOException {

//源文件

File sourceFile = new File("D:\\software\\Test\\xuecheng\\video\\1.mp4");

//分块文件存储路径

File chunkFolderPath = new File("D:\\software\\Test\\xuecheng\\chunk\\");

if (!chunkFolderPath.exists()) {

chunkFolderPath.mkdir();

}

//合并后的文件

File mergeFile = new File("D:\\software\\Test\\xuecheng\\video\\1_01.mp4");

boolean newFile1 = mergeFile.createNewFile();

//思路,使用流对象读取分块文件,按顺序将分块文件依次向合并文件写数据

//获取分块文件列表,按文件名升序排序

File[] chunkFiles = chunkFolderPath.listFiles();

List chunkFileList = Arrays.asList(chunkFiles);

//按文件名升序排序

Collections.sort(chunkFileList, new Comparator() {

@Override

public int compare(File o1, File o2) {

return Integer.parseInt(o1.getName()) - Integer.parseInt(o2.getName());

}

});

//创建合并文件的流对象

RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");

//缓冲区

byte[] b = new byte[1024];

for (File file : chunkFileList) {

//读取分块文件的流对象

RandomAccessFile raf_read = new RandomAccessFile(file, "r");

int len = -1;

while ((len = raf_read.read(b))!=-1){

//向合并文件写数据

raf_write.write(b,0,len);

}

}

//校验合并后的文件是否正确

FileInputStream sourceFileStream = new FileInputStream(sourceFile);

FileInputStream mergeFileStream = new FileInputStream(mergeFile);

//源文件

String sourceMd5Hex = DigestUtils.md5Hex(sourceFileStream);

//合并后的文件

String mergeMd5Hex = DigestUtils.md5Hex(mergeFileStream);

if (sourceMd5Hex.equals(mergeMd5Hex)){

System.out.println("合并成功");

}

}

}

好文链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: