断点续传实现

断点续传

1、 什么是断点续传

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

什么是断点续传:

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

断点续传流程如下图:

断点续传实现_第1张图片

流程如下:

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("合并成功");
        }

    }

}

你可能感兴趣的:(分布式,java,java,junit,开发语言)