Skip to content

Node.js 大文件上传(分片 + 断点续传)

大文件上传通过分片上传 + 断点续传提高稳定性和速度。

核心概念

  • 分片上传:将大文件切分成小块(如 4MB),并行上传,失败只重传该片
  • 断点续传:记录已上传分片,中断后从断点继续,无需重新上传整个文件

前端实现

js
// 切片函数(每片 4MB)
const chunkFun = (file, size = 1024 * 1024 * 4) => {
  const chunks = []
  for (let i = 0; i < file.size; i += size) {
    chunks.push(file.slice(i, i + size))
  }
  return chunks
}

// 并行上传 + 发起合并请求
const uploadFile = (chunks) => {
  const list = chunks.map((chunk, i) => {
    const fd = new FormData()
    fd.append('index', i)
    fd.append('total', chunks.length)
    fd.append('fileName', 'video')
    fd.append('file', chunk)
    return fetch('http://127.0.0.1:3000/up', { method: 'POST', body: fd })
  })

  Promise.all(list).then(() => {
    fetch('http://127.0.0.1:3000/merge', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ fileName: 'video' })
    })
  })
}

注意:file 必须放在 FormData 最后一个,因为 multer 按顺序读取字段。

后端实现

bash
npm install express multer cors
js
import express from 'express'
import multer from 'multer'
import cors from 'cors'
import fs from 'node:fs'
import path from 'node:path'

// 存储配置:文件名为 index-文件名 格式
const storage = multer.diskStorage({
  destination: (req, file, cb) => cb(null, 'uploads/'),
  filename: (req, file, cb) => cb(null, `${req.body.index}-${req.body.fileName}`)
})
const upload = multer({ storage })
const app = express()

app.use(cors())
app.use(express.json())

// 接收分片
app.post('/up', upload.single('file'), (req, res) => res.send('ok'))

// 合并分片
app.post('/merge', async (req, res) => {
  const uploadPath = './uploads'
  let files = fs.readdirSync(path.join(process.cwd(), uploadPath))

  // 按 index 排序(避免乱序)
  files = files.sort((a, b) => a.split('-')[0] - b.split('-')[0])

  const writePath = path.join(process.cwd(), 'video', `${req.body.fileName}.mp4`)

  // 逐个追加合并
  files.forEach((item) => {
    fs.appendFileSync(writePath, fs.readFileSync(path.join(uploadPath, item)))
    fs.unlinkSync(path.join(uploadPath, item))  // 合并后删除分片
  })

  res.send('合并完成')
})

app.listen(3000)

合并逻辑

uploads/
  0-video  1-video  2-video  3-video
      ↓ sort 排序(避免顺序错乱)
      ↓ forEach → appendFileSync 追加
      ↓ unlinkSync 删除已合并的分片
video/video.mp4  ← 完整文件