断点续传在多个场景中非常有用,包括但不限于大文件上传、跨国或跨区域文件传输、移动设备文件传输、备份和同步以及软件更新等。接下来,我将为你提供一个基于Java的后端实现示例,结合前端逻辑来完成整个断点续传的功能,包括上传、下载和验证。
后端实现(使用Spring Boot)
添加依赖
首先,在pom.xml
中添加必要的依赖:
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Apache Commons IO for file handling -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<!-- Jackson for JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
控制器代码
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.springframework.http.HttpStatus;
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.Paths;
import java.util.*;
@RestController
@RequestMapping("/api")
public class FileController {
private static final String UPLOAD_DIR = "uploads/";
@PostMapping("/upload")
public ResponseEntity<?> uploadChunk(@RequestParam("file") MultipartFile file,
@RequestParam("fileName") String fileName,
@RequestParam("chunkIndex") int chunkIndex,
@RequestParam("totalChunks") int totalChunks) throws IOException {
// 创建目录
String dirPath = UPLOAD_DIR + fileName;
new File(dirPath).mkdirs();
// 保存分片
File chunkFile = new File(dirPath, chunkIndex + ".part");
try (OutputStream os = new FileOutputStream(chunkFile)) {
os.write(file.getBytes());
}
// 记录已上传的分片
Set<Integer> uploadedChunks = loadUploadedChunks(fileName);
uploadedChunks.add(chunkIndex);
saveUploadedChunks(fileName, uploadedChunks);
if (uploadedChunks.size() == totalChunks) {
mergeChunks(fileName, totalChunks);
}
return ResponseEntity.ok().build();
}
@GetMapping("/download")
public ResponseEntity<byte[]> download(@RequestParam("fileName") String fileName) throws IOException {
String filePath = UPLOAD_DIR + fileName + "/" + fileName;
byte[] bytes = Files.readAllBytes(Paths.get(filePath));
return ResponseEntity.ok()
.header("Content-Disposition", "attachment; filename=" + fileName)
.body(bytes);
}
@GetMapping("/check")
public ResponseEntity<Map<String, Object>> check(@RequestParam("fileName") String fileName) {
Set<Integer> uploadedChunks = loadUploadedChunks(fileName);
Map<String, Object> response = new HashMap<>();
response.put("uploadedChunks", uploadedChunks);
return ResponseEntity.ok(response);
}
private Set<Integer> loadUploadedChunks(String fileName) {
String filePath = UPLOAD_DIR + fileName + "/uploadedChunks.json";
File file = new File(filePath);
Set<Integer> chunks = new HashSet<>();
if (file.exists()) {
try {
chunks = new HashSet<>(Arrays.asList(FileUtils.readFileToString(file, "UTF-8").split(",")))
.stream().map(Integer::parseInt).collect(Collectors.toSet());
} catch (IOException e) {
e.printStackTrace();
}
}
return chunks;
}
private void saveUploadedChunks(String fileName, Set<Integer> uploadedChunks) {
String filePath = UPLOAD_DIR + fileName + "/uploadedChunks.json";
try {
FileUtils.writeStringToFile(new File(filePath), String.join(",", uploadedChunks.stream().map(String::valueOf).toList()), "UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
}
private void mergeChunks(String fileName, int totalChunks) throws IOException {
String dirPath = UPLOAD_DIR + fileName;
String mergedFilePath = UPLOAD_DIR + fileName + "/" + fileName;
try (OutputStream os = new FileOutputStream(mergedFilePath)) {
for (int i = 0; i < totalChunks; i++) {
File chunkFile = new File(dirPath, i + ".part");
byte[] bytes = Files.readAllBytes(chunkFile.toPath());
os.write(bytes);
Files.delete(chunkFile.toPath());
}
}
}
}
前端实现(使用Vue.js)
安装依赖
确保你已经安装了axios
用于HTTP请求处理:
npm install axios
上传组件
<template>
<div>
<input type="file" @change="selectFile">
<button @click="upload">上传</button>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
file: null,
chunkSize: 5 * 1024 * 1024, // 5MB
uploadedChunks: []
};
},
methods: {
selectFile(e) {
this.file = e.target.files[0];
this.uploadedChunks = [];
},
async upload() {
const fileName = this.file.name;
const totalChunks = Math.ceil(this.file.size / this.chunkSize);
// 获取已上传的分片信息
const { data } = await axios.get('/api/check', { params: { fileName } });
this.uploadedChunks = data.uploadedChunks;
for (let i = 0; i < totalChunks; i++) {
if (this.uploadedChunks.includes(i)) continue;
const start = i * this.chunkSize;
const end = Math.min(start + this.chunkSize, this.file.size);
const chunk = this.file.slice(start, end);
const formData = new FormData();
formData.append('file', new Blob([chunk]));
formData.append('fileName', fileName);
formData.append('chunkIndex', i);
formData.append('totalChunks', totalChunks);
await axios.post('/api/upload', formData);
this.uploadedChunks.push(i);
}
alert('文件上传成功');
}
}
};
</script>
下载组件
<template>
<div>
<input type="text" v-model="fileName">
<button @click="download">下载</button>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
fileName: ''
};
},
methods: {
async download() {
const response = await axios.get('/api/download', { params: { fileName: this.fileName }, responseType: 'blob' });
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', this.fileName);
document.body.appendChild(link);
link.click();
}
}
};
</script>
验证组件
<template>
<div>
<input type="text" v-model="fileName">
<button @click="check">验证</button>
<p>已上传分片:{{ uploadedChunks }}</p>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
fileName: '',
uploadedChunks: []
};
},
methods: {
async check() {
const { data } = await axios.get('/api/check', { params: { fileName: this.fileName } });
this.uploadedChunks = data.uploadedChunks;
}
}
};
</script>
这个示例展示了如何使用Spring Boot作为后端,并结合Vue.js前端来实现断点续传功能。你可以根据自己的需求进行调整和优化,例如增加错误处理、进度条显示等功能。