前端代码
要优化前端的文件分片上传代码,我们可以考虑以下几点:
- 异步上传与并发控制:上传分片时使用异步请求,并控制同时上传的分片数量,避免同时发送过多请求导致浏览器或服务器压力过大。
- 上传进度显示:向用户显示每个分片的上传进度和总体进度。
- 断点续传:在上传之前检查哪些分片已经上传过,只上传未完成的分片。
- 错误处理与重试机制:对于上传失败的分片,实现自动重试逻辑。
以下是优化后的前端代码示例:
HTML 部分
<!DOCTYPE html>
<html>
<head>
<title>File Upload</title>
</head>
<body>
<input type="file" id="fileInput">
<button onclick="uploadFile()">Upload</button>
<div id="uploadStatus"></div>
<progress id="uploadProgress" value="0" max="100"></progress>
</body>
</html>
JavaScript 部分
const MAX_CONCURRENT_UPLOADS = 5; // 最大并发上传数
let concurrentUploads = 0; // 当前并发上传数
let fileChunksQueue = []; // 待上传的文件分片队列
function uploadFile() {
var fileInput = document.getElementById('fileInput');
var file = fileInput.files[0];
var chunkSize = 1024 * 1024; // 分片大小,这里设置为 1MB
var totalChunks = Math.ceil(file.size / chunkSize);
// 初始化分片队列
for (let i = 0; i < totalChunks; i++) {
let start = i * chunkSize;
let end = Math.min(file.size, start + chunkSize);
let chunk = file.slice(start, end);
fileChunksQueue.push({ chunk, chunkNumber: i + 1, totalChunks, fileName: file.name });
}
// 开始上传
startUploads();
}
function startUploads() {
while (concurrentUploads < MAX_CONCURRENT_UPLOADS && fileChunksQueue.length > 0) {
const chunkData = fileChunksQueue.shift();
uploadChunk(chunkData);
concurrentUploads++;
}
}
function uploadChunk({ chunk, chunkNumber, totalChunks, fileName }) {
var formData = new FormData();
formData.append('file', chunk);
formData.append('chunkNumber', chunkNumber);
formData.append('totalChunks', totalChunks);
formData.append('fileId', fileName);
fetch('http://localhost:8080/file/upload', {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(data => {
concurrentUploads--;
updateUploadStatus(chunkNumber, totalChunks);
startUploads(); // 继续上传下一个分片
})
.catch(error => {
console.error('Error:', error);
fileChunksQueue.push({ chunk, chunkNumber, totalChunks, fileName }); // 重试
concurrentUploads--;
startUploads();
});
}
function updateUploadStatus(chunkNumber, totalChunks) {
let uploadedChunks = chunkNumber;
let progress = (uploadedChunks / totalChunks) * 100;
document.getElementById('uploadProgress').value = progress;
document.getElementById('uploadStatus').innerHTML = `Uploaded ${uploadedChunks} of ${totalChunks} chunks (${progress.toFixed(2)}%)`;
}
优化点说明
- 并发控制:通过
MAX_CONCURRENT_UPLOADS
控制同时上传的分片数量。 - 上传队列:使用
fileChunksQueue
管理待上传的分片队列。 - 进度显示:使用 HTML
progress
元素显示上传进度。 - 错误处理与重试:上传失败的分片会被重新放回队列中等待重试。
这种优化可以提高上传效率,同时提供更好的用户体验。还可以根据需要进一步增加例如上传失败后的最大重试次数等更复杂的逻辑。
后端代码
为了优化 Spring Boot 后端代码以更好地支持文件分片上传和断点续传,我们可以考虑以下几个方面:
- 增加断点续传支持:提供一个接口来检查特定文件的已上传分片。
- 优化文件存储:使用更高效的方式来存储和访问分片。
- 增强错误处理:确保在读写操作中适当处理异常。
- 验证分片完整性:在接收到分片时验证其完整性,如校验分片大小或内容。
- 日志记录:记录重要操作,帮助调试和监控上传过程。
示例代码
这是一个优化后的 Spring Boot 控制器实现示例:
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.BitSet;
@RestController
@RequestMapping("/file")
public class FileUploadController {
private final String UPLOAD_DIR = "/path/to/upload/dir/";
@PostMapping("/upload")
public ResponseEntity<String> uploadFileChunk(
@RequestParam("file") MultipartFile file,
@RequestParam("chunkNumber") int chunkNumber,
@RequestParam("totalChunks") int totalChunks,
@RequestParam("fileId") String fileId) {
try {
Path chunkFile = Paths.get(UPLOAD_DIR + fileId + "_" + chunkNumber);
Files.write(chunkFile, file.getBytes());
return ResponseEntity.ok("Chunk uploaded successfully");
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(500).body("Error uploading chunk");
}
}
@GetMapping("/checkUploadedChunks")
public ResponseEntity<BitSet> checkUploadedChunks(@RequestParam("fileId") String fileId,
@RequestParam("totalChunks") int totalChunks) {
BitSet uploadedChunks = new BitSet(totalChunks);
for (int i = 1; i <= totalChunks; i++) {
if (Files.exists(Paths.get(UPLOAD_DIR + fileId + "_" + i))) {
uploadedChunks.set(i - 1);
}
}
return ResponseEntity.ok(uploadedChunks);
}
@PostMapping("/mergeChunks")
public ResponseEntity<String> mergeChunks(@RequestParam("fileId") String fileId,
@RequestParam("totalChunks") int totalChunks) {
try {
mergeFileChunks(fileId, totalChunks);
return ResponseEntity.ok("File merged successfully");
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(500).body("Error merging chunks");
}
}
private void mergeFileChunks(String fileId, int totalChunks) throws IOException {
try (FileOutputStream fos = new FileOutputStream(UPLOAD_DIR + fileId);
BufferedOutputStream mergingStream = new BufferedOutputStream(fos)) {
for (int i = 1; i <= totalChunks; i++) {
Path chunkFile = Paths.get(UPLOAD_DIR + fileId + "_" + i);
Files.copy(chunkFile, mergingStream);
Files.delete(chunkFile);
}
}
}
}
优化点说明
- 断点续传接口:
checkUploadedChunks
接口允许客户端查询特定文件的已上传分片。 - 分片合并接口:
mergeChunks
接口用于合并所有上传的分片。 - 错误处理和日志记录:在关键操作中加入了异常捕获和日志记录。
这些优化有助于提高后端处理的效率和可靠性,并支持更复杂的上传场景。根据实际需求,可以进一步增加如上传权限检查、分片完整性校验等功能。