一、环境准备
- 安装依赖
需安装@nestjs/platform-express
(Nest自带) 和multer
,部分场景还需@types/multer
(类型支持):npm install multer @types/multer
二、配置 Multer
Multer 用于处理 multipart/form-data
类型的请求,支持两种存储方式:
• 内存存储:适合临时处理文件(如直接上传到云存储):
import { MulterModule } from '@nestjs/platform-express';
import { memoryStorage } from 'multer';
@Module({
imports: [MulterModule.register({ storage: memoryStorage() })],
})
• 磁盘存储:保存文件到本地目录:
import { diskStorage } from 'multer';
import { join } from 'path';
MulterModule.register({
storage: diskStorage({
destination: join(__dirname, 'uploads'), // 保存路径
filename: (req, file, cb) => {
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1e9)}`;
const ext = extname(file.originalname);
cb(null, `${file.fieldname}-${uniqueName}${ext}`);
},
}),
})
代码实现
nest g res upload
upload.module.ts
import { join, extname } from 'node:path'
import { Module } from '@nestjs/common';
import { UploadService } from './upload.service';
import { UploadController } from './upload.controller';
import {diskStorage} from "multer";
import {MulterModule} from "@nestjs/platform-express";
@Module({
imports: [ MulterModule.register({
storage: diskStorage({
destination: join(__dirname, '../images'), // 保存路径
filename: (req, file, cb) => {
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1e9)}`;
const ext = extname(file.originalname);
cb(null, `${file.fieldname}-${uniqueName}${ext}`);
},
}),
})],
controllers: [UploadController],
providers: [UploadService],
})
export class UploadModule {}
三、创建文件上传控制器
通过拦截器(Interceptors)和装饰器处理不同上传场景:
-
单个文件上传
使用FileInterceptor
和@UploadedFile()
:@Post('upload') @UseInterceptors(FileInterceptor('file')) // 'file'为前端表单字段名 uploadFile(@UploadedFile() file: Express.Multer.File) { return { path: file.path }; }
-
多文件或文件数组
• 文件数组:使用FilesInterceptor
:@UseInterceptors(FilesInterceptor('files', 5)) // 最多5个文件 uploadFiles(@UploadedFiles() files: Array<Express.Multer.File>) {}
• 多个字段文件:使用
FileFieldsInterceptor
:@UseInterceptors(FileFieldsInterceptor([ { name: 'avatar', maxCount: 1 }, { name: 'background', maxCount: 1 }, ]))
-
结合 DTO 验证
若需同时传递其他表单数据(如 JSON 参数),可通过@Body()
接收并校验:@Post() @UseInterceptors(FileInterceptor('file')) uploadFile( @UploadedFile() file: Express.Multer.File, @Body() dto: FileUploadDto // 自定义DTO类 ) {}
代码实现:
upload.controller.ts
import {
Controller,
Post,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { UploadService } from './upload.service';
import { FileInterceptor } from '@nestjs/platform-express';
@Controller('upload')
export class UploadController {
constructor(private readonly uploadService: UploadService) {}
@Post('album')
@UseInterceptors(FileInterceptor('file')) // file 为前端表单字段名
uploadFile(@UploadedFile() file: Express.Multer.File) {
console.log(file);
return {
localPath: file.path,
onlinePath: 'http://localhost:3000/images/' + file.filename,
};
}
}
四、静态文件访问配置
上传后需通过 HTTP 暴露静态资源,在 main.ts
中配置:
import { NestExpressApplication } from '@nestjs/platform-express';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useStaticAssets(join(__dirname, 'uploads'), {
prefix: '/static/', // 访问前缀(如 http://localhost:3000/static/filename.jpg)
});
}
代码实现:
main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'node:path';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useStaticAssets(join(__dirname, 'images'), {
prefix: '/images',
});
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
此时,我们通过本地路径和线上路径都可以访问到。
五、自定义文件名与路径
为避免文件名冲突,可通过以下方式生成唯一标识:
• 时间戳 + 随机数:如 Date.now() + '-' + Math.random()
。
• UUID:安装 uuid
生成唯一字符串:
import { v4 as uuidv4 } from 'uuid';
filename: `${uuidv4()}${extname(file.originalname)}`
六、前端调用示例
前端通过 FormData
发送文件:
const formData = new FormData();
formData.append('file', fileInput.files[0]);
// 可选附加参数
formData.append('metadata', JSON.stringify({ userId: 123 }));
fetch('/upload', {
method: 'POST',
body: formData,
});