如何在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
相关产品推荐
相关产品推荐

