使用MinIO文件存储系统【完成视频断点续传】业务逻辑

news2024/11/26 16:50:26

目录

视频上传

接口一:检查该视频/媒资文件是否已经上传完成

接口二:检查视频分块是否已经在minio中已经存在

接口三:上传分块文件到minio中(已经上传的分块会在接口二进行校验)

接口四:合并上传的分块文件保存文件合并后的文件信息


视频上传

视频上传流程图

 

接口一:检查该视频/媒资文件是否已经上传完成

首先:前端计算上传视频的MD5传递给后端,后端接收到MD5值之后,通过MD5到媒资管理表中查询是否存在该文件信息,如果存在并且从媒资管理表中获取这个文件存放在minio的位置,通过位置到minio分布式文件系统中查询文件是否存在,如果mysql媒资信息表和minio中的媒资文件都存在返回true,无需进行后续上传,如果不存在返回前端false用来。

controller层

    @ApiOperation(value = "文件上传前检查文件")
    @PostMapping("/upload/checkfile")
    public RestResponse<Boolean> checkfile(@RequestParam("fileMd5") String fileMd5) throws Exception {
        RestResponse<Boolean> booleanRestResponse = mediaFilesService.checkFile(fileMd5);
        return booleanRestResponse;
    }

 service层

接口:

    public RestResponse<Boolean> checkFile(String fileMd5);

实现类:

    /**
     * 通过md5值在数据库判断该文件是否存在
     * @param fileMd5 文件的md5
     * @return
     */
    @Override
    public RestResponse<Boolean> checkFile(String fileMd5) {
        MediaFiles mediaFiles = baseMapper.selectById(fileMd5);
        //说明在数据库中已经存在
        if (mediaFiles!=null){
            //检查在minio中是否存在
            //桶
            String bucket = mediaFiles.getBucket();
            //存储目录
            String filePath = mediaFiles.getFilePath();
            //文件流
            InputStream stream = null;
            try {
                stream = minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(filePath).build());
                if (stream!=null){
                    //return 文件已经存在
                    return RestResponse.success(true);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return RestResponse.success(false);
    }

 

接口二:检查视频分块是否已经在minio中已经存在

首先接收前端上传媒资文件所生成的md5和分块序号,通过文件的md5值和文件每一块的序号组成在minio中存储分块文件的位置,获取该位置的分块文件是否存在,如果存在返回true(说明该分块文件已经在此前完成过上传,无需再次上传),如果不存在返回false,说明该分块文件还未上传,重新上传这一块文件。

controller层(控制层)

    @ApiOperation(value = "分块文件上传前的检测")
    @PostMapping("/upload/checkchunk")
    public RestResponse<Boolean> checkchunk(@RequestParam("fileMd5") String fileMd5, @RequestParam("chunk") int chunk) throws Exception {
        RestResponse<Boolean> booleanRestResponse = mediaFilesService.checkChunk(fileMd5,chunk);
        return booleanRestResponse;
    }

 service层(业务逻辑层)

接口:

    public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex);

接口实现类:

 /**
     * 检查分块是否已经存在
     * @param fileMd5  文件的md5
     * @param chunkIndex  分块序号
     * @return
     */
    @Override
    public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex) {
        //获取所在的桶
        String bucket = BUCKET_VIDEO;
        //分块文件的路径
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5) + chunkIndex;

        //文件流
        InputStream inputStream = null;
        try {
            inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(chunkFileFolderPath).build());
        } catch (Exception e) {
            e.printStackTrace();
            log.debug("分块不存在");
        }
        if (inputStream !=null){
            return RestResponse.success(true);
        }
        return RestResponse.success(false);
    }

 

接口三:上传分块文件到minio中(已经上传的分块会在接口二进行校验)

接收前端传来的分割好的5M文件和这个文件的分割序号以及完整文件的md5值,将文件在本地转储一下,进行上传,如果是用户原先上传一半停止了上传或者由于网络波动导致上传中断,下一次上传原先文件时会在接口二中判断原先文件的哪几块已经存在minio中,存在的分块无需再次上传,不存在的分块走上传分块文件接口进行上传分块文件。

