SpringBoot整合Minio實(shí)現(xiàn)文件上傳和讀取功能
最近公司有一個(gè)需求是關(guān)于視頻上傳播放的,需要設(shè)計(jì)一個(gè)方案,中間談到了Minio這個(gè)技術(shù),于是來學(xué)習(xí)一下
一、簡(jiǎn)介
1.分布式文件系統(tǒng)應(yīng)用場(chǎng)景
互聯(lián)網(wǎng)海量非結(jié)構(gòu)化數(shù)據(jù)的存儲(chǔ)需求
- 電商網(wǎng)絡(luò):海量商品圖片
- 視頻網(wǎng)站:海量視頻文件
- 網(wǎng)盤:海量文件
- 社交網(wǎng)站:海量圖片
2.Minio介紹
- Go語言開發(fā),開源,免費(fèi)的對(duì)象存儲(chǔ)服務(wù),可以存儲(chǔ)海量非結(jié)構(gòu)化的數(shù)據(jù),一個(gè)對(duì)象文件可以是任意大小,從幾kb到最大5T不等
3.Minio優(yōu)點(diǎn)
- 部署簡(jiǎn)單
- 讀寫性能優(yōu)異
- 支持海量存儲(chǔ)
二、docker部署(windows系統(tǒng))
這里我是用自己電腦(windows系統(tǒng))安裝了docker,然后使用docker來部署的
中文官網(wǎng):單節(jié)點(diǎn)多硬盤部署MinIO — MinIO中文文檔 | MinIO Container中文文檔
1.創(chuàng)建目錄
先進(jìn)入D盤,創(chuàng)建docker的工作目錄 docker_workplace
,再創(chuàng)建minio的目錄minio
,再創(chuàng)建兩個(gè)文件夾data
和config
,如圖所示
拉取鏡像,創(chuàng)建容器并運(yùn)行
2.拉取鏡像
直接cmd,敲docker命令即可,和linux的語法一樣
3.創(chuàng)建容器并運(yùn)行
多行:
docker run -d --name minio \ --privileged=true \ --restart=always \ -p 9000:9000 -p 50000:50000 \ -e "MINIO_ROOT_USER=minio" \ -e "MINIO_ROOT_PASSWORD=miniominio" \ -v D:/docker_workplace/data/minio/config:/root/.minio \ -v D:/docker_workplace/data/minio/data1:/data1 \ -v D:/docker_workplace/data/minio/data2:/data2 \ -v D:/docker_workplace/data/minio/data3:/data3 \ -v D:/docker_workplace/data/minio/data4:/data4 \ minio/minio \ server \ --console-address ":50000" /data{1...4}
單行:
docker run -p 9000:9000 -p 50000:50000 -d --name minio -e "MINIO_ACCESS_KEY=minio" -e "MINIO_SECRET_KEY=miniominio" -v D:/docker_workplace/data/minio/config:/root/.minio -v D:/docker_workplace/data/minio/data1:/data1 -v D:/docker_workplace/data/minio/data2:/data2 -v D:/docker_workplace/data/minio/data3:/data3 -v D:/docker_workplace/data/minio/data4:/data4 --restart always minio/minio server --console-address ":50000" /data{1...4}
4.訪問控制臺(tái)
瀏覽器訪問:
http://localhost:50000/login
賬號(hào)/密碼(剛剛docker運(yùn)行命令設(shè)置的):minio/miniominio
這里我第一次訪問失敗了,看了下docker日志,發(fā)現(xiàn)是賬號(hào)密碼長(zhǎng)度不符合規(guī)范導(dǎo)致(一開始密碼是
minio
,達(dá)不到8個(gè)字符)
于是改了密碼為
miniominio
,成功運(yùn)行
5.初始化配置
創(chuàng)建一個(gè)桶 Bucket
這里出現(xiàn)了一個(gè)報(bào)錯(cuò),原因是說需要分布式部署才能使用,解決辦法:掛載多個(gè)卷
配置桶權(quán)限為public
到這里,minio單機(jī)版就部署好了
三、Spring Boot整合Minio
1.創(chuàng)建demo項(xiàng)目
新建一個(gè)spring boot項(xiàng)目
2.引入依賴
<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.3.7</version> </dependency>
3.配置
application.yml
spring: application: name: miniodemo servlet: multipart: # 文件上傳大小限制。超過該值直接報(bào)錯(cuò) max-file-size: 20MB # 文件最大請(qǐng)求限制,用于批量上傳 max-request-size: 20MB datasource: url: jdbc:mysql://localhost:3306/minio?useSSL=false&serverTimezone=UTC username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver # MinIO 配置 minio: endpoint: http://localhost:9000 # MinIO服務(wù)地址 fileHost: http://localhost:9000 # 文件地址host bucketName: test # 存儲(chǔ)桶bucket名稱 accessKey: minio # 用戶名 secretKey: miniominio # 密碼 imgSize: 20 # 圖片大小限制,單位:m fileSize: 20 # 文件大小限制,單位:m mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.xuyue.miniodemo.domain
4.編寫配置類
MinIOConfig.java
package com.xuyue.miniodemo.config; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; @Configuration @Data public class MinIOConfig { @Value("${minio.endpoint}") private String endpoint; @Value("${minio.fileHost}") private String fileHost; @Value("${minio.bucketName}") private String bucketName; @Value("${minio.accessKey}") private String accessKey; @Value("${minio.secretKey}") private String secretKey; @Value("${minio.imgSize}") private Integer imgSize; @Value("${minio.fileSize}") private Integer fileSize; }
5.MinIO工具類
MinIoUploadService.java
package com.xuyue.miniodemo.service; import com.xuyue.miniodemo.config.MinIOConfig; import io.minio.*; import io.minio.http.Method; import io.minio.messages.Bucket; import io.minio.messages.DeleteObject; import io.minio.messages.Item; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Optional; /** * MinIO工具類 */ @Slf4j @Service public class MinIoUploadService { @Resource private MinIOConfig minIOConfig; private MinioClient minioClient; private String endpoint; private String bucketName; private String accessKey; private String secretKey; private Integer imgSize; private Integer fileSize; private final String SEPARATOR = "/"; @PostConstruct public void init() { this.endpoint = minIOConfig.getEndpoint(); this.bucketName = minIOConfig.getBucketName(); this.accessKey = minIOConfig.getAccessKey(); this.secretKey = minIOConfig.getSecretKey(); this.imgSize = minIOConfig.getImgSize(); this.fileSize = minIOConfig.getFileSize(); createMinioClient(); } /** * 創(chuàng)建基于Java端的MinioClient */ public void createMinioClient() { try { if (null == minioClient) { log.info("開始創(chuàng)建 MinioClient..."); minioClient = MinioClient .builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); createBucket(bucketName); log.info("創(chuàng)建完畢 MinioClient..."); } } catch (Exception e) { log.error("MinIO服務(wù)器異常:{}", e); } } /** * 獲取上傳文件前綴路徑 * * @return */ public String getBasisUrl() { return endpoint + SEPARATOR + bucketName + SEPARATOR; } /****************************** Operate Bucket Start ******************************/ /** * 啟動(dòng)SpringBoot容器的時(shí)候初始化Bucket * 如果沒有Bucket則創(chuàng)建 * * @throws Exception */ private void createBucket(String bucketName) throws Exception { if (!bucketExists(bucketName)) { minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); } } /** * 判斷Bucket是否存在,true:存在,false:不存在 * * @return * @throws Exception */ public boolean bucketExists(String bucketName) throws Exception { return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); } /** * 獲得Bucket的策略 * * @param bucketName * @return * @throws Exception */ public String getBucketPolicy(String bucketName) throws Exception { String bucketPolicy = minioClient .getBucketPolicy( GetBucketPolicyArgs .builder() .bucket(bucketName) .build() ); return bucketPolicy; } /** * 獲得所有Bucket列表 * * @return * @throws Exception */ public List<Bucket> getAllBuckets() throws Exception { return minioClient.listBuckets(); } /** * 根據(jù)bucketName獲取其相關(guān)信息 * * @param bucketName * @return * @throws Exception */ public Optional<Bucket> getBucket(String bucketName) throws Exception { return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst(); } /** * 根據(jù)bucketName刪除Bucket,true:刪除成功; false:刪除失敗,文件或已不存在 * * @param bucketName * @throws Exception */ public void removeBucket(String bucketName) throws Exception { minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); } /****************************** Operate Bucket End ******************************/ /****************************** Operate Files Start ******************************/ /** * 判斷文件是否存在 * * @param bucketName 存儲(chǔ)桶 * @param objectName 文件名 * @return */ public boolean isObjectExist(String bucketName, String objectName) { boolean exist = true; try { minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()); } catch (Exception e) { exist = false; } return exist; } /** * 判斷文件夾是否存在 * * @param bucketName 存儲(chǔ)桶 * @param objectName 文件夾名稱 * @return */ public boolean isFolderExist(String bucketName, String objectName) { boolean exist = false; try { Iterable<Result<Item>> results = minioClient.listObjects( ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build()); for (Result<Item> result : results) { Item item = result.get(); if (item.isDir() && objectName.equals(item.objectName())) { exist = true; } } } catch (Exception e) { exist = false; } return exist; } /** * 根據(jù)文件前綴查詢文件 * * @param bucketName 存儲(chǔ)桶 * @param prefix 前綴 * @param recursive 是否使用遞歸查詢 * @return MinioItem 列表 * @throws Exception */ public List<Item> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) throws Exception { List<Item> list = new ArrayList<>(); Iterable<Result<Item>> objectsIterator = minioClient.listObjects( ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build()); if (objectsIterator != null) { for (Result<Item> o : objectsIterator) { Item item = o.get(); list.add(item); } } return list; } /** * 獲取文件流 * * @param bucketName 存儲(chǔ)桶 * @param objectName 文件名 * @return 二進(jìn)制流 */ public InputStream getObject(String bucketName, String objectName) throws Exception { return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build()); } /** * 斷點(diǎn)下載 * * @param bucketName 存儲(chǔ)桶 * @param objectName 文件名稱 * @param offset 起始字節(jié)的位置 * @param length 要讀取的長(zhǎng)度 * @return 二進(jìn)制流 */ public InputStream getObject(String bucketName, String objectName, long offset, long length) throws Exception { return minioClient.getObject( GetObjectArgs.builder() .bucket(bucketName) .object(objectName) .offset(offset) .length(length) .build()); } /** * 獲取路徑下文件列表 * * @param bucketName 存儲(chǔ)桶 * @param prefix 文件名稱 * @param recursive 是否遞歸查找,false:模擬文件夾結(jié)構(gòu)查找 * @return 二進(jìn)制流 */ public Iterable<Result<Item>> listObjects(String bucketName, String prefix, boolean recursive) { return minioClient.listObjects( ListObjectsArgs.builder() .bucket(bucketName) .prefix(prefix) .recursive(recursive) .build()); } /** * 使用MultipartFile進(jìn)行文件上傳 * * @param bucketName 存儲(chǔ)桶 * @param file 文件名 * @param objectName 對(duì)象名 * @param contentType 類型 * @return * @throws Exception */ public ObjectWriteResponse uploadFile(String bucketName, MultipartFile file, String objectName, String contentType) throws Exception { InputStream inputStream = file.getInputStream(); return minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .contentType(contentType) .stream(inputStream, inputStream.available(), -1) .build()); } /** * 上傳本地文件 * * @param bucketName 存儲(chǔ)桶 * @param objectName 對(duì)象名稱 * @param fileName 本地文件路徑 */ public ObjectWriteResponse uploadFile(String bucketName, String objectName, String fileName) throws Exception { return minioClient.uploadObject( UploadObjectArgs.builder() .bucket(bucketName) .object(objectName) .filename(fileName) .build()); } /** * 通過流上傳文件 * * @param bucketName 存儲(chǔ)桶 * @param objectName 文件對(duì)象 * @param inputStream 文件流 */ public ObjectWriteResponse uploadFile(String bucketName, String objectName, InputStream inputStream) throws Exception { return minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(inputStream, inputStream.available(), -1) .build()); } /** * 創(chuàng)建文件夾或目錄 * * @param bucketName 存儲(chǔ)桶 * @param objectName 目錄路徑 */ public ObjectWriteResponse createDir(String bucketName, String objectName) throws Exception { return minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(new ByteArrayInputStream(new byte[]{}), 0, -1) .build()); } /** * 獲取文件信息, 如果拋出異常則說明文件不存在 * * @param bucketName 存儲(chǔ)桶 * @param objectName 文件名稱 */ public String getFileStatusInfo(String bucketName, String objectName) throws Exception { return minioClient.statObject( StatObjectArgs.builder() .bucket(bucketName) .object(objectName) .build()).toString(); } /** * 拷貝文件 * * @param bucketName 存儲(chǔ)桶 * @param objectName 文件名 * @param srcBucketName 目標(biāo)存儲(chǔ)桶 * @param srcObjectName 目標(biāo)文件名 */ public ObjectWriteResponse copyFile(String bucketName, String objectName, String srcBucketName, String srcObjectName) throws Exception { return minioClient.copyObject( CopyObjectArgs.builder() .source(CopySource.builder().bucket(bucketName).object(objectName).build()) .bucket(srcBucketName) .object(srcObjectName) .build()); } /** * 刪除文件 * * @param bucketName 存儲(chǔ)桶 * @param objectName 文件名稱 */ public void removeFile(String bucketName, String objectName) throws Exception { minioClient.removeObject( RemoveObjectArgs.builder() .bucket(bucketName) .object(objectName) .build()); } /** * 批量刪除文件 * * @param bucketName 存儲(chǔ)桶 * @param keys 需要?jiǎng)h除的文件列表 * @return */ public void removeFiles(String bucketName, List<String> keys) { List<DeleteObject> objects = new LinkedList<>(); keys.forEach(s -> { objects.add(new DeleteObject(s)); try { removeFile(bucketName, s); } catch (Exception e) { log.error("批量刪除失??!error:{}", e); } }); } /** * 獲取文件外鏈 * * @param bucketName 存儲(chǔ)桶 * @param objectName 文件名 * @param expires 過期時(shí)間 <=7 秒 (外鏈有效時(shí)間(單位:秒)) * @return url * @throws Exception */ public String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) throws Exception { GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().expiry(expires).bucket(bucketName).object(objectName).build(); return minioClient.getPresignedObjectUrl(args); } /** * 獲得文件外鏈 * * @param bucketName * @param objectName * @return url * @throws Exception */ public String getPresignedObjectUrl(String bucketName, String objectName) throws Exception { GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder() .bucket(bucketName) .object(objectName) .method(Method.GET).build(); return minioClient.getPresignedObjectUrl(args); } /** * 將URLDecoder編碼轉(zhuǎn)成UTF8 * * @param str * @return * @throws UnsupportedEncodingException */ public String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException { String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25"); return URLDecoder.decode(url, "UTF-8"); } /****************************** Operate Files End ******************************/ }
6.文件上傳
FileController.java
package com.xuyue.miniodemo.controller; import com.xuyue.miniodemo.config.MinIOConfig; import com.xuyue.miniodemo.service.MinIoUploadService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @RestController @Slf4j public class FileController { @Autowired private MinIoUploadService minIoUploadService; @Autowired private MinIOConfig minIOConfig; /** * 上傳文件,返回url * @param file * @return * @throws Exception */ @PostMapping("upload") public String upload(MultipartFile file) throws Exception { String fileName = file.getOriginalFilename(); minIoUploadService.uploadFile(minIOConfig.getBucketName(), fileName, file.getInputStream()); String imgUrl = minIOConfig.getFileHost() + "/" + minIOConfig.getBucketName() + "/" + fileName; return imgUrl; } }
測(cè)試
啟動(dòng)項(xiàng)目
使用postman請(qǐng)求接口,返回文件地址
訪問地址:
查看minio控制臺(tái):
以上就是SpringBoot整合Minio實(shí)現(xiàn)文件上傳和讀取功能的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Minio文件上傳讀取的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章

解決mybatis plus報(bào)錯(cuò)com.microsoft.sqlserver.jdbc.SQLServerE