SpringBoot + minio實(shí)現(xiàn)分片上傳、秒傳、續(xù)傳功能
什么是minio
MinIO是一個(gè)基于Go實(shí)現(xiàn)的高性能、兼容S3協(xié)議的對(duì)象存儲(chǔ)。它采用GNU AGPL v3開(kāi)源協(xié)議,項(xiàng)目地址是https://github.com/minio/minio。
引用官網(wǎng):
MinIO是根據(jù)GNU Affero通用公共許可證v3.0發(fā)布的高性能對(duì)象存儲(chǔ)。它與Amazon S3云存儲(chǔ)服務(wù)兼容。使用MinIO構(gòu)建用于機(jī)器學(xué)習(xí),分析和應(yīng)用程序數(shù)據(jù)工作負(fù)載的高性能基礎(chǔ)架構(gòu)。
官網(wǎng)地址:
https://min.io/
文檔地址:
https://docs.min.io/
一. 使用docker 搭建minio 服務(wù)
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
:為用戶密鑰
以上搭建的都是單機(jī)版的。想要了解分布式 的方式請(qǐng)查看官網(wǎng)文檔。
這就是在win的docker上運(yùn)行的。
更多開(kāi)源項(xiàng)目:https://www.yoodb.com/projects/springboot-user-manger.html
當(dāng)啟動(dòng)后在瀏覽器訪問(wèn)http://localhost:9000
就可以訪問(wèn)minio的圖形化界面了,如圖所示:
二. 下面開(kāi)始搭建springboot 環(huán)境
初始化一個(gè)springboot項(xiàng)目大家都會(huì),這里不多做介紹。
主要是介紹需要引入的依賴:
<!-- 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初始化是設(shè)置的,密鑰相同 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(); } }
使用配置屬性綁定進(jìn)行參數(shù)綁定,并初始化一個(gè)minio client對(duì)象放入容器中。
下面就是我封裝的minio client 操作minio的簡(jiǎn)單方法的組件。
@Component public class MinioComp { @Autowired private MinioClient minioClient; @Autowired private MinioConfiguration configuration; /** * @description: 獲取上傳臨時(shí)簽名,公眾 號(hào)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,公眾 號(hào)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上傳一個(gè)文件到存儲(chǔ)桶中。 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獲取文件訪問(wèn)地址 * @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; } }
簡(jiǎn)單說(shuō)明:
- 使用MultipartFile接收前端文件流,再上傳到minio。
- 構(gòu)建一個(gè)formData的簽名數(shù)據(jù),給前端,讓前端之前上傳到minio。
- 構(gòu)建一個(gè)可以上傳的臨時(shí)URL給前端,前端通過(guò)攜帶文件請(qǐng)求該URL進(jìn)行上傳。
- 使用filename請(qǐng)求服務(wù)端獲取臨時(shí)訪問(wèn)文件的URL。(最長(zhǎng)時(shí)間為7 天,想要永久性訪問(wèn),需要其他設(shè)置,這里不做說(shuō)明。)
下面展示頁(yè)面html,使用的是VUE+element-ui進(jìn)行渲染。
<!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>點(diǎn)擊上傳</em></div> <div class="el-upload__tip" slot="tip">只能上傳jpg/png文件,且不超過(guò)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>點(diǎn)擊上傳</em></div> <div class="el-upload__tip" slot="tip">只能上傳jpg/png文件,且不超過(guò)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>點(diǎn)擊上傳</em></div> <div class="el-upload__tip" slot="tip">只能上傳jpg/png文件,且不超過(guò)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); // 讓服務(wù)端返回200,不設(shè)置則默認(rèn)返回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 請(qǐng)求 _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 請(qǐng)求 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請(qǐng)求 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>
頁(yè)面的效果就如上圖所示。
可以分別體驗(yàn)不同的實(shí)現(xiàn)效果。
以上就是使用springboot搭建基于minio的高性能存儲(chǔ)服務(wù)的全部步驟了。
本項(xiàng)目地址:
https://gitee.com/jack_whh/minio-upload
到此這篇關(guān)于SpringBoot + minio實(shí)現(xiàn)分片上傳、秒傳、續(xù)傳的文章就介紹到這了,更多相關(guān)SpringBoot minio分片上傳內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
2023最新版本idea用maven新建web項(xiàng)目(親測(cè)不報(bào)錯(cuò))
這篇文章主要給大家介紹了關(guān)于2023最新版本idea用maven新建web項(xiàng)目,Maven是當(dāng)今Java開(kāi)發(fā)中主流的依賴管理工具,文中介紹的步驟親測(cè)不報(bào)錯(cuò),需要的朋友可以參考下2023-07-07如何使用java agent修改字節(jié)碼并在springboot啟動(dòng)時(shí)自動(dòng)生效
本文介紹了JavaAgent的使用方法和在SpringBoot中的應(yīng)用,JavaAgent可以通過(guò)修改類的字節(jié)碼,實(shí)現(xiàn)對(duì)非Spring容器管理對(duì)象的AOP處理,演示了如何定義切面邏輯,實(shí)現(xiàn)接口mock,感興趣的朋友跟隨小編一起看看吧2024-10-10SpringMVC使用JsonView針對(duì)統(tǒng)一實(shí)體返回不同信息
這篇文章主要為大家介紹了SpringMVC使用JsonView針對(duì)統(tǒng)一實(shí)體返回不同信息,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03spring boot整合shiro安全框架過(guò)程解析
這篇文章主要介紹了spring boot整合shiro安全框架過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Java中List集合去除重復(fù)數(shù)據(jù)的方法匯總
這篇文章主要給大家介紹了關(guān)于Java中List集合去除重復(fù)數(shù)據(jù)的方法,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02IDEA 程序包不存在,找不到符號(hào)但是明明存在對(duì)應(yīng)的jar包(問(wèn)題分析及解決方案)
這篇文章主要介紹了IDEA 程序包不存在,找不到符號(hào)但是明明存在對(duì)應(yīng)的jar包 的解決方案,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08Java基本數(shù)據(jù)類型(動(dòng)力節(jié)點(diǎn)java學(xué)院整理)
Java數(shù)據(jù)類型(type)可以分為兩大類:基本類型(primitive types)和引用類型(reference types)。下面是動(dòng)力節(jié)點(diǎn)給大家整理java基本數(shù)據(jù)類型相關(guān)知識(shí),感興趣的朋友一起學(xué)習(xí)吧2017-03-03基于OpenCv與JVM實(shí)現(xiàn)加載保存圖像功能(JAVA?圖像處理)
openCv有一個(gè)名imread的簡(jiǎn)單函數(shù),用于從文件中讀取圖像,本文給大家介紹JAVA?圖像處理基于OpenCv與JVM實(shí)現(xiàn)加載保存圖像功能,感興趣的朋友一起看看吧2022-01-01