NestJS Fastify处理Multipart/FormData及S3文件上传优化问询
NestJS Fastify 处理Multipart/FormData与S3文件上传的问题及解决方案
问题说明
- 注册
fastify-multipart时必须设置{ attachFieldsToBody: true },否则接口中的createAuthorDto会返回undefined - 设置该参数后,
createAuthorDto接收的字段变为包含fieldname、value等元数据的对象,而非预期的原始类型值 - 需要同时处理包含字符串、布尔值、数字类型的常规DTO,以及实现S3文件上传的规范方案
现有代码
main.ts 配置
async function bootstrap() { const app = await NestFactory.create<NestFastifyApplication>( AppModule, new FastifyAdapter(), { bufferLogs: true, bodyParser: false }, ); const configService = app.get(ConfigService); const PORT = configService.get('port'); app.register(fmp, { attachFieldsToBody: true }); await app.listen(PORT); logger.log(`Application is running on: ${await app.getUrl()}`); }
author.controller.ts 接口
@Post('/create') async createAuthor( @AuthUser() user: any, @Body() createAuthorDto: CreateAuthorDto, ): Promise<BaseResponseEntity> { return await this.authorsService.createAuthor( user, createAuthorDto, ); }
CreateAuthorDto 定义
export class CreateAuthorDto { @IsString() first_name: string; @IsString() last_name: string; @IsString() email_id: string; @IsString() passwd: string; passdis?: string; @IsBoolean() delete_allowed: boolean; @IsBoolean() posted_by_admin: boolean; @IsNumber() status: number; }
解决方案
1. 调整Fastify Multipart配置
去掉attachFieldsToBody参数,改用onFile钩子手动处理文件,并将文件信息挂载到请求对象上,避免字段嵌套问题:
async function bootstrap() { const app = await NestFactory.create<NestFastifyApplication>( AppModule, new FastifyAdapter(), { bufferLogs: true, bodyParser: false }, ); const configService = app.get(ConfigService); const PORT = configService.get('port'); app.register(fmp, { onFile: (fieldName, file) => { // 将文件对象挂载到request,供控制器后续获取 (file.request as any).uploadedFiles = (file.request as any).uploadedFiles || []; (file.request as any).uploadedFiles.push({ fieldName, filename: file.filename, mimetype: file.mimetype, stream: file }); } }); await app.listen(PORT); logger.log(`Application is running on: ${await app.getUrl()}`); }
2. 自定义管道提取原始字段值
创建ExtractMultipartFieldsPipe,自动提取字段的value属性并完成类型转换,匹配DTO的类型要求:
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'; @Injectable() export class ExtractMultipartFieldsPipe implements PipeTransform { transform(value: any, metadata: ArgumentMetadata) { if (typeof value !== 'object' || value === null) return value; const extracted = {}; // 提取每个字段的value值 for (const key in value) { extracted[key] = value[key]?.value ?? value[key]; } // 类型转换:布尔值、数字字符串转对应类型 for (const key in extracted) { const val = extracted[key]; if (typeof val === 'string') { if (val === 'true' || val === 'false') { extracted[key] = val === 'true'; } else if (!isNaN(Number(val))) { extracted[key] = Number(val); } } } return extracted; } }
3. 控制器中处理DTO与S3上传
在控制器中使用自定义管道解析DTO,同时从请求对象获取文件并流式上传到S3:
import { ExtractMultipartFieldsPipe } from './extract-multipart-fields.pipe'; import { S3 } from 'aws-sdk'; import { Req } from '@nestjs/common'; @Post('/create') async createAuthor( @AuthUser() user: any, @Body(new ExtractMultipartFieldsPipe()) createAuthorDto: CreateAuthorDto, @Req() req: any, ): Promise<BaseResponseEntity> { // 初始化S3客户端 const s3 = new S3({ accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, region: process.env.AWS_REGION }); // 处理上传文件 if (req.uploadedFiles?.length) { for (const file of req.uploadedFiles) { const uploadParams = { Bucket: process.env.AWS_S3_BUCKET, Key: `authors/${Date.now()}-${file.filename}`, // 自定义文件路径 Body: file.stream, ContentType: file.mimetype }; const uploadResult = await s3.upload(uploadParams).promise(); // 可选:将文件URL存入DTO或数据库 // createAuthorDto.profileImageUrl = uploadResult.Location; } } return await this.authorsService.createAuthor(user, createAuthorDto); }
方案优势
- 避免
attachFieldsToBody导致的字段嵌套问题,保持DTO字段的原始类型 - 流式上传S3,无需缓存整个文件到内存,适合大文件场景
- 自定义管道可复用,适配所有需要处理Multipart字段的接口
内容的提问来源于stack exchange,提问作者Deep Mandal
相关产品推荐
相关产品推荐

