一、安裝Minio
1.1 拉取鏡像
docker pull minio/minio
docker images
1.2創建掛載目錄
1.2.1 創建數據目錄
mkdir -p /docker-minio/data
1.2.2 創建配置文件目錄
mkdir -p /docker-minio/config
1.2.3 設置權限
chmod -R 777 /docker-minio/data /docker-minio/config
1.3啟動MinIO容器
docker run -d --name minio \-p 9000:9000 -p 9090:9090 \-e "MINIO_ROOT_USER=minioAdmin" \-e "MINIO_ROOT_PASSWORD=minioAdmin" \-v /docker-minio/data:/data \-v /docker-minio/config:/root/.minio \minio/minio server /data \--console-address ":9090" \--address ":9000"
注意:MinIO 規定 Access key 長度至少為 3,Secret key 長度至少為 8。
這個 Docker 命令用于啟動一個?MinIO 對象存儲服務器容器。MinIO 是一個高性能、與 Amazon S3 API 兼容的開源對象存儲解決方案。下面是詳細解釋:
1.?
docker run
作用:?Docker 命令的核心部分,用于創建并啟動一個新的容器。
2.?
-d
作用:?
detach
?的縮寫。以后臺(守護進程)模式運行容器。容器啟動后,控制臺會立即返回,你可以繼續使用終端,而容器在后臺運行。要查看容器日志,可以使用?docker logs minio
。3.?
--name minio
作用:?為容器指定一個名稱?
minio
。這使得后續管理容器(如啟動、停止、查看日志)更加方便,無需使用復雜的容器 ID。例如:
docker stop minio
docker start minio
docker logs minio
4.?
-p 9000:9000 -p 9090:9090
作用:?將容器內部的端口映射到宿主機的端口。
-p 9000:9000
: 將容器內部的?9000 端口映射到宿主機的?9000 端口。這個端口是?MinIO API 服務端口,用于客戶端(SDK、命令行工具?mc
、應用程序)與 MinIO 服務進行交互(上傳、下載、管理對象等),兼容 S3 API。
-p 9090:9090
: 將容器內部的?9090 端口映射到宿主機的?9090 端口。這個端口是?MinIO 控制臺(Web UI)端口,用于通過瀏覽器訪問圖形化管理界面來管理存儲桶、用戶、策略等。5.?
-e "MINIO_ROOT_USER=minioAdmin"
作用:?設置容器內的環境變量?
MINIO_ROOT_USER
。這個變量定義了 MinIO 的?根用戶(管理員)用戶名。這里設置為?minioAdmin
。非常重要!6.?
-e "MINIO_ROOT_PASSWORD=minioAdmin"
作用:?設置容器內的環境變量?
MINIO_ROOT_PASSWORD
。這個變量定義了 MinIO 的?根用戶(管理員)密碼。這里設置為?minioAdmin
。非常重要!強烈建議在生產環境中使用強密碼替代這個示例密碼。7.?
-v /docker-minio/data:/data
作用:?創建一個數據卷,將宿主機上的目錄?
/docker-minio/data
?掛載到容器內部的目錄?/data
。
/docker-minio/data
: 宿主機上的目錄路徑,這個目錄將持久化存儲 MinIO 對象(文件)本身。
/data
: 容器內部的路徑。MinIO 服務器會將所有上傳的對象存儲在這個目錄下。意義:?這個掛載確保了 MinIO 存儲的實際文件數據不會丟失。即使容器被刪除或重啟,宿主機?
/docker-minio/data
?下的數據仍然存在。下次啟動新容器并掛載同一目錄,數據即可恢復。8.?
-v /docker-minio/config:/root/.minio
作用:?創建另一個數據卷,將宿主機上的目錄?
/docker-minio/config
?掛載到容器內部的目錄?/root/.minio
。
/docker-minio/config
: 宿主機上的目錄路徑,這個目錄將持久化存儲 MinIO 的服務器配置、用戶信息、策略等元數據。
/root/.minio
: 容器內部 MinIO 服務默認存儲其配置文件和狀態的路徑。意義:?這個掛載確保了 MinIO 的配置、用戶賬號、訪問策略等關鍵元數據不會丟失。刪除或重建容器后,只要掛載回這個目錄,所有配置和用戶信息都會保留。
9.?
minio/minio
作用:?指定要運行的?Docker 鏡像。這里是官方 MinIO 鏡像,從 Docker Hub 的?
minio/minio
?倉庫拉取。如果本地沒有,Docker 會自動從 Hub 下載。10.?
server /data
作用:?這是傳遞給?
minio/minio
?鏡像的?啟動命令。
server
: 告訴 MinIO 可執行文件以服務器模式運行。
/data
: 指定 MinIO 服務器在容器內部存儲對象數據的路徑。這必須與第 7 條 (-v ...:/data
) 中掛載到容器內部的路徑一致。MinIO 會使用這個目錄來保存上傳的文件。11.?
--console-address ":9090"
作用:?MinIO 服務器啟動參數。顯式指定 MinIO 控制臺(Web UI)監聽的地址和端口。
:9090
: 表示綁定到容器內的所有網絡接口 (0.0.0.0
) 的 9090 端口。這直接對應了第 4 條 (-p 9090:9090
) 中映射的端口。訪問?http://<宿主機IP>:9090
?即可打開管理控制臺。12.?
--address ":9000"
作用:?MinIO 服務器啟動參數。顯式指定 MinIO API 服務(S3 兼容接口)監聽的地址和端口。
:9000
: 表示綁定到容器內的所有網絡接口 (0.0.0.0
) 的 9000 端口。這直接對應了第 4 條 (-p 9000:9000
) 中映射的端口。客戶端(如?mc
,AWS SDK, 應用程序)通過?http://<宿主機IP>:9000
?來訪問存儲服務。
1.4 訪問Minio控制臺
啟動容器后,可以通過瀏覽器訪問 MinIO 控制臺:
http://<宿主機IP>:9001
使用之前設置的用戶名和密碼(minioAdmin
?和?minioAdmin
)登錄。
二、SpringBoot集成minio
2.1 導入maven做標
<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.2.0</version></dependency>
2.2 自定義配置
在yml添加如下配置:
minio:url: http://121.40.159.231:9000#minio賬戶accessKey: minioAdmin#minio密碼secretKey: minioAdminbucketName: nbsjfx
然后創建對應的配置類:
@Data
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioProp {/*** Minio存儲服務服務的地址*/private String url;/*** Minio的訪問賬號*/private String accessKey;/*** Minio的訪問密碼*/private String secretKey;/*** Minio的存儲桶名稱*/private String bucketName;}
2.3 創建MinioClient(向 Spring 容器中注冊一個 MinioClient
類型的 Bean),并創建Bucket
@Configuration
@RequiredArgsConstructor
public class MinioConfig {private final MinioProp minioProp;/*** 創建MinioClient實例*/@Beanpublic MinioClient minioClient() {MinioClient minioClient = MinioClient.builder().endpoint(minioProp.getUrl()).credentials(minioProp.getAccessKey(), minioProp.getSecretKey()).build();return minioClient;}/*** 創建Bucket*/@SneakyThrows@PostConstructpublic void createBucket() {MinioClient minioClient = minioClient();boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(minioProp.getBucketName()).build());if (!bucketExists) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(minioProp.getBucketName()).build());}}}
2.4 自定義工具類
自定義的MinioUtil代碼如下:
@Component
@RequiredArgsConstructor
public class MinioUtil {private final MinioClient minioClient;private final MinioProp minioProp;private final HttpServletResponse httpServletResponse;/*** 檢查bucket是否存在,不存在則創建** @param bucketName bucket名稱* @return 是否創建成功*/@SneakyThrowspublic Boolean createBucket(String bucketName) {boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());if (!exists) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}return Boolean.TRUE;}/*** 獲取所有bucket列表** @return bucket列表*/@SneakyThrowspublic List<Bucket> getAllBuckets() {List<Bucket> bucketList = minioClient.listBuckets();return bucketList;}/*** 刪除bucket (桶為空時,才能刪除)** @param bucketName bucket名稱* @return 是否刪除成功*/@SneakyThrowspublic Boolean deleteBucket(String bucketName) {minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());return Boolean.TRUE;}/*** 獲取bucket中的所有文件列表** @param bucketName bucket名稱* @return 文件列表*/@SneakyThrowspublic List<Item> getAllFilesByBucket(String bucketName) {Iterable<Result<Item>> iterable = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());List<Item> itemList = new ArrayList<>();for (Result<Item> result : iterable) {Item item = result.get();itemList.add(item);}return itemList;}/*** 上傳文件** @param inputStream 文件輸入流* @param bucketName bucket名稱* @param fileName 文件名稱* @return 是否上傳成功*/@SneakyThrowspublic Boolean uploadFile(InputStream inputStream, String bucketName, String fileName) {minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(inputStream, -1, 5242889L).build());return Boolean.TRUE;}/*** 上傳文件** @param multipartFile 文件對象* @return 是否上傳成功*/@SneakyThrowspublic Boolean uploadFile(MultipartFile multipartFile) {return uploadFile(multipartFile.getInputStream(), minioProp.getBucketName(), multipartFile.getOriginalFilename());}/*** 刪除文件** @param bucketName bucket名稱* @param fileName 文件名稱* @return 是否刪除成功*/@SneakyThrowspublic Boolean deleteFile(String bucketName, String fileName) {minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());return Boolean.TRUE;}/*** 刪除文件** @param fileName 文件名稱* @return 是否刪除成功*/public Boolean deleteFile(String fileName) {return deleteFile(minioProp.getBucketName(), fileName);}/*** 下載文件** @param bucketName bucket名稱* @param fileName 文件名稱*/@SneakyThrowspublic void downloadFile(String bucketName, String fileName) {GetObjectResponse getObjectResponse = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());byte[] buf = new byte[1024];int len;try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {while ((len = getObjectResponse.read(buf)) != -1) {os.write(buf, 0, len);}os.flush();byte[] bytes = os.toByteArray();httpServletResponse.setCharacterEncoding("utf-8");// 設置強制下載不打開//httpServletResponse.setContentType("application/force-download");httpServletResponse.addHeader("Access-Control-Expose-Headers", "Content-Disposition");httpServletResponse.addHeader("Content-Disposition", "attachment;filename=".concat(URLEncoder.encode(fileName, "UTF-8")));try (ServletOutputStream stream = httpServletResponse.getOutputStream()) {stream.write(bytes);stream.flush();}}}/*** 下載文件** @param fileName 文件名稱*/public void downloadFile(String fileName) {downloadFile(minioProp.getBucketName(), fileName);}/*** 獲取預覽文件url (預覽鏈接默認7天后過期)** @param bucketName bucket名稱* @param fileName 文件名稱* @return 文件url*/@SneakyThrowspublic String getPreviewFileUrl(String bucketName, String fileName) {GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(fileName).build();return minioClient.getPresignedObjectUrl(args);}/*** 獲取預覽文件url (預覽鏈接默認7天后過期)** @param fileName 文件名稱* @return 文件url*/public String getPreviewFileUrl(String fileName) {return getPreviewFileUrl(minioProp.getBucketName(), fileName);}}
三、nginx代理minio的文件鏈接
3.1 前置說明
當我們創建桶后,Minio會在數據掛載目錄下,創建一個和桶同名的文件夾。
上面的過程中,我們創建了一個為nbsjfx的桶,對應的文件夾如下:
目前桶里有一個文件,為1.jpg:
3.2 反向代理
server {listen 9999;server_name 127.0.0.1;location / {root /docker-minio/data;}
測試結果:
四、適配器模式(實戰教程)
百度百科對于適配器的介紹:適配器模式_百度百科
????????在計算機編程中,適配器模式(有時候也稱包裝樣式或者包裝)將一個類的接口適配成用戶所期待的。一個適配允許通常因為接口不兼容而不能在一起工作的類工作在一起,做法是將類自己的接口包裹在一個已存在的類中。
????????適配器模式(Adapter Pattern)是一種結構型設計模式,它允許接口不兼容的類能夠一起工作。適配器模式通過將一個類的接口轉換成客戶期望的另一個接口,使得原本由于接口不兼容而不能一起工作的類可以一起工作。
????????在這個場景中,我們需要實現一個統一的OSS(對象存儲服務)接口,然后為不同的云存儲服務(如minio、阿里云OSS、京東云OSS)提供適配器。這樣,客戶端就可以通過統一的接口來操作不同的云存儲服務,而無需關心底層不同服務的具體實現細節。
4.1 創建OSS適配器接口
/*** OSS適配器接口*/
public interface OssAdapter {/*** 上傳文件* @param multipartFile 上傳的文件* @return 上傳成功返回true,否則返回false*/Boolean uploadFile(MultipartFile multipartFile);}
我們這里通過上傳文件方法作為案例,希望不管什么廠商的Oss都通過這個接口的方式進行文件上傳
4.2 創建對應的適配器實現類
4.2.1 創建minio適配器實現類
/*** Minio Oss 適配器*/
@Slf4j
public class MinioOssAdapter implements OssAdapter {@Autowiredprivate MinioUtil minioUtil;@Overridepublic Boolean uploadFile(MultipartFile multipartFile) {log.info("Minio適配器實現文件上傳");return minioUtil.uploadFile(multipartFile);}}
注意到,此處我們沒有把該類注冊為spring的bean,所以@Autowired注解在提升錯誤。
因為我們的系統,一般都只使用一種文件存儲服務,使用我們這里預期是到時候通過配置類的形式交由spring容器進行管理。
4.2.2 創建aliyun適配器實現類
/*** 阿里云 OSS 適配器*/
@Slf4j
public class AliyunOssAdapter implements OssAdapter {//也是注入阿里云Oss相關的工具類和實現等@Overridepublic Boolean uploadFile(MultipartFile multipartFile) {log.info("阿里云適配器實現文件上傳");// TODO 阿里云 OSS 上傳文件return null;}}
4.2.3 創建配置類,按需注入
首先在yml文件中,自定義類型:
oss:oss-type: minio
然后創建配置類如下:(根據類型完成注入)
@Slf4j
@Configuration
public class OssConfig {@Value("${oss.oss-type}")private String ossType;@Beanpublic OssAdapter ossAdapter(){log.info("文件存儲ossType: {}", ossType);if ("minio".equals(ossType)){return new MinioOssAdapter();}else if ("aliyun".equals(ossType)){return new AliyunOssAdapter();}throw new IllegalArgumentException("未找到對應的Oss文件存儲適配器");}}
4.3 抽取文件服務類(根據自己的系統自定義)
@Service
public class FileService {@Autowiredprivate OssAdapter ossAdapter;public Boolean uploadFile(MultipartFile multipartFile) {return ossAdapter.uploadFile(multipartFile);}}
4.4 編寫控制層進行測試
@RestController
@RequestMapping("/file")
public class FileController {@Autowiredprivate FileService fileService;@RequestMapping("/uploadFile")public IResult uploadFile(MultipartFile multipartFile) {return new ResultBean<>(fileService.uploadFile(multipartFile));}}
當配置文件配置的類型為minio時,測試結果:
同時minio中也確實上傳了文件:
當我們把配置文件的類型改為aliyun時,測試結果:
4.5 總結
至此,我們實現將不同 OSS 提供商的接口抽象成一個統一的接口,從而實現解耦和靈活切換。
優勢:
-
統一接口:客戶端使用一致的API操作不同云存儲
-
解耦:業務代碼與具體云服務實現解耦
-
可擴展:新增云服務只需添加新適配器
-
易維護:各云服務實現相互隔離,修改互不影響