controller层(控制层)

    @ApiOperation(value = "上传分块文件")
    @PostMapping("/upload/uploadchunk")
    public RestResponse uploadchunk(@RequestParam("file") MultipartFile file, @RequestParam("fileMd5") String fileMd5, @RequestParam("chunk") int chunk) throws Exception {
        System.out.println(fileMd5);
        System.out.println("分块文件序号:"+chunk);
        //创建临时文件
        File tempFile = File.createTempFile("minio", "temp");
        //上传的文件拷贝到临时文件
        file.transferTo(tempFile);
        //文件路径
        String absolutePath = tempFile.getAbsolutePath();

        RestResponse restResponse = mediaFilesService.uploadChunk(fileMd5, chunk, absolutePath);
        return restResponse;
    }

service(业务逻辑层)

接口:

    /**
     * @description 上传分块
     * @param fileMd5  文件md5
     * @param chunk  分块序号
     * @param  localfChunkFilePath 本地文件路径
     */
    public RestResponse uploadChunk(String fileMd5,int chunk,String localfChunkFilePath);

实现类:

@Override
    public RestResponse uploadChunk(String fileMd5, int chunk, String localfChunkFilePath) {
        //得到分块文件的路径
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //
        String chunkFilePath = chunkFileFolderPath + chunk;
        //得到文件类型
        String mimeType = getMimeType(null);
        //上传分块文件到minio
        boolean flag = false;
        try {
            flag = addMediaFilesToMinIO(localfChunkFilePath, mimeType, BUCKET_VIDEO, chunkFilePath);
        } catch (Exception e) {
            log.debug("上传分块文件:{},失败:{}",chunkFilePath,e.getMessage());
        }
        if (!flag){
            return RestResponse.validfail(false,"上传分块文件失败");
        }

        return RestResponse.success(true);
    }

上传文件的方法

    /**
     * @description 将文件写入minIO
     * @param localFilePath  文件地址
     * @param bucket  桶
     * @param objectName 对象名称
     */
    public boolean addMediaFilesToMinIO(String localFilePath,String mimeType,String bucket, String objectName) {
        try {
            UploadObjectArgs testbucket = UploadObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .filename(localFilePath)
                    .contentType(mimeType)
                    .build();
            minioClient.uploadObject(testbucket);
            log.info("上传文件到minio成功,bucket:{},objectName:{}",bucket,objectName);
            System.out.println("上传成功");
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("上传文件到minio出错,bucket:{},objectName:{},错误原因:{}",bucket,objectName,e.getMessage(),e);
            XueChengPlusException.cast("上传文件到文件系统失败");
        }
        return false;
    }

 

接口四:合并上传的分块文件保存文件合并后的文件信息

当前端按照5M将文件切分好依次上传所有之后,会请求接口四(合并文件),后端接收原文件的md5值和上传文件的名称和分块总数,在业务层进行处理,

1:首先会通过md5值获取在minio中存储该文件的分块位置目录

2:通过接收到分块总数和存储分块的位置得到所有分块文件的存储的具体位置存放到集合中

3:通过位置路径调用minio的合并api,将文件合并。

4:将合并好的文件临时下载到本地获得合并文件的md5值和前端上传文件的md5进行比较

5:删除临时本地的临时文件,如果校验通过存储该文件的相关信息到mysql媒资表中

6:就是将minio中存储的分块文件给删除。

controller层

    @ApiOperation(value = "合并文件")
    @PostMapping("/upload/mergechunks")
    public RestResponse mergechunks(@RequestParam("fileMd5") String fileMd5, @RequestParam("fileName") String fileName, @RequestParam("chunkTotal") int chunkTotal) throws Exception {
        System.out.println(fileMd5);
        System.out.println(chunkTotal);
        Long companyId = 1232141425L;
        UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
        uploadFileParamsDto.setFileType("001002");
        uploadFileParamsDto.setTags("课程视频");
        uploadFileParamsDto.setRemark("");
        uploadFileParamsDto.setFilename(fileName);
        return mediaFilesService.mergechunks(companyId,fileMd5,chunkTotal,uploadFileParamsDto);
    }

业务层

