在前端上传 大文件(比如几百 MB 或几 GB 时,一般会用 文件切片(chunk upload)+ 断点续传 的方式。核心思路是:
- 前端把文件 按固定大小切成多个 chunk
- 逐个上传 chunk
- 服务端 按顺序合并 chunk
- 如果中断,可以 只上传缺失的 chunk
一、实现流程(大文件切片上传)
流程通常是:
选择文件
↓
计算文件hash(可选,用于秒传)
↓
切片 file.slice()
↓
并发上传 chunk
↓
服务端记录 chunk
↓
全部上传完成
↓
请求服务端 merge
常见参数:
chunkIndex 当前分片序号
chunkSize 分片大小
totalChunks 分片总数
fileHash 文件hash
二、Vue 实现案例
1 选择文件
html
<input type="file" @change="handleFileChange" />
js
data() {
return {
file: null
}
},
methods: {
handleFileChange(e) {
this.file = e.target.files[0]
}
}
三、文件切片
假设每片 5MB
js
const CHUNK_SIZE = 5 * 1024 * 1024
function createChunks(file) {
const chunks = []
let start = 0
while (start < file.size) {
chunks.push(file.slice(start, start + CHUNK_SIZE))
start += CHUNK_SIZE
}
return chunks
}
返回:
[chunk0, chunk1, chunk2 ...]
四、上传切片
js
async uploadFile() {
const chunks = createChunks(this.file)
const requests = chunks.map((chunk, index) => {
const formData = new FormData()
formData.append("file", chunk)
formData.append("index", index)
formData.append("fileName", this.file.name)
formData.append("total", chunks.length)
return fetch("/upload", {
method: "POST",
body: formData
})
})
await Promise.all(requests)
await this.mergeChunks()
}
五、通知服务端合并
js
async mergeChunks() {
await fetch("/merge", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
fileName: this.file.name
})
})
}
六、服务端逻辑(核心思路)
无论你是用 Node.js / Java / Go,都一样。
上传 chunk
/upload
保存为
/upload
fileHash/
0
1
2
3
合并文件
/merge
伪代码
js
for (let i = 0; i < total; i++) {
读取 chunk i
append 到最终文件
}
七、优化(生产级必须做)
1 并发控制(避免100个请求)
推荐 5~10 并发
js
async function uploadChunks(chunks, limit = 5) {
}
2 文件 hash(实现秒传)
使用
spark-md5
计算 hash
js
hash = md5(file)
服务器如果存在
直接返回 秒传成功
3 断点续传
流程:
-
上传前询问服务器
/check
返回
已上传 chunks = [0,1,4,5]
-
前端只上传
[2,3]
4 上传进度
js
xhr.upload.onprogress = (e)=>{
progress = e.loaded / e.total
}
八、完整 Vue 示例(简化版)
js
async uploadFile() {
const CHUNK_SIZE = 5 * 1024 * 1024
const file = this.file
const chunks = []
let start = 0
while (start < file.size) {
chunks.push(file.slice(start, start + CHUNK_SIZE))
start += CHUNK_SIZE
}
for (let i = 0; i < chunks.length; i++) {
const formData = new FormData()
formData.append("file", chunks[i])
formData.append("index", i)
formData.append("total", chunks.length)
formData.append("fileName", file.name)
await fetch("/upload", {
method: "POST",
body: formData
})
}
await fetch("/merge", {
method: "POST",
body: JSON.stringify({ fileName: file.name })
})
}