SpringBoot + minio實(shí)現(xiàn)分片上傳、秒傳、續(xù)傳功能
什么是minio
MinIO是一個(gè)基于Go實(shí)現(xiàn)的高性能、兼容S3協(xié)議的對(duì)象存儲(chǔ)。它采用GNU AGPL v3開源協(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)行的。
更多開源項(xiàng)目:https://www.yoodb.com/projects/springboot-user-manger.html
當(dāng)啟動(dòng)后在瀏覽器訪問(wèn)http://localhost:9000就可以訪問(wèn)minio的圖形化界面了,如圖所示:


二. 下面開始搭建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開發(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-10
SpringMVC使用JsonView針對(duì)統(tǒng)一實(shí)體返回不同信息
這篇文章主要為大家介紹了SpringMVC使用JsonView針對(duì)統(tǒng)一實(shí)體返回不同信息,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
spring boot整合shiro安全框架過(guò)程解析
這篇文章主要介紹了spring boot整合shiro安全框架過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
Java中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-02
IDEA 程序包不存在,找不到符號(hào)但是明明存在對(duì)應(yīng)的jar包(問(wèn)題分析及解決方案)
這篇文章主要介紹了IDEA 程序包不存在,找不到符號(hào)但是明明存在對(duì)應(yīng)的jar包 的解決方案,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08
Java基本數(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

