SpringBoot整合MinIO實(shí)現(xiàn)全場(chǎng)景文件操作管理
最近項(xiàng)目中需要處理大量文件存儲(chǔ)和管理的需求,對(duì)比了 Nginx、FastDFS、阿里云 OSS 等多種方案后,最終選擇了 MinIO。今天就來(lái)和大家分享一下 SpringBoot 整合 MinIO 的全過(guò)程,從為什么選擇 MinIO,到各種文件操作的實(shí)現(xiàn),包括簡(jiǎn)單的文件上傳、批量上傳、文件下載、文件預(yù)覽,再到大文件分片上傳和秒傳功能。
一、為什么選擇 MinIO
在選擇文件存儲(chǔ)方案時(shí),我們需要考慮多個(gè)因素,如功能、性能、成本、擴(kuò)展性等。對(duì)比其他常見(jiàn)的文件存儲(chǔ)方案,MinIO 具有以下優(yōu)勢(shì):
1. 功能豐富
MinIO 支持標(biāo)準(zhǔn)的 S3 協(xié)議,可以與其他支持 S3 協(xié)議的工具和服務(wù)無(wú)縫集成。同時(shí),它還提供了豐富的 API,包括文件上傳、下載、預(yù)覽、刪除、版本控制等,滿足各種文件管理需求。
2. 高性能
MinIO 專(zhuān)為高性能設(shè)計(jì),采用分布式架構(gòu),可以橫向擴(kuò)展,支持 PB 級(jí)數(shù)據(jù)存儲(chǔ)。在讀寫(xiě)性能方面,MinIO 表現(xiàn)出色,尤其適合大文件的存儲(chǔ)和處理。
3. 開(kāi)源免費(fèi)
MinIO 是開(kāi)源項(xiàng)目,采用 AGPL v3 許可證,企業(yè)可以免費(fèi)使用。對(duì)于中小企業(yè)來(lái)說(shuō),這無(wú)疑是一個(gè)很大的優(yōu)勢(shì)。
4. 易于部署和管理
MinIO 提供了簡(jiǎn)單易用的命令行工具和 Web 界面,部署和管理都非常方便。可以在幾分鐘內(nèi)完成部署,并開(kāi)始使用。
5. 數(shù)據(jù)安全
MinIO 支持?jǐn)?shù)據(jù)加密、訪問(wèn)控制、多因素認(rèn)證等安全功能,保障數(shù)據(jù)的安全性和隱私性。
對(duì)比其他方案
- Nginx:主要用于靜態(tài)文件服務(wù),不支持分布式存儲(chǔ)和大規(guī)模文件管理。
- FastDFS:功能相對(duì)簡(jiǎn)單,缺乏統(tǒng)一的管理界面,擴(kuò)展性有限。
- 阿里云 OSS:云服務(wù)成本較高,依賴于網(wǎng)絡(luò)環(huán)境,不適合對(duì)數(shù)據(jù)隱私要求較高的場(chǎng)景。
綜上所述,MinIO 是一個(gè)功能強(qiáng)大、性能出色、易于部署和管理的文件存儲(chǔ)方案,非常適合作為企業(yè)級(jí)文件存儲(chǔ)系統(tǒng)。
二、環(huán)境準(zhǔn)備
1. 安裝 MinIO
可以通過(guò) Docker 快速安裝 MinIO:
docker run -p 9000:9000 -p 9001:9001 \ --name minio \ -v /data/minio/data:/data \ -v /data/minio/config:/root/.minio \ -e "MINIO_ROOT_USER=minioadmin" \ -e "MINIO_ROOT_PASSWORD=minioadmin" \ minio/minio server /data --console-address ":9001"
安裝完成后,可以通過(guò)訪問(wèn)http://localhost:9001
進(jìn)入 MinIO 管理界面,使用用戶名minioadmin
和密碼minioadmin
登錄。
2. 創(chuàng)建 SpringBoot 項(xiàng)目
使用 Spring Initializr 創(chuàng)建一個(gè) SpringBoot 項(xiàng)目,添加以下依賴:
- Spring Web
- Lombok
- MinIO Client
三、整合 MinIO
1. 添加依賴
在pom.xml
中添加 MinIO 客戶端依賴:
<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.5.5</version> </dependency>
2. 配置 MinIO 連接信息
在application.yml
中添加 MinIO 配置信息:
minio: endpoint: http://localhost:9000 access-key: minioadmin secret-key: minioadmin bucket-name: test-bucket
3. 創(chuàng)建 MinIO 配置類(lèi)
創(chuàng)建一個(gè)配置類(lèi),用于創(chuàng)建 MinIO 客戶端:
import io.minio.MinioClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MinIOConfig { @Value("${minio.endpoint}") private String endpoint; @Value("${minio.access-key}") private String accessKey; @Value("${minio.secret-key}") private String secretKey; @Bean public MinioClient minioClient() { return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } }
四、創(chuàng)建 MinIO 工具類(lèi)
為了方便使用 MinIO 的各種功能,我們創(chuàng)建一個(gè)工具類(lèi),封裝 MinIO 的常用操作:
import io.minio.*; import io.minio.http.Method; import io.minio.messages.Bucket; import io.minio.messages.DeleteError; import io.minio.messages.DeleteObject; import io.minio.messages.Item; import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Component public class MinioUtil { @Autowired private MinioClient minioClient; @Value("${minio.bucket-name}") private String defaultBucketName; /** * 檢查存儲(chǔ)桶是否存在 * @param bucketName 存儲(chǔ)桶名稱 * @return 是否存在 */ @SneakyThrows public boolean bucketExists(String bucketName) { return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); } /** * 創(chuàng)建存儲(chǔ)桶 * @param bucketName 存儲(chǔ)桶名稱 */ @SneakyThrows public void makeBucket(String bucketName) { if (!bucketExists(bucketName)) { minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); } } /** * 獲取所有存儲(chǔ)桶 * @return 存儲(chǔ)桶列表 */ @SneakyThrows public List<Bucket> listBuckets() { return minioClient.listBuckets(); } /** * 刪除存儲(chǔ)桶 * @param bucketName 存儲(chǔ)桶名稱 */ @SneakyThrows public void removeBucket(String bucketName) { minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); } /** * 簡(jiǎn)單文件上傳 * @param file 文件 * @param bucketName 存儲(chǔ)桶名稱 * @return 文件信息 */ @SneakyThrows public Map<String, String> uploadFile(MultipartFile file, String bucketName) { if (file == null || file.isEmpty()) { return null; } if (!bucketExists(bucketName)) { makeBucket(bucketName); } String originalFilename = file.getOriginalFilename(); String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(".")); minioClient.putObject(PutObjectArgs.builder() .bucket(bucketName) .object(fileName) .contentType(file.getContentType()) .stream(file.getInputStream(), file.getSize(), -1) .build()); Map<String, String> resultMap = new HashMap<>(); resultMap.put("fileName", fileName); resultMap.put("originalFilename", originalFilename); resultMap.put("url", getObjectUrl(bucketName, fileName, 7)); return resultMap; } /** * 簡(jiǎn)單文件上傳(使用默認(rèn)存儲(chǔ)桶) * @param file 文件 * @return 文件信息 */ public Map<String, String> uploadFile(MultipartFile file) { return uploadFile(file, defaultBucketName); } /** * 批量文件上傳 * @param files 文件列表 * @param bucketName 存儲(chǔ)桶名稱 * @return 文件信息列表 */ public List<Map<String, String>> uploadFiles(List<MultipartFile> files, String bucketName) { return files.stream() .map(file -> uploadFile(file, bucketName)) .filter(Objects::nonNull) .collect(Collectors.toList()); } /** * 批量文件上傳(使用默認(rèn)存儲(chǔ)桶) * @param files 文件列表 * @return 文件信息列表 */ public List<Map<String, String>> uploadFiles(List<MultipartFile> files) { return uploadFiles(files, defaultBucketName); } /** * 下載文件 * @param bucketName 存儲(chǔ)桶名稱 * @param objectName 對(duì)象名稱 * @return 輸入流 */ @SneakyThrows public InputStream downloadFile(String bucketName, String objectName) { return minioClient.getObject(GetObjectArgs.builder() .bucket(bucketName) .object(objectName) .build()); } /** * 下載文件(使用默認(rèn)存儲(chǔ)桶) * @param objectName 對(duì)象名稱 * @return 輸入流 */ public InputStream downloadFile(String objectName) { return downloadFile(defaultBucketName, objectName); } /** * 刪除文件 * @param bucketName 存儲(chǔ)桶名稱 * @param objectName 對(duì)象名稱 */ @SneakyThrows public void deleteFile(String bucketName, String objectName) { minioClient.removeObject(RemoveObjectArgs.builder() .bucket(bucketName) .object(objectName) .build()); } /** * 刪除文件(使用默認(rèn)存儲(chǔ)桶) * @param objectName 對(duì)象名稱 */ public void deleteFile(String objectName) { deleteFile(defaultBucketName, objectName); } /** * 批量刪除文件 * @param bucketName 存儲(chǔ)桶名稱 * @param objectNames 對(duì)象名稱列表 * @return 刪除錯(cuò)誤列表 */ @SneakyThrows public List<DeleteError> deleteFiles(String bucketName, List<String> objectNames) { List<DeleteObject> objects = objectNames.stream() .map(DeleteObject::new) .collect(Collectors.toList()); Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder() .bucket(bucketName) .objects(objects) .build()); List<DeleteError> errors = new ArrayList<>(); for (Result<DeleteError> result : results) { errors.add(result.get()); } return errors; } /** * 批量刪除文件(使用默認(rèn)存儲(chǔ)桶) * @param objectNames 對(duì)象名稱列表 * @return 刪除錯(cuò)誤列表 */ public List<DeleteError> deleteFiles(List<String> objectNames) { return deleteFiles(defaultBucketName, objectNames); } /** * 獲取文件URL * @param bucketName 存儲(chǔ)桶名稱 * @param objectName 對(duì)象名稱 * @param expires 過(guò)期時(shí)間(天) * @return 文件URL */ @SneakyThrows public String getObjectUrl(String bucketName, String objectName, int expires) { return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(bucketName) .object(objectName) .expiry(expires, TimeUnit.DAYS) .build()); } /** * 獲取文件URL(使用默認(rèn)存儲(chǔ)桶) * @param objectName 對(duì)象名稱 * @param expires 過(guò)期時(shí)間(天) * @return 文件URL */ public String getObjectUrl(String objectName, int expires) { return getObjectUrl(defaultBucketName, objectName, expires); } /** * 檢查文件是否存在 * @param bucketName 存儲(chǔ)桶名稱 * @param objectName 對(duì)象名稱 * @return 是否存在 */ @SneakyThrows public boolean objectExists(String bucketName, String objectName) { try { minioClient.statObject(StatObjectArgs.builder() .bucket(bucketName) .object(objectName) .build()); return true; } catch (Exception e) { return false; } } /** * 檢查文件是否存在(使用默認(rèn)存儲(chǔ)桶) * @param objectName 對(duì)象名稱 * @return 是否存在 */ public boolean objectExists(String objectName) { return objectExists(defaultBucketName, objectName); } /** * 列出存儲(chǔ)桶中的所有對(duì)象 * @param bucketName 存儲(chǔ)桶名稱 * @return 對(duì)象列表 */ @SneakyThrows public List<Item> listObjects(String bucketName) { Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder() .bucket(bucketName) .build()); List<Item> items = new ArrayList<>(); for (Result<Item> result : results) { items.add(result.get()); } return items; } /** * 列出存儲(chǔ)桶中的所有對(duì)象(使用默認(rèn)存儲(chǔ)桶) * @return 對(duì)象列表 */ public List<Item> listObjects() { return listObjects(defaultBucketName); } /** * 創(chuàng)建分片上傳 * @param bucketName 存儲(chǔ)桶名稱 * @param objectName 對(duì)象名稱 * @return 上傳ID */ @SneakyThrows public String createMultipartUpload(String bucketName, String objectName) { CreateMultipartUploadResponse response = minioClient.createMultipartUpload(CreateMultipartUploadArgs.builder() .bucket(bucketName) .object(objectName) .build()); return response.result().uploadId(); } /** * 上傳分片 * @param bucketName 存儲(chǔ)桶名稱 * @param objectName 對(duì)象名稱 * @param uploadId 上傳ID * @param partNumber 分片編號(hào) * @param stream 輸入流 * @param size 大小 * @return 分片ETag */ @SneakyThrows public String uploadPart(String bucketName, String objectName, String uploadId, int partNumber, InputStream stream, long size) { UploadPartResponse response = minioClient.uploadPart(UploadPartArgs.builder() .bucket(bucketName) .object(objectName) .uploadId(uploadId) .partNumber(partNumber) .stream(stream, size, -1) .build()); return response.etag(); } /** * 完成分片上傳 * @param bucketName 存儲(chǔ)桶名稱 * @param objectName 對(duì)象名稱 * @param uploadId 上傳ID * @param etags 分片ETag列表 */ @SneakyThrows public void completeMultipartUpload(String bucketName, String objectName, String uploadId, List<String> etags) { List<CompletePart> completeParts = new ArrayList<>(); for (int i = 0; i < etags.size(); i++) { completeParts.add(new CompletePart(i + 1, etags.get(i))); } minioClient.completeMultipartUpload(CompleteMultipartUploadArgs.builder() .bucket(bucketName) .object(objectName) .uploadId(uploadId) .parts(completeParts) .build()); } /** * 生成文件哈希值(用于秒傳判斷) * @param file 文件 * @return 哈希值 */ @SneakyThrows public String generateFileHash(MultipartFile file) { // 這里使用簡(jiǎn)單的文件大小和修改時(shí)間作為哈希值,實(shí)際應(yīng)用中應(yīng)使用MD5或SHA-1等算法 return file.getSize() + "-" + file.getOriginalFilename(); } }
五、創(chuàng)建 Controller
接下來(lái),我們創(chuàng)建一個(gè) Controller,提供各種文件操作的接口:
import io.minio.messages.Item; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController @RequestMapping("/api/minio") public class MinioController { @Autowired private MinioUtil minioUtil; /** * 簡(jiǎn)單文件上傳 */ @PostMapping("/upload") public ResponseEntity<Map<String, Object>> uploadFile(@RequestParam("file") MultipartFile file) { Map<String, Object> result = new HashMap<>(); try { Map<String, String> fileInfo = minioUtil.uploadFile(file); if (fileInfo != null) { result.put("code", 200); result.put("message", "上傳成功"); result.put("data", fileInfo); return ResponseEntity.ok(result); } else { result.put("code", 500); result.put("message", "上傳失敗"); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } catch (Exception e) { result.put("code", 500); result.put("message", "上傳異常:" + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } /** * 批量文件上傳 */ @PostMapping("/upload/batch") public ResponseEntity<Map<String, Object>> uploadFiles(@RequestParam("files") List<MultipartFile> files) { Map<String, Object> result = new HashMap<>(); try { List<Map<String, String>> fileInfos = minioUtil.uploadFiles(files); result.put("code", 200); result.put("message", "上傳成功"); result.put("data", fileInfos); return ResponseEntity.ok(result); } catch (Exception e) { result.put("code", 500); result.put("message", "上傳異常:" + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } /** * 文件下載 */ @GetMapping("/download/{fileName}") public ResponseEntity<byte[]> downloadFile(@PathVariable("fileName") String fileName) { try { InputStream inputStream = minioUtil.downloadFile(fileName); byte[] bytes = inputStream.readAllBytes(); inputStream.close(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.setContentDispositionFormData("attachment", fileName); return new ResponseEntity<>(bytes, headers, HttpStatus.OK); } catch (Exception e) { return new ResponseEntity<>(null, null, HttpStatus.INTERNAL_SERVER_ERROR); } } /** * 文件預(yù)覽 */ @GetMapping("/preview/{fileName}") public ResponseEntity<Map<String, Object>> previewFile(@PathVariable("fileName") String fileName) { Map<String, Object> result = new HashMap<>(); try { String url = minioUtil.getObjectUrl(fileName, 1); result.put("code", 200); result.put("message", "獲取成功"); result.put("url", url); return ResponseEntity.ok(result); } catch (Exception e) { result.put("code", 500); result.put("message", "獲取異常:" + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } /** * 刪除文件 */ @DeleteMapping("/delete/{fileName}") public ResponseEntity<Map<String, Object>> deleteFile(@PathVariable("fileName") String fileName) { Map<String, Object> result = new HashMap<>(); try { minioUtil.deleteFile(fileName); result.put("code", 200); result.put("message", "刪除成功"); return ResponseEntity.ok(result); } catch (Exception e) { result.put("code", 500); result.put("message", "刪除異常:" + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } /** * 列出所有文件 */ @GetMapping("/list") public ResponseEntity<Map<String, Object>> listFiles() { Map<String, Object> result = new HashMap<>(); try { List<Item> items = minioUtil.listObjects(); result.put("code", 200); result.put("message", "獲取成功"); result.put("data", items); return ResponseEntity.ok(result); } catch (Exception e) { result.put("code", 500); result.put("message", "獲取異常:" + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } /** * 初始化分片上傳 */ @PostMapping("/multipart/init") public ResponseEntity<Map<String, Object>> initMultipartUpload(@RequestParam("fileName") String fileName) { Map<String, Object> result = new HashMap<>(); try { String uploadId = minioUtil.createMultipartUpload("test-bucket", fileName); result.put("code", 200); result.put("message", "初始化成功"); result.put("uploadId", uploadId); return ResponseEntity.ok(result); } catch (Exception e) { result.put("code", 500); result.put("message", "初始化異常:" + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } /** * 上傳分片 */ @PostMapping("/multipart/upload") public ResponseEntity<Map<String, Object>> uploadPart( @RequestParam("fileName") String fileName, @RequestParam("uploadId") String uploadId, @RequestParam("partNumber") int partNumber, @RequestParam("file") MultipartFile file) { Map<String, Object> result = new HashMap<>(); try { String etag = minioUtil.uploadPart("test-bucket", fileName, uploadId, partNumber, file.getInputStream(), file.getSize()); result.put("code", 200); result.put("message", "分片上傳成功"); result.put("etag", etag); return ResponseEntity.ok(result); } catch (Exception e) { result.put("code", 500); result.put("message", "分片上傳異常:" + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } /** * 完成分片上傳 */ @PostMapping("/multipart/complete") public ResponseEntity<Map<String, Object>> completeMultipartUpload( @RequestParam("fileName") String fileName, @RequestParam("uploadId") String uploadId, @RequestParam("etags") List<String> etags) { Map<String, Object> result = new HashMap<>(); try { minioUtil.completeMultipartUpload("test-bucket", fileName, uploadId, etags); result.put("code", 200); result.put("message", "分片合并成功"); return ResponseEntity.ok(result); } catch (Exception e) { result.put("code", 500); result.put("message", "分片合并異常:" + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } /** * 文件秒傳檢查 */ @PostMapping("/check") public ResponseEntity<Map<String, Object>> checkFile(@RequestParam("file") MultipartFile file) { Map<String, Object> result = new HashMap<>(); try { String fileHash = minioUtil.generateFileHash(file); // 這里應(yīng)該查詢數(shù)據(jù)庫(kù)或緩存,檢查是否存在相同哈希值的文件 // 為簡(jiǎn)化示例,直接返回不存在 boolean exists = false; result.put("code", 200); result.put("message", "檢查成功"); result.put("exists", exists); result.put("fileHash", fileHash); return ResponseEntity.ok(result); } catch (Exception e) { result.put("code", 500); result.put("message", "檢查異常:" + e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } }
六、大文件分片上傳和秒傳實(shí)現(xiàn)原理
1. 大文件分片上傳
大文件分片上傳是將一個(gè)大文件分成多個(gè)小片段,分別上傳這些片段,最后在服務(wù)器端將這些片段合并成一個(gè)完整的文件。實(shí)現(xiàn)步驟如下:
前端:將文件切成固定大小的片段(如 1MB / 片),為每個(gè)片段生成唯一標(biāo)識(shí)(如序號(hào)),按順序上傳這些片段。
后端:
- 接收前端上傳的片段,保存到臨時(shí)目錄。
- 記錄已上傳的片段信息(如文件名、片段序號(hào)、ETag 等)。
- 當(dāng)所有片段上傳完成后,按順序合并這些片段。
2. 秒傳功能
秒傳功能是指當(dāng)用戶上傳一個(gè)文件時(shí),系統(tǒng)首先檢查該文件是否已經(jīng)存在,如果存在則直接返回文件鏈接,無(wú)需重新上傳。實(shí)現(xiàn)步驟如下:
前端:計(jì)算文件的哈希值(如 MD5、SHA-1),并將哈希值發(fā)送給后端。
后端:
- 根據(jù)哈希值查詢數(shù)據(jù)庫(kù)或緩存,檢查是否存在相同哈希值的文件。
- 如果存在,返回文件鏈接;如果不存在,通知前端正常上傳。
七、測(cè)試與驗(yàn)證
1. 簡(jiǎn)單文件上傳測(cè)試
使用 Postman 或其他工具,向/api/minio/upload
接口發(fā)送 POST 請(qǐng)求,上傳一個(gè)文件,驗(yàn)證是否能成功上傳并返回文件信息。
2. 批量文件上傳測(cè)試
向/api/minio/upload/batch
接口發(fā)送 POST 請(qǐng)求,上傳多個(gè)文件,驗(yàn)證是否能成功批量上傳。
3. 文件下載測(cè)試
訪問(wèn)/api/minio/download/{fileName}
接口,驗(yàn)證是否能成功下載文件。
4. 文件預(yù)覽測(cè)試
訪問(wèn)/api/minio/preview/{fileName}
接口,驗(yàn)證是否能獲取文件預(yù)覽鏈接。
5. 大文件分片上傳測(cè)試
使用前端工具(如 webuploader、plupload 等)實(shí)現(xiàn)大文件分片上傳功能,調(diào)用后端提供的分片上傳接口,驗(yàn)證大文件是否能成功上傳。
6. 秒傳功能測(cè)試
上傳一個(gè)文件,記錄文件哈希值,再次上傳相同文件,驗(yàn)證是否能秒傳成功。
到此這篇關(guān)于SpringBoot整合MinIO實(shí)現(xiàn)全場(chǎng)景文件操作管理的文章就介紹到這了,更多相關(guān)SpringBoot MinIO文件操作內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java 多線程實(shí)現(xiàn)在線咨詢(udp)
這篇文章主要介紹了java 多線程實(shí)現(xiàn)在線咨詢(udp)的示例,幫助大家更好的理解和學(xué)習(xí)Java 網(wǎng)絡(luò)編程的相關(guān)內(nèi)容,感興趣的朋友可以了解下2020-11-11Java重寫(xiě)(Override)與重載(Overload)區(qū)別原理解析
這篇文章主要介紹了Java重寫(xiě)(Override)與重載(Overload)區(qū)別原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02Java 中函數(shù) Function 的使用和定義示例小結(jié)
這篇文章主要介紹了Java 中函數(shù) Function 的使用和定義小結(jié),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-07-07Java中二叉樹(shù)的先序、中序、后序遍歷以及代碼實(shí)現(xiàn)
這篇文章主要介紹了Java中二叉樹(shù)的先序、中序、后序遍歷以及代碼實(shí)現(xiàn),一棵二叉樹(shù)是結(jié)點(diǎn)的一個(gè)有限集合,該集合或者為空,或者是由一個(gè)根節(jié)點(diǎn)加上兩棵別稱為左子樹(shù)和右子樹(shù)的二叉樹(shù)組成,需要的朋友可以參考下2023-11-11springboot3+r2dbc響應(yīng)式編程實(shí)踐
本文主要介紹了springboot3+r2dbc響應(yīng)式編程實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02SpringBoot項(xiàng)目中如何解決跨域問(wèn)題的最新方案?
跨域問(wèn)題是瀏覽器為了保護(hù)用戶的信息安全,實(shí)施了同源策略(Same-Origin Policy),即只允許頁(yè)面請(qǐng)求同源(相同協(xié)議、域名和端口)的資源,當(dāng) JavaScript 發(fā)起的請(qǐng)求跨越了同源策略,即請(qǐng)求的目標(biāo)與當(dāng)前頁(yè)面的域名、端口、協(xié)議不一致時(shí),瀏覽器會(huì)阻止請(qǐng)求的發(fā)送或接收2025-03-03通過(guò)實(shí)例學(xué)習(xí)Spring @Required注釋原理
這篇文章主要介紹了通過(guò)實(shí)例學(xué)習(xí)Spring @Required注釋原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03