好的,下面是一個基於 Spring Boot 和 MinIO 實現文件分片上傳、斷點續傳、秒傳的示例代碼:
@RestController
@RequestMapping("/file")
public class FileController {
private final MinioClient minioClient;
@Autowired
public FileController(MinioClient minioClient) {
this.minioClient = minioClient;
}
@PostMapping("/upload")
public ResponseEntity<String> upload(@RequestParam("file") MultipartFile file) throws Exception {
String md5 = DigestUtils.md5Hex(file.getInputStream());
String objectName = md5 + "/" + file.getOriginalFilename();
boolean objectExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket("my-bucket").build());
if (!objectExists) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket("my-bucket").build());
}
if (minioClient.objectExists(ObjectExistsArgs.builder().bucket("my-bucket").object(objectName).build())) {
return ResponseEntity.ok("秒傳成功");
}
int partSize = 5 * 1024 * 1024;
long objectSize = file.getSize();
int partCount = (int) Math.ceil(objectSize / (double) partSize);
List<PartETag> partETags = new ArrayList<>(partCount);
InputStream inputStream = file.getInputStream();
for (int i = 0; i < partCount; i++) {
long offset = i * partSize;
long size = Math.min(partSize, objectSize - offset);
UploadPartArgs uploadPartArgs = UploadPartArgs.builder()
.bucket("my-bucket")
.object(objectName)
.uploadId(md5)
.partNumber(i + 1)
.partSize(size)
.stream(inputStream, size, offset)
.build();
partETags.add(minioClient.uploadPart(uploadPartArgs));
}
CompleteMultipartUploadArgs completeMultipartUploadArgs = CompleteMultipartUploadArgs.builder()
.bucket("my-bucket")
.object(objectName)
.uploadId(md5)
.partETags(partETags)
.build();
minioClient.completeMultipartUpload(completeMultipartUploadArgs);
return ResponseEntity.ok("上傳成功");
}
@GetMapping("/download")
public ResponseEntity<InputStreamResource> download(@RequestParam("filename") String filename) throws Exception {
String md5 = DigestUtils.md5Hex(new ByteArrayInputStream(filename.getBytes()));
String objectName = md5 + "/" + filename;
boolean objectExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket("my-bucket").build());
if (!objectExists) {
return ResponseEntity.notFound().build();
}
boolean objectPartExists = minioClient.objectExists(ObjectExistsArgs.builder().bucket("my-bucket").object(objectName).build());
if (!objectPartExists) {
return ResponseEntity.notFound().build();
}
GetObjectArgs getObjectArgs = GetObjectArgs.builder()
.bucket("my-bucket")
.object(objectName)
.build();
InputStream inputStream = minioClient.getObject(getObjectArgs);
InputStreamResource inputStreamResource = new InputStreamResource(inputStream);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
.contentLength(inputStream.available())
.body(inputStreamResource);
}
@GetMapping("/resume")
public ResponseEntity<String> resume(@RequestParam("file") MultipartFile file) throws Exception {
String md5 = DigestUtils.md5Hex(file.getInputStream());
String objectName = md5 + "/" + file.getOriginalFilename();
boolean objectExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket("my-bucket").build());
if (!objectExists) {
return ResponseEntity.notFound().build();
}
boolean objectPartExists = minioClient.objectExists(ObjectExistsArgs.builder().bucket("my-bucket").object(objectName).build());
if (!objectPartExists) {
return ResponseEntity.notFound().build();
}
int partSize = 5 * 1024 * 1024;
long objectSize = file.getSize();
int partCount = (int) Math.ceil(objectSize / (double) partSize);
List<PartETag> partETags = new ArrayList<>(partCount);
InputStream inputStream = file.getInputStream();
for (int i = 0; i < partCount; i++) {
long offset = i * partSize;
long size = Math.min(partSize, objectSize - offset);
UploadPartArgs uploadPartArgs = UploadPartArgs.builder()
.bucket("my-bucket")
.object(objectName)
.uploadId(md5)
.partNumber(i + 1)
.partSize(size)
.stream(inputStream, size, offset)
.build();
partETags.add(minioClient.uploadPart(uploadPartArgs));
}
CompleteMultipartUploadArgs completeMultipartUploadArgs = CompleteMultipartUploadArgs.builder()
.bucket("my-bucket")
.object(objectName)
.uploadId(md5)
.partETags(partETags)
.build();
minioClient.completeMultipartUpload(completeMultipartUploadArgs);
return ResponseEntity.ok("續傳成功");
}
}
在上面的代碼中,我們使用了MinioClient
來連接到 MinIO 伺服器,實現了文件的上傳、下載和續傳功能。
上傳文件時,我們首先計算出文件的 MD5 值,然後將 MD5 值作為文件夾名稱,將上傳的文件保存在該文件夾下。如果該文件已經存在於 MinIO 伺服器上,則直接返回秒傳成功
,否則我們將文件分成若干個分片進行上傳,最後將分片合併成一個完整的文件。
下載文件時,我們先計算出文件的 MD5 值,然後將 MD5 值作為文件夾名稱,從該文件夾下下載指定的文件。
續傳文件時,我們先計算出文件的 MD5 值,然後判斷該文件是否已經存在於 MinIO 伺服器上。如果存在,則按照上傳文件的方式進行續傳,否則返回文件不存在
的錯誤信息。
需要注意的是,在實際使用中,您需要將代碼中的my-bucket
替換成您自己的 Bucket 名稱,並且確保 MinIO 伺服器已經正確地配置和啟動。
好的,下面是一個簡單的 Vue3 代碼示例,實現了文件分片上傳和斷點續傳的功能。
<template>
<div>
<input type="file" @change="onFileChange" />
<button @click="upload">上傳</button>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
file: null,
uploaded: 0,
total: 0,
progress: 0,
chunkSize: 1024 * 1024,
chunks: [],
uploadedChunks: [],
};
},
methods: {
onFileChange(event) {
this.file = event.target.files[0];
this.total = this.file.size;
const chunksCount = Math.ceil(this.total / this.chunkSize);
for (let i = 0; i < chunksCount; i++) {
const start = i * this.chunkSize;
const end = Math.min(start + this.chunkSize, this.total);
const chunk = this.file.slice(start, end);
this.chunks.push(chunk);
this.uploadedChunks.push(false);
}
},
async upload() {
const config = {
onUploadProgress: (progressEvent) => {
this.uploaded = progressEvent.loaded;
this.progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
},
};
const url = 'http://localhost:8080/upload';
for (let i = 0; i < this.chunks.length; i++) {
if (this.uploadedChunks[i]) {
continue;
}
const formData = new FormData();
formData.append('file', this.chunks[i]);
formData.append('index', i.toString());
formData.append('chunksCount', this.chunks.length.toString());
await axios.post(url, formData, config);
this.uploadedChunks[i] = true;
}
console.log('上傳完成');
},
},
};
</script>
這個示例中,我們使用了 axios 庫來發送上傳文件的請求。在 onFileChange
方法中,我們將文件分成了多個大小相同的塊,並將它們存儲在 chunks
陣列中。我們還創建了一個 uploadedChunks
陣列來跟蹤哪些塊已經上傳完成。
在 upload
方法中,我們循環遍歷塊陣列,並使用 FormData 對象將每個塊上傳到伺服器。在上傳過程中,我們使用 onUploadProgress
回調來跟蹤上傳進度,並更新 uploaded
和 progress
變量。
請注意,這裡的代碼只是一個示例,您需要根據您的具體需求進行修改。如果您需要更詳細的代碼示例或者教程,請告訴我。