一、前端
前端将文件分成固定大小的若干个,在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 {@Autowiredprivate 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();// 将分片上传到MinIOminioTemplate.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;}
}