前端大文件上传

为什么要分片上传?

  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>
相关推荐
Aliex_git2 小时前
前端监控笔记(一)
前端·笔记·学习
Highcharts.js2 小时前
Highcharts Grid Lite:企业免费表格数据的基本工具
前端·javascript·信息可视化·免费·highcharts·表格工具
老萬頭2 小时前
【技术深水区】抖音 WEB 端逆向:从零到一拿下 a_bogus 参数
前端·爬虫·python
程序员小李白2 小时前
Vue 组件通信 极简速记版
前端·javascript·vue.js
光影少年2 小时前
跨域问题如何解决?
前端·nginx·前端框架
C澒2 小时前
微前端容器标准化 —— 公共能力篇:通用监控能力
前端·架构
念念不忘 必有回响2 小时前
Next.js 14-16 全栈开发实战:从 App Router 核心原理到 Server Actions 深度剖析
前端·nextjs
英俊潇洒美少年2 小时前
React 16 → 17 → 18 → 19 完整区别
前端·javascript·react.js
你会发光哎u2 小时前
了解React并解析JSX语法
前端·react.js·前端框架