<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,
};
},
methods: {
handleFileChange(event) {
this.file = event.target.files[0];
this.totalChunks = Math.ceil(this.file.size / this.chunkSize);
this.uploadChunks();
},
async uploadChunks() {
for (let index = 0; index < this.totalChunks; 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);
await this.uploadChunk(formData);
this.uploadedChunks++;
this.progress = Math.floor(
(this.uploadedChunks / this.totalChunks) * 100
);
}
},
async uploadChunk(formData) {
try {
await this.$axios.post('/uploadChunk', formData);
} catch (error) {
console.error('Error uploading chunk', error);
}
},
},
};
</script>
@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 uploadDir = new File(UPLOAD_DIR);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 保存分片文件
File chunkFile = new File(UPLOAD_DIR + fileName + "_" + index);
chunk.transferTo(chunkFile);
return ResponseEntity.ok("Chunk uploaded successfully");
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Chunk upload failed");
}
}
@PostMapping("/mergeChunks")
public ResponseEntity<String> mergeChunks(@RequestParam("fileName") String fileName) {
try {
File mergedFile = new File(UPLOAD_DIR + fileName);
try (FileOutputStream outputStream = new FileOutputStream(mergedFile, true)) {
int index = 0;
while (true) {
File chunkFile = new File(UPLOAD_DIR + fileName + "_" + index);
if (!chunkFile.exists()) {
break;
}
Files.copy(chunkFile.toPath(), outputStream);
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");
}
}
// 如果传了分片数量(totalChunks)的话,上面俩接口可以写成一个
@PostMapping("/uploadChunk")
public ResponseEntity<String> uploadChunk(
@RequestParam("chunk") MultipartFile chunk,
@RequestParam("index") int index, // 从0开始
@RequestParam("totalChunks") int totalChunks,
@RequestParam("fileName") String fileName) {
try {
File uploadDir = new File(UPLOAD_DIR);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 保存分片文件
File chunkFile = new File(UPLOAD_DIR + fileName + "_" + index);
chunk.transferTo(chunkFile); // 如果前端传的是Base64的文件加密字符串(而不是MultipartFile),那么这里就用流的方式把图片存到指定的位置,Base64的前缀要去掉,逗号后面才是实际的Base64编码数据(看另一篇博客)
if (index == totalChunks - 1) { // 必须在上面保存完分片文件后再判断,确保分片全部保存
try (FileOutputStream fos = new FileOutputStream(UPLOAD_DIR + fileName)) {
for (int i = 0; i < totalChunks; i++) {
File tempFile = new File(UPLOAD_DIR + fileName + "_" + i);
Files.copy(tempFile.toPath(), fos);
tempFile.delete(); // Clean up temp file
}
}
}
return ResponseEntity.ok("Chunk uploaded successfully");
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Chunk upload failed");
}
}
}
<template>
<div>
<button @click="downloadFile">Download File</button>
</div>
</template>
<script>
export default {
methods: {
async downloadFile() {
let fileName = "largefile.zip"; // 需要下载的文件名
let totalChunks = 10; // 文件分片数
let chunkSize = 1024 * 1024; // 1MB 分片大小
let blobParts = [];
for (let index = 0; index < totalChunks; index++) {
const response = await this.$axios.get(`/downloadChunk`, {
params: { fileName, index, chunkSize },
responseType: 'blob',
});
blobParts.push(response.data);
}
const blob = new Blob(blobParts);
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.click();
},
},
};
</script>
// 也可以并发的调用接口
<template>
<div>
<button @click="downloadFile">Download File</button>
</div>
</template>
<script>
export default {
methods: {
async downloadFile() {
const fileName = "largefile.zip"; // 需要下载的文件名
const totalChunks = 10; // 文件分片数
const chunkSize = 1024 * 1024; // 1MB 分片大小
const requests = [];
// 发起所有文件块的请求
for (let index = 0; index < totalChunks; index++) {
const request = this.$axios.get(`/download/downloadChunk`, {
params: { fileName, index, chunkSize },
responseType: 'blob',
});
requests.push(request);
}
try {
// 并行请求所有文件块
const responses = await Promise.all(requests);
// 处理所有文件块的响应
const blobParts = responses.map(response => response.data);
const blob = new Blob(blobParts);
// 创建一个下载链接并触发下载
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.click();
} catch (error) {
console.error('Error downloading file:', error);
// 处理错误情况,例如显示错误消息给用户
}
},
},
};
</script>
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
@RestController
@RequestMapping("/download")
public class FileDownloadController {
private static final String UPLOAD_DIR = "uploads/";
@GetMapping("/downloadChunk")
public ResponseEntity<Resource> downloadChunk(
@RequestParam("fileName") String fileName,
@RequestParam("index") int index,
@RequestParam("chunkSize") int chunkSize) {
File file = new File(UPLOAD_DIR + fileName);
if (!file.exists()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
long start = index * chunkSize;
long fileSize = file.length();
long end = Math.min(start + chunkSize, fileSize);
if (start >= fileSize) {
return ResponseEntity.status(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE).build();
}
// Move file pointer to the start position
raf.seek(start);
// Calculate the length of the chunk to read
int length = (int) (end - start);
byte[] buffer = new byte[length];
raf.readFully(buffer); // Read the exact number of bytes into the buffer
InputStreamResource resource = new InputStreamResource(new ByteArrayInputStream(buffer));
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.CONTENT_RANGE, "bytes " + start + "-" + (end - 1) + "/" + fileSize);
headers.set(HttpHeaders.CONTENT_LENGTH, String.valueOf(length));
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return ResponseEntity.ok()
.headers(headers)
.body(resource);
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
@RestController
@RequestMapping("/download")
public class FileDownloadController {
private static final String UPLOAD_DIR = "uploads/";
@GetMapping("/downloadChunk")
public ResponseEntity<Resource> downloadChunk(
@RequestParam("fileName") String fileName,
@RequestParam("index") int index,
@RequestParam("chunkSize") int chunkSize) {
File file = new File(UPLOAD_DIR + fileName);
if (!file.exists()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
try (RandomAccessFile raf = new RandomAccessFile(file, "r");
FileChannel fileChannel = raf.getChannel()) {
long start = index * chunkSize;
long fileSize = file.length();
long end = Math.min(start + chunkSize, fileSize);
if (start >= fileSize) {
return ResponseEntity.status(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE).build();
}
// Calculate the size of the chunk to map
long size = end - start;
// Map the file chunk into memory
MappedByteBuffer buffer = fileChannel.map(MapMode.READ_ONLY, start, size);
// Create a byte array to hold the mapped content
byte[] chunkData = new byte[(int) size];
buffer.get(chunkData); // Transfer the mapped content to the byte array
// Create InputStream from byte array
InputStreamResource resource = new InputStreamResource(new ByteArrayInputStream(chunkData));
// Set headers
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.CONTENT_RANGE, "bytes " + start + "-" + (end - 1) + "/" + fileSize);
headers.set(HttpHeaders.CONTENT_LENGTH, String.valueOf(size));
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return ResponseEntity.ok()
.headers(headers)
.body(resource);
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}