一、Linux 安裝Minio
安裝
在/root/xxkfz/soft
目錄下面創建文件minio
文件夾,進入minio
文件夾,并創建data
目錄;
[root@xxkfz?soft]#?mkdir?minio
[root@xxkfz?soft]#?cd?minio
[root@xxkfz?minio]#?mkdir?data
執行如下命令進行下載
[root@xxkfz?minio]#?wget?https://dl.min.io/server/minio/release/linux-amd64/minio
[root@xxkfz?minio]#?chmod?+x?minio??#?賦權
下載完成后如下所示:
設置賬號密碼
minio 默認賬號密碼為 minioadmin/minioadmin
[root@xxkfz?minio]#?export?MINIO_ACCESS_KEY=admin?#?設置控制臺賬號(最少3位)
[root@xxkfz?minio]#?export?MINIO_SECRET_KEY=12345678?#?設置密碼(最少8位)
直接設置管理員賬號密碼 編輯 /etc/profile
文件即可
[root@xxkfz?minio]#?vim?/etc/profile
編輯/etc/profile
文件,追加如下內容:
#===============================Minio=============================================
#?set?minio?environment
export?MINIO_ROOT_USER=admin
export?MINIO_ROOT_PASSWORD=admin123
可省略 設置賬號密碼
此步驟!12
啟動
進入執行文件目錄/root/xxkfz/soft/minio
,自定義端口啟動(默認端口:9000)
[root@xxkfz?minio]#?nohup?/root/xxkfz/soft/minio/minio?server?--address?:9001?--console-address?:9002?/root/xxkfz/soft/minio/data?>/root/xxkfz/soft/minio/minio.log?2>&1?&
說明:
nohup
為后臺啟動./minio server
啟動命令--address :9001
指定API端口--console-address :9002
指定控制臺端口/usr/local/minio/data
指定存儲目錄>/usr/local/minio/minio.log 2>&1
控制臺日志重定向到/usr/local/minio/minio.log文件中&
后臺運行
啟動成功:
啟動成功
注意:瀏覽器訪問需要開啟防火墻端口!
阿里云配置開放9001、9002端口
測試訪問:http://IP地址:9002
輸入賬號密碼: admin/12345678
登錄成功!
設置開機自啟動
設置Minio服務器宕機后自動重啟
進入init.d目錄
[root@xxkfz?minio]#?cd?/etc/rc.d/init.d
新建minio.sh shell腳本文件
[root@xxkfz?init.d]#?vim?minio.sh?
shell腳本內容
#!/bin/bash
#chkconfig:?2345?10?90
#description:?ping10
nohup?/root/xxkfz/soft/minio/minio?server?--address?:9001?--console-address?:9002?/root/xxkfz/soft/minio/data?>/root/xxkfz/soft/minio/minio.log?2>&1?&
給shell腳本賦權
chmod?+x?minio.sh
添加到開機自啟動服務中
chkconfig?--add?minio.sh
設置開機自啟動
chkconfig?minio.sh?on
查看是否添加成功
chkconfig?--list
二、Spring Boot整合Minio
引入依賴
pom.xml
???
????????????org.springframework.boot
????????????spring-boot-starter-web
????????
????????
????????????org.projectlombok
????????????lombok
????????????true
????????
????????
????????????io.minio
????????????minio
????????????8.2.2
????????
????????
????????????org.apache.commons
????????????commons-lang3
????????????3.11
????????
配置MinIo
application.yml
minio:
??endpoint:?http://IP地址:9001
??accessKey:?admin
??secretKey:?12345678
??bucketName:?xk-admin
#?配置端口號
server:
??port:?8099
編寫配置類
MinioConfig.java
/**
?*?@program:?xxkfz-minio
?*?@ClassName?MinioConfig.java
?*?@author:?公眾號:小小開發者
?*?@create:?2024-03-13?10:53
?*?@description:?Minio?配置類
?**/
@Data
@Configuration
public?class?MinioConfig?{
????/**
?????*?訪問地址
?????*/
????@Value("${minio.endpoint}")
????private?String?endpoint;
????/**
?????*?accessKey類似于用戶ID,用于唯一標識你的賬戶
?????*/
????@Value("${minio.accessKey}")
????private?String?accessKey;
????/**
?????*?secretKey是你賬戶的密碼
?????*/
????@Value("${minio.secretKey}")
????private?String?secretKey;
????/**
?????*?默認存儲桶
?????*/
????@Value("${minio.bucketName}")
????private?String?bucketName;
????@Bean
????public?MinioClient?minioClient()?{
????????MinioClient?minioClient?=?MinioClient.builder().endpoint(endpoint).credentials(accessKey,?secretKey).build();
????????return?minioClient;
????}
}
編寫Minio操作工具類
MinioUtils.java
/**
?*?@program:?xxkfz-minio
?*?@ClassName?MinioUtils.java
?*?@author:?公眾號:小小開發者
?*?@create:?2024-03-13?10:55
?*?@description:?MinIO操作工具類
?**/
@Slf4j
@Component
public?class?MinioUtils?{
????@Autowired
????private?MinioClient?minioClient;
????/**
?????*?啟動SpringBoot容器的時候初始化Bucket
?????*?如果沒有Bucket則創建
?????*
?????*?@param?bucketName
?????*/
????public?void?createBucket(String?bucketName)?{
????????try?{
????????????if?(!bucketExists(bucketName))?{
????????????????minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
????????????????log.info("創建bucketName?=?{}完成!",?bucketName);
????????????????return;
????????????}
????????????log.info("bucketName?=?{}已存在!策略為:{}",?bucketName,?getBucketPolicy(bucketName));
????????}?catch?(Exception?e)?{
????????????log.error("創建bucketName?=?{}異常!e?=?{}",?bucketName,?e);
????????}
????}
????/**
?????*?判斷Bucket是否存在,true:存在,false:不存在
?????*
?????*?@param?bucketName
?????*?@return
?????*/
????@SneakyThrows
????public?boolean?bucketExists(String?bucketName)?{
????????return?minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
????}
????/**
?????*?獲得Bucket的策略
?????*
?????*?@param?bucketName
?????*?@return
?????*/
????@SneakyThrows
????public?String?getBucketPolicy(String?bucketName)?{
????????return?minioClient.getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());
????}
????/**
?????*?獲得所有Bucket列表
?????*
?????*?@return
?????*/
????@SneakyThrows
????public?List?getAllBuckets()?{
????????return?minioClient.listBuckets();
????}
????/**
?????*?根據bucketName獲取其相關信息
?????*
?????*?@param?bucketName
?????*?@return
?????*/
????@SneakyThrows(Exception.class)
????public?Optional?getBucket(String?bucketName)?{
????????return?getAllBuckets().stream().filter(b?->?b.name().equals(bucketName)).findFirst();
????}
????/**
?????*?根據bucketName刪除Bucket,true:刪除成功;?false:刪除失敗,文件或已不存在
?????*
?????*?@param?bucketName
?????*?@throws?Exception
?????*/
????@SneakyThrows(Exception.class)
????public?void?removeBucket(String?bucketName)?{
????????minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
????}
????/**
?????*?判斷文件是否存在
?????*
?????*?@param?bucketName
?????*?@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)?{
????????????log.error("[Minio工具類]>>>>?判斷文件是否存在,?異常:",?e);
????????????exist?=?false;
????????}
????????return?exist;
????}
????/**
?????*?判斷文件夾是否存在
?????*
?????*?@param?bucketName
?????*?@param?objectName
?????*?@return
?????*/
????public?boolean?isFolderExist(String?bucketName,?String?objectName)?{
????????boolean?exist?=?false;
????????try?{
????????????Iterable>?results?=?minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());
????????????for?(Result?result?:?results)?{
????????????????Item?item?=?result.get();
????????????????if?(item.isDir()?&&?objectName.equals(item.objectName()))?{
????????????????????exist?=?true;
????????????????}
????????????}
????????}?catch?(Exception?e)?{
????????????log.error("[Minio工具類]>>>>?判斷文件夾是否存在,異常:",?e);
????????????exist?=?false;
????????}
????????return?exist;
????}
????/**
?????*?根據文件前置查詢文件
?????*
?????*?@param?bucketName?存儲桶
?????*?@param?prefix?????前綴
?????*?@param?recursive??是否使用遞歸查詢
?????*?@return?MinioItem?列表
?????*/
????@SneakyThrows(Exception.class)
????public?List?getAllObjectsByPrefix(String?bucketName,?String?prefix,?boolean?recursive)?{
????????List?list?=?new?ArrayList<>();
????????Iterable>?objectsIterator?=?minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
????????if?(objectsIterator?!=?null)?{
????????????for?(Result?o?:?objectsIterator)?{
????????????????Item?item?=?o.get();
????????????????list.add(item);
????????????}
????????}
????????return?list;
????}
????/**
?????*?獲取文件流
?????*
?????*?@param?bucketName?存儲桶
?????*?@param?objectName?文件名
?????*?@return?二進制流
?????*/
????@SneakyThrows(Exception.class)
????public?InputStream?getObject(String?bucketName,?String?objectName)?{
????????return?minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
????}
????/**
?????*?斷點下載
?????*
?????*?@param?bucketName?存儲桶
?????*?@param?objectName?文件名稱
?????*?@param?offset?????起始字節的位置
?????*?@param?length?????要讀取的長度
?????*?@return?二進制流
?????*/
????@SneakyThrows(Exception.class)
????public?InputStream?getObject(String?bucketName,?String?objectName,?long?offset,?long?length)?{
????????return?minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length).build());
????}
????/**
?????*?獲取路徑下文件列表
?????*
?????*?@param?bucketName?存儲桶
?????*?@param?prefix?????文件名稱
?????*?@param?recursive??是否遞歸查找,false:模擬文件夾結構查找
?????*?@return?二進制流
?????*/
????public?Iterable>?listObjects(String?bucketName,?String?prefix,?boolean?recursive)?{
????????return?minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
????}
????/**
?????*?使用MultipartFile進行文件上傳
?????*
?????*?@param?bucketName??存儲桶
?????*?@param?file????????文件名
?????*?@param?objectName??對象名
?????*?@param?contentType?類型
?????*?@return
?????*/
????@SneakyThrows(Exception.class)
????public?ObjectWriteResponse?uploadFile(String?bucketName,?MultipartFile?file,?String?objectName,?String?contentType)?{
????????InputStream?inputStream?=?file.getInputStream();
????????return?minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).contentType(contentType).stream(inputStream,?inputStream.available(),?-1).build());
????}
????/**
?????*?圖片上傳
?????*
?????*?@param?bucketName
?????*?@param?imageBase64
?????*?@param?imageName
?????*?@return
?????*/
????public?ObjectWriteResponse?uploadImage(String?bucketName,?String?imageBase64,?String?imageName)?{
????????if?(!StringUtils.isEmpty(imageBase64))?{
????????????InputStream?in?=?base64ToInputStream(imageBase64);
????????????String?newName?=?System.currentTimeMillis()?+?"_"?+?imageName?+?".jpg";
????????????String?year?=?String.valueOf(new?Date().getYear());
????????????String?month?=?String.valueOf(new?Date().getMonth());
????????????return?uploadFile(bucketName,?year?+?"/"?+?month?+?"/"?+?newName,?in);
????????}
????????return?null;
????}
????public?static?InputStream?base64ToInputStream(String?base64)?{
????????ByteArrayInputStream?stream?=?null;
????????try?{
????????????byte[]?bytes?=?Base64.getEncoder().encode(base64.trim().getBytes());
????????????stream?=?new?ByteArrayInputStream(bytes);
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
????????return?stream;
????}
????/**
?????*?上傳本地文件
?????*
?????*?@param?bucketName?存儲桶
?????*?@param?objectName?對象名稱
?????*?@param?fileName???本地文件路徑
?????*?@return
?????*/
????@SneakyThrows(Exception.class)
????public?ObjectWriteResponse?uploadFile(String?bucketName,?String?objectName,?String?fileName)?{
????????return?minioClient.uploadObject(UploadObjectArgs.builder().bucket(bucketName).object(objectName).filename(fileName).build());
????}
????/**
?????*?通過流上傳文件
?????*
?????*?@param?bucketName??存儲桶
?????*?@param?objectName??文件對象
?????*?@param?inputStream?文件流
?????*?@return
?????*/
????@SneakyThrows(Exception.class)
????public?ObjectWriteResponse?uploadFile(String?bucketName,?String?objectName,?InputStream?inputStream)?{
????????return?minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(inputStream,?inputStream.available(),?-1).build());
????}
????/**
?????*?創建文件夾或目錄
?????*
?????*?@param?bucketName?存儲桶
?????*?@param?objectName?目錄路徑
?????*?@return
?????*/
????@SneakyThrows(Exception.class)
????public?ObjectWriteResponse?createDir(String?bucketName,?String?objectName)?{
????????return?minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(new?ByteArrayInputStream(new?byte[]{}),?0,?-1).build());
????}
????/**
?????*?獲取文件信息,?如果拋出異常則說明文件不存在
?????*
?????*?@param?bucketName?存儲桶
?????*?@param?objectName?文件名稱
?????*?@return
?????*/
????@SneakyThrows(Exception.class)
????public?String?getFileStatusInfo(String?bucketName,?String?objectName)?{
????????return?minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()).toString();
????}
????/**
?????*?拷貝文件
?????*
?????*?@param?bucketName????存儲桶
?????*?@param?objectName????文件名
?????*?@param?srcBucketName?目標存儲桶
?????*?@param?srcObjectName?目標文件名
?????*/
????@SneakyThrows(Exception.class)
????public?ObjectWriteResponse?copyFile(String?bucketName,?String?objectName,?String?srcBucketName,?String?srcObjectName)?{
????????return?minioClient.copyObject(CopyObjectArgs.builder().source(CopySource.builder().bucket(bucketName).object(objectName).build()).bucket(srcBucketName).object(srcObjectName).build());
????}
????/**
?????*?刪除文件
?????*
?????*?@param?bucketName?存儲桶
?????*?@param?objectName?文件名稱
?????*/
????@SneakyThrows(Exception.class)
????public?void?removeFile(String?bucketName,?String?objectName)?{
????????minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
????}
????/**
?????*?批量刪除文件
?????*
?????*?@param?bucketName?存儲桶
?????*?@param?keys???????需要刪除的文件列表
?????*?@return
?????*/
????public?void?removeFiles(String?bucketName,?List?keys)?{
????????List?objects?=?new?LinkedList<>();
????????keys.forEach(s?->?{
????????????objects.add(new?DeleteObject(s));
????????????try?{
????????????????removeFile(bucketName,?s);
????????????}?catch?(Exception?e)?{
????????????????log.error("[Minio工具類]>>>>?批量刪除文件,異常:",?e);
????????????}
????????});
????}
????/**
?????*?獲取文件外鏈
?????*
?????*?@param?bucketName?存儲桶
?????*?@param?objectName?文件名
?????*?@param?expires????過期時間?<=7?秒?(外鏈有效時間(單位:秒))
?????*?@return?url
?????*/
????@SneakyThrows(Exception.class)
????public?String?getPresignedObjectUrl(String?bucketName,?String?objectName,?Integer?expires)?{
????????GetPresignedObjectUrlArgs?args?=?GetPresignedObjectUrlArgs.builder().expiry(expires).bucket(bucketName).object(objectName).build();
????????return?minioClient.getPresignedObjectUrl(args);
????}
????/**
?????*?獲得文件外鏈
?????*
?????*?@param?bucketName
?????*?@param?objectName
?????*?@return?url
?????*/
????@SneakyThrows(Exception.class)
????public?String?getPresignedObjectUrl(String?bucketName,?String?objectName)?{
????????GetPresignedObjectUrlArgs?args?=?GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(objectName).method(Method.GET).build();
????????return?minioClient.getPresignedObjectUrl(args);
????}
????/**
?????*?將URLDecoder編碼轉成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");
????}
}
項目啟動初始化配置
創建配置類InitConfig.java
,并實現了InitializingBean接口
,重寫afterPropertiesSet
方法。
該方法主要實現邏輯:在項目啟動的時候初始化Bucket,如果沒有則進行創建!
InitConfig.java
/**
?*?@program:?xxkfz-minio
?*?@ClassName?Init.java
?*?@author:?wust
?*?@create:?2024-03-16?10:34
?*?@description:?項目啟動初始化配置
?**/
@Component
@Slf4j
????public?class?InitConfig?implements?InitializingBean?{
????@Autowired
????private?MinioUtils?minioUtils;
????@Autowired
????private?MinioConfig?minioConfig;
????@Override
????public?void?afterPropertiesSet()?throws?Exception?{
????????//?項目啟動創建Bucket,不存在則進行創建
????????minioUtils.createBucket(minioConfig.getBucketName());
????}
}
編寫測試接口
MinioController.java
/**
?*?@program:?xxkfz-minio
?*?@ClassName?OSSController.java
?*?@author:?wust
?*?@create:?2024-03-13?11:01
?*?@description:
?**/
@Slf4j
@RestController
@RequestMapping("/oss")
public?class?MinioController?{
????@Autowired
????private?MinioUtils?minioUtils;
????@Autowired
????private?MinioConfig?minioConfig;
????/**
?????*?文件上傳
?????*
?????*?@param?file
?????*/
????@PostMapping("/upload")
????public?String?upload(@RequestParam("file")?MultipartFile?file)?{
????????try?{
????????????//文件名
????????????String?fileName?=?file.getOriginalFilename();
????????????String?newFileName?=?System.currentTimeMillis()?+?"."?+?StringUtils.substringAfterLast(fileName,?".");
????????????//類型
????????????String?contentType?=?file.getContentType();
????????????minioUtils.uploadFile(minioConfig.getBucketName(),?file,?newFileName,?contentType);
????????????return?"上傳成功,文件名:"?+?newFileName;
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????????return?"上傳失敗";
????????}
????}
????/**
?????*?刪除
?????*
?????*?@param?fileName
?????*/
????@DeleteMapping("/")
????public?void?delete(@RequestParam("fileName")?String?fileName)?{
????????minioUtils.removeFile(minioConfig.getBucketName(),?fileName);
????}
????/**
?????*?獲取文件信息
?????*
?????*?@param?fileName
?????*?@return
?????*/
????@GetMapping("/info")
????public?String?getFileStatusInfo(@RequestParam("fileName")?String?fileName)?{
????????return?minioUtils.getFileStatusInfo(minioConfig.getBucketName(),?fileName);
????}
????/**
?????*?獲取文件外鏈
?????*
?????*?@param?fileName
?????*?@return
?????*/
????@GetMapping("/url")
????public?String?getPresignedObjectUrl(@RequestParam("fileName")?String?fileName)?{
????????return?minioUtils.getPresignedObjectUrl(minioConfig.getBucketName(),?fileName);
????}
????/**
?????*?文件下載
?????*
?????*?@param?fileName
?????*?@param?response
?????*/
????@GetMapping("/download")
????public?void?download(@RequestParam("fileName")?String?fileName,?HttpServletResponse?response)?{
????????try?{
????????????InputStream?fileInputStream?=?minioUtils.getObject(minioConfig.getBucketName(),?fileName);
????????????response.setHeader("Content-Disposition",?"attachment;filename="?+?fileName);
????????????response.setContentType("application/force-download");
????????????response.setCharacterEncoding("UTF-8");
????????????IOUtils.copy(fileInputStream,?response.getOutputStream());
????????}?catch?(Exception?e)?{
????????????log.error("下載失敗");
????????}
????}
}
測試驗證
啟動項目:
上傳圖片
測試接口:http://localhost:8099/oss/upload
進入服務器查看文件上傳情況。
進入目錄:/root/xxkfz/soft/minio/data/xk-admin
當然,也可以直接訪問minio的地址:http://IP地址:9001/xk-admin/1710558001536.jpg。驗證文件是否上傳成功。
獲取文件信息
測試接口:http://localhost:8099/oss/info
獲取文件外鏈
測試接口:http://localhost:8099/oss/url
下載文件
測試接口:http://localhost:8099/oss/download