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

简介: 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();
    }
}


相关文章
|
存储 JavaScript API
vue3+ant design vue 自定义文件上传和自定义上传进度条
1.关键点是Upload组件中customRequest的API,它能通过覆盖默认的上传行为,可以自定义自己的上传实现(具体可看官方文档); 2.通过在Upload组件中绑定progress实现。 下面是详细代码
4085 0
|
开发工具 iOS开发 MacOS
解决VScode文件无法编辑(删除键 换行键失去作用)
解决VScode文件无法编辑(删除键 换行键失去作用)
4150 0
|
存储 设计模式 缓存
OkHttp源码解析(小白必看,建议收藏)
本文详细解析了OkHttp3的源码及其实现机制,适合初学者学习。文章从Request与Response结构、HTTPS握手过程、响应码含义、Socket概念到责任链模式的应用逐一讲解。重点分析了OkHttp的工作流程:通过Call对象的enqueue()或execute()方法发起请求,核心是getResponseWithInterceptorChain(),利用拦截器链处理请求,包括用户自定义拦截器、重试、桥接、缓存、连接等步骤。最后还探讨了Dispatcher的任务调度机制。
1421 71
|
11月前
|
机器学习/深度学习 文字识别 算法
报关单OCR识别-进出口海关报关单识别接口返回参数-文字识别
报关单识别接口基于OCR与深度学习技术,精准提取进出口报关单关键信息,输出结构化数据。支持API调用与私有化部署,适用于智能通关、跨境物流等场景,提升申报效率与准确性。
|
存储 机器学习/深度学习 人工智能
阿里云环境下 Runway 深度部署:从技术原理到 AIGC 视频生成落地
Runway作为AI视频生成标杆,融合扩散模型与多模态技术,依托潜空间优化与时空注意力机制,实现高效高质视频生成。结合阿里云算力与API生态,支持版权合规、运镜控制与多模态联动,广泛应用于影视、广告与游戏领域,推动内容创作智能化升级。
1690 0
|
存储 Kubernetes API
Kubernetes权威指南-原理篇
Kubernetes核心组件通过声明式API与调和循环实现自动化管控,API Server为中枢,etcd存储集群状态,Controller Manager驱动控制器维护期望状态,Scheduler负责Pod调度,Kubelet管理Pod生命周期,Kube Proxy实现服务网络代理。安全体系涵盖认证、授权与准入控制,网络基于CNI模型支持多种方案,存储通过PV/PVC解耦并支持动态供给,CSI推动存储生态标准化。
869 42
|
人工智能 测试技术 API
PaperBench:OpenAI开源AI智能体评测基准,8316节点精准考核复现能力
PaperBench是OpenAI推出的开源评测框架,通过8316个评分节点系统评估AI智能体复现学术论文的能力,涵盖理论理解、代码实现到实验执行全流程。
1146 30
PaperBench:OpenAI开源AI智能体评测基准,8316节点精准考核复现能力
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
812 0
探索Spring Boot的@Conditional注解的上下文配置
|
数据安全/隐私保护 安全
单点登录(SSO)看这一篇就够了
背景 在企业发展初期,企业使用的系统很少,通常一个或者两个,每个系统都有自己的登录模块,运营人员每天用自己的账号登录,很方便。但随着企业的发展,用到的系统随之增多,运营人员在操作不同的系统时,需要多次登录,而且每个系统的账号都不一样,这对于运营人员来说,很不方便。
283987 15
|
Kubernetes 负载均衡 前端开发
二进制部署Kubernetes 1.23.15版本高可用集群实战
使用二进制文件部署Kubernetes 1.23.15版本高可用集群的详细教程,涵盖了从环境准备到网络插件部署的完整流程。
1238 4
二进制部署Kubernetes 1.23.15版本高可用集群实战