Spring Boot 2.x 整合 MinIO 8.x
- MinIO概要
- MinIO & FastDFS 對比
- 思路
- 附件資訊表 資料庫表結構設計參考
- talk is cheap -> show me the code(核心代碼)
MinIO概要
MinIO 是一個基于Apache License v2.0開源協議的物件存盤服務,它兼容亞馬遜S3云存盤服務介面,非常適合于存盤大容量非結構化的資料,例如圖片、視頻、日志檔案、備份資料和容器/虛擬機鏡像等,而一個物件檔案可以是任意大小,從幾kb到最大5T不等,
MinIO是一個非常輕量的服務,可以很簡單的和其他應用的結合,類似 NodeJS, Redis 或者 MySQL,
快速入門地址 -> http://docs.minio.org.cn/docs/
PS: 之前官網檔案API有些已廢棄,提過issue,官方已更新,也許還有部分檔案未更新
MinIO & FastDFS 對比
MinIO:
Kubernetes原生支持,高性能,物件存盤,有官方檔案,API簡單,有控制臺
FastDFS:
部署較為復雜,要理解FastDFS的架構才好上手部署開發,沒有官方檔案,沒有控制臺
思路
- 方案一:
前端 將附件與表單屬性一同提交 - 方案二:
前端分兩步
2.1 前端 上傳圖片 呼叫“附件上傳介面” -> 成功,回傳attachmentId;
2.2 前端 提交表單 將 attachmentId 和其他表單資訊一同提交,
選擇方案二,理由:解耦,成功率更高,
附件資訊表 資料庫表結構設計參考

talk is cheap -> show me the code(核心代碼)
Spring Boot 工程 application.yml
spring:
servlet:
# 附件上傳限制大小
multipart:
# 單個檔案的最大值
max-file-size: 10MB
# 最大請求檔案的大小
max-request-size: 100MB
# MinIO
minio:
url: http://127.0.0.1:9000
access-key: minioadmin
secret-key: minioadmin
bucket-name: spring-festival
MinIOConfig.java
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MinIO config
*
* @author Jiahai
*/
@Data
@Configuration
@ConfigurationProperties(value = "minio")
public class MinIOConfig {
/**
* URL
*/
private String url;
/**
* access-key
*/
private String accessKey;
/**
* secretKey
*/
private String secretKey;
/**
* bucket name
*/
private String bucketName;
/**
* 初始化 MinIO Client
*
* @return
*/
@Bean
public MinioClient initMinioClient () {
return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();
}
}
MinIOComponent.java
import com.wisdom.attachment.service.AttachmentInfoService;
import com.wisdom.config.MinIOConfig;
import com.wisdom.exception.BusinessException;
import io.minio.*;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.concurrent.ThreadPoolExecutor;
/**
* MinIO 組件
*
* @author Jiahai
*/
@Slf4j
@Component
public class MinIOComponent {
@Autowired
private MinIOConfig minIOConfig;
@Autowired
private MinioClient minioClient;
/**
* 自定義的執行緒池
**/
@Resource(name = "updateAttachmentInfoThreadPoolExecutor")
private ThreadPoolExecutor updateAttachmentInfoThreadPoolExecutor;
@Autowired
private AttachmentInfoService attachmentInfoService;
/**
* 附件上傳
*
* @param attachmentInfoId 附件ID
* @param multipartFile 附件
* @param attachmentNameInServer 存盤在檔案服務器中的附件名稱
*/
public String upload(Long attachmentInfoId, MultipartFile multipartFile, String attachmentNameInServer) {
// 獲取 原始檔案名,例如 hello.txt
String originalFilename = multipartFile.getOriginalFilename();
String contentType = multipartFile.getContentType();
// 桶名稱
String bucketName = minIOConfig.getBucketName();
try (InputStream inputStream = multipartFile.getInputStream()) {
boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!bucketExists) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName)
.object(attachmentNameInServer)
.stream(inputStream, multipartFile.getSize(), -1)
.contentType(contentType).build()
);
log.info("附件上傳成功, fileName: {}, contentType: {}, size(Byte): {}", originalFilename, contentType, multipartFile.getSize());
// 異步更新附件URL
String presignedObjectUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(attachmentNameInServer).build());
updateAttachmentInfoThreadPoolExecutor.execute(() -> {
try {
int row = attachmentInfoService.updateAttachmentUrlByAttachmentInfoId(attachmentInfoId, presignedObjectUrl);
log.info("ID為{}的附件,URL更新{}", attachmentInfoId, row > 0 ? "成功" : "失敗");
} catch (Exception e) {
e.printStackTrace();
}
});
return presignedObjectUrl;
} catch (Exception e) {
log.error("附件上傳失敗, fileName: {}, contentType: {}, size(Byte): {}", originalFilename, contentType, multipartFile.getSize());
e.printStackTrace();
throw new BusinessException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "附件上傳失敗");
}
}
/**
* 附件下載
*
* @param attachmentName 附件原始名稱
* @param bucketName 桶名稱
* @param attachmentNameInServer 存盤在檔案服務器中的附件名稱
* @param httpServletResponse httpServletResponse
*/
public void download(String attachmentName, String bucketName, String attachmentNameInServer, HttpServletResponse httpServletResponse) {
try {
StatObjectResponse statObjectResponse = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(attachmentNameInServer).build());
httpServletResponse.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(attachmentName, "UTF-8"));
httpServletResponse.setContentType(statObjectResponse.contentType());
httpServletResponse.setCharacterEncoding("UTF-8");
InputStream inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(attachmentNameInServer).build());
IOUtils.copy(inputStream, httpServletResponse.getOutputStream());
} catch (Exception e) {
log.error("附件下載失敗, fileName: {}, bucketName: {}, attachmentNameInServer: {}", attachmentName, bucketName, attachmentNameInServer);
e.printStackTrace();
}
}
/**
* 獲取預覽URL
* 注:.txt的附件會亂碼,PDF正常
* 注:默認有效期7天
*
* @param bucketName 桶名稱
* @param attachmentNameInServer 存盤在檔案服務器中的附件名稱
* @return
*/
public String getPresignedUrl(String bucketName, String attachmentNameInServer) {
try {
minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(attachmentNameInServer).build());
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(attachmentNameInServer).build());
} catch (Exception e) {
throw new BusinessException(HttpStatus.BAD_REQUEST.value(), e.getMessage());
}
}
/**
* 洗掉附件
*
* @param bucketName 桶名稱
* @param attachmentNameInServer 存盤在檔案服務器中的附件名稱
*/
public void remove(String bucketName, String attachmentNameInServer) {
try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(attachmentNameInServer).build());
log.info("附件: {}, 洗掉成功!", attachmentNameInServer);
} catch (Exception e) {
log.error("附件: {}, 洗掉失敗...", attachmentNameInServer);
e.printStackTrace();
}
}
}
PS:
- 新版MinIO的API主要使用建造者模式;
- MinIO的預覽URL有時效性,注意使用定時任務去完成重繪,自行把握時間視窗,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/259222.html
標籤:java
下一篇:redis分布式鎖
