Java 操作 MinIO詳細步驟
Java 操作 MinIO 全指南:從 API 詳解到實戰(zhàn)場景
引言:為什么選擇 MinIO?
在分布式存儲領(lǐng)域,MinIO 是一款輕量級、高性能的對象存儲服務(wù),兼容 Amazon S3 API,支持海量文件存儲(從 KB 級小文件到 TB 級大文件)。無論是企業(yè)級文檔管理、大數(shù)據(jù)存儲,還是 RAG 系統(tǒng)中的文件上傳解析(如 PaiSmart 項目),MinIO 都能滿足需求 —— 它的核心優(yōu)勢在于:
- 輕量易部署:單節(jié)點即可啟動,集群部署也只需簡單配置;
- S3 兼容:支持所有 S3 核心 API,學(xué)習(xí)成本低;
- 高性能:針對大文件和批量操作優(yōu)化,吞吐量高;
- 彈性擴展:支持水平擴展,按需增加存儲節(jié)點;
- 開源免費:無商業(yè)許可成本,適合中小企業(yè)和個人項目。
本文從Java 開發(fā)者視角,從環(huán)境準備到核心 API,再到實戰(zhàn)場景,手把手教你掌握 MinIO 的使用,0 基礎(chǔ)也能看懂每一行代碼,明確每一個 API 的適用場景。
一、環(huán)境準備:Java 操作 MinIO 的前置步驟
在寫代碼前,先完成 “工具準備”—— 引入依賴、初始化客戶端,這是所有操作的基礎(chǔ)。
1.1 引入 MinIO Java SDK 依賴
MinIO 提供官方 Java SDK,通過 Maven/Gradle 引入即可。以 Maven 為例,在pom.xml中添加:
<!-- MinIO Java SDK(兼容S3 API) -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.10</version> <!-- 建議使用最新穩(wěn)定版 -->
</dependency>
<!-- 可選:Apache HttpClient(用于HTTP請求,部分場景依賴) -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3</version>
</dependency>為什么選 8.5.10 版本? 該版本穩(wěn)定性高,兼容主流 MinIO 服務(wù)版本(如 RELEASE.2024-05-06T14-58-48Z),后續(xù)版本 API 無重大變更,學(xué)習(xí)成本低。
1.2 初始化 MinIO 客戶端
所有操作都需要通過MinioClient對象執(zhí)行,初始化時需傳入 MinIO 服務(wù)的核心配置(地址、賬號、密碼):
import io.minio.MinioClient;
import io.minio.errors.InvalidEndpointException;
import io.minio.errors.InvalidPortException;
public class MinioClientUtil {
// MinIO服務(wù)地址(格式:http://IP:端口 或 https://IP:端口)
private static final String MINIO_ENDPOINT = "http://localhost:9000";
// 訪問密鑰(MinIO控制臺創(chuàng)建的accessKey)
private static final String MINIO_ACCESS_KEY = "minioadmin";
// 秘密密鑰(MinIO控制臺創(chuàng)建的secretKey)
private static final String MINIO_SECRET_KEY = "minioadmin";
/**
* 初始化MinIO客戶端(單例模式,避免重復(fù)創(chuàng)建連接)
* @return MinioClient對象
*/
public static MinioClient getMinioClient() {
try {
// 構(gòu)建客戶端:傳入地址、賬號、密碼
MinioClient client = MinioClient.builder()
.endpoint(MINIO_ENDPOINT)
.credentials(MINIO_ACCESS_KEY, MINIO_SECRET_KEY)
.build();
// 可選:驗證客戶端是否可用(避免后續(xù)操作才發(fā)現(xiàn)連接失?。?
client.listBuckets(); // 調(diào)用簡單接口測試連接
return client;
} catch (InvalidEndpointException | InvalidPortException e) {
throw new RuntimeException("MinIO地址/端口錯誤:" + e.getMessage(), e);
} catch (Exception e) {
throw new RuntimeException("MinIO客戶端初始化失?。? + e.getMessage(), e);
}
}
}關(guān)鍵參數(shù)解釋:
endpoint:MinIO 服務(wù)的訪問地址,本地部署通常是http://localhost:9000,集群部署需傳入多個節(jié)點地址(用逗號分隔);credentials:MinIO 的訪問密鑰(accessKey)和秘密密鑰(secretKey),在 MinIO 控制臺 “Identity> Users” 中創(chuàng)建;- 單例模式:
MinioClient是線程安全的,全局創(chuàng)建一個實例即可,避免頻繁創(chuàng)建連接消耗資源。
二、核心 API 詳解:從桶操作到對象管理
MinIO 的 API 圍繞 “桶(Bucket)” 和 “對象(Object)” 展開 —— 桶是存儲對象的 “文件夾”,對象是具體的文件(如 PDF、圖片)。下面按 “桶操作→對象操作→分片操作→預(yù)簽名 URL” 的順序,詳解每個 API 的用法。
2.1 桶操作 API:管理存儲 “文件夾”
桶是 MinIO 的頂層存儲單元,所有對象都必須放在桶中。常見操作包括 “創(chuàng)建桶、查詢桶、刪除桶、設(shè)置桶權(quán)限”。
2.1.1 創(chuàng)建桶(createBucket)
作用:創(chuàng)建一個新的桶(類似新建文件夾),MinIO 中桶名全局唯一(同一集群內(nèi)不能重復(fù))。
import io.minio.BucketArgs;
import io.minio.MinioClient;
import io.minio.errors.MinioException;
public class MinioBucketDemo {
public static void main(String[] args) {
MinioClient client = MinioClientUtil.getMinioClient();
String bucketName = "uploads"; // 桶名(必須小寫,不能包含特殊字符)
try {
// 1. 檢查桶是否已存在(避免重復(fù)創(chuàng)建報錯)
boolean bucketExists = client.bucketExists(
BucketArgs.builder().bucket(bucketName).build()
);
if (bucketExists) {
System.out.println("桶已存在:" + bucketName);
return;
}
// 2. 創(chuàng)建桶(設(shè)置桶的區(qū)域,默認"us-east-1"即可)
client.makeBucket(
io.minio.MakeBucketArgs.builder()
.bucket(bucketName)
.region("us-east-1") // 區(qū)域配置,無需修改(MinIO兼容S3的區(qū)域概念)
.build()
);
System.out.println("桶創(chuàng)建成功:" + bucketName);
} catch (MinioException e) {
throw new RuntimeException("創(chuàng)建桶失?。? + e.getMessage(), e);
} catch (Exception e) {
throw new RuntimeException("未知錯誤:" + e.getMessage(), e);
}
}
}代碼逐行解釋:
bucketExists:檢查桶是否存在,避免重復(fù)創(chuàng)建(MinIO 不允許創(chuàng)建同名桶);makeBucket:核心創(chuàng)建方法,region參數(shù)是 S3 兼容必填項,無需實際對應(yīng)物理區(qū)域,填默認值即可;- 桶名規(guī)則:必須小寫,可包含字母、數(shù)字、連字符(-)、點(.),不能以連字符開頭 / 結(jié)尾,長度 3-63 字符。
適用場景:項目初始化時創(chuàng)建專用桶(如 PaiSmart 項目的uploads桶,用于存儲用戶上傳的文件)。
2.1.2 查詢桶列表(listBuckets)
作用:查詢 MinIO 中所有已創(chuàng)建的桶,用于批量管理或狀態(tài)檢查。
import io.minio.Bucket;
import io.minio.MinioClient;
import java.util.List;
public class MinioListBucketsDemo {
public static void main(String[] args) {
MinioClient client = MinioClientUtil.getMinioClient();
try {
// 調(diào)用listBuckets()獲取所有桶
List<Bucket> buckets = client.listBuckets();
// 遍歷桶列表,打印桶名和創(chuàng)建時間
System.out.println("MinIO中的桶列表:");
for (Bucket bucket : buckets) {
System.out.println("桶名:" + bucket.name() + ",創(chuàng)建時間:" + bucket.creationDate());
}
} catch (Exception e) {
throw new RuntimeException("查詢桶列表失?。? + e.getMessage(), e);
}
}
}核心方法:listBuckets()返回List<Bucket>,每個Bucket對象包含name()(桶名)和creationDate()(創(chuàng)建時間)。
適用場景:管理員后臺展示所有存儲桶,或定期檢查桶是否存在。
2.1.3 刪除桶(removeBucket)
作用:刪除指定桶(注意:桶必須為空,否則刪除失?。?。
import io.minio.BucketArgs;
import io.minio.MinioClient;
public class MinioDeleteBucketDemo {
public static void main(String[] args) {
MinioClient client = MinioClientUtil.getMinioClient();
String bucketName = "test-bucket";
try {
// 1. 檢查桶是否存在
boolean bucketExists = client.bucketExists(
BucketArgs.builder().bucket(bucketName).build()
);
if (!bucketExists) {
System.out.println("桶不存在:" + bucketName);
return;
}
// 2. (可選)刪除桶內(nèi)所有對象(桶必須為空才能刪除)
deleteAllObjectsInBucket(client, bucketName);
// 3. 刪除桶
client.removeBucket(
BucketArgs.builder().bucket(bucketName).build()
);
System.out.println("桶刪除成功:" + bucketName);
} catch (Exception e) {
throw new RuntimeException("刪除桶失?。? + e.getMessage(), e);
}
}
/**
* 輔助方法:刪除桶內(nèi)所有對象
*/
private static void deleteAllObjectsInBucket(MinioClient client, String bucketName) throws Exception {
// 遍歷桶內(nèi)所有對象并刪除
client.listObjects(
io.minio.ListObjectsArgs.builder()
.bucket(bucketName)
.recursive(true) // 遞歸查詢所有對象(包括子目錄)
.build()
).forEach(object -> {
try {
client.removeObject(
io.minio.RemoveObjectArgs.builder()
.bucket(bucketName)
.object(object.get().objectName())
.build()
);
} catch (Exception e) {
throw new RuntimeException("刪除對象失?。? + object.get().objectName(), e);
}
});
}
}關(guān)鍵注意點:
- MinIO 不允許刪除非空桶,必須先刪除桶內(nèi)所有對象(通過
listObjects遍歷 +removeObject刪除); recursive(true):遞歸查詢桶內(nèi)所有對象(包括子目錄下的對象),避免遺漏。
適用場景:刪除廢棄的存儲桶(如測試環(huán)境的臨時桶),或項目卸載時清理資源。
2.1.4 設(shè)置桶權(quán)限(setBucketPolicy)
作用:控制桶的訪問權(quán)限(如公開讀、私有),避免未授權(quán)訪問。
import io.minio.BucketArgs;
import io.minio.MinioClient;
import io.minio.SetBucketPolicyArgs;
public class MinioBucketPolicyDemo {
public static void main(String[] args) {
MinioClient client = MinioClientUtil.getMinioClient();
String bucketName = "uploads";
try {
// 1. 定義桶策略(JSON格式):公開讀(所有人可下載桶內(nèi)對象,不可寫)
String publicReadPolicy = "{\n" +
" \"Version\": \"2012-10-17\",\n" +
" \"Statement\": [\n" +
" {\n" +
" \"Effect\": \"Allow\",\n" +
" \"Principal\": \"*\",\n" +
" \"Action\": \"s3:GetObject\",\n" +
" \"Resource\": \"arn:aws:s3:::" + bucketName + "/*\"\n" +
" }\n" +
" ]\n" +
"}";
// 2. 設(shè)置桶策略
client.setBucketPolicy(
SetBucketPolicyArgs.builder()
.bucket(bucketName)
.config(publicReadPolicy) // 傳入JSON格式的策略
.build()
);
System.out.println("桶策略設(shè)置成功:" + bucketName + "(公開讀)");
} catch (Exception e) {
throw new RuntimeException("設(shè)置桶策略失?。? + e.getMessage(), e);
}
}
}策略 JSON 解釋:
Version:策略版本(固定為2012-10-17,S3 標準);Effect:Allow(允許)或Deny(拒絕);Principal:授權(quán)對象(*表示所有人);Action:授權(quán)操作(s3:GetObject表示下載對象,s3:PutObject表示上傳對象);Resource:授權(quán)范圍(arn:aws:s3:::uploads/*表示uploads桶內(nèi)所有對象)。
常見策略場景:
- 公開讀:適合靜態(tài)資源桶(如圖片、前端靜態(tài)文件);
- 私有:適合敏感文件桶(如用戶上傳的隱私文檔,PaiSmart 項目的
uploads桶默認私有)。
2.2 對象操作 API:管理具體文件
對象是 MinIO 的實際存儲單元(如 PDF、圖片、視頻),常見操作包括 “上傳、下載、刪除、查詢元數(shù)據(jù)”。
2.2.1 簡單上傳(putObject)
作用:上傳小文件(建議 100MB 以內(nèi)),直接將文件流或本地文件上傳到 MinIO。
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import java.io.FileInputStream;
import java.io.InputStream;
public class MinioSimpleUploadDemo {
public static void main(String[] args) {
MinioClient client = MinioClientUtil.getMinioClient();
String bucketName = "uploads";
String objectName = "docs/test.pdf"; // 對象在桶中的路徑(類似"文件夾/文件名")
String localFilePath = "C:\\test.pdf"; // 本地文件路徑
try (InputStream fileStream = new FileInputStream(localFilePath)) {
// 上傳對象:傳入桶名、對象路徑、文件流、文件大小、MIME類型
client.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName) // 桶內(nèi)路徑,支持多級目錄(如"docs/2024/test.pdf")
.stream(fileStream, fileStream.available(), -1) // 流、大?。?1表示自動識別)
.contentType("application/pdf") // MIME類型(如PDF是"application/pdf",圖片是"image/jpeg")
.build()
);
System.out.println("文件上傳成功:" + objectName);
} catch (Exception e) {
throw new RuntimeException("文件上傳失敗:" + e.getMessage(), e);
}
}
}核心參數(shù)解釋:
object:對象在桶中的路徑,支持多級目錄(如docs/2024/test.pdf,MinIO 會自動創(chuàng)建不存在的目錄);stream:文件輸入流,fileStream.available()獲取文件大小(字節(jié)),-1表示讓 MinIO 自動計算大小(適合流大小未知的場景);contentType:MIME 類型,用于 MinIO 識別文件格式(如瀏覽器下載時正確顯示文件名)。
適用場景:上傳小文件(如配置文件、圖片、小型文檔),PaiSmart 項目中 “解析后的文本切片” 若以文件形式存儲,也可使用此 API。
2.2.2 流式下載(getObject)
作用:下載 MinIO 中的對象到本地文件或內(nèi)存流(避免大文件一次性讀入內(nèi)存)。
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.errors.MinioException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MinioDownloadDemo {
public static void main(String[] args) {
MinioClient client = MinioClientUtil.getMinioClient();
String bucketName = "uploads";
String objectName = "docs/test.pdf"; // 要下載的對象路徑
String localSavePath = "C:\\downloads\\test.pdf"; // 本地保存路徑
try (
// 1. 獲取MinIO對象的輸入流
InputStream minioStream = client.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
// 2. 創(chuàng)建本地文件輸出流
OutputStream localStream = new FileOutputStream(localSavePath)
) {
// 3. 流拷貝(邊讀邊寫,避免大文件OOM)
byte[] buffer = new byte[8192]; // 8KB緩沖(平衡性能和內(nèi)存)
int bytesRead;
while ((bytesRead = minioStream.read(buffer)) != -1) {
localStream.write(buffer, 0, bytesRead);
}
System.out.println("文件下載成功:" + localSavePath);
} catch (MinioException e) {
throw new RuntimeException("下載對象失?。? + e.getMessage(), e);
} catch (Exception e) {
throw new RuntimeException("本地寫入失?。? + e.getMessage(), e);
}
}
}
關(guān)鍵優(yōu)化點:
- 流拷貝:使用
byte[] buffer邊讀邊寫,避免將整個文件讀入內(nèi)存(如 1GB 文件只需 8KB 內(nèi)存); - try-with-resources:自動關(guān)閉流,避免內(nèi)存泄漏。
適用場景:下載文件到本地(如用戶下載自己上傳的文檔),或讀取文件流進行后續(xù)處理(如 PaiSmart 項目中讀取合并后的文件流進行解析)。
2.2.3 刪除對象(removeObject)
作用:刪除 MinIO 中的單個對象(文件),支持刪除子目錄下的對象。
import io.minio.MinioClient;
import io.minio.RemoveObjectArgs;
public class MinioDeleteObjectDemo {
public static void main(String[] args) {
MinioClient client = MinioClientUtil.getMinioClient();
String bucketName = "uploads";
String objectName = "docs/test.pdf"; // 要刪除的對象路徑
try {
// 1. 檢查對象是否存在(可選,避免刪除不存在的對象報錯)
boolean objectExists = checkObjectExists(client, bucketName, objectName);
if (!objectExists) {
System.out.println("對象不存在:" + objectName);
return;
}
// 2. 刪除對象
client.removeObject(
RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
System.out.println("對象刪除成功:" + objectName);
} catch (Exception e) {
throw new RuntimeException("刪除對象失敗:" + e.getMessage(), e);
}
}
/**
* 輔助方法:檢查對象是否存在
*/
private static boolean checkObjectExists(MinioClient client, String bucketName, String objectName) throws Exception {
try {
// 調(diào)用statObject獲取對象元數(shù)據(jù),若不存在會拋異常
client.statObject(
io.minio.StatObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
return true;
} catch (io.minio.errors.ErrorResponseException e) {
// 404錯誤表示對象不存在
if (e.errorResponse().code().equals("NoSuchKey")) {
return false;
}
throw e; // 其他錯誤(如權(quán)限不足)重新拋出
}
}
}注意點:
- 檢查對象存在:通過
statObject獲取對象元數(shù)據(jù),若對象不存在會拋出ErrorResponseException,其中code為NoSuchKey; - 刪除目錄:MinIO 沒有真正的 “目錄” 概念,若要刪除 “docs” 目錄,需遍歷目錄下所有對象并刪除(類似 2.1.3 節(jié)的
deleteAllObjectsInBucket方法)。
適用場景:刪除過期文件(如用戶刪除自己的文檔),或分片合并后刪除臨時分片(PaiSmart 項目中合并分片后刪除原始分片)。
2.2.4 查詢對象元數(shù)據(jù)(statObject)
作用:獲取對象的詳細信息(如大小、創(chuàng)建時間、MIME 類型),用于校驗或狀態(tài)檢查。
import io.minio.MinioClient;
import io.minio.StatObjectArgs;
import io.minio.StatObjectResponse;
public class MinioObjectMetaDemo {
public static void main(String[] args) {
MinioClient client = MinioClientUtil.getMinioClient();
String bucketName = "uploads";
String objectName = "docs/test.pdf";
try {
// 獲取對象元數(shù)據(jù)
StatObjectResponse meta = client.statObject(
StatObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
// 打印元數(shù)據(jù)信息
System.out.println("對象元數(shù)據(jù):");
System.out.println("對象名稱:" + meta.objectName());
System.out.println("文件大?。ㄗ止?jié)):" + meta.size());
System.out.println("創(chuàng)建時間:" + meta.lastModified());
System.out.println("MIME類型:" + meta.contentType());
System.out.println("ETag(文件MD5的十六進制):" + meta.etag());
} catch (Exception e) {
throw new RuntimeException("查詢對象元數(shù)據(jù)失?。? + e.getMessage(), e);
}
}
}核心元數(shù)據(jù)字段:
size():對象大小(字節(jié)),用于校驗文件是否完整;lastModified():最后修改時間,用于判斷文件是否更新;etag():對象的 ETag(通常是文件內(nèi)容的 MD5 哈希),用于校驗文件完整性(如 PaiSmart 項目中校驗分片是否傳錯)。
適用場景:上傳后校驗文件大小是否正確,或下載前確認文件是否存在 / 是否更新。
2.3 分片上傳 API:處理大文件(重點)
對于大文件(如 1GB 以上的視頻、壓縮包),簡單上傳容易因網(wǎng)絡(luò)中斷導(dǎo)致重傳 ——分片上傳將大文件拆成小分片(如 5MB / 片),支持斷點續(xù)傳(某一片傳錯只需重傳該片),是 PaiSmart 項目中 “用戶上傳大文檔” 的核心技術(shù)。
分片上傳的核心流程:
- 初始化分片:告訴 MinIO“我要上傳一個大文件,分片大小是多少”;
- 上傳分片:將拆分后的分片逐個上傳到 MinIO;
- 合并分片:所有分片上傳完成后,告訴 MinIO “將分片合并成完整文件”。
2.3.1 初始化分片(createMultipartUpload)
作用:創(chuàng)建分片上傳任務(wù),獲取 “上傳 ID”(后續(xù)上傳分片和合并時需要)。
import io.minio.CreateMultipartUploadArgs;
import io.minio.MinioClient;
import io.minio.Result;
import io.minio.UploadPartResponse;
public class MinioMultipartInitDemo {
public static void main(String[] args) {
MinioClient client = MinioClientUtil.getMinioClient();
String bucketName = "uploads";
String objectName = "large-files/video.mp4"; // 大文件在桶中的路徑
String contentType = "video/mp4"; // MIME類型
try {
// 初始化分片上傳,獲取上傳ID
String uploadId = client.createMultipartUpload(
CreateMultipartUploadArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(contentType)
.build()
).result().uploadId();
System.out.println("分片上傳初始化成功,上傳ID:" + uploadId);
// 后續(xù)上傳分片和合并時,需要用到這個uploadId
} catch (Exception e) {
throw new RuntimeException("初始化分片上傳失?。? + e.getMessage(), e);
}
}
}核心返回值:uploadId是分片上傳任務(wù)的唯一標識,后續(xù)所有操作(上傳分片、合并、取消)都需要傳入該 ID。
2.3.2 上傳分片(uploadPart)
作用:上傳單個分片到 MinIO,每個分片需要指定 “分片編號”(從 1 開始)。
import io.minio.MinioClient;
import io.minio.UploadPartArgs;
import io.minio.UploadPartResponse;
import java.io.FileInputStream;
import java.io.InputStream;
public class MinioUploadPartDemo {
public static void main(String[] args) {
MinioClient client = MinioClientUtil.getMinioClient();
String bucketName = "uploads";
String objectName = "large-files/video.mp4";
String uploadId = "mpu-1234567890"; // 從初始化分片獲取的uploadId
int partNumber = 1; // 分片編號(從1開始,必須連續(xù))
String partFilePath = "C:\\parts\\video_part_1.mp4"; // 單個分片的本地路徑
try (InputStream partStream = new FileInputStream(partFilePath)) {
// 上傳分片
UploadPartResponse response = client.uploadPart(
UploadPartArgs.builder()
.bucket(bucketName)
.object(objectName)
.uploadId(uploadId)
.partNumber(partNumber) // 分片編號(1-10000,MinIO限制最大10000片)
.stream(partStream, partStream.available(), -1) // 分片流
.build()
);
// 保存分片的ETag(合并分片時需要傳入所有分片的ETag和編號)
String partEtag = response.etag();
System.out.println("分片上傳成功:編號=" + partNumber + ",ETag=" + partEtag);
} catch (Exception e) {
throw new RuntimeException("上傳分片失敗:" + e.getMessage(), e);
}
}
}關(guān)鍵注意點:
- 分片編號:從 1 開始,必須連續(xù)(如 1、2、3…),不能跳過;
- ETag 保存:每個分片上傳后會返回
ETag(分片內(nèi)容的 MD5),合并時需要將 “分片編號→ETag” 的映射傳給 MinIO,用于校驗分片完整性; - 分片大?。航ㄗh 5MB-10MB(MinIO 默認最小分片 100KB,最大 10GB),PaiSmart 項目中用 5MB 分片,平衡上傳效率和重試成本。
2.3.3 合并分片(completeMultipartUpload)
作用:所有分片上傳完成后,將分片合并成完整文件。
import io.minio.CompleteMultipartUploadArgs;
import io.minio.CompletedPart;
import io.minio.MinioClient;
import java.util.ArrayList;
import java.util.List;
public class MinioCompleteMultipartDemo {
public static void main(String[] args) {
MinioClient client = MinioClientUtil.getMinioClient();
String bucketName = "uploads";
String objectName = "large-files/video.mp4";
String uploadId = "mpu-1234567890"; // 初始化分片時的uploadId
try {
// 1. 準備分片列表(編號→ETag的映射,需按編號排序)
List<CompletedPart> completedParts = new ArrayList<>();
// 假設(shè)上傳了3個分片,添加每個分片的編號和ETag
completedParts.add(new CompletedPart(1, "etag-1"));
completedParts.add(new CompletedPart(2, "etag-2"));
completedParts.add(new CompletedPart(3, "etag-3"));
// 2. 合并分片
client.completeMultipartUpload(
CompleteMultipartUploadArgs.builder()
.bucket(bucketName)
.object(objectName)
.uploadId(uploadId)
.parts(completedParts) // 所有分片的編號和ETag
.build()
);
System.out.println("分片合并成功:" + objectName);
} catch (Exception e) {
throw new RuntimeException("合并分片失?。? + e.getMessage(), e);
}
}
}核心參數(shù):parts是CompletedPart列表,每個元素包含partNumber(分片編號)和etag(分片的 ETag),必須與上傳分片時的信息一致,否則合并失敗。
適用場景:大文件上傳(如視頻、壓縮包、大型文檔),PaiSmart 項目中用戶上傳 100MB 以上的 PDF/Word 文檔時,就是通過 “分片上傳→合并” 實現(xiàn)的。
2.4 預(yù)簽名 URL API:臨時訪問文件
作用:生成一個臨時的文件訪問 URL(帶權(quán)限),避免直接暴露 MinIO 的賬號密碼,適合 “臨時分享文件” 或 “前端直接下載”。
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.http.Method;
import java.util.concurrent.TimeUnit;
public class MinioPresignedUrlDemo {
public static void main(String[] args) {
MinioClient client = MinioClientUtil.getMinioClient();
String bucketName = "uploads";
String objectName = "docs/test.pdf";
int expiryHours = 1; // URL有效期(1小時)
try {
// 生成預(yù)簽名URL(支持GET下載、PUT上傳、DELETE刪除等方法)
String presignedUrl = client.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET) // 方法:GET=下載,PUT=上傳,DELETE=刪除
.bucket(bucketName)
.object(objectName)
.expiry(expiryHours, TimeUnit.HOURS) // 有效期(1小時)
.build()
);
System.out.println("預(yù)簽名下載URL(" + expiryHours + "小時內(nèi)有效):");
System.out.println(presignedUrl);
// 前端可直接用這個URL下載文件,無需MinIO賬號密碼
} catch (Exception e) {
throw new RuntimeException("生成預(yù)簽名URL失?。? + e.getMessage(), e);
}
}
}關(guān)鍵參數(shù)解釋:
method:URL 支持的 HTTP 方法,GET用于下載,PUT用于前端直接上傳(無需后端中轉(zhuǎn)),DELETE用于臨時刪除權(quán)限;expiry:URL 有效期,避免長期暴露(PaiSmart 項目中合并文件后生成的預(yù)簽名 URL 有效期為 1 小時,供解析服務(wù)下載文件)。
適用場景:
- 臨時分享文件(如給同事分享一個文檔,1 小時后鏈接失效);
- 前端直傳文件(生成 PUT 方法的預(yù)簽名 URL,前端直接上傳到 MinIO,減輕后端壓力);
- 服務(wù)間文件訪問(如 PaiSmart 項目中 Kafka 消費者用預(yù)簽名 URL 下載合并后的文件進行解析)。
三、進階實戰(zhàn):MinIO 在 項目中的應(yīng)用
看看 MinIO 的 API 是如何串聯(lián)起來的:
3.1 項目中的 MinIO 核心流程
- 用戶上傳分片:調(diào)用
putObject上傳每個分片到 MinIO,路徑為chunks/{fileMd5}/{chunkIndex}; - 合并分片:調(diào)用
composeObject(MinIO 的合并 API,類似completeMultipartUpload)將分片合并成完整文件,路徑為merged/{fileName}; - 生成預(yù)簽名 URL:調(diào)用
getPresignedObjectUrl生成臨時 URL,傳給 Kafka 任務(wù); - 解析服務(wù)下載:Kafka 消費者調(diào)用
getObject下載文件流進行解析; - 清理臨時分片:合并完成后,調(diào)用
removeObject刪除原始分片。
3.2 關(guān)鍵代碼片段
// 合并分片(用composeObject合并,適合已上傳的分片)
public String mergeChunks(String fileMd5, String fileName, List<String> partPaths) throws Exception {
MinioClient client = MinioClientUtil.getMinioClient();
String bucketName = "uploads";
String mergedPath = "merged/" + fileName;
// 準備分片源(每個分片的路徑)
List<ComposeSource> sources = partPaths.stream()
.map(path -> ComposeSource.builder()
.bucket(bucketName)
.object(path)
.build())
.collect(Collectors.toList());
// 合并分片(composeObject適用于合并已存在的對象)
client.composeObject(
ComposeObjectArgs.builder()
.bucket(bucketName)
.object(mergedPath)
.sources(sources)
.build()
);
// 生成預(yù)簽名URL
String presignedUrl = client.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(mergedPath)
.expiry(1, TimeUnit.HOURS)
.build()
);
// 刪除臨時分片
for (String partPath : partPaths) {
client.removeObject(
RemoveObjectArgs.builder()
.bucket(bucketName)
.object(partPath)
.build()
);
}
return presignedUrl;
}為什么用composeObject而不是completeMultipartUpload?composeObject用于合并 MinIO 中已存在的對象(如分片),而completeMultipartUpload用于合并 “通過分片上傳 API(uploadPart)上傳的分片”。分片是通過putObject單獨上傳的,因此用composeObject更合適。
四、性能優(yōu)化與最佳實踐
- 合理設(shè)置分片大小:
- 小文件(<100MB):直接用
putObject簡單上傳; - 大文件(>100MB):用分片上傳,分片大小設(shè)為 5MB-10MB(平衡網(wǎng)絡(luò)傳輸和重試成本)。
- 小文件(<100MB):直接用
- 使用連接池:
- MinIO 客戶端默認不使用連接池,高并發(fā)場景下需配置 HTTP 連接池:
// 配置連接池(添加到MinioClientUtil)
private static final HttpClient httpClient = HttpClient.newBuilder()
.connectionPoolSize(20) // 最大連接數(shù)
.connectTimeout(Duration.ofSeconds(30))
.build();
public static MinioClient getMinioClient() {
return MinioClient.builder()
.endpoint(MINIO_ENDPOINT)
.credentials(MINIO_ACCESS_KEY, MINIO_SECRET_KEY)
.httpClient(httpClient) // 傳入連接池
.build();
}- 批量操作優(yōu)化:
- 批量刪除 / 查詢對象時,用
listObjects遍歷 + 批量處理,避免單次操作過多(如每次處理 100 個對象); - 避免循環(huán)中頻繁調(diào)用 MinIO API(如循環(huán)上傳 1000 個小文件,可批量打包后上傳)。
- 批量刪除 / 查詢對象時,用
- 權(quán)限最小化:
- 為 MinIO 用戶分配最小權(quán)限(如上傳用戶只給
s3:PutObject權(quán)限,下載用戶只給s3:GetObject權(quán)限); - 桶默認設(shè)為私有,僅通過預(yù)簽名 URL 或策略開放臨時訪問。
- 為 MinIO 用戶分配最小權(quán)限(如上傳用戶只給
五、常見問題與解決方案
| 問題現(xiàn)象 | 可能原因 | 解決方案 |
|---|---|---|
| 客戶端初始化失敗,報 “Endpoint 錯誤” | MinIO 地址格式錯誤(如少寫 http://) | 確保 endpoint 是http://IP:端口或https://IP:端口 |
| 上傳失敗,報 “AccessDenied” | accessKey/secretKey 錯誤,或無桶權(quán)限 | 檢查賬號密碼,確保用戶有s3:PutObject權(quán)限 |
| 合并分片失敗,報 “Parts missing” | 分片編號不連續(xù),或 ETag 不匹配 | 確保分片編號從 1 開始連續(xù),ETag 與上傳時一致 |
| 預(yù)簽名 URL 訪問失敗,報 “Expired” | URL 已過期 | 重新生成 URL,適當(dāng)延長有效期(如 1 小時) |
| 下載大文件 OOM | 一次性讀入內(nèi)存,未用流拷貝 | 使用byte[] buffer邊讀邊寫,避免全量讀入 |
六、總結(jié)
MinIO 的 Java API 圍繞 “桶” 和 “對象” 設(shè)計,核心能力覆蓋 “存儲管理→文件操作→大文件處理→臨時訪問”,無論是小文件上傳還是 TB 級大文件存儲,都能滿足需求。
對于 0 基礎(chǔ)開發(fā)者,建議按 “環(huán)境準備→簡單上傳 / 下載→分片上傳→預(yù)簽名 URL” 的順序?qū)W習(xí),每個 API 都動手寫代碼測試;對于有基礎(chǔ)的開發(fā)者,可深入研究性能優(yōu)化(如連接池、批量操作)和企業(yè)級特性(如生命周期管理、版本控制)。
在實際項目中,MinIO 不僅是 “文件存儲工具”,更是 “數(shù)據(jù)流轉(zhuǎn)的核心樞紐”—— 從用戶上傳到服務(wù)解析,再到后續(xù)搜索,所有文件相關(guān)的操作都依賴 MinIO 的 API,掌握它能讓你在分布式存儲場景中事半功倍。
到此這篇關(guān)于Java 操作 MinIO詳細步驟的文章就介紹到這了,更多相關(guān)java操作minio內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?IoC容器Bean作用域的singleton與prototype使用配置
這篇文章主要為大家介紹了Spring?IoC容器Bean作用域的singleton與prototype使用配置詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12
Springboot實現(xiàn)多服務(wù)器session共享
這篇文章主要為大家詳細介紹了Springboot實現(xiàn)多服務(wù)器session共享,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-05-05
spring為類的靜態(tài)屬性實現(xiàn)注入實例方法
在本篇文章里小編給大家整理的是關(guān)于spring為類的靜態(tài)屬性實現(xiàn)注入實例方法,有需要的朋友們可以參考下。2019-10-10
基于Map的computeIfAbsent的使用場景和使用方式
這篇文章主要介紹了基于Map的computeIfAbsent的使用場景和使用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
淺談Java并發(fā)中ReentrantLock鎖應(yīng)該怎么用
本文主要介紹了ava并發(fā)中ReentrantLock鎖的具體使用,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11
java?poi導(dǎo)入純數(shù)字等格式問題及解決
這篇文章主要介紹了java?poi導(dǎo)入純數(shù)字等格式問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03

