Spring Boot 集成 MinIO 實現分布式文件存儲與管理

Spring Boot 集成 MinIO 實現分布式文件存儲與管理

在這里插入圖片描述

一、MinIO 簡介

MinIO 是一個高性能的分布式對象存儲服務器,兼容 Amazon S3 API。它具有以下特點:

  • 輕量級且易于部署
  • 高性能(讀寫速度可達每秒數GB)
  • 支持數據加密和訪問控制
  • 提供多種語言的SDK
  • 開源且社區活躍

二、Spring Boot 集成 MinIO

1. 添加依賴

pom.xml 中添加 MinIO Java SDK 依賴:

<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.7</version>
</dependency>

2. 配置 MinIO 連接

application.yml 中配置:

minio:endpoint: http://localhost:9000accessKey: minioadminsecretKey: minioadminbucketName: default-bucketsecure: false

3. 創建配置類

@Configuration
public class MinioConfig {@Value("${minio.endpoint}")private String endpoint;@Value("${minio.accessKey}")private String accessKey;@Value("${minio.secretKey}")private String secretKey;@Beanpublic MinioClient minioClient() {return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();}
}

三、實現文件服務

@Service
@RequiredArgsConstructor
public class MinioService {private final MinioClient minioClient;@Value("${minio.bucketName}")private String bucketName;/*** 檢查存儲桶是否存在** @param bucketName 存儲桶名稱* @return 存儲桶是否存在 狀態碼 true:存在 false:不存在*/public boolean bucketExists(String bucketName) throws Exception {return !minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());}/*** 創建存儲桶*/public void makeBucket(String bucketName) throws Exception {if (bucketExists(bucketName)) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}}/*** 列出所有存儲桶*/public List<Bucket> listBuckets() throws Exception {return minioClient.listBuckets();}/*** 上傳文件** @param file       文件* @param bucketName 存儲桶名稱* @param rename     是否重命名*/public String uploadFile(MultipartFile file, String bucketName, boolean rename) throws Exception {// 如果未指定bucketName,使用默認的bucketName = getBucketName(bucketName);// 檢查存儲桶是否存在,不存在則創建ensureBucketExists(bucketName);// 生成唯一文件名String objectName = generateObjectName(file, rename);// 上傳文件minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build());return objectName;}/*** 下載文件*/public InputStream downloadFile(String objectName, String bucketName) throws Exception {return minioClient.getObject(GetObjectArgs.builder().bucket(getBucketName(bucketName)).object(objectName).build());}/*** 刪除文件*/public void removeFile(String objectName, String bucketName) throws Exception {minioClient.removeObject(RemoveObjectArgs.builder().bucket(getBucketName(bucketName)).object(objectName).build());}/*** 獲取文件URL(先檢查文件是否存在)*/public String getFileUrl(String objectName, String bucketName) throws Exception {try {minioClient.statObject(StatObjectArgs.builder().bucket(getBucketName(bucketName)).object(objectName).build());} catch (ErrorResponseException e) {// 文件不存在時拋出異常throw new FileNotFoundException("File not found: " + objectName);}// 文件存在,生成URLreturn minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(getBucketName(bucketName)).object(objectName).build());}/*** 生成唯一文件名*/private static @Nullable String generateObjectName(MultipartFile file, boolean rename) {String fileName = file.getOriginalFilename();String objectName = fileName;if (rename && fileName != null) {objectName = UUID.randomUUID().toString().replaceAll("-", "")+ fileName.substring(fileName.lastIndexOf("."));}return objectName;}/*** 檢查存儲桶是否存在,不存在則創建*/private void ensureBucketExists(String bucketName) throws Exception {if (bucketExists(bucketName)) {makeBucket(bucketName);}}/*** 獲取存儲桶名稱*/private String getBucketName(String bucketName) {if (bucketName == null || bucketName.isEmpty()) {bucketName = this.bucketName;}return bucketName;}
}

