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

相关文章

Mysql删除几亿条数据表中的部分数据的方法实现

《Mysql删除几亿条数据表中的部分数据的方法实现》在MySQL中删除一个大表中的数据时,需要特别注意操作的性能和对系统的影响,本文主要介绍了Mysql删除几亿条数据表中的部分数据的方法实现,具有一定... 目录1、需求2、方案1. 使用 DELETE 语句分批删除2. 使用 INPLACE ALTER T

C++从序列容器中删除元素的四种方法

《C++从序列容器中删除元素的四种方法》删除元素的方法在序列容器和关联容器之间是非常不同的,在序列容器中,vector和string是最常用的,但这里也会介绍deque和list以供全面了解,尽管在一... 目录一、简介二、移除给定位置的元素三、移除与某个值相等的元素3.1、序列容器vector、deque

C++原地删除有序数组重复项的N种方法

《C++原地删除有序数组重复项的N种方法》给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度,不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(... 目录一、问题二、问题分析三、算法实现四、问题变体:最多保留两次五、分析和代码实现5.1、问题分析5.

SQL Server清除日志文件ERRORLOG和删除tempdb.mdf

《SQLServer清除日志文件ERRORLOG和删除tempdb.mdf》数据库再使用一段时间后,日志文件会增大,特别是在磁盘容量不足的情况下,更是需要缩减,以下为缩减方法:如果可以停止SQLSe... 目录缩减 ERRORLOG 文件(停止服务后)停止 SQL Server 服务:找到错误日志文件:删除

Python实现合并与拆分多个PDF文档中的指定页

《Python实现合并与拆分多个PDF文档中的指定页》这篇文章主要为大家详细介绍了如何使用Python实现将多个PDF文档中的指定页合并生成新的PDF以及拆分PDF,感兴趣的小伙伴可以参考一下... 安装所需要的库pip install PyPDF2 -i https://pypi.tuna.tsingh

Java实现数据库图片上传与存储功能

《Java实现数据库图片上传与存储功能》在现代的Web开发中,上传图片并将其存储在数据库中是常见的需求之一,本文将介绍如何通过Java实现图片上传,存储到数据库的完整过程,希望对大家有所帮助... 目录1. 项目结构2. 数据库表设计3. 实现图片上传功能3.1 文件上传控制器3.2 图片上传服务4. 实现

mysql删除无用用户的方法实现

《mysql删除无用用户的方法实现》本文主要介绍了mysql删除无用用户的方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 1、删除不用的账户(1) 查看当前已存在账户mysql> select user,host,pa

使用mvn deploy命令上传jar包的实现

《使用mvndeploy命令上传jar包的实现》本文介绍了使用mvndeploy:deploy-file命令将本地仓库中的JAR包重新发布到Maven私服,文中通过示例代码介绍的非常详细,对大家的学... 目录一、背景二、环境三、配置nexus上传账号四、执行deploy命令上传包1. 首先需要把本地仓中要

Java实现数据库图片上传功能详解

《Java实现数据库图片上传功能详解》这篇文章主要为大家详细介绍了如何使用Java实现数据库图片上传功能,包含从数据库拿图片传递前端渲染,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、前言2、数据库搭建&nbsChina编程p; 3、后端实现将图片存储进数据库4、后端实现从数据库取出图片给前端5、前端拿到

使用Apache POI在Java中实现Excel单元格的合并

《使用ApachePOI在Java中实现Excel单元格的合并》在日常工作中,Excel是一个不可或缺的工具,尤其是在处理大量数据时,本文将介绍如何使用ApachePOI库在Java中实现Excel... 目录工具类介绍工具类代码调用示例依赖配置总结在日常工作中,Excel 是一个不可或缺的工http://