大文件上传之断点续传实现方案与原理详解

一、实现原理

文件分块:将大文件切割为固定大小的块(如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 = file
    this.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)) continue
      
      const 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.files
  const 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.body
  const 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.query
  const 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级文件上传,通过分块策略断点记录机制可显著提升大文件传输的可靠性。
实际部署时建议结合对象存储服务实现,可进一步降低服务器存储压力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刺客-Andy

努力将爱好变成更有价值的事物

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值