接口

    /**
     * @description 合并分块
     * @param companyId  机构id
     * @param fileMd5  文件md5
     * @param chunkTotal 分块总和
     * @param uploadFileParamsDto 文件信息
     */
    public RestResponse mergechunks(Long companyId,String fileMd5,int chunkTotal,UploadFileParamsDto uploadFileParamsDto);

实现类:

    @Override
    public RestResponse mergechunks(Long companyId, String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto) {
        //=====获取分块文件路径=====
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //组成将分块文件路径组成 List<ComposeSource>
        List<ComposeSource> sourceObjectList = Stream.iterate(0, i -> ++i)
                .limit(chunkTotal)
                .map(i -> ComposeSource.builder()
                        .bucket(BUCKET_VIDEO)
                        .object(chunkFileFolderPath.concat(Integer.toString(i)))
                        .build())
                .collect(Collectors.toList());
        //=====合并=====
        //文件名称
        String fileName = uploadFileParamsDto.getFilename();
        //文件扩展名
        String extName = fileName.substring(fileName.lastIndexOf("."));
        //合并文件路径
        String mergeFilePath = getFilePathByMd5(fileMd5, extName);
        try {
            //合并文件
            ObjectWriteResponse response = minioClient.composeObject(
                    ComposeObjectArgs.builder()
                            .bucket(BUCKET_VIDEO)
                            .object(mergeFilePath)
                            .sources(sourceObjectList)
                            .build());
            log.info("合并文件成功:{}",mergeFilePath);
        } catch (Exception e) {
            log.info("合并文件失败,fileMd5:{},异常:{}",fileMd5,e.getMessage(),e);
            return RestResponse.validfail(false, "合并文件失败。");
        }

        // ====验证md5====
        File minioFile = downloadFileFromMinIO(BUCKET_VIDEO,mergeFilePath);
        if(minioFile == null){
            log.debug("下载合并后文件失败,mergeFilePath:{}",mergeFilePath);
            return RestResponse.validfail(false, "下载合并后文件失败。");
        }

        try (InputStream newFileInputStream = new FileInputStream(minioFile)) {
            //minio上文件的md5值
            String md5Hex = DigestUtils.md5Hex(newFileInputStream);
            //比较md5值,不一致则说明文件不完整
            if(!fileMd5.equals(md5Hex)){
                return RestResponse.validfail(false, "文件合并校验失败,最终上传失败。");
            }
            //文件大小
            uploadFileParamsDto.setFileSize(minioFile.length());
        }catch (Exception e){
            log.debug("校验文件失败,fileMd5:{},异常:{}",fileMd5,e.getMessage(),e);
            return RestResponse.validfail(false, "文件合并校验失败,最终上传失败。");
        }finally {
            if(minioFile!=null){
                minioFile.delete();
            }
        }

        //文件入库
        currentProxy.addMediaFilesToDb(companyId,fileMd5,uploadFileParamsDto,BUCKET_VIDEO,mergeFilePath);
        //=====清除分块文件=====
        clearChunkFiles(chunkFileFolderPath,chunkTotal);
        return RestResponse.success(true);
    }

