JavaScript实现大文件分片上传:代码与Node.js后端合并示例

Luna
Luna
发布时间:2026-10-16| 3人浏览过
原创

在处理视频等大体积文件时,直接上传易遭遇服务端限制、请求超时或断点重传导致前功尽弃。通过 JavaScript 结合 SparkMD5 计算文件唯一标识,将大文件切割为多个切片并发上传,并在前端增加进度条与暂停控制,最后由 Node.js 后端接收并合并文件,可显著提升传输稳定性与用户体验。

实现原理与目标

大文件上传的核心逻辑分为三部分:

  1. 识别文件:利用 SparkMD5 对文件内容+文件名进行加密,生成唯一的 hash 值,用于区分文件及作为切片存储目录名。
  2. 切分文件:File 对象继承自 Blob,利用其 slice 方法按指定大小(如 1MB)截取片段。
  3. 合并还原:所有切片上传至服务器后,调用合并接口,将临时文件按顺序读取并追加写入目标文件。

准备条件

  • 前端资源:需引入 axios 发送请求,以及 spark-md5 库处理 MD5 算法。
  • 依赖环境:后端使用 Node.js,配合 koa 框架、koamulter 处理上传、fs-extra 操作文件系统。
  • 基础设置:确定每个切片的大小 chunkSize,建议范围 1MB-5MB,过大会占用带宽时间过长,过小会增加请求次数。

前端实现步骤

第1步:构建 HTML 页面结构

包含文件选择器、提交/暂停按钮以及自定义样式的进度条输入框。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>文件上传</title>
  <!-- 引入必要库 -->
  <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
  <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.js"></script>
</head>
<body>
  <h1>大文件分片上传测试</h1>
  <input id="file" type="file" name="avatar" />
  <button id="submitBtn">提交</button>
  <button id="pauseBtn">暂停</button>
  <div class="precent">
    <input type="range" value="0" />
    <span id="precentVal">0%</span>
  </div>
</body>
</html>

第2步:计算文件 MD5 Hash

异步读取文件流并计算哈希。为避免内存溢出,采用分段读取方式计算 ArrayBuffer 的 MD5,最后再拼接文件名再次计算以获得文件全局唯一性。

const chunkSize = 1 * 1024 * 1024; // 1MB
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;

const hashFile = (file) => {
  return new Promise((resolve, reject) => {
    const chunks = Math.ceil(file.size / chunkSize);
    let currentChunk = 0;
    const spark = new SparkMD5.ArrayBuffer();
    const fileReader = new FileReader();

    function loadNext() {
      const start = currentChunk * chunkSize;
      const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
      fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
    }

    fileReader.onload = e => {
      spark.append(e.target.result);
      currentChunk += 1;
      if (currentChunk < chunks) {
        loadNext();
      } else {
        // 拼接文件名防止同名不同文件被覆盖
        const sparkMd5 = new SparkMD5();
        sparkMd5.append(spark.end());
        sparkMd5.append(file.name);
        resolve(sparkMd5.end());
      }
    };
    fileReader.onerror = () => console.warn('文件读取失败');
    loadNext();
  });
};

第3步:切片上传、进度控制与暂停

点击提交后,根据总片数循环发起 POST 请求。上传过程中实时计算百分比更新 UI,并支持通过标记变量 pauseStatus 中断后续上传。

