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

2024-08-25 06:20

本文主要是介绍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 里有没有文件

这篇关于minio 后端大文件分片上传,合并,删除分片的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1104786

相关文章

hdu2241(二分+合并数组)

题意:判断是否存在a+b+c = x,a,b,c分别属于集合A,B,C 如果用暴力会超时,所以这里用到了数组合并,将b,c数组合并成d,d数组存的是b,c数组元素的和,然后对d数组进行二分就可以了 代码如下(附注释): #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<que

电脑桌面文件删除了怎么找回来?别急,快速恢复攻略在此

在日常使用电脑的过程中,我们经常会遇到这样的情况:一不小心,桌面上的某个重要文件被删除了。这时,大多数人可能会感到惊慌失措,不知所措。 其实,不必过于担心,因为有很多方法可以帮助我们找回被删除的桌面文件。下面,就让我们一起来了解一下这些恢复桌面文件的方法吧。 一、使用撤销操作 如果我们刚刚删除了桌面上的文件,并且还没有进行其他操作,那么可以尝试使用撤销操作来恢复文件。在键盘上同时按下“C

day-51 合并零之间的节点

思路 直接遍历链表即可,遇到val=0跳过,val非零则加在一起,最后返回即可 解题过程 返回链表可以有头结点,方便插入,返回head.next Code /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}*

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟) 题目描述 给定一个链表,链表中的每个节点代表一个整数。链表中的整数由 0 分隔开,表示不同的区间。链表的开始和结束节点的值都为 0。任务是将每两个相邻的 0 之间的所有节点合并成一个节点,新节点的值为原区间内所有节点值的和。合并后,需要移除所有的 0,并返回修改后的链表头节点。 思路分析 初始化:创建一个虚拟头节点

Spring MVC 图片上传

引入需要的包 <dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-

学习记录:js算法(二十八):删除排序链表中的重复元素、删除排序链表中的重复元素II

文章目录 删除排序链表中的重复元素我的思路解法一:循环解法二:递归 网上思路 删除排序链表中的重复元素 II我的思路网上思路 总结 删除排序链表中的重复元素 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 图一 图二 示例 1:(图一)输入:head = [1,1,2]输出:[1,2]示例 2:(图

如何恢复回收站中已删除/清空的文件

回收站清空后如何恢复已删除的文件?是否可以恢复永久删除的文件?或者最糟糕的是,如果文件直接被删除怎么办?本文将向您展示清空回收站后恢复已删除数据的最佳方法。 回收站清空后如何恢复已删除的文件? “回收站清空后我还能恢复已删除的文件吗?” 答案是肯定的,但是在这种情况下您将需要一个  回收站恢复工具 来从回收站中检索文件: 错误/永久删除回收站或任何数字存储设备中的文件 直接删除的文件/

在SSH的基础上使用jquery.uploadify.js上传文件

在SSH框架的基础上,使用jquery.uploadify.js实现文件的上传,之前搞了好几天,都上传不了, 在Action那边File接收到的总是为null, 为了这个还上网搜了好多相关的信息,但都不行,最后还是搜到一篇文章帮助到我了,希望能帮助到为之困扰的人。 jsp页面的关键代码: <link rel="stylesheet" type="text/css" href="${page

Java后端微服务架构下的API限流策略:Guava RateLimiter

Java后端微服务架构下的API限流策略:Guava RateLimiter 大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿! 在微服务架构中,API限流是保护服务不受过度使用和拒绝服务攻击的重要手段。Guava RateLimiter是Google开源的Java库中的一个组件,提供了简单易用的限流功能。 API限流概述 API限流通过控制请求的速率来防止

【Python从入门到进阶】64、Pandas如何实现数据的Concat合并

接上篇《63.Pandas如何实现数据的Merge》 上一篇我们学习了Pandas如何实现数据的Merge,本篇我们来继续学习Pandas如何实现数据的Concat合并。 一、引言 在数据处理过程中,经常需要将多个数据集合并为一个统一的数据集,以便进行进一步的分析或建模。这种需求在多种场景下都非常常见,比如合并不同来源的数据集以获取更全面的信息、将时间序列数据按时间顺序拼接起来以观察长期趋势等