minio&前后端分离上传视频/上传大文件——前后端分离断点续传&minio分片上传实现(一)

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: minio&前后端分离上传视频/上传大文件——前后端分离断点续传&minio分片上传实现

1.断点续传

  • 断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载,断点续传可以提高节省操作时间,提高用户体验性。
  • 通常视频文件都比较大,所以对于媒资系统上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大小没有限制,但是客户的网络环境质量、电脑硬件环境等参差不齐,如果一个大文件快上传完了网断了没有上传完成,需要客户重新上传,用户体验非常差,所以对于大文件上传的要求最基本的是断点续传。

断点续传流程如下图:

流程如下:

  1. 前端上传前先把文件分成块。
  2. 一块一块的上传,上传中断后重新上传,已上传的分块则不用再上传。
  3. 各分块上传完成最后在服务端合并文件。

2.分块与合并测试

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

2.1 分块测试

2.1.1 流程分析

文件分块的流程如下:

  1. 获取源文件长度
  2. 根据设定的分块文件的大小计算出块数
  3. 从源文件读数据依次向每一个块文件写数据。

2.1.2 代码实现

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
 * 分块文件测试
 *
 * @author 狐狸半面添
 * @create 2023-02-07 17:14
 */
public class BigFileChunkDemo {
    public static void main(String[] args) throws IOException {
        // 1.指定要进行分块的源文件
        File sourceFile = new File("D:\\SystemDefault\\video\\不为谁而作的歌.mp4");
        // 2.指定分块文件存储路径
        File chunkFolderPath = new File("D:\\SystemDefault\\video\\chunk\\");
        // chunk文件夹不存在则创建
        if (!chunkFolderPath.exists()) {
            chunkFolderPath.mkdirs();
        }
        // 3.分块的大小 - 10MB
        int chunkSize = 1024 * 1024 * 10;
        // 4.根据分块大小得到源文件的分块数量(向上转型)
        long chunkNum = (long) Math.ceil(sourceFile.length() * 1.0 / chunkSize);
        /*
            分块思路:使用流对象读取源文件,向分块文件写数据,达到分块大小不再写
         */
        // 5.使用流对象 rafRead 读取源文件
        RandomAccessFile rafRead = new RandomAccessFile(sourceFile, "r");
        // 6.设置每次读取的缓冲区大小
        byte[] b = new byte[1024];
        RandomAccessFile rafWrite;
        // 7.开始分块
        for (long i = 0; i < chunkNum; i++) {
            // 7.1 指定分块文件
            File file = new File("D:\\SystemDefault\\video\\chunk\\" + i);
            // 7.2 如果分块文件存在,则删除
            if (file.exists()) {
                file.delete();
            }
            // 7.3 创建一个空的分块文件
            boolean newFile = file.createNewFile();
            if (newFile) {
                // 7.4 向分块文件写数据流对象
                rafWrite = new RandomAccessFile(file, "rw");
                int len = -1;
                // 7.5 读取源文件,每次读取的大小为设置的缓冲区的大小
                while ((len = rafRead.read(b)) != -1) {
                    // 7.6 将缓冲区的数据写入到分块文件中
                    rafWrite.write(b, 0, len);
                    // 7.7 达到分块大小不再写了,继续下一次循环,将后面的数据写入新的分块文件中
                    if (file.length() >= chunkSize) {
                        break;
                    }
                }
                // 7.8 关闭该分块文件流,释放资源
                rafWrite.close();
            }
        }
        // 8.分块完成,关闭源文件流,释放资源
        rafRead.close();
        System.out.println("分块文件完成");
    }
}

2.2 合并测试

2.2.1 流程分析

  1. 找到要合并的文件并按文件合并的先后进行排序。
  2. 创建合并文件。
  3. 依次从合并的文件中读取数据向合并文件写入数。

2.2.2 代码实现

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.13</version>
</dependency>
import org.apache.commons.codec.digest.DigestUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.List;
/**
 * 合并文件测试
 *
 * @author 狐狸半面添
 * @create 2023-02-07 17:37
 */