submitBtn.on('click', async () => {
  var pauseStatus = false;
  var nowUploadNums = 0;
  const fileDom = $('#file')[0];
  const file = fileDom.files[0];
  if (!file) { alert('请选文件'); return; }

  const hash = await hashFile(file); 
  const blockCount = Math.ceil(file.size / chunkSize);
  const axiosPromiseArray = [];

  const uploadFile = (offset = 0) => {
    const start = offset * chunkSize;
    const end = Math.min(file.size, start + chunkSize);
    const form = new FormData();
    form.append('file', blobSlice.call(file, start, end));
    form.append('index', offset);
    form.append('hash', hash);

    const onProgress = (e) => {
      nowUploadNums++;
      const pct = ((nowUploadNums / blockCount) * 100).toFixed(2);
      updateProgressBar(pct);
      
      if (nowUploadNums < blockCount) {
        uploadFile(nowUploadNums);
      } else {
        // 全部分片上传完毕,触发合并
        axios.all(axiosPromiseArray).then(() => {
          mergeChunks(hash, file.name, blockCount);
        });
      }
    };

    if (!pauseStatus) {
      const req = axios.post('/file/upload', form, { onUploadProgress: onProgress });
      axiosPromiseArray.push(req);
    }
  };

  // 暂停逻辑
  pauseBtn.on('click', (e) => {
    pauseStatus = !pauseStatus;
    e.currentTarget.value = pauseStatus ? '继续' : '暂停';
    if (!pauseStatus) uploadFile(nowUploadNums);
  });

  uploadFile();
});

后端实现步骤

第4步:配置 Koa 接收分片

使用 koa-multer 拦截文件上传,并根据 MD5 创建临时文件夹 temp/{hash}/ 保存切片。切片文件名格式为 {hash}-{index}。

const Router = require('koa-router');
const multer = require('koa-multer');
const fs = require('fs-extra');
const path = require('path');
const router = new Router();
const uploadPath = path.join(__dirname, 'upload');
const chunkUploadPath = path.join(uploadPath, 'temp');
const upload = multer({ dest: chunkUploadPath });

router.post('/file/upload', upload.single('file'), async (ctx) => {
  const { index, hash } = ctx.req.body;
  const dirPath = path.join(chunkUploadPath, hash);
  
  // 确保目录存在
  await fs.ensureDir(dirPath);
  
  // 重命名文件到目标路径
  const finalName = `${hash}-${index}`;
  await fs.move(ctx.req.file.path, path.join(dirPath, finalName));
  
  ctx.status = 200;
  ctx.body = 'Success';
});

第5步:合并切片文件

当所有切片上传完毕后调用合并接口。遍历该哈希目录下所有切片,按索引顺序 0 到 N 读取并追加到最终文件中,随后清理临时文件。

router.post('/file/merge_chunks', async (ctx) => {
  const { name, total, hash } = ctx.request.body;
  const chunkDir = path.join(chunkUploadPath, hash);
  const destFile = path.join(uploadPath, name);

  // 验证切片数量是否一致
  const files = await fs.readdir(chunkDir);
  if (files.length !== parseInt(total)) {
    ctx.status = 200;
    ctx.body = '切片数量不符';
    return;
  }

  const destStream = fs.createWriteStream(destFile);
  // 由于需要按顺序,建议使用循环同步或有序队列处理
  for (let i = 0; i < total; i++) {
    const chunkPath = path.join(chunkDir, `${hash}-${i}`);
    const data = await fs.readFile(chunkPath);
    destStream.write(data);
    // 可选:边写边删释放空间
    await fs.unlink(chunkPath);
  }
  
  destStream.end();
  
  // 清理空目录
  await fs.rmdir(chunkDir);
  ctx.body = 'Success';
});

注意事项与排错

在实施过程中可能遇到以下情况:

  • 内存溢出:若文件极大且浏览器环境较差,建议增大 chunkSize 或在计算 MD5 时严格控制 buffer 大小,上述示例已做分段处理。
  • 并发风暴:若同时上传几十个并行请求,可能会占满客户端出口带宽。建议在 `uploadFile` 中使用信号量或队列(Queue)限制同时运行的 axios 数量。
  • 切片丢失:如果合并时发现 total 与实际切片文件数量不匹配,应检查网络状态日志,允许用户针对特定 index 发起断点续传而非重新上传所有数据。

总结

通过 JavaScript 分片技术与后端简单的文件流操作,可有效解决大文件上传的不稳定性问题。核心在于通过 MD5 确保一致性,利用 Blob 切片保证低内存消耗,并通过异步回调机制优化用户体验。以上就是 JavaScript 实现大文件分片上传处理的详细内容,更多关于前端大文件处理与 Node.js 后端交互的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。