Skip to content

大文件分片上传与断点续传实现指南


目录

  1. 核心原理
  2. 前端实现
  3. 后端实现
  4. 时序图
  5. 优化与注意事项

1. 核心原理

1.1 分片上传

  • 将大文件切割为多个小分片(如 5MB/片)
  • 并行上传分片,提升传输效率

1.2 断点续传

  • 通过文件哈希值唯一标识文件
  • 记录已上传分片索引
  • 网络中断后跳过已传分片

1.3 自动合并

  • 服务器检测分片完整性
  • 按序号合并分片生成完整文件

2. 前端实现

2.1 文件分片与哈希计算

javascript
// 文件分片
function splitFile(file, chunkSize = 5 * 1024 * 1024) {
    const chunks = [];
    let start = 0;
    while (start < file.size) {
        chunks.push(file.slice(start, start + chunkSize));
        start += chunkSize;
    }
    return chunks;
}

// 计算文件哈希(使用 SparkMD5)
async function calculateFileHash(file) {
    // ...(同前文实现)
}

2.2 上传控制(带并发限制)

javascript
class Uploader {
    constructor() {
        this.MAX_CONCURRENT = 3; // 最大并发数
        this.retryLimit = 3; // 分片重试次数
    }

    async upload(file) {
        const fileHash = await calculateFileHash(file);
        const { uploadedChunks } = await checkProgress(fileHash);

        const chunks = splitFile(file);
        const queue = chunks
            .map((chunk, index) => ({ chunk, index }))
            .filter(({ index }) => !uploadedChunks.includes(index));

        // 并发控制
        while (queue.length > 0) {
            const tasks = queue.splice(0, this.MAX_CONCURRENT);
            await Promise.all(tasks.map((task) => this.uploadChunk(task)));
        }
    }

    async uploadChunk({ chunk, index }) {
        for (let attempt = 0; attempt < this.retryLimit; attempt++) {
            try {
                const formData = new FormData();
                formData.append('chunk', chunk);
                formData.append('hash', fileHash);
                formData.append('index', index);

                await fetch('/api/upload', { method: 'POST', body: formData });
                saveProgress(fileHash, index);
                return;
            } catch (error) {
                if (attempt === this.retryLimit - 1) throw error;
            }
        }
    }
}

3. 后端实现

3.1 接口设计

端点方法功能
/api/uploadPOST上传分片
/api/progressGET查询上传进度
/api/merge-checkGET检查合并状态(可选)

3.2 核心逻辑

javascript
// 分片存储结构
uploads/
├─ {fileHash}/
│  ├─ 0
│  ├─ 1
│  └─ ... (分片文件)

// 自动合并实现
app.post('/api/upload', (req, res) => {
  const { hash, index, total } = req.body;

  // 存储分片
  saveChunk(hash, index, req.file);

  // 检查合并条件
  if (isUploadComplete(hash, total)) {
    mergeChunks(hash); // 异步执行合并
  }

  res.sendStatus(200);
});

// 合并操作(异步)
async function mergeChunks(hash) {
  const chunkDir = path.join(UPLOAD_DIR, hash);
  const chunks = fs.readdirSync(chunkDir).sort((a, b) => a - b);

  await new Promise((resolve) => {
    const writeStream = fs.createWriteStream(`${UPLOAD_DIR}/${hash}.file`);
    chunks.forEach(chunk => {
      writeStream.write(fs.readFileSync(path.join(chunkDir, chunk)));
      fs.unlinkSync(path.join(chunkDir, chunk)); // 删除分片
    });
    writeStream.end(() => {
      fs.rmdirSync(chunkDir);
      resolve();
    });
  });
}

4. 时序图


5. 优化与注意事项

5.1 性能优化

  • 分片大小调整:根据网络状况动态调整分片大小(1MB~10MB)
  • 内存管理:使用流式处理避免大文件内存溢出
  • CDN加速:分片上传使用不同CDN节点

5.2 可靠性保障

  • 分片校验:对每个分片进行MD5校验
  • 断点记录:使用IndexedDB存储本地进度
  • 过期清理:服务器定时清理未完成的上传任务

5.3 安全防护

  • 身份验证:上传接口添加JWT校验
  • 大小限制:限制单文件最大尺寸(如10GB)
  • 防重放攻击:分片上传添加时间戳签名

5.4 扩展功能

  • 进度恢复:页面刷新后自动恢复上传
  • 秒传功能:服务器存在相同哈希文件时直接返回
  • 分片压缩:在客户端对分片进行gzip压缩

通过以上方案,可实现支持百万级大文件上传、网络中断自动恢复、服务器负载均衡的高可靠性文件上传系统。实际部署时建议结合对象存储(如AWS S3)实现分片直传,进一步降低服务器压力。