四、REST API 實現

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/file")
public class MinioController {private final MinioService minioService;@PostMapping("/upload")public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file,@RequestParam(value = "bucketName", required = false) String bucketName) {try {String objectName = minioService.uploadFile(file, bucketName, false);return ResponseEntity.ok(objectName);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());}}@GetMapping("/download")public ResponseEntity<byte[]> downloadFile(@RequestParam String objectName,@RequestParam(value = "bucketName", required = false) String bucketName) {try {InputStream stream = minioService.downloadFile(objectName, bucketName);byte[] bytes = stream.readAllBytes();HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);headers.setContentDispositionFormData("attachment", objectName);return new ResponseEntity<>(bytes, headers, HttpStatus.OK);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);}}@DeleteMapping("/delete")public ResponseEntity<String> deleteFile(@RequestParam String objectName,@RequestParam(value = "bucketName", required = false) String bucketName) {try {minioService.removeFile(objectName, bucketName);return ResponseEntity.ok("File deleted successfully");} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());}}@GetMapping("/url")public ResponseEntity<String> getFileUrl(@RequestParam String objectName,@RequestParam(value = "bucketName", required = false) String bucketName) {try {String url = minioService.getFileUrl(objectName, bucketName);return ResponseEntity.ok(url);} catch (FileNotFoundException e) {return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());}}
}

五、高級功能實現

1. 分片上傳

public String multipartUpload(MultipartFile file, String bucketName) {// 1. 初始化分片上傳String uploadId = minioClient.initiateMultipartUpload(...);// 2. 分片上傳Map<Integer, String> etags = new HashMap<>();for (int partNumber = 1; partNumber <= totalParts; partNumber++) {PartSource partSource = getPartSource(file, partNumber);String etag = minioClient.uploadPart(...);etags.put(partNumber, etag);}// 3. 完成分片上傳minioClient.completeMultipartUpload(...);return objectName;
}

2. 文件預覽

    @GetMapping("/preview/{objectName}")public ResponseEntity<Resource> previewFile(@PathVariable String objectName,@RequestParam(value = "bucketName", required = false) String bucketName) throws Exception {String contentType = minioService.getFileContentType(objectName, bucketName);InputStream inputStream = minioService.downloadFile(objectName, bucketName);return ResponseEntity.ok().contentType(MediaType.parseMediaType(contentType)).body(new InputStreamResource(inputStream));}

六、最佳實踐

  1. 安全性考慮

    • 為預簽名URL設置合理的過期時間
    • 實現細粒度的訪問控制
    • 對上傳文件進行病毒掃描
  2. 性能優化

    • 使用CDN加速文件訪問
    • 對大文件使用分片上傳
    • 實現客戶端直傳(Presigned URL)
  3. 監控與日志

    • 記錄所有文件操作
    • 監控存儲空間使用情況
    • 設置自動清理策略

七、常見問題解決

  1. 連接超時問題

    @Bean
    public MinioClient minioClient() {return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).httpClient(HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(30)).build()).build();
    }
    
  2. 文件存在性檢查優化

    public boolean fileExists(String objectName, String bucketName) {try {minioClient.statObject(StatObjectArgs.builder().bucket(getBucketName(bucketName)).object(objectName).build());return true;} catch (ErrorResponseException e) {if (e.errorResponse().code().equals("NoSuchKey")) {return false;}throw new FileStorageException("檢查文件存在性失敗", e);} catch (Exception e) {throw new FileStorageException("檢查文件存在性失敗", e);}
    }
    

八、總結

通過本文的介紹,我們實現了:

  1. Spring Boot 與 MinIO 的基本集成
  2. 文件上傳、下載、刪除等基礎功能
  3. 文件預覽、分片上傳等高級功能
  4. 安全性、性能等方面的最佳實踐

MinIO 作為輕量級的對象存儲解決方案,非常適合中小型項目使用。結合 Spring Boot 可以快速構建強大的文件存儲服務。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/87423.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/87423.shtml
英文地址,請注明出處:http://en.pswp.cn/web/87423.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

從小白入門,基于Cursor開發一個前端小程序之Cursor 編程實踐與案例分析

