minio 后端大文件分片上传,合并,删除分片

背景

网上大多数minio大文件上传都是采用后台返回前端预上传链接,然后由前端去put请求直接和minio通信上传分片文件,然后调用后台合并分片逻辑来达到快申诉上传的目的,详情可以参考我的上两篇文章

最近有个项目域名是https的,但是上传大文件走https太慢,而且服务器配置很拉跨,https里走http预上传不知道为啥老是报错。所以研究下直接从后台分片,然后逐个上传,然后合并,删除分片。

springboot+elementui

集成minio

 <dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.3.1</version>
</dependency>

yml配置

minio:url: http://127.0.0.1:9000 //用于后台内部domain: http://127.0.0.1:9000//用于返回给前端,这个可以改成线上域名accessKey: minioadminsecretKey: minioadminbucketName: minioBackName#默认文件存放路径filePath: common/

MinioConfig 配置

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.minio.MinioClient;/*** Minio 配置信息** @author */
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig
{/*** 服务地址*/private String url;private String domain;public String getDomain() {return domain;}public void setDomain(String domain) {this.domain = domain;}/*** 用户名*/private String accessKey;/*** 密码*/private String secretKey;/*** 存储桶名称*/private String bucketName;/*** 文件存储指定位置路径*/private String filePath;public String getUrl(){return url;}public void setUrl(String url){this.url = url;}public String getAccessKey(){return accessKey;}public void setAccessKey(String accessKey){this.accessKey = accessKey;}public String getSecretKey(){return secretKey;}public void setSecretKey(String secretKey){this.secretKey = secretKey;}public String getBucketName(){return bucketName;}public void setBucketName(String bucketName){this.bucketName = bucketName;}public String getFilePath() {return filePath;}public void setFilePath(String filePath) {this.filePath = filePath;}@Beanpublic MinioClient getMinioClient(){return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();}
}

ISysFileService 文件上传接口

import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** 文件上传接口** @author */
public interface ISysFileService {/*** 文件上传接口** @param file 上传的文件* @return 访问地址* @throws Exception*/public String uploadFile(MultipartFile file) throws Exception;/*** 异步文件上传接口** @param file 上传的文件* @return 访问地址* @throws Exception*/public String uploadFileAsync(MultipartFile file) throws Exception;}

MinioSysFileServiceImpl 实现类

