一、前端
前端将文件分成固定大小的若干个,在Vue前端,可以使用File
API和Blob
对象将文件分片
// 分片上传函数
async function uploadFile(file) {
const chunkSize = 5 * 1024 * 1024; // 5MB每片
const totalChunks = Math.ceil(file.size / chunkSize);
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
// 逐个上传分片
await uploadChunk(chunk, i, totalChunks);
}
}
// 上传分片函数
async function uploadChunk(chunk, chunkIndex, totalChunks) {
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', chunkIndex);
formData.append('totalChunks', totalChunks);
await fetch('/api/upload', {
method: 'POST',
body: formData
});
}
二、后端
1、分片上传
前端每一个分片通过调用upload接口上传,每个片段有个索引index
import io.minio.MinioTemplate;
import io.minio.PutObjectArgs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
@RestController
@RequestMapping("/api")
public class FileUploadController {
@Autowired
private MinioTemplate minioTemplate;
private final String BUCKET_NAME = "your-bucket-name";
@PostMapping("/upload")
public String uploadChunk(@RequestParam("chunk") MultipartFile chunk,
@RequestParam("chunkIndex") int chunkIndex,
@RequestParam("totalChunks") int totalChunks) {
try {
// 保存分片到本地或临时存储
String chunkFileName = "file_part_" + chunkIndex;
InputStream inputStream = chunk.getInputStream();
// 将分片上传到MinIO
minioTemplate.putObject(BUCKET_NAME, chunkFileName, inputStream, chunk.getSize());
// 如果所有分片上传完毕,进行合并
if (chunkIndex == totalChunks - 1) {
mergeChunks(totalChunks);
}
return "Chunk uploaded successfully";
} catch (Exception e) {
e.printStackTrace();
return "Upload failed";
}
}
}
2、分片合并
逐个分片上传完成之后,调用这个方法进行合并
private void mergeChunks(int totalChunks) {
// 合并逻辑
try {
String mergedFileName = "merged_file"; // 合并后文件名
// 准备分片列表
List<ComposeSource> sources = new ArrayList<>();
for (int i = 0; i < totalChunks; i++) {
sources.add(ComposeSource.builder()
.bucket(BUCKET_NAME)
.object("file_part_" + i)
.build());
}
// 调用MinIO合并接口
minioTemplate.composeObject(BUCKET_NAME, mergedFileName, sources);
} catch (Exception e) {
e.printStackTrace();
}
}
3、断点续传
@GetMapping("/check")
public List<Integer> checkUploadedChunks(@RequestParam String fileMd5) {
List<Integer> uploadedChunks = new ArrayList<>();
try {
// 检查存储中是否存在分片
for (int i = 0; i < totalChunks; i++) {
String chunkFileName = "file_part_" + i;
if (minioTemplate.isObjectExist(BUCKET_NAME, chunkFileName)) {
uploadedChunks.add(i);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return uploadedChunks;
}
4、秒传
根据生成对应的md5,和minIO中的文件的MD5去对比,如果存在,直接返回域名加路径即可,不存在再执行上传
// 检查文件是否已上传
@GetMapping("/check")
public boolean checkFile(@RequestParam String fileMd5) {
try {
// 查询MinIO是否存在对应的文件
return minioTemplate.isObjectExist(BUCKET_NAME, fileMd5);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}