Cursor 編程實踐與案例分析 Cursor 編程實踐與案例分析 1. 什么是 Cursor&#xff1f; Cursor 是一款面向開發者的 AI 編程助手&#xff0c;集成于本地 IDE&#xff0c;支持自然語言與代碼的無縫協作。它不僅能自動補全、重構、查找代碼&#xff0c;還能理解業務上下文&#…

一、如何用MATLAB畫一個三角形 代碼

一、如何用MATLAB畫一個三角形 代碼在MATLAB中繪制三角形可以通過指定三個頂點的坐標并使用 fill 或 patch 函數實現。以下是詳細代碼示例&#xff1a;方法1&#xff1a;使用 fill 函數&#xff08;簡單填充&#xff09;% 定義三角形的三個頂點坐標 (x, y) x [0, 1, 0.5]; % …

Postman自動化測試提取相應body體中的參數

文章目錄Postman自動化測試提取相應body體中的參數1. 示例響應 Body 參數2. 提取響應 Body 參數Postman自動化測試提取相應body體中的參數 上一篇的文中介紹了使用postman自動化測試時從響應的header中提取token參數&#xff0c;很多同學私信問如何從響應體body中提取參數。 有…

vue-39(為復雜 Vue 組件編寫單元測試)

實際練習:為復雜 Vue 組件編寫單元測試 單元測試對于確保復雜 Vue 組件的可靠性和可維護性至關重要。通過隔離和測試代碼的各個單元,您可以在開發過程的早期發現并修復錯誤,從而構建更健壯和可預測的應用程序。本課程重點介紹為復雜 Vue 組件編寫單元測試的實用方面,建立在…

c語言中的函數IV

函數的先后關系 直接把函數放在程序上方是可以的 在實際開發中&#xff0c;我們更希望把main函數放在前面 這樣子直接把自己定義的函數放在main函數下方&#xff0c;編譯會出現warning和error正確的解決方案是&#xff1a;把函數的頭放到main函數上方&#xff0c;這樣就能正常…

大模型Decoder-Only深入解析

Decoder-Only整體結構 我們以模型Llama-3.1-8B-Instruct為例&#xff0c;打印其結構如下&#xff08;后面會慢慢解析每一部分&#xff0c;莫慌&#xff09;&#xff1a; LlamaForCausalLM((model): LlamaModel((embed_tokens): VocabParallelEmbedding(num_embeddings128256,…

web網頁,在線%電商,茶葉,商城,網上商城系統%分析系統demo,于vscode,vue,java,jdk,springboot,mysql數據庫

經驗心得 這也是幫之前一客戶加了幾個功能&#xff0c;需要掌握crud&#xff0c;前后端開發&#xff0c;前后端怎么對接&#xff0c;前后端通訊是以那種格式&#xff0c;把這些掌握后咱們就可以進行網站開發了。后端記好一定要分層開發&#xff0c;不要像老早一起所有代碼寫到一…

MybatisPlus-05.核心功能-條件構造器

一.條件構造器 我們前面使用的MP功能主要是根據id進行操作的&#xff0c;并未涉及到復雜查詢。而根據id所進行的增刪改查操作在MP中都有直接的封裝。但是遇到復雜的查詢條件時&#xff0c;如何使用MP進行操作是我們要考慮的問題。因此MP為我們提供了條件構造器。 在BaseMapper…

ES6從入門到精通:常用知識點

變量聲明ES6引入了let和const替代var。let用于聲明可變的塊級作用域變量&#xff0c;const用于聲明不可變的常量。塊級作用域有效避免了變量提升和污染全局的問題。let name Alice; const PI 3.1415;箭頭函數箭頭函數簡化了函數寫法&#xff0c;且自動綁定當前上下文的this值…

51單片機教程(十一)- 單片機定時器

11、單片機定時器 項目目標 通過定時器/計數器實現流水燈控制。知識要點 定時器的結構。TMOD和TCON;定時/計數器工作方式;定時/計數器編程步驟;1、項目分析 前面的流水燈的時間控制通過空循環語句來實現,定時不是很精確。本章通過用定時器來控制流水燈任務可以實現精確的時…

基于opencv的疲勞駕駛監測系統