下载合并文件的方法

    /**
     * 从minio下载文件
     * @param bucket 桶
     * @param objectName 对象名称
     * @return 下载后的文件
     */
    public File downloadFileFromMinIO(String bucket,String objectName){
        //临时文件
        File minioFile = null;
        FileOutputStream outputStream = null;
        try{
            InputStream stream = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .build());
            //创建临时文件
            minioFile=File.createTempFile("minio", ".merge");
            outputStream = new FileOutputStream(minioFile);
            IOUtils.copy(stream,outputStream);
            return minioFile;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

清楚分块文件的方法

 /**
     * 清除分块文件
     * @param chunkFileFolderPath 分块文件路径
     * @param chunkTotal 分块文件总数
     */
    /**
     * 清除分块文件
     * @param chunkFileFolderPath 分块文件路径
     * @param chunkTotal 分块文件总数
     */
    private void clearChunkFiles(String chunkFileFolderPath,int chunkTotal){

        try {
            List<DeleteObject> deleteObjects = Stream.iterate(0, i -> ++i)
                    .limit(chunkTotal)
                    .map(i -> new DeleteObject(chunkFileFolderPath.concat(Integer.toString(i))))
                    .collect(Collectors.toList());

            RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket(BUCKET_VIDEO).objects(deleteObjects).build();
            Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
            results.forEach(r->{
                DeleteError deleteError = null;
                try {
                    deleteError = r.get();
                } catch (Exception e) {
                    e.printStackTrace();
                    log.error("清楚分块文件失败,objectname:{}",deleteError.objectName(),e);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            log.error("清楚分块文件失败,chunkFileFolderPath:{}",chunkFileFolderPath,e);
        }
    }

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/551643.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

git生成密钥方法

1、密钥生成 打开Git Bash&#xff0c;查看ls ~/.ssh下是否有密钥文件id_rsa*&#xff0c;有的话可先进行备份。 然后用如下命令生成新密钥&#xff1a; ssh-keygen -t rsa -C "your_emailexample.com" 参数含义&#xff1a; -t 指定密钥类型&#xff0c;默认是 …

chatgpt赋能Python-pythonbif

Python Bif: 简介和使用指南 Python Bif是一种用于机器学习和数据科学的强大包。它提供了一些重要的功能&#xff0c;例如分类、集成和回归。这篇文章将向您介绍Python Bif的主要特性和如何使用它来完成各种数据科学任务。 什么是Python Bif&#xff1f; Python Bif是一个通…

Spring源码阅读:AOP原理

一、概述 以下便是Spring Aop的流程&#xff0c;下面我将一一介绍下面的各个方法。 下面是流程中的主要方法。 二、测试代码 下面我将写一个例子介绍Spring Aop的流程。 被增强类&#xff1a; public class MyCalculator {public Integer add(Integer i, Integer j) throw…

逼近GPT-4!BLOOMChat: 开源可商用支持多语言的大语言模型

背景 SambaNova和Together这2家公司于2023.05.19开源了可商用的支持多语言的微调模型BLOOMChat。 SambaNova这家公司专注于为企业和政府提供生成式AI平台&#xff0c;Together专注于用开源的方式打造一站式的foundation model&#xff0c;赋能各个行业。 OpenAI的GPT-4和Goo…

【将maven源改为国内阿里云镜像】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 1. 如何配置Maven镜像&#xff1f; 2. Idea中m…

【Java入门】初识Java

前言 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff0c;喜爱音乐的一位博主。 &#x1f4d7;本文收录于Java入门篇系列&#xff0c;该专栏主要讲解&#xff1a;什么是java、java的数据类型与变…

MATLAB|004|MATLAB M-Files|MATLAB数据类型

MATLAB M-Files 我们一直强调MATLAB是一个功能强大的编程语言及交互式计算环境&#xff0c;之前学习的内容中&#xff0c;我们学会了在 MATLAB 命令提示符下输入命令&#xff0c;而且我们主要是把MATLAB环境作为一个计算器使用。其实&#xff0c;MATLAB 还允许写入到一个文件中…

5分钟掌握利用pycharm插件BitoAI 实现chatgpt自动编写代码

一、BitoAI 简介 最近出现了一款新型编程助手BitoAI。今天的主要内容就是给大家介绍它&#xff0c;号称 IDE 的“瑞士军刀”&#xff0c;可以提升开发 10 倍的效率。 简言之它的强大之处就是可以通过类似于ChatGPT对话的方式来编写代码&#xff0c;分析代码&#xff0c;生成代…

Flutter控件之Tab选项卡封装

Tab选项卡&#xff0c;这是一个非常常见且权重很高的一个组件&#xff0c;随便打开一个App&#xff0c;比如CSDN&#xff0c;如下图&#xff0c;首页顶部就是一个Tab选项卡&#xff0c;这个功能可以说&#xff0c;几乎每个App都会存在。 在Android中&#xff0c;我们可以使用Ta…

“Shell“iptales防火墙设置

文章目录 一.Linux防火墙基础1.1Linux包过滤防火墙概述1.2四表五链1.3规则链之间的匹配顺序1.4规则链内的匹配顺序1.5总结 二.编写防火墙规则2.1iptables防火墙的配置方法2.2规则的匹配2.3命令使用 一.Linux防火墙基础 1.1Linux包过滤防火墙概述 Linux 系统的防火墙: IP信息包…

惯性导航论文详解:神经惯性定位

来源&#xff1a;投稿 作者&#xff1a;小灰灰 编辑&#xff1a;学姐 论文标题&#xff1a;Neural Inertial Localization 论文链接: https://arxiv.org/pdf/2203.15851v1.pdf 图1.从IMU测量到位置估计。给定惯性传感器数据&#xff08;左&#xff09;&#xff0c;我们的方法…

C#,生信软件实践(04)——DNA数据库EMBL格式文件的解释器之完整源代码

EMBL文件的格式详解请阅读前面的文章&#xff1a; C#&#xff0c;生信软件实践&#xff08;02&#xff09;——DNA数据库EMBL格式详解及转为FASTA格式文件的源代码https://blog.csdn.net/beijinghorn/article/details/130462070 本文的代码用于&#xff1a; &#xff08;1&a…

Linux---文件操作命令(cp、mv、rm)

1. cp命令 cp命令可以用于复制文件\文件夹&#xff0c;cp命令来自英文单词&#xff1a;copy。 语法&#xff1a;cp [选项] 参数1 参数2 参数1&#xff1a;Linux路径&#xff0c;表示被复制的文件或文件夹 参数2&#xff1a;Linux路径&#xff0c;表示要复制去的地方 选…

《Java 核心技术面试》课程笔记(十一)

Java 提供了哪些 IO 方式&#xff1f; 典型回答 Java IO 基于不同的 IO 抽象模型和交互方式&#xff0c;可以分为&#xff1a; BIO&#xff0c;传统的 java.io 包&#xff0c;它基于流模型实现。 提供了我们最熟知的⼀些 IO 功能&#xff0c;比如 File 抽象、输入输出流等。交…

安装编译PostgreSql15.3.0

一、下载源码 方式一 官网手动下载 https://www.postgresql.org/download/. 解压 tar -zxvf postgresql-14.2.tar.gz方式二 git clone git clone https://github.com/postgres/postgres.git解压或下载后计入postgres目录 cd postgres-15.3二、创建目录 用root账户创建 创建…

[iOS开发]<多线程-NSOperation操作队列NSOperationQueue>

前言 寒假期间学习过GCD。今天学习NSOperation。同样都是多线程封装&#xff0c;NSOperation和NSOperationQueue是基于GCD的更高一层的封装&#xff0c;完全的面向对象&#xff0c;相比于GCD复杂的各种API方法&#xff0c;它的优势就是更加的简单实用&#xff0c;代码的可读性…

电力电子课设—数控产生PWM波——使用51单片机输出占空比可调PWM波(按钮控制、数码管显示)控制速成教程

我们学校电气专业开始做电力电子的课设了&#xff0c;小组选了一项制作硬件电路的任务&#xff0c;里面有要求采用数控方式实现DC-DC电压变换的输出电压调节&#xff0c;数控在电路中的体现就是用单片机输出可调占空比的PWM作用于产生PWM波控制IGBT的芯片。考虑到可能有同学没接…

金领冠520解密母乳源代码,助推婴配粉中国式现代化高速发展

又是一年520&#xff0c;又是一个“全国母乳喂养宣传日”。 1990年5月10日&#xff0c;为保护、促进和支持母乳喂养&#xff0c;更好地实行优生优育&#xff0c;原中华人民共和国国家卫生部召开新闻发布会&#xff0c;确立每年5月20日为“全国母乳喂养宣传日”。 那时&#x…

[GXYCTF2019]BabySQli1

拿到题目一看就是sql注入&#xff0c;所以还是老样子账号admin密码随便输入&#xff0c;回显但是密码错误 当用户名随便输入时&#xff0c;回显用户名错误&#xff0c;说明是先检测用户名&#xff0c;再检测密码 应该是存在过滤 通过burp爆破大致找出过滤字符&#xff0c;还有就…

css flex布局

css flex布局 flex是flexible Box的缩写&#xff0c;意为“弹性布局”&#xff0c;任何一个容器都可以指定为flex布局。 当我们为父盒子设为flex布局以后&#xff0c;子元素的float、clear和vertical-align属性将失效 总结flex布局原理&#xff1a; 就是通过给父盒子添加fl…