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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 19:40:37