public class BigFileMergeDemo {
    public static void main(String[] args) throws IOException {
        // 1.指定源文件 - 目的是后面比较合并后的文件与源文件是否相同
        File sourceFile = new File("D:\\SystemDefault\\video\\不为谁而作的歌.mp4");
        // 2.指定分块文件存储路径
        File chunkFolderPath = new File("D:\\SystemDefault\\video\\chunk\\");
        // 3.指定并创建合并后的文件
        File mergeFile = new File("D:\\SystemDefault\\video\\不为谁而作的歌(合并).mp4");
        boolean success = mergeFile.createNewFile();
        if (!success) {
            System.out.println("error: 文件创建失败");
            return;
        }
        /*
            思路:使用流对象读取分块文件,按顺序将分块文件依次向合并文件写数据
         */
        // 4.获取分块文件列表,按文件名升序排序
        // 4.1 获取分块文件列表
        File[] chunkFiles = chunkFolderPath.listFiles();
        if (chunkFiles == null) {
            System.out.println("error: 分块文件列表为空");
            return;
        }
        // 4.2 由数组变为list集合
        List<File> chunkFileList = Arrays.asList(chunkFiles);
        // 4.3 按文件名升序排序
        chunkFileList.sort((o1, o2) -> Integer.parseInt(o1.getName()) - Integer.parseInt(o2.getName()));
        // 5.创建合并文件的流对象
        RandomAccessFile rafWrite = new RandomAccessFile(mergeFile, "rw");
        // 6.设置每次读取的缓冲区大小
        byte[] b = new byte[1024];
        // 7.逐一读取分块文件,将数据写入到合并文件中
        for (File file : chunkFileList) {
            // 7.1 获取读取分块文件的流对象
            RandomAccessFile rafRead = new RandomAccessFile(file, "r");
            int len = -1;
            // 7.2 读取当前分块文件,每次读取的大小为设置的缓冲区的大小,直到将当前分块文件读取完毕
            while ((len = rafRead.read(b)) != -1) {
                // 7.3 将缓冲区数据写入到合并文件中
                rafWrite.write(b, 0, len);
            }
            // 7.4 关闭分块文件流对象,释放资源
            rafRead.close();
        }
        // 8.关闭流对象,释放资源
        rafWrite.close();
        // 9.校验合并后的文件是否正确
        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("合并成功");
        }
    }
}

3.前后端分离上传视频流程分析

  1. 前端上传文件前请求媒资接口层检查文件是否存在,如果已经存在则不再上传。
  2. 如果文件在系统不存在则前端开始上传,首先对视频文件进行分块。
  3. 前端分块进行上传,上传前首先检查分块是否上传,如已上传则不再上传,如果未上传则开始上传分块。
  4. 前端请求媒资管理接口层请求上传分块。
  5. 接口层请求服务层上传分块。
  6. 服务端将分块信息上传到MinIO。
  7. 前端将分块上传完毕请求接口层合并分块。
  8. 接口层请求服务层合并分块。
  9. 服务层根据文件信息找到MinIO中的分块文件,下载到本地临时目录,将所有分块下载完毕后开始合并 。
  10. 合并完成将合并后的文件上传到MinIO。

4.实战开发 - 思路分析

  1. 前端准备上传一个视频文件,需要先发送请求——检查文件是否已存在,携带参数为文件的Md5十六进制值。
  2. 后端根据 md5十六进制值 去数据库查询该文件是否存在。
  • 如果存在,则将文件信息加密返回,前端拿到加密信息再发送其它请求保存到数据库如课程资源表中。流程结束。
  • 如果不存在,就提醒前端不存在该文件,前端接下来就需要进行分块上传处理。
  1. 前端发现不存在该文件,就将文件进行分块,每上传一个分块文件前,都需要发送请求——检查当前分块文件是否存在,携带参数为文件的Md5十六进制值以及当前分块索引(第几块,从0开始)。
  2. 后端就根据参数去 minio 中查找是否存在该分块文件。
  • 如果存在了,就告诉前端不需要上传该分块文件了。
  • 如果不存在,就告诉前端需要发送请求上传分块文件。
  1. 前端发现不存在该分块文件,就发送请求——上传分块文件,携带参数为文件的分块文件,Md5十六进制值以及当前分块索引(第几块,从0开始)。
  2. 后端根据参数将 分块文件 保存在 minio 中。
  3. 所有分块文件上传完毕,前端发送请求——合并分块文件与上传合并后的文件,携带参数为文件的Md5十六进制值,文件名,文件标签,文件块总数。
  4. 后端就根据参数进行合并与上传处理:
  1. 从 minio 下载所有原文件的分块文件。
  2. 将分块文件进行合并处理。
  3. 计算合并后文件的md5值,如果和前端参数中的md5值一致,则说明正确合并。否则上传失败,流程结束。
  4. 再将合并后的文件断点续传到 minio。
  5. 将文件信息保存至数据库中。
  6. 最后将文件信息加密返回,前端拿到加密信息再发送其它请求保存到数据库如课程资源表中。流程结束。

5.实战开发 - 准备工作

5.1 数据库设计