博主介紹&#xff1a;java高級開發&#xff0c;從事互聯網行業多年&#xff0c;熟悉各種主流語言&#xff0c;精通java、python、php、爬蟲、web開發&#xff0c;已經做了多年的畢業設計程序開發&#xff0c;開發過上千套畢業設計程序&#xff0c;沒有什么華麗的語言&#xff0…

Vue 2 和 Vue 3 區別

1. 響應式系統原理 Vue 2&#xff1a;利用Object.defineProperty()實現屬性攔截。存在局限性&#xff0c;無法自動監測對象屬性增減&#xff0c;需用Vue.set/delete&#xff1b;數組變異方法要重寫&#xff1b;深層對象遞歸轉換性能差。Vue 3&#xff1a;采用 ES6 Proxy代理對…

mv重命名報錯:-bash:syntax error near unexpected token ‘(‘

文章目錄 一、報錯背景二、解決方法2.1、方法一&#xff1a;文件名加引號2.2、方法二&#xff1a;特殊字符前加\進行轉義 一、報錯背景 在linux上對一文件執行重命名時報錯。原因是該文件名包含空格與括號。 文件名如下&#xff1a; aa &#xff08;1).txt執行命令及報錯如下…

AWS 開源 Strands Agents SDK,簡化 AI 代理開發流程

最近&#xff0c;亞馬遜網絡服務&#xff08;AWS&#xff09;宣布推出 Strands Agents(https://github.com/strands-agents/sdk-python)&#xff0c;這一開源軟件開發工具包&#xff08;SDK&#xff09;采用模型驅動的方法&#xff0c;助力開發者僅用數行代碼即可構建并運行人工…

利用 AI 打造的開發者工具集合

如圖. 我利用 AI 開發了這個網站花了半個小時. 目前就上了 四個 我想到的工具。 大家可以自行體驗下&#xff1a;https://xiaojinzi123.github.io 本文并不是宣傳什么產品. 只是感概 Ai 真的改變我的工作方式啊. 雖然現在 AI 對于一些已有的項目進行更改代碼. 由于不了解業務,…

[自然語言處理]計算語言的熵

一、要求利用給定的中英文語料&#xff0c;分別計算英語字母、英語單詞、漢字、漢語詞的熵&#xff0c;并和已公開結果比較&#xff0c;思考漢語的熵對漢語編碼和處理的影響。二、實驗內容2.1 統計英文語料的熵1.代碼(1)計算英文字母的熵import math #計算每個英文字母的熵 def…

如何處理“協議異常”錯誤

在Java中&#xff0c;“協議異常”通常是指在網絡通信或者處理特定協議相關操作時出現的異常。以下是一些處理“協議異常”錯誤的方法&#xff1a;一、理解協議異常的類型和原因HTTP協議異常原因&#xff1a;在進行HTTP通信時&#xff0c;可能會因為請求格式錯誤、響應狀態碼異…

Spark 4.0的VariantType 類型以及內部存儲

背景 本文基于Spark 4.0 總結 Spark中的 VariantType 類型,用盡量少的字節來存儲Json的格式化數據 分析 這里主要介紹 Variant 的存儲,我們從VariantBuilder.buildJson方法(把對應的json數據存儲為VariantType類型)開始: public static Variant parseJson(JsonParser …

跨越十年的C++演進:C++20新特性全解析

跨越十年的C演進系列&#xff0c;分為5篇&#xff0c;本文為第四篇&#xff0c;后續會持續更新C23~ 前3篇如下&#xff1a; 跨越十年的C演進&#xff1a;C11新特性全解析 跨越十年的C演進&#xff1a;C14新特性全解析 跨越十年的C演進&#xff1a;C17新特性全解析 C20標準…

LeetCode--40.組合總和II

前言&#xff1a;如果你做出來了39題&#xff0c;但是遇到40題就不會做了&#xff0c;那我建議你去再好好縷清39題的思路&#xff0c;再來看這道題&#xff0c;會有種豁然開朗的感覺解題思路&#xff1a;這道題其實與39題基本一致&#xff0c;所以本次題解是借著39題為基礎來講…