SpringBoot + minio實現(xiàn)分片上傳、秒傳、續(xù)傳功能
什么是minio
MinIO是一個基于Go實現(xiàn)的高性能、兼容S3協(xié)議的對象存儲。它采用GNU AGPL v3開源協(xié)議,項目地址是https://github.com/minio/minio。
引用官網(wǎng):
MinIO是根據(jù)GNU Affero通用公共許可證v3.0發(fā)布的高性能對象存儲。它與Amazon S3云存儲服務兼容。使用MinIO構建用于機器學習,分析和應用程序數(shù)據(jù)工作負載的高性能基礎架構。
官網(wǎng)地址:
https://min.io/
文檔地址:
https://docs.min.io/
一. 使用docker 搭建minio 服務
GNU / Linux和macOS
docker run -p 9000:9000 \ --name minio1 \ -v /mnt/data:/data \ -e "MINIO_ROOT_USER=AKIAIOSFODNN7EXAMPLE" \ -e "MINIO_ROOT_PASSWORD=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \ minio/minio server /data
windows
docker run -p 9000:9000 \ --name minio1 \ -v D:\data:/data \ -e "MINIO_ROOT_USER=AKIAIOSFODNN7EXAMPLE" \ -e "MINIO_ROOT_PASSWORD=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \ minio/minio server /data
MINIO_ROOT_USER
:為用戶keyMINIO_ROOT_PASSWORD
:為用戶密鑰
以上搭建的都是單機版的。想要了解分布式 的方式請查看官網(wǎng)文檔。
這就是在win的docker上運行的。
更多開源項目:https://www.yoodb.com/projects/springboot-user-manger.html
當啟動后在瀏覽器訪問http://localhost:9000
就可以訪問minio的圖形化界面了,如圖所示:
二. 下面開始搭建springboot 環(huán)境
初始化一個springboot項目大家都會,這里不多做介紹。
主要是介紹需要引入的依賴:
<!-- thymeleaf模板渲染引擎--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- 操作minio的java客戶端--> <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.2.1</version> </dependency> <!-- lombok插件--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
依賴可以官方文檔里找:https://docs.min.io/docs/java-client-quickstart-guide.html
下面介紹配置文件:
spring: servlet: multipart: max-file-size: 10MB max-request-size: 10MB #minio配置 minio: access-key: AKIAIOSFODNN7EXAMPLE #key就是docker初始化是設置的,密鑰相同 secret-key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY url: http://localhost:9000 bucket-name: wdhcr thymeleaf: cache: false
創(chuàng)建minio的配置類:
@Configuration @ConfigurationProperties(prefix = "spring.minio") @Data public class MinioConfiguration { private String accessKey; private String secretKey; private String url; private String bucketName; @Bean public MinioClient minioClient() { return MinioClient.builder() .endpoint(url) .credentials(accessKey, secretKey) .build(); } }
使用配置屬性綁定進行參數(shù)綁定,并初始化一個minio client對象放入容器中。
下面就是我封裝的minio client 操作minio的簡單方法的組件。
@Component public class MinioComp { @Autowired private MinioClient minioClient; @Autowired private MinioConfiguration configuration; /** * @description: 獲取上傳臨時簽名,公眾 號Java精選 * @dateTime: 2021/5/13 14:12 */ public Map getPolicy(String fileName, ZonedDateTime time) { PostPolicy postPolicy = new PostPolicy(configuration.getBucketName(), time); postPolicy.addEqualsCondition("key", fileName); try { Map<String, String> map = minioClient.getPresignedPostFormData(postPolicy); HashMap<String, String> map1 = new HashMap<>(); map.forEach((k,v)->{ map1.put(k.replaceAll("-",""),v); }); map1.put("host",configuration.getUrl()+"/"+configuration.getBucketName()); return map1; } 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(); } return null; } /** * @description: 獲取上傳文件的url,公眾 號Java精選,有驚喜! * @dateTime: 2021/5/13 14:15 */ public String getPolicyUrl(String objectName, Method method, int time, TimeUnit timeUnit) { try { return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() .method(method) .bucket(configuration.getBucketName()) .object(objectName) .expiry(time, timeUnit).build()); } 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 (XmlParserException e) { e.printStackTrace(); } catch (ServerException e) { e.printStackTrace(); } return null; } /** * @description: 上傳文件 * @dateTime: 2021/5/13 14:17 */ public void upload(MultipartFile file, String fileName) { // 使用putObject上傳一個文件到存儲桶中。 try { InputStream inputStream = file.getInputStream(); minioClient.putObject(PutObjectArgs.builder() .bucket(configuration.getBucketName()) .object(fileName) .stream(inputStream, file.getSize(), -1) .contentType(file.getContentType()) .build()); } 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(); } } /** * @description: 根據(jù)filename獲取文件訪問地址 * @dateTime: 2021/5/17 11:28 */ public String getUrl(String objectName, int time, TimeUnit timeUnit) { String url = null; try { url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(configuration.getBucketName()) .object(objectName) .expiry(time, timeUnit).build()); } 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 (XmlParserException e) { e.printStackTrace(); } catch (ServerException e) { e.printStackTrace(); } return url; } }
簡單說明:
- 使用MultipartFile接收前端文件流,再上傳到minio。
- 構建一個formData的簽名數(shù)據(jù),給前端,讓前端之前上傳到minio。
- 構建一個可以上傳的臨時URL給前端,前端通過攜帶文件請求該URL進行上傳。
- 使用filename請求服務端獲取臨時訪問文件的URL。(最長時間為7 天,想要永久性訪問,需要其他設置,這里不做說明。)
下面展示頁面html,使用的是VUE+element-ui進行渲染。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <!-- import CSS --> <link rel="stylesheet" rel="external nofollow" > <title>上傳圖片</title> </head> <body> <div id="app"> <el-row :gutter="2"> <el-col :span="8"> <div class="div-center-class"> <div class=""> <center><h3>傳統(tǒng)上傳</h3></center> <el-upload class="upload-demo" action="#" drag :http-request="uploadHandle"> <i class="el-icon-upload"></i> <div class="el-upload__text">將文件拖到此處,或<em>點擊上傳</em></div> <div class="el-upload__tip" slot="tip">只能上傳jpg/png文件,且不超過500kb</div> </el-upload> <div v-if="imgUrl"> <img :src="imgUrl" style="width: 40px;height: 40px"></img> </div> </div> </div> </el-col> <el-col :span="8"> <div class="div-center-class"> <div class=""> <center><h3>前端formData直傳</h3></center> <el-upload class="upload-demo" action="#" drag :http-request="httpRequestHandle"> <i class="el-icon-upload"></i> <div class="el-upload__text">將文件拖到此處,或<em>點擊上傳</em></div> <div class="el-upload__tip" slot="tip">只能上傳jpg/png文件,且不超過500kb</div> </el-upload> <div v-if="directUrl"> <img :src="directUrl" style="width: 40px;height: 40px"></img> </div> </div> </div> </el-col> <el-col :span="8"> <div class="div-center-class"> <div class=""> <center><h3>前端Url直傳</h3></center> <el-upload class="upload-demo" action="#" drag :http-request="UrlUploadHandle"> <i class="el-icon-upload"></i> <div class="el-upload__text">將文件拖到此處,或<em>點擊上傳</em></div> <div class="el-upload__tip" slot="tip">只能上傳jpg/png文件,且不超過500kb</div> </el-upload> <div v-if="uploadUrl"> <img :src="uploadUrl" style="width: 40px;height: 40px"></img> </div> </div> </div> </el-col> </el-row> </div> </body> <!-- import Vue before Element --> <script src="https://unpkg.com/vue/dist/vue.js"></script> <!-- import JavaScript --> <script src="https://unpkg.com/element-ui/lib/index.js"></script> <!--import axios --> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> new Vue({ el: '#app', data: function () { return { imgUrl: '', directUrl: '', uploadUrl: '' } }, methods: { uploadHandle(options) { let {file} = options; this.traditionPost(file); }, traditionPost(file) { _that = this const form = new FormData(); form.append("fileName", file.name); form.append("file", file); this.axiosPost("post", "/upload", form).then(function (res) { if (res.status === 200) { _that.imgUrl = res.data.data } else { alert("上傳失??!") } }) }, getpolicy(file) { _that = this axios.get('policy?fileName=' + file.name) .then(function (response) { let {xamzalgorithm, xamzcredential, policy, xamzsignature, xamzdate, host} = response.data.data; let formData = new FormData(); formData.append("key", file.name); formData.append("x-amz-algorithm", xamzalgorithm); // 讓服務端返回200,不設置則默認返回204。 formData.append("x-amz-credential", xamzcredential); formData.append("policy", policy); formData.append("x-amz-signature", xamzsignature); formData.append("x-amz-date", xamzdate); formData.append("file", file); // 發(fā)送 POST 請求 _that.axiosPost("post", host, formData).then(function (res) { if (res.status === 204) { axios.get('url?fileName=' + file.name).then(function (res) { _that.directUrl = res.data.data; }) } else { alert("上傳失敗!") } }) }) }, httpRequestHandle(options) { let {file} = options; this.getpolicy(file); }, UrlUploadHandle(options) { let {file} = options; this.getUploadUrl(file); }, getUploadUrl(file) { _that = this console.log(file) axios.get('uploadUrl?fileName=' + file.name) .then(function (response) { let url = response.data.data; // 發(fā)送 put 請求 let config = {'Content-Type': file.type} _that.axiosPost("put", url, file, config).then(function (res) { if (res.status === 200) { axios.get('url?fileName=' + file.name).then(function (res) { _that.uploadUrl = res.data.data; }) } else { alert("上傳失敗!") } }) }) }, //封裝 //axios封裝post請求 axiosPost(method, url, data, config) { let result = axios({ method: method, url: url, data: data, headers: config }).then(resp => { return resp }).catch(error => { return "exception=" + error; }); return result; } } }) </script> <style> .div-center-class { padding: 28% 0%; text-align: center; background: beige; } </style> </html>
頁面的效果就如上圖所示。
可以分別體驗不同的實現(xiàn)效果。
以上就是使用springboot搭建基于minio的高性能存儲服務的全部步驟了。
本項目地址:
https://gitee.com/jack_whh/minio-upload
到此這篇關于SpringBoot + minio實現(xiàn)分片上傳、秒傳、續(xù)傳的文章就介紹到這了,更多相關SpringBoot minio分片上傳內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
2023最新版本idea用maven新建web項目(親測不報錯)
這篇文章主要給大家介紹了關于2023最新版本idea用maven新建web項目,Maven是當今Java開發(fā)中主流的依賴管理工具,文中介紹的步驟親測不報錯,需要的朋友可以參考下2023-07-07如何使用java agent修改字節(jié)碼并在springboot啟動時自動生效
本文介紹了JavaAgent的使用方法和在SpringBoot中的應用,JavaAgent可以通過修改類的字節(jié)碼,實現(xiàn)對非Spring容器管理對象的AOP處理,演示了如何定義切面邏輯,實現(xiàn)接口mock,感興趣的朋友跟隨小編一起看看吧2024-10-10SpringMVC使用JsonView針對統(tǒng)一實體返回不同信息
這篇文章主要為大家介紹了SpringMVC使用JsonView針對統(tǒng)一實體返回不同信息,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-03-03Java中List集合去除重復數(shù)據(jù)的方法匯總
這篇文章主要給大家介紹了關于Java中List集合去除重復數(shù)據(jù)的方法,文中通過圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-02-02IDEA 程序包不存在,找不到符號但是明明存在對應的jar包(問題分析及解決方案)
這篇文章主要介紹了IDEA 程序包不存在,找不到符號但是明明存在對應的jar包 的解決方案,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08Java基本數(shù)據(jù)類型(動力節(jié)點java學院整理)
Java數(shù)據(jù)類型(type)可以分為兩大類:基本類型(primitive types)和引用類型(reference types)。下面是動力節(jié)點給大家整理java基本數(shù)據(jù)類型相關知識,感興趣的朋友一起學習吧2017-03-03基于OpenCv與JVM實現(xiàn)加載保存圖像功能(JAVA?圖像處理)
openCv有一個名imread的簡單函數(shù),用于從文件中讀取圖像,本文給大家介紹JAVA?圖像處理基于OpenCv與JVM實現(xiàn)加載保存圖像功能,感興趣的朋友一起看看吧2022-01-01