前端大文件上传

为什么要分片上传?

  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>
相关推荐
IT_陈寒15 分钟前
React状态更新总是不及时?你可能漏了这步批处理机制
前端·人工智能·后端
恋猫de小郭24 分钟前
AI Agent 开发究竟是啥?如何用 AI 开发 Agent ?深入浅出给你一套概念
android·前端·ai编程
前端双越老师27 分钟前
我开发 AI Agent 项目踩过的 5个坑
前端·agent·全栈
晓得迷路了1 小时前
栗子前端技术周刊第 134 期 - React Router v8、TypeScript 7 RC、React Native 0.86...
前端·javascript·react.js
Carson带你学Android1 小时前
Android 17 正式发布:AI 终于成了系统能力
android·前端·ai编程
Mike_jia1 小时前
ZbxTable:Zabbix开源报表神器,从运维数据到决策洞察的最后一公里
前端
LinXunFeng10 小时前
Obsidian - 使用 Share Note 分享笔记并自部署
前端·笔记·github
乘风gg14 小时前
为什么AI 时代来临,大部分人吃不到红利
前端·ai编程·claude
恋猫de小郭14 小时前
Android 限制侧载新进展,谷歌联合国内厂商推验证计划
android·前端·flutter
IT_陈寒14 小时前
Redis内存爆了,原来我漏掉了这个致命配置
前端·人工智能·后端