一、实现原理
文件分块:将大文件切割为固定大小的块(如5MB)
进度记录:持久化存储已上传分块信息
续传能力:上传中断后根据记录继续上传未完成块
块校验机制:通过哈希值验证块完整性
合并策略:所有块上传完成后进行有序合并
二、前端实现(JavaScript)
// 文件分块(示例使用Blob.slice)
// 默认 5M 一个片段
function createChunks(file, chunkSize = 5 * 1024 * 1024) {const chunks = [];let offset = 0;while (offset < file.size) {const chunk = file.slice(offset, offset + chunkSize);chunks.push({chunk,index: chunks.length,hash: file.name + '-' + chunks.length});offset += chunkSize;}return chunks;
}// 上传控制器
class Uploader {constructor(file) {this.file = filethis.chunks = createChunks(file)this.uploaded = new Set() // 已上传分块索引}async checkProgress() {// 查询服务端已上传分块const { data } = await axios.get('/progress', {params: { hash: this.fileHash }})this.uploaded = new Set(data.uploaded)}async upload() {await this.checkProgress()for (const chunk of this.chunks) {if (this.uploaded.has(chunk.index)) continueconst formData = new FormData()formData.append('chunk', chunk.chunk)formData.append('hash', chunk.hash)formData.append('index', chunk.index)formData.append('total', this.chunks.length)await axios.post('/upload', formData, {onUploadProgress: progress => {console.log(`块${chunk.index}上传进度:`, progress)}})this.uploaded.add(chunk.index)}await axios.post('/merge', { filename: this.file.name,total: this.chunks.length })}
}
三、服务端实现(Node.js + Express
)
const express = require('express')
const fs = require('fs-extra')
const path = require('path')
const app = express()// 临时存储目录
const UPLOAD_DIR = path.resolve(__dirname, 'temp')// 处理分块上传
app.post('/upload', async (req, res) => {const { chunk, hash, index } = req.filesconst chunkDir = path.resolve(UPLOAD_DIR, hash.split('-')[0])await fs.ensureDir(chunkDir)await fs.move(chunk.path, path.resolve(chunkDir, hash))res.json({ code: 0 })
})// 合并分块
app.post('/merge', async (req, res) => {const { filename, total } = req.bodyconst fileHash = filename + '-' + Date.now()const chunkDir = path.resolve(UPLOAD_DIR, fileHash)const destFile = path.resolve(UPLOAD_DIR, filename)// 按索引顺序合并await fs.ensureDir(chunkDir)for (let i = 0; i < total; i++) {const chunkPath = path.resolve(chunkDir, `${fileHash}-${i}`)await fs.appendFile(destFile, await fs.readFile(chunkPath))await fs.unlink(chunkPath)}await fs.rmdir(chunkDir)res.json({ code: 0 })
})// 查询上传进度
app.get('/progress', async (req, res) => {const { hash } = req.queryconst chunkDir = path.resolve(UPLOAD_DIR, hash.split('-')[0])if (!await fs.pathExists(chunkDir)) {return res.json({ uploaded: [] })}const uploaded = (await fs.readdir(chunkDir)).map(name => parseInt(name.split('-').pop()))res.json({ uploaded })
})
四、关键实现步骤
分块生成:前端使用Blob.slice
进行文件切割
唯一标识:使用"文件名+哈希值
"生成文件唯一标识
断点记录:
服务端保存每个文件的分块目录
使用Set结构
记录已上传分块索引
恢复机制:
上传前先查询服务端上传进度
跳过已上传成功的分块
合并验证:
按索引顺序合并保证文件正确性
合并完成后清理临时分块
五、注意事项
哈希校验:对每个分块计算MD5
进行完整性验证
并发控制:前端使用Promise.all
实现并行上传
错误重试:为每个分块添加重试机制
秒传功能:通过文件哈希值检测服务端已存在文件
分块大小自适应:根据网络状况动态调整分块尺寸
该方案,支持TB级
文件上传,通过分块策略
和断点记录机制
可显著提升大文件传输的可靠性。
实际部署时建议结合对象存储服务实现,可进一步降低服务器存储压力。