⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议
Skip to content

12.5.1 文件拆开传——分片上传原理:大文件切分与并行传输

一句话破题

分片上传就是把大文件切成固定大小的小块,分别上传后在服务端拼接——这样即使某块失败也只需重传那一块。

本质还原

前端分片实现

typescript
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB

interface Chunk {
  index: number;
  blob: Blob;
  start: number;
  end: number;
}

function createChunks(file: File): Chunk[] {
  const chunks: Chunk[] = [];
  let start = 0;
  let index = 0;

  while (start < file.size) {
    const end = Math.min(start + CHUNK_SIZE, file.size);
    chunks.push({
      index,
      blob: file.slice(start, end),
      start,
      end,
    });
    start = end;
    index++;
  }

  return chunks;
}

// 使用示例
const file = document.getElementById('file').files[0];
const chunks = createChunks(file);
console.log(`文件大小: ${file.size}, 分片数: ${chunks.length}`);

并行上传

typescript
async function uploadChunk(
  chunk: Chunk,
  fileId: string,
  onProgress?: (percent: number) => void
): Promise<void> {
  const formData = new FormData();
  formData.append('chunk', chunk.blob);
  formData.append('index', chunk.index.toString());
  formData.append('fileId', fileId);

  await fetch('/api/upload/chunk', {
    method: 'POST',
    body: formData,
  });
}

async function uploadFile(file: File) {
  // 1. 初始化上传,获取 fileId
  const { fileId } = await fetch('/api/upload/init', {
    method: 'POST',
    body: JSON.stringify({ fileName: file.name, fileSize: file.size }),
    headers: { 'Content-Type': 'application/json' },
  }).then((r) => r.json());

  // 2. 创建分片
  const chunks = createChunks(file);

  // 3. 并行上传(限制并发数)
  const concurrency = 3;
  for (let i = 0; i < chunks.length; i += concurrency) {
    const batch = chunks.slice(i, i + concurrency);
    await Promise.all(batch.map((chunk) => uploadChunk(chunk, fileId)));
    console.log(`已上传 ${Math.min(i + concurrency, chunks.length)} / ${chunks.length}`);
  }

  // 4. 通知服务端合并
  await fetch('/api/upload/merge', {
    method: 'POST',
    body: JSON.stringify({ fileId, totalChunks: chunks.length }),
    headers: { 'Content-Type': 'application/json' },
  });
}

服务端处理

typescript
// app/api/upload/chunk/route.ts
import { writeFile, mkdir } from 'fs/promises';
import { join } from 'path';

export async function POST(req: Request) {
  const formData = await req.formData();
  const chunk = formData.get('chunk') as File;
  const index = formData.get('index') as string;
  const fileId = formData.get('fileId') as string;

  const chunkDir = join(process.cwd(), 'uploads', fileId);
  await mkdir(chunkDir, { recursive: true });

  const buffer = Buffer.from(await chunk.arrayBuffer());
  await writeFile(join(chunkDir, index), buffer);

  return Response.json({ success: true });
}

// app/api/upload/merge/route.ts
import { readdir, readFile, writeFile, rm } from 'fs/promises';
import { join } from 'path';

export async function POST(req: Request) {
  const { fileId, totalChunks } = await req.json();
  const chunkDir = join(process.cwd(), 'uploads', fileId);

  // 按顺序读取并合并分片
  const chunks: Buffer[] = [];
  for (let i = 0; i < totalChunks; i++) {
    const chunkPath = join(chunkDir, i.toString());
    chunks.push(await readFile(chunkPath));
  }

  const mergedBuffer = Buffer.concat(chunks);
  await writeFile(join(process.cwd(), 'uploads', `${fileId}.file`), mergedBuffer);

  // 清理临时分片
  await rm(chunkDir, { recursive: true });

  return Response.json({ success: true });
}

AI 协作指南

  • 核心意图:让 AI 帮你实现分片上传功能。
  • 需求定义公式"请帮我实现一个支持大文件分片上传的组件,包括前端分片逻辑和后端接收合并,使用 Next.js App Router。"
  • 关键术语分片 (chunk)并行上传合并 (merge)Blob.slice()

避坑指南

  • 分片大小选择:太小会增加请求数,太大可能超时。通常 1-10MB。
  • 并发控制:不要一次性上传所有分片,会导致浏览器卡顿。
  • 临时文件清理:记得清理上传失败或超时的临时分片。