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ù)成本較高,依賴(lài)于網(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)目,添加以下依賴(lài):
- Spring Web
- Lombok
- MinIO Client
三、整合 MinIO
1. 添加依賴(lài)
在pom.xml中添加 MinIO 客戶端依賴(lài):
<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ǔ)桶名稱(chēng)
* @return 是否存在
*/
@SneakyThrows
public boolean bucketExists(String bucketName) {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
}
/**
* 創(chuàng)建存儲(chǔ)桶
* @param bucketName 存儲(chǔ)桶名稱(chēng)
*/
@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ǔ)桶名稱(chēng)
*/
@SneakyThrows
public void removeBucket(String bucketName) {
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
}
/**
* 簡(jiǎn)單文件上傳
* @param file 文件
* @param bucketName 存儲(chǔ)桶名稱(chēng)
* @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ǔ)桶名稱(chēng)
* @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ǔ)桶名稱(chēng)
* @param objectName 對(duì)象名稱(chēng)
* @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ì)象名稱(chēng)
* @return 輸入流
*/
public InputStream downloadFile(String objectName) {
return downloadFile(defaultBucketName, objectName);
}
/**
* 刪除文件
* @param bucketName 存儲(chǔ)桶名稱(chēng)
* @param objectName 對(duì)象名稱(chēng)
*/
@SneakyThrows
public void deleteFile(String bucketName, String objectName) {
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
}
/**
* 刪除文件(使用默認(rèn)存儲(chǔ)桶)
* @param objectName 對(duì)象名稱(chēng)
*/
public void deleteFile(String objectName) {
deleteFile(defaultBucketName, objectName);
}
/**
* 批量刪除文件
* @param bucketName 存儲(chǔ)桶名稱(chēng)
* @param objectNames 對(duì)象名稱(chēng)列表
* @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ì)象名稱(chēng)列表
* @return 刪除錯(cuò)誤列表
*/
public List<DeleteError> deleteFiles(List<String> objectNames) {
return deleteFiles(defaultBucketName, objectNames);
}
/**
* 獲取文件URL
* @param bucketName 存儲(chǔ)桶名稱(chēng)
* @param objectName 對(duì)象名稱(chēng)
* @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ì)象名稱(chēng)
* @param expires 過(guò)期時(shí)間(天)
* @return 文件URL
*/
public String getObjectUrl(String objectName, int expires) {
return getObjectUrl(defaultBucketName, objectName, expires);
}
/**
* 檢查文件是否存在
* @param bucketName 存儲(chǔ)桶名稱(chēng)
* @param objectName 對(duì)象名稱(chēng)
* @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ì)象名稱(chēng)
* @return 是否存在
*/
public boolean objectExists(String objectName) {
return objectExists(defaultBucketName, objectName);
}
/**
* 列出存儲(chǔ)桶中的所有對(duì)象
* @param bucketName 存儲(chǔ)桶名稱(chēng)
* @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ǔ)桶名稱(chēng)
* @param objectName 對(duì)象名稱(chēng)
* @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ǔ)桶名稱(chēng)
* @param objectName 對(duì)象名稱(chēng)
* @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ǔ)桶名稱(chēng)
* @param objectName 對(duì)象名稱(chēng)
* @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)該查詢(xún)數(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ù)哈希值查詢(xún)數(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)文章
UrlDecoder和UrlEncoder使用詳解_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了UrlDecoder和UrlEncoder使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
Java利用ITextPdf庫(kù)生成PDF預(yù)覽文件的具體實(shí)現(xiàn)
這篇文章主要給大家介紹了Java利用ITextPdf庫(kù)生成PDF預(yù)覽文件的具體實(shí)現(xiàn),文中通過(guò)代碼示例和圖文給大家介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-04-04
Java設(shè)計(jì)模式之工廠模式(Factory模式)介紹
這篇文章主要介紹了Java設(shè)計(jì)模式之工廠模式(Factory模式)介紹,本文講解了為何使用工廠模式、工廠方法、抽象工廠、Java工廠模式舉例等內(nèi)容,需要的朋友可以參考下2015-03-03
SpringBoot Mybatis批量插入Oracle數(shù)據(jù)庫(kù)數(shù)據(jù)
這篇文章主要介紹了SpringBoot Mybatis批量插入Oracle數(shù)據(jù)庫(kù)數(shù)據(jù),文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-08-08
SpringBoot使用Mybatis&Mybatis-plus文件映射配置方法
這篇文章主要介紹了SpringBoot使用Mybatis&Mybatis-plus文件映射配置方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-05-05
Spring?Data?Jpa?中原生查詢(xún)?REGEXP?的使用詳解
這篇文章主要介紹了Spring?Data?Jpa?中原生查詢(xún)?REGEXP?的使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Java 內(nèi)置Http Server構(gòu)建web應(yīng)用案例詳解
這篇文章主要介紹了Java 內(nèi)置Http Server構(gòu)建web應(yīng)用案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
Java中的隨機(jī)數(shù)生成案例從范圍字符串到動(dòng)態(tài)區(qū)間應(yīng)用
本文介紹了在Java中生成隨機(jī)數(shù)的多種方法,并通過(guò)兩個(gè)案例解析如何根據(jù)業(yè)務(wù)需求生成特定范圍的隨機(jī)數(shù),本文通過(guò)兩個(gè)實(shí)際案例詳細(xì)介紹如何在java中應(yīng)用,感興趣的朋友跟隨小編一起看看吧2025-11-11
Java構(gòu)造器(構(gòu)造方法)與方法區(qū)別說(shuō)明
這篇文章主要介紹了Java構(gòu)造器(構(gòu)造方法)與方法區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09

