前端大文件上传

为什么要分片上传?

  1. 大文件直接上传容易超时、中断、服务器压力大
  2. 分片失败可以重传单个分片,不用重传整个文件
  3. 支持并发上传,速度更快

前端负责

  1. 哈希(给文件生成唯一 ID;MD5:是哈希里最常用、最简单的一种算法)
  2. 分片(把大文件切成小碎片)
  3. 分片上传 / 断点续传(查询是否上传,服务器如有,可直接过滤)
  4. 通知后端合并

后端负责

1. 校验接口(秒传 / 断点续传用)

前端传:文件hash

文件不存在 → 返回已上传的分片序号
2. 上传分片接口

前端传:分片文件 + 文件hash + 分片序号

保存这个小分片到服务器
3. 合并接口

前端传:文件hash + 文件名

把所有分片按顺序合并成完整文件

完整代码

html 复制代码
<input type="file" id="file">
<button onclick="upload()">上传</button>

<script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.2/spark-md5.min.js"></script>
<script>
const CHUNK_SIZE = 2 * 1024 * 1024;

// 计算hash
function getHash(file) {
  return new Promise((resolve, reject) => {
    const spark = new SparkMD5.ArrayBuffer();
    const reader = new FileReader();
    reader.readAsArrayBuffer(file);
    reader.onload = e => { spark.append(e.target.result); resolve(spark.end()) };
    reader.onerror = reject;
  });
}

// 上传单个分片(带失败捕获)
async function uploadChunk(chunk, hash, index) {
  const fd = new FormData();
  fd.append("chunk", chunk);
  fd.append("hash", hash);
  fd.append("index", index);

  const res = await fetch("http://localhost:3000/upload-chunk", {
    method: "POST",
    body: fd
  });

  if (!res.ok) throw new Error(`分片 ${index} 上传失败`);
}

// 主上传(完整版)
async function upload() {
  try {
    const file = document.getElementById("file").files[0];
    if (!file) return alert("请选择文件");

    const hash = await getHash(file);

    // 切片
    const chunks = [];
    for (let i = 0; i < file.size; i += CHUNK_SIZE) {
      chunks.push(file.slice(i, i + CHUNK_SIZE));
    }

    // 1. 校验已上传分片(断点续传核心)
    const verifyRes = await fetch("http://localhost:3000/verify", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ hash })
    });
    const { uploaded } = await verifyRes.json();

    // 2. 只传缺失的分片(失败自动抛错)
    for (let i = 0; i < chunks.length; i++) {
      if (uploaded.includes(i)) {
        console.log("跳过分片:", i);
        continue;
      }
      await uploadChunk(chunks[i], hash, i);
    }

    // 3. 全部传完 → 合并
    const mergeRes = await fetch("http://localhost:3000/merge", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ hash, filename: file.name })
    });

    if (!mergeRes.ok) throw new Error("合并失败");
    alert("上传成功!");
  } catch (err) {
    // ✅ 所有错误统一捕获!
    console.error(err);
    alert("上传失败:" + err.message);
  }
}
</script>
相关推荐
打妖妖灵滴哪吒6 小时前
前端开发/UI设计师-APP中如何进行可发现性设计
前端·ui
Kinghiee6 小时前
从使用场景切入前端常见设计模式
前端·设计模式
郭老二16 小时前
【前端】vue3:编译步骤
前端
不在逃避q17 小时前
Trae/Vs Code/Cursor命令行无法跑npm命令
前端·arcgis·npm
夏殇之殁17 小时前
包中创建自定义列表项。 . 使用自定义列表项进行数据绑定 . 将天气预报数据保存到本地内存表,通过LiveBindings进行显示。 ...
服务器·前端·javascript
The Chosen One98517 小时前
高进度算法模板速记(待完善)
java·前端·算法
陈随易17 小时前
MoonBit抓包模块pcap,查看电脑的每一次联网通信
前端·后端·程序员
新中地GIS开发老师18 小时前
WebGIS开发学生作品|低空航天管理与航线规划系统
前端·javascript·webgis·三维gis开发
用户0595401744618 小时前
大模型对话记忆持久化踩坑实录:用 Playwright 自动化测了 300 次,终于揪出会话丢失的真凶
前端·css
丙氨酸長鏈18 小时前
Web前端入门第 问:JavaScript 一个简单的 IndexedDB 数据库入门示例
前端·javascript·数据库