/*** Minio 文件存储** @author */
@Primary
@Service
@Slf4j
public class MinioSysFileServiceImpl implements ISysFileService {//minio每个分片不能低于5MB,最后一个分片可以不管 13MB文件可分成3个分片 5MB 5MB 3MBprivate static final int PART_SIZE = 5 * 1024 * 1024; // 5MB parts/*** minio基础参数 配置类*/@Autowiredprivate MinioConfig minioConfig;/*** minio客户端连接 连接minio工具*/@Autowiredprivate MinioClient client;/*** 本地文件上传接口** @param file 上传的文件* @return 访问地址* @throws Exception*/@Overridepublic String uploadFile(MultipartFile file) throws Exception {String fileName = file.getOriginalFilename();//获取文件名称fileName = minioConfig.getFilePath()+ DateUtils.getDate()+"/"+fileName.substring(fileName.lastIndexOf("/") + 1, fileName.length());long startTime = System.currentTimeMillis()/1000;//获取文件流InputStream inputStream = file.getInputStream();//获取文件大小long fileSize = file.getSize();//计算分片数量int partCount = (int) (fileSize / PART_SIZE);if (fileSize % PART_SIZE > 0) {partCount++;}long partTime =  System.currentTimeMillis()/1000;System.out.println("分片耗时"+(partTime-startTime));//存放分片流List<InputStream> parts = new ArrayList<>();//存放分片minio地址List<String> fileList = new ArrayList<>();//分配分片流 for (int i = 0; i < partCount; i++) {// 每次只需要从原始文件InputStream中读取指定大小的数据即可byte[] partData = new byte[PART_SIZE];int read = inputStream.read(partData);if (read == -1) {break; // 文件已经读完了}// 将读取的数据作为一个新的InputStream添加到parts列表中parts.add(new ByteArrayInputStream(partData, 0, read));}long readTime = System.currentTimeMillis()/1000;System.out.println("读取文件耗时"+(readTime-partTime));//上传分片流到miniofor (int i = 0; i < parts.size(); i++) {// 构建每个part的object nameString partObjectName = fileName + ".part" + i;fileList.add(partObjectName);InputStream partStream = parts.get(i);PutObjectArgs args = PutObjectArgs.builder().bucket(minioConfig.getBucketName()).object(partObjectName).stream(partStream, partStream.available(), -1).contentType(file.getContentType()).build();ObjectWriteResponse objectWriteResponse = client.putObject(args);//System.out.println("分片上传结果======++++++"+objectWriteResponse);}long upLoadTime = System.currentTimeMillis()/1000;System.out.println("上传分片耗时"+(upLoadTime-readTime));//关闭主文件输入流和分片输入流inputStream.close();for (InputStream part : parts) {part.close();}//获取需要合并的分片组装成ComposeSourceList<ComposeSource> sourceObjectList = new ArrayList<>(fileList.size());for (String chunk : fileList){sourceObjectList.add(ComposeSource.builder().bucket(minioConfig.getBucketName()).object(chunk).build());}//合并分片ComposeObjectArgs composeObjectArgs = ComposeObjectArgs.builder().bucket(minioConfig.getBucketName())//合并后的文件的objectname.object(fileName)//指定源文件.sources(sourceObjectList).build();client.composeObject(composeObjectArgs);long mergeTime = System.currentTimeMillis()/1000;System.out.println("合并分片耗时"+(mergeTime-upLoadTime));//删除已经上传的分片,组装成DeleteObjectList<DeleteObject> collect = fileList.stream().map(DeleteObject::new).collect(Collectors.toList());//执行删除RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket(minioConfig.getBucketName()).objects(collect).build();Iterable<Result<DeleteError>> results = client.removeObjects(removeObjectsArgs);//如果没有下面try的代码,文件史删除不了的,加上下面的代码就可以删除了try{for (Result<DeleteError> result : results){DeleteError deleteError = result.get();System.out.println("error in deleteing object"+deleteError.objectName()+";"+deleteError.message());}}catch (Exception e){System.out.println("minio删除文件失败");e.printStackTrace();}long deleteTime = System.currentTimeMillis()/1000;System.out.println("删除分片耗时"+(deleteTime-mergeTime));return fileName;}/*** 异步上传大文件采用链式** @param file* @return* @throws Exception*/@Overridepublic String uploadFileAsync(MultipartFile file) throws Exception {long startTimes = System.currentTimeMillis() / 1000;assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);String fileName = FileUploadUtils.extractFilename(file);fileName = minioConfig.getFilePath() + DateUtils.getDate() + "/" + fileName.substring(fileName.lastIndexOf("/") + 1, fileName.length());InputStream inputStream = file.getInputStream();ForkJoinPool pool = new ForkJoinPool();// 创建分片流异步执行任务:读取大文件分成N个流CompletableFuture<List<InputStream>> createPartNumTask = CompletableFuture.supplyAsync(() -> {List<InputStream> parts = new ArrayList<>();long fileSize = file.getSize();int partCount = (int) (fileSize / PART_SIZE);if (fileSize % PART_SIZE > 0) {partCount++;}long startTime = System.currentTimeMillis() / 1000;for (int i = 0; i < partCount; i++) {// 每次只需要从原始文件InputStream中读取指定大小的数据即可byte[] partData = new byte[PART_SIZE];int read = 0;try {read = inputStream.read(partData);} catch (IOException e) {e.printStackTrace();}if (read == -1) {break; // 文件已经读完了}// 将读取的数据作为一个新的InputStream添加到parts列表中parts.add(new ByteArrayInputStream(partData, 0, read));}long endTime = System.currentTimeMillis() / 1000;System.out.println(Thread.currentThread() + "执行创建分片流任务耗时->" + (endTime - startTime) + "秒");return parts;}, pool);//createPartNum关联的异步任务的返回值作为方法入参,传入到thenApply的方法中//thenApply这里实际创建了一个新的CompletableFuture实例String finalFileName = fileName;CompletableFuture<List<String>> createUploadTask = createPartNumTask.thenApply((parts) -> {long startTime = System.currentTimeMillis() / 1000;List<String> fileList = new ArrayList<>();for (int i = 0; i < parts.size(); i++) {// 构建每个part的object nameString partObjectName = finalFileName + ".part" + i;fileList.add(partObjectName);InputStream partStream = parts.get(i);PutObjectArgs args = null;try {args = PutObjectArgs.builder().bucket(minioConfig.getBucketName()).object(partObjectName).stream(partStream, partStream.available(), -1).contentType(file.getContentType()).build();} catch (IOException e) {e.printStackTrace();}try {ObjectWriteResponse objectWriteResponse = client.putObject(args);} catch (ErrorResponseException e) {e.printStackTrace();} catch (InsufficientDataException e) {e.printStackTrace();} catch (InternalException e) {e.printStackTrace();} catch (InvalidKeyException e) {e.printStackTrace();} catch (InvalidResponseException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (ServerException e) {e.printStackTrace();} catch (XmlParserException e) {e.printStackTrace();}}try {inputStream.close();} catch (IOException e) {e.printStackTrace();}for (InputStream part : parts) {try {part.close();} catch (IOException e) {e.printStackTrace();}}long endTime = System.currentTimeMillis() / 1000;System.out.println(Thread.currentThread() + "执行上传分片流任务->" + (endTime - startTime) + "秒");return fileList;});String finalFileName1 = fileName;CompletableFuture<List<String>> megreTask = createUploadTask.thenApply((fileList) -> {long startTime = System.currentTimeMillis() / 1000;List<ComposeSource> sourceObjectList = new ArrayList<>(fileList.size());for (String chunk : fileList) {sourceObjectList.add(ComposeSource.builder().bucket(minioConfig.getBucketName()).object(chunk).build());}ComposeObjectArgs composeObjectArgs = ComposeObjectArgs.builder().bucket(minioConfig.getBucketName())//合并后的文件的objectname.object(finalFileName1)//指定源文件.sources(sourceObjectList).build();try {client.composeObject(composeObjectArgs);} catch (ErrorResponseException e) {e.printStackTrace();} catch (InsufficientDataException e) {e.printStackTrace();} catch (InternalException e) {e.printStackTrace();} catch (InvalidKeyException e) {e.printStackTrace();} catch (InvalidResponseException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (ServerException e) {e.printStackTrace();} catch (XmlParserException e) {e.printStackTrace();}long endTime = System.currentTimeMillis() / 1000;System.out.println(Thread.currentThread() + "执行合并分片任务->" + (endTime - startTime) + "秒");return fileList;});CompletableFuture<Boolean> deleteTask = megreTask.thenApply((fileList) -> {long startTime = System.currentTimeMillis() / 1000;List<DeleteObject> collect = fileList.stream().map(DeleteObject::new).collect(Collectors.toList());RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket(minioConfig.getBucketName()).objects(collect).build();Iterable<Result<DeleteError>> results = client.removeObjects(removeObjectsArgs);try {for (Result<DeleteError> result : results) {DeleteError deleteError = result.get();System.out.println("error in deleteing object" + deleteError.objectName() + ";" + deleteError.message());}} catch (Exception e) {System.out.println("minio删除文件失败");e.printStackTrace();}long endTime = System.currentTimeMillis() / 1000;System.out.println(Thread.currentThread() + "执行删除分片任务->" + (endTime - startTime) + "秒");return true;});long endTimes = System.currentTimeMillis() / 1000;System.out.println("主线程执行耗时" + (endTimes - startTimes) + "秒");return fileName;}}

controller 访问接口

@RestController
public class CommonController
{@Value("${minio.domain}")//线上的域名private String minioUrl;@Value("${minio.bucketName}")//桶名private String minioBucketName;/*** 文件上传请求*/@PostMapping("/common/upload/minio")public AjaxResult upload(MultipartFile file){try {String url = sysFileService.uploadFile(file);String filePath = minioUrl.+ "/"+minioBucketName+"/"+ url;AjaxResult ajax = AjaxResult.success();ajax.put("fileName", file.getOriginalFilename());//我这里返回的是视频原来的文件名ajax.put("url", filePath);return ajax;}catch (Exception e){log.error("上传文件失败", e);return AjaxResult.error(e.getMessage());}}/*** 异步文件上传请求*/@PostMapping("/common/upload/minioAsync")public AjaxResult upload(MultipartFile file){try {long startTime = System.currentTimeMillis()/1000;String url = sysFileService.uploadFileAsync(file);System.out.println("文件返回时间_END耗时"+(System.currentTimeMillis()/1000-startTime)+"秒");String filePath = minioUrl.+ "/"+minioBucketName+"/"+ url;AjaxResult ajax = AjaxResult.success();ajax.put("fileName", file.getOriginalFilename());//我这里返回的是视频原来的文件名ajax.put("url", filePath);return ajax;}catch (Exception e){log.error("上传文件失败", e);return AjaxResult.error(e.getMessage());}}
}

优化空间

MinioSysFileServiceImpl 实现类可以采用异步多线程的方式去执行,前端调用直接返回文件路径,不用管文件是否上传完,异步执行完成以后 调用mino 判断是否存在该文件,来判断该文件是否上传完成,另外想要实现,秒传,断点续传,只要加上MD5编码和redis就可以实现。后面我会补充

优化一 异步上传

代码我我已经再上面补充过了,主要讲下异步多线程 

CompletableFuturejava.util.concurrent库在java 8中新增的主要工具,同传统的Future相比,其支持流式计算、函数式编程、完成通知、自定义异常处理等很多新的特性

CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(()->{System.out.println("compute 1");return 1;});CompletableFuture<Integer> future2 = future1.thenApply((p)->{System.out.println("compute 2");return p+10;});System.out.println("result: " + future2.join());

这个例子中展示了任务链

在上面的示例中,future1通过调用thenApply将后置任务连接起来,并形成future2。该示例的最终打印结果为11,可见程序在运行中,future1的结果计算出来后,会传递给通过thenApply连接的任务,从而产生future2的最终结果为1+10=11。当然,在实际使用中,我们理论上可以无限连接后续计算任务,从而实现链条更长的流式计算。

需要注意的是,通过thenApply连接的任务,当且仅当前置任务计算完成时,才会开始后置任务的计算。因此,这组函数主要用于连接前后有依赖的任务链。

利用异步多线程执行流程截图

看一下minio 里有没有文件

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

详解华为项目管理,附华为高级项目管理内训材料

&#xff08;一&#xff09;华为在项目管理中通过有效的沟通、灵活的组织结构、坚持不懈的努力、细致的管理和科学的考核体系&#xff0c;实现了持续的创新和发展。通过引进先进的管理模式&#xff0c;强调以客户需求为导向&#xff0c;华为不仅优化了技术管理和项目研发流程&a…

单片机原理及技术(八)—— 串行口的工作原理及应用

目录 一、串行通信基础 1.1 并行通信与串行通信 1.1.1 并行通信 1.1.2 串行通信 1.2 同步通信与异步通信 1.3 串行通信的传输模式 二、串行口的结构 2.1 串行口控制寄存器SCON 2.1.1 SM0、SM1 2.1.2 SM2 2.1.3 REN 2.1.4 TB8 2.1.5 RB8 2.1.6 TI 2.1.7 RI 2.2 …

数据结构: 树状数组

在OI赛事中&#xff0c;数据结构是非常重要的一个内容&#xff0c;更是有人说过&#xff0c;算法数据结构程序: A l g o r i t h m D a t a AlgorithmData AlgorithmData S t r u c t u r e P r o g r a m m i n g StructureProgramming StructureProgramming 接下来&#…

如何在 Ubuntu 系统中安装PyCharm集成开发环境?

在上一篇文章中&#xff0c;我们探讨了Jupyter notebook&#xff0c;今天再来看看另一款常用的Python 工具&#xff0c;Pycharm。 PyCharm也是我们日常开发和学习常用的Python 集成开发环境 (IDE)&#xff0c;由 JetBrains 开发。 PyCharm 带有一整套可以帮助用户在使用Pytho…

docker映射了端口,宿主机不生效

1、问题产生原因 docker run -d --name my-redis -p 6379:6379 -v /usr/redis.conf:/usr/local/etc/redis/redis.conf team-redis:3.2 redis-server /usr/local/etc/redis/redis.conf 这容器跑起来了&#xff0c;端口6379没用。搞的我一直怀疑哪里出错了&#xff0c;查看配置…

【网络安全】服务基础第一阶段——第二节:网络测试与用户

一、Windows网络测试工具 CMD&#xff08;命令提示符&#xff09;中&#xff0c;ping和tracert是两个非常有用的网络诊断工具 1.1.ping命令 ping命令是Windows和其他操作系统中用于测试主机之间网络连接是否可达的基本命令行工具。它通过发送ICMP&#xff08;Internet Contr…

CSS中的元素布局与定位详细说明

1、前言 在CSS开发中&#xff0c;很重要的一个工作就是根据UI设计稿&#xff0c;进行元素的布局与定位&#xff0c;使得元素&#xff08;比如某一段文本、按钮、图片等&#xff09;显示在页面正确的位置。本文就元素的布局与定位方面&#xff0c;做一些讲解和说明。 2、元素的…

Markdown 美化 Github 个人主页

注&#xff1a;本文参考这篇博客 http://t.csdnimg.cn/KXhSw 目录 1 效果展示2 创建仓库3 编写 Markdown3.1 动态波浪图3.2 打字机动图3.3 技术栈图标3.4 项目贡献统计3.5 连续贡献统计3.6 贡献统计图3.7 代码时长统计3.8 仓库代码占比 1 效果展示 先来看看效果&#xff1a; 动…

OSPF路由配置--多区域

目录 不理解OSPF路由动态协议的可以回顾一下OSPF详解&#xff0c;下这一系列的实验都不再做解释,直接开始配置 一. 实验拓扑 二. 实验配置 (命令可以直接复制粘贴到CLI中) 三. 实验验证 不理解OSPF路由动态协议的可以回顾一下OSPF详解&#xff0c;下这一系列的实验都不…

C++ 设计模式——迭代器模式

迭代器模式 C 设计模式——迭代器模式1. 主要组成成分2. 迭代器模式范例2.1 抽象迭代器2.2 抽象容器2.3 具体的迭代器2.4 具体的容器2.5 主函数示例 3. 迭代器 UML 图3.1 迭代器 UML 图解析 4. 迭代器模式的优点5. 迭代器模式的缺点6. 迭代器模式的适用场景7. 现代C中的迭代器总…

【深度学习】使用Conda虚拟环境安装多个版本的CUDA和CUDNN方便切换

conda虚拟环境安装CUDA和CUDNN 官网教程 https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#conda-installation 1. 背景 深度学习用显卡训练的时候&#xff0c;需要安装与显卡对应的cuda和cudnn。但不同的项目所支持的pytorch版本是不一样的&#x…

Openssl Infinite Loop 漏洞(CVE-2022-0778)

Openssl Infinite Loop 漏洞&#xff08;CVE-2022-0778&#xff09; 1. 漏洞详情 在该漏洞中由于证书解析时使用的 BN_mod_sqrt() 函数存在一个错误&#xff0c;它会导致在非质数的情况下永远循环。可通过生成包含无效的显式曲线参数的证书来触发无限循环。由于证书解析是在验…

视频监控汇聚算法平台训练站车辆类型算法分析车辆类型检测应用方案

车辆类型检测算法是计算机视觉和深度学习技术在交通管理和智能车辆系统中的重要应用之一。这种算法通过自动分析和识别车辆图像&#xff0c;能够准确判断车辆的类型&#xff0c;如轿车、SUV、货车等。 运用方案 数据采集与预处理 采集包含车辆的图像或视频数据&#xff0c;包…

自学成才

软件只是一种工具&#xff0c;正如给你一张纸和一支笔&#xff0c;有人满纸疙瘩&#xff0c;有人行云流水唱成一曲绝唱&#xff0c;全在于笔头功夫。使用软件一样需要智慧&#xff0c;不光是懂了就行&#xff0c;还得创造性使用&#xff0c;才会成就别人望洋兴叹的绝活。 Core…

【实施】软件实施方案(word套用)

软件实施方案 二、 项目介绍 三、 项目实施 四、 项目实施计划 五、 人员培训 六、 项目验收 七、 售后服务 八、 项目保障措施 软件开发全套资料获取&#xff1a;&#xff08;本文末个人名片也可直接获取&#xff09; 软件产品&#xff0c;特别是行业解决方案软件产品不同于一…

【Electron】桌面应用开发electron-builder打包报错问题处理

Electron 桌面应用开发electron-builder打包过程中各种报错问题处理 前一篇有写过 Electron 桌面应用开发快速入门到打包Windows应用程序 在安装到打包的整个过程中&#xff0c;我们都会遇到很多诡异的问题&#xff0c;接下来我将介绍我遇到的几个问题的解决方案 一、拉包的时…

VBA技术资料MF191:将源文件夹所有文件移动到目标文件夹

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

iOS profiles文件过期如何更新

创建发布用的Certificates 首先进入到https://developer.apple.com/account页面选择【证书】进入【新建证书】页面 点击【新建证书】按钮&#xff1a; 根据需求选中对应的【证书类型】&#xff0c;我选的是【Apple Distribution】&#xff0c; 开发者证书选择【Apple Devel…

react antd TreeSelect实现自定义标签

<ProFormTreeSelectlabel"接收对象"name"receiverObjects"colProps{{ span: 16 }}labelCol{{span: 6,}}wrapperCol{{span: 18,}}rules{[{ required: true }]}fieldProps{{showSearch: true,multiple: true,// autoClearSearchValue: true,filterTreeNod…

探索条形码与二维码的秘密:pyzbar库的神奇之旅

文章目录 探索条形码与二维码的秘密&#xff1a;pyzbar库的神奇之旅背景&#xff1a;为什么选择pyzbar&#xff1f;pyzbar是什么&#xff1f;如何安装pyzbar&#xff1f;简单库函数使用方法场景应用常见Bug及解决方案总结 探索条形码与二维码的秘密&#xff1a;pyzbar库的神奇之…