<template>
<div>
<input type="file" @change="handleFileChange" />
<progress :value="progress" max="100">{{ progress }}%</progress>
</div>
</template>
<script>
export default {
data() {
return {
file: null,
chunkSize: 1024 * 1024, // 1MB 分片大小
totalChunks: 0,
uploadedChunks: 0,
progress: 0,
uploadedChunkIndices: [], // 记录已上传的分片索引
};
},
methods: {
handleFileChange(event) {
this.file = event.target.files[0];
this.totalChunks = Math.ceil(this.file.size / this.chunkSize);
this.getUploadedChunkIndices();
},
async getUploadedChunkIndices() {
try {
const response = await this.$axios.get('/getUploadedChunks', {
params: { fileName: this.file.name },
});
this.uploadedChunkIndices = response.data;
this.uploadChunks();
} catch (error) {
console.error('Error fetching uploaded chunks', error);
}
},
async uploadChunks() {
for (let index = 0; index < this.totalChunks; index++) {
if (!this.uploadedChunkIndices.includes(index)) {
const chunk = this.file.slice(
index * this.chunkSize,
(index + 1) * this.chunkSize
);
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('index', index);
formData.append('fileName', this.file.name);
try {
await this.$axios.post('/uploadChunk', formData);
this.uploadedChunkIndices.push(index);
this.uploadedChunks++;
this.progress = Math.floor(
(this.uploadedChunks / this.totalChunks) * 100
);
} catch (error) {
console.error('Error uploading chunk', error);
}
}
}
},
},
};
</script>
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.util.HashSet;
import java.util.Set;
@RestController
@RequestMapping("/upload")
public class FileUploadController {
private static final String UPLOAD_DIR = "uploads/";
@PostMapping("/uploadChunk")
public ResponseEntity<String> uploadChunk(
@RequestParam("chunk") MultipartFile chunk,
@RequestParam("index") int index,
@RequestParam("fileName") String fileName) {
try {
File file = new File(UPLOAD_DIR + fileName);
// 使用 RandomAccessFile 进行分片写入
try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
raf.seek(index * chunk.getSize()); // index从0开始
raf.write(chunk.getBytes());
}
return ResponseEntity.ok("Chunk uploaded successfully");
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Chunk upload failed");
}
}
@GetMapping("/getUploadedChunks")
public ResponseEntity<Set<Integer>> getUploadedChunks(@RequestParam("fileName") String fileName) {
File file = new File(UPLOAD_DIR + fileName);
Set<Integer> uploadedChunks = new HashSet<>();
if (file.exists()) {
long fileSize = file.length();
int chunkSize = 1024 * 1024; // 1MB
int totalChunks = (int) Math.ceil((double) fileSize / chunkSize);
for (int i = 0; i < totalChunks; i++) {
uploadedChunks.add(i);
}
}
return ResponseEntity.ok(uploadedChunks);
}
}
直接在目标文件的指定位置写入每个分块(chunk)。这种方式的确可以避免在上传文件的过程中额外进行文件合并操作。因为每个分块(chunk)都是在文件的正确位置上写入,所以上传结束后,整个文件已经是完整的,不需要再额外进行合并。
优化:
1、同步方法: uploadChunk() 方法被标记为 synchronized,确保同一时间只能有一个线程写入同一个文件的同一位置,防止并发上传同一文件的不同分片时造成写入冲突。
public synchronized ResponseEntity<String> uploadChunk()
2、更精确的上传进度:当前通过文件大小来判断已上传的分片,可以进一步改进,在上传成功后将已上传的分片索引存储在持久化存储中(redis中),避免重复上传。
(1)getUploadedChunks() 接口,直接返回redis的数据;
(2)uploadChunk()接口,判断传过来的index在redis中是否存在,存在直接return,不存在加入redis。
不需要进行合并!!!!!,以下代码只是演示下怎么合并
/*import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@RestController
@RequestMapping("/merge")
public class FileMergeController {
private static final String UPLOAD_DIR = "uploads/";
@PostMapping("/mergeChunks")
public ResponseEntity<String> mergeChunks(@RequestParam("fileName") String fileName) {
File file = new File(UPLOAD_DIR + fileName);
if (file.exists()) {
try (FileOutputStream fos = new FileOutputStream(file)) {
int index = 0;
File chunkFile;
while ((chunkFile = new File(UPLOAD_DIR + fileName + "_" + index)).exists()) {
Files.copy(chunkFile.toPath(), fos);
chunkFile.delete();
index++;
}
return ResponseEntity.ok("File merged successfully");
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("File merge failed");
}
}
return ResponseEntity.badRequest().body("File does not exist");
}
}*/