CREATE TABLE service_media_file(
    `id` BIGINT UNSIGNED PRIMARY KEY COMMENT '主键id(雪花算法)',
    `file_name` VARCHAR(255) NOT NULL COMMENT '文件名称',
    `file_type` CHAR(2) NOT NULL COMMENT '文件类型:文本,图片,音频,视频,其它',
    `file_format` VARCHAR(128) NOT NULL COMMENT '文件格式', 
    `tag` VARCHAR(32) NOT NULL COMMENT '标签',
    `bucket` VARCHAR(32) NOT NULL COMMENT '存储桶',
    `file_path` VARCHAR(512) NOT NULL COMMENT '文件存储路径',
    `file_md5` CHAR(32) NOT NULL UNIQUE COMMENT '文件的md5值',
    `file_byte_size` BIGINT UNSIGNED NOT NULL COMMENT '文件的字节大小',
    `file_format_size` VARCHAR(24) NOT NULL COMMENT '文件的格式大小', 
    `user_id` BIGINT NOT NULL COMMENT '上传人id',
    `create_time` DATETIME NOT NULL COMMENT '创建时间(上传时间)',
    `update_time` DATETIME NOT NULL COMMENT '修改时间'
)ENGINE = INNODB  CHARACTER SET = utf8mb4 COMMENT '第三方服务-媒资文件表';

5.2 核心 pom.xml

<!--根据扩展名取mimetype-->
<dependency>
    <groupId>com.j256.simplemagic</groupId>
    <artifactId>simplemagic</artifactId>
    <version>1.17</version>
</dependency>
<!--对象存储服务-->
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.2.1</version>
</dependency>
<dependency>
    <groupId>me.tongfei</groupId>
    <artifactId>progressbar</artifactId>
    <version>0.5.3</version>
</dependency>
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.8.1</version>
</dependency>
<!--生成文件对象的md5十六进制值-->
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.13</version>
</dependency>
<!--用于文件类型判断-->
<dependency>
    <groupId>org.apache.tika</groupId>
    <artifactId>tika-core</artifactId>
    <version>2.4.0</version>
</dependency>

5.3 application.yaml核心配置

spring:
  servlet:
    multipart:
      max-file-size: 3MB
      max-request-size: 5MB
minio:
  # 指定连接的ip和端口
  endpoint: http://192.168.65.129:9000
  # 指定 访问秘钥(也称用户id)
  accessKey: minioadmin
  # 指定 私有秘钥(也称密码)
  secretKey: minioadmin

5.4 MinioConfig.java配置类

package com.zhulang.waveedu.service.config;
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @author 狐狸半面添
 * @create 2023-02-08 16:26
 */
@Configuration
public class MinioConfig {
    /**
     * 连接的ip和端口
     */
    @Value("${minio.endpoint}")
    private String endpoint;
    /**
     * 访问秘钥(也称用户id)
     */
    @Value("${minio.accessKey}")
    private String accessKey;
    /**
     * 私有秘钥(也称密码)
     */
    @Value("${minio.secretKey}")
    private String secretKey;
    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
    }
}


相关文章
|
6月前
|
JavaScript 前端开发 Java
springboot整合minio+vue实现大文件分片上传,断点续传(复制可用,包含minio工具类)
springboot整合minio+vue实现大文件分片上传,断点续传(复制可用,包含minio工具类)
1696 0
|
6月前
|
前端开发 NoSQL Redis
如何实现大文件上传:秒传、断点续传、分片上传
如何实现大文件上传:秒传、断点续传、分片上传
511 0
|
6月前
|
存储
fastdfs源码阅读:上传和下载(文件客户端逻辑)
fastdfs源码阅读:上传和下载(文件客户端逻辑)
253 0
|
6月前
|
Kubernetes Windows 容器
minio上传下载
minio上传下载
133 0
|
存储 前端开发 NoSQL
注册java实现文件分片上传并且断点续传
一、简单的分片上传 针对第一个问题,如果文件过大,上传到一半断开了,若重新开始上传的话,会很消耗时间,并且你也并不知道距离上次断开时,已经上传到哪一部分了。因此我们应该先对大文件进行分片处理,防止上面提到的问题。
|
存储 前端开发 算法
minio&前后端分离上传视频/上传大文件——前后端分离断点续传&minio分片上传实现(三)
minio&前后端分离上传视频/上传大文件——前后端分离断点续传&minio分片上传实现
615 0
|
数据安全/隐私保护
minio&前后端分离上传视频/上传大文件——前后端分离断点续传&minio分片上传实现(二)
minio&前后端分离上传视频/上传大文件——前后端分离断点续传&minio分片上传实现
421 0
|
存储 分布式计算 网络协议
文件上传下载系列——大文件分片上传
文件上传下载系列——大文件分片上传
|
前端开发
后端处理图片的上传和下载
后端处理图片的上传和下载
166 0
|
前端开发 关系型数据库 MySQL
大文件上传
大文件上传
166 0