You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在NestJS中实现单文件分片上传?求可行示例

NestJS单文件分片上传实现方案

一、先解决你当前file为undefined的问题

  • 检查前端请求格式:必须以multipart/form-data类型发送,且表单文件字段名必须是file(与你代码中FileInterceptor('file')的参数一致)
  • 排查fileFilter和editFileName函数:确认这两个函数没有抛出异常或意外拒绝文件,比如fileFilter是否错误返回false,editFileName是否存在路径拼接错误
  • 验证目录权限:确保ENV.MNT指定的目录存在且应用有读写权限,Multer无法写入时会导致文件接收失败

二、完整单文件分片上传可运行实现

分片上传需要前后端配合完成分片接收、合并逻辑,以下是后端核心实现:

1. Controller核心代码

import { Controller, Post, UploadedFile, UseInterceptors, Body } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import * as path from 'path';
import * as fs from 'fs';
import { ENV } from './env.config'; // 替换为你的环境配置文件

// 生成临时分片文件名,关联文件唯一标识和分片索引
const generateChunkFileName = (req, file, cb) => {
  const { fileId, chunkIndex } = req.body;
  cb(null, `${fileId}_chunk_${chunkIndex}`);
};

// 自定义文件过滤器,可根据需求限制文件类型
const fileFilter = (req, file, cb) => {
  // 这里允许所有文件类型,如需限制可添加判断逻辑
  cb(null, true);
};

@Controller('upload')
export class UploadController {
  // 接收单个分片
  @Post('chunk')
  @UseInterceptors(
    FileInterceptor('file', {
      storage: diskStorage({
        destination: path.join(ENV.MNT, 'temp-chunks'), // 临时存储分片的目录
        filename: generateChunkFileName,
      }),
      fileFilter,
    }),
  )
  async uploadChunk(
    @UploadedFile() file: Express.Multer.File,
    @Body() body: { fileId: string; chunkIndex: number; totalChunks: number; fileName: string },
  ) {
    if (!file) {
      return { success: false, msg: '分片上传失败' };
    }

    // 若当前是最后一个分片,触发合并逻辑
    if (body.chunkIndex === body.totalChunks - 1) {
      await this.mergeChunks(body.fileId, body.totalChunks, body.fileName);
    }

    return { success: true, chunkIndex: body.chunkIndex };
  }

  // 合并所有分片为完整文件
  private async mergeChunks(fileId: string, totalChunks: number, fileName: string) {
    const chunkDir = path.join(ENV.MNT, 'temp-chunks');
    const finalFilePath = path.join(ENV.MNT, 'uploads', fileName);

    // 创建最终文件存储目录(不存在则递归创建)
    if (!fs.existsSync(path.dirname(finalFilePath))) {
      fs.mkdirSync(path.dirname(finalFilePath), { recursive: true });
    }

    // 创建文件写入流,避免大文件占用过多内存
    const writeStream = fs.createWriteStream(finalFilePath);

    for (let i = 0; i < totalChunks; i++) {
      const chunkPath = path.join(chunkDir, `${fileId}_chunk_${i}`);
      // 读取分片并写入最终文件
      const chunkBuffer = fs.readFileSync(chunkPath);
      writeStream.write(chunkBuffer);
      // 删除临时分片文件
      fs.unlinkSync(chunkPath);
    }

    return new Promise((resolve) => {
      writeStream.on('finish', resolve);
    });
  }
}

2. 关键实现说明

  • 前端需要将文件分割为固定大小的分片(比如10MB),每个分片请求需携带:
    • fileId:文件唯一标识(如UUID,用于关联同一文件的所有分片)
    • chunkIndex:当前分片索引(从0开始)
    • totalChunks:文件总分片数
    • fileName:原始文件名
  • 后端通过临时目录存储分片,合并完成后自动删除临时文件,避免磁盘占用
  • 采用流式写入合并文件,优化大文件上传时的内存占用

3. 额外优化建议

  • 添加分片MD5校验:前端计算每个分片的MD5值,后端接收时验证,避免分片损坏
  • 断点续传:记录已上传的分片索引,前端重新上传时可跳过已完成的分片
  • 超时清理:定时清理长时间未完成合并的临时分片文件

内容的提问来源于stack exchange,提问作者La Lumière Huguenote

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 18:15:16