前端大文件上传

为什么要分片上传?

  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>
相关推荐
火柴就是我41 分钟前
Android 打包报错 25.0.3
android·前端
BD_Marathon41 分钟前
消息对象中字段的说明
java·前端·python
八荒启·交互动画43 分钟前
Web特效025—用 Canvas 2D 做Web特效的定义与边界:这支画笔能做到哪一步
前端·webgl·网页特效·八荒启-交互动画·八荒启
Ai-_Man1 小时前
您您这可以把Dola的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。用AI导出鸭,答案是可以的
开发语言·前端·人工智能·小程序
excel1 小时前
Nuxt 中使用 useHead 优化 SEO 与 GEO
前端
IT_陈寒2 小时前
SpringBoot启动慢得像蜗牛?原来是这个配置在捣鬼
前端·人工智能·后端
计算机魔术师2 小时前
Muse Spark跑赢Gemini,但真正的底牌是这种设计
前端
PYB32 小时前
【Web·JS·基础】函数的使用和展运算符...objs
前端·javascript
指针向南3 小时前
Chrome读不了HEIC怎么办:原生解码和WASM两条路
前端·图像处理·人工智能·chrome·计算机视觉·wasm
默_笙3 小时前
🚥 给 RAG 装一台心电监护仪:LangSmith 全链路观测(上)
前端·javascript