文件上传 File upload

导读

为了处理文件上传,Nest 提供了一个基于 Express 中间件包 multer 的内置模块。Multer 处理以 multipart/form-data 格式发布的数据,该格式主要用于通过 HTTP POST 请求上传文件。此模块完全可配置,您可以根据应用程序要求调整其行为。

警告

Multer 无法处理不支持的多部分格式(multipart/form-data)的数据。另请注意,此包与 FastifyAdapter 不兼容。

为了更好的类型安全性,让我们安装 Multer 类型包:

shell
$ npm i -D @types/multer

安装此包后,我们现在可以使用 Express.Multer.File 类型(您可以按如下方式导入此类型:import {{ '{' }} Express {{ '}' }} from 'express')。

基本示例

要上传单个文件,只需将 FileInterceptor() 拦截器绑定到路由处理程序,并使用 @UploadedFile() 装饰器从 request 中提取 file

ts
TS
ts
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
uploadFile(@UploadedFile() file: Express.Multer.File) {
  console.log(file);
}
提示

FileInterceptor() 装饰器从 @nestjs/platform-express 包导出。@UploadedFile() 装饰器从 @nestjs/common 导出。

FileInterceptor() 装饰器接受两个参数:

  • fieldName:提供包含文件的 HTML 表单字段名称的字符串
  • options:类型为 MulterOptions 的可选对象。这是 multer 构造函数使用的相同对象(更多详细信息请参见 此处)。
警告

FileInterceptor() 可能与 Google Firebase 等第三方云提供商不兼容。

文件验证

验证传入的文件元数据(如文件大小或文件 mime 类型)通常很有用。为此,您可以创建自己的 管道 并将其绑定到使用 UploadedFile 装饰器注释的参数。下面的示例演示了如何实现基本的文件大小验证器管道:

ts
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common'

@Injectable()
export class FileSizeValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    // `value` 是包含文件属性和元数据的对象
    const oneKb = 1000
    return value.size < oneKb
  }
}

Nest 提供了一个内置管道来处理常见用例并促进/标准化新用例的添加。此管道称为ParseFilePipe,您可以按如下方式使用它:

ts
@Post('file')
uploadFileAndPassValidation(
  @Body() body: SampleDto,
  @UploadedFile(
    new ParseFilePipe({
      validators: [
        // ... Set of file validator instances here
      ]
    })
  )
  file: Express.Multer.File,
) {
  return {
    body,
    file: file.buffer.toString(),
  };
}

如您所见,需要指定将由ParseFilePipe执行的文件验证器数组。我们将讨论验证器的接口,但值得一提的是,此管道还有两个额外的可选选项:

errorHttpStatusCode如果任何验证器失败,则抛出的 HTTP 状态代码。默认值为 400(错误请求)
exceptionFactory接收错误消息并返回错误的工厂。

现在,回到FileValidator接口。要将验证器与此管道集成,您必须使用内置实现或提供自己的自定义FileValidator。请参阅以下示例:

ts
export abstract class FileValidator<TValidationOptions = Record<string, any>> {
  constructor(protected readonly validationOptions: TValidationOptions) {}

  /**
   * 根据构造函数中传递的选项,指示此文件是否应被视为有效。
   * @param file the file from the request object
   */
  abstract isValid(file?: any): boolean | Promise<boolean>

  /**
   * 如果验证失败,则生成错误消息。
   * @param file the file from the request object
   */
  abstract buildErrorMessage(file: any): string
}
提示

FileValidator 接口通过其 isValid 函数支持异步验证。要利用类型安全性,如果您使用 express(默认)作为驱动程序,您还可以将 file 参数键入为 Express.Multer.File

FileValidator 是一个常规类,可以访问文件对象并根据客户端提供的选项对其进行验证。Nest 有两个内置的 FileValidator 实现,您可以在项目中使用它们:

  • MaxFileSizeValidator - 检查给定文件的大小是否小于提供的值(以 bytes 为单位)
  • FileTypeValidator - 检查给定文件的 mime 类型是否与给定值匹配。
警告

要验证文件类型,FileTypeValidator 类使用 multer 检测到的类型。默认情况下,multer 从用户设备上的文件扩展名派生文件类型。但是,它不会检查实际文件内容。由于文件可以重命名为任意扩展名,如果您的应用需要更安全的解决方案,请考虑使用自定义实现(例如检查文件的 magic number)。

要了解如何将它们与前面提到的 FileParsePipe 结合使用,我们将使用最后一个示例的修改片段:

ts
@UploadedFile(
  new ParseFilePipe({
    validators: [
      new MaxFileSizeValidator({ maxSize: 1000 }),
      new FileTypeValidator({ fileType: 'image/jpeg' }),
    ],
  }),
)
file: Express.Multer.File,
提示

如果验证器的数量大幅增加,或者它们的选项使文件变得杂乱,您可以在单独的文件中定义此数组,并将其作为命名常量(如fileValidators)导入此处。

最后,您可以使用特殊的ParseFilePipeBuilder类,该类允许您编写和构造验证器。通过如下所示使用它,您可以避免手动实例化每个验证器,而只需直接传递它们的选项:

ts
@UploadedFile(
  new ParseFilePipeBuilder()
    .addFileTypeValidator({
      fileType: 'jpeg',
    })
    .addMaxSizeValidator({
      maxSize: 1000
    })
    .build({
      errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY
    }),
)
file: Express.Multer.File,
提示

默认情况下,文件存在是必需的,但您可以通过在 build 函数选项(与 errorHttpStatusCode 处于同一级别)中添加 fileIsRequired: false 参数使其成为可选项。

文件数组

要上传文件数组(用单个字段名称标识),请使用 FilesInterceptor() 装饰器(请注意装饰器名称中的复数 Files)。此装饰器有三个参数:

  • fieldName:如上所述
  • maxCount:可选数字,定义要接受的最大文件数
  • options:可选 MulterOptions 对象,如上所述

使用 FilesInterceptor() 时,使用 @UploadedFiles() 装饰器从 request 中提取文件。

ts
TS
ts
@Post('upload')
@UseInterceptors(FilesInterceptor('files'))
uploadFile(@UploadedFiles() files: Array<Express.Multer.File>) {
  console.log(files);
}
提示

FilesInterceptor() 装饰器从 @nestjs/platform-express 包导出。@UploadedFiles() 装饰器从 @nestjs/common 导出。

多个文件

要上传多个文件(所有文件都具有不同的字段名称键),请使用 FileFieldsInterceptor() 装饰器。此装饰器有两个参数:

  • uploadedFields:一个对象数组,其中每个对象指定一个必需的 name 属性,该属性具有一个指定字段名称的字符串值(如上所述),以及一个可选的 maxCount 属性(如上所述)
  • options:可选的 MulterOptions 对象(如上所述)

使用 FileFieldsInterceptor() 时,使用 @UploadedFiles() 装饰器从 request 中提取文件。

ts
TS
ts
@Post('upload')
@UseInterceptors(FileFieldsInterceptor([
  { name: 'avatar', maxCount: 1 },
  { name: 'background', maxCount: 1 },
]))
uploadFile(@UploadedFiles() files: { avatar?: Express.Multer.File[], background?: Express.Multer.File[] }) {
  console.log(files);
}

任何文件

要上传具有任意字段名称键的所有字段,请使用 AnyFilesInterceptor() 装饰器。此装饰器可以接受如上所述的可选 options 对象。

使用 AnyFilesInterceptor() 时,使用 @UploadedFiles() 装饰器从 request 中提取文件。

ts
TS
ts
@Post('upload')
@UseInterceptors(AnyFilesInterceptor())
uploadFile(@UploadedFiles() files: Array<Express.Multer.File>) {
  console.log(files);
}

没有文件

要接受multipart/form-data但不允许上传任何文件,请使用NoFilesInterceptor。这会将多部分数据设置为请求主体上的属性。随请求发送的任何文件都将抛出BadRequestException

ts
@Post('upload')
@UseInterceptors(NoFilesInterceptor())
handleMultiPartData(@Body() body) {
  console.log(body)
}

默认选项

您可以如上所述在文件拦截器中指定 multer 选项。要设置默认选项,您可以在导入 MulterModule 时调用静态 register() 方法,并传入支持的选项。您可以使用 此处 列出的所有选项。

ts
MulterModule.register({
  dest: './upload',
})
提示

MulterModule 类从 @nestjs/platform-express 包导出。

异步配置

当您需要异步(而不是静态)设置 MulterModule 选项时,请使用 registerAsync() 方法。与大多数动态模块一样,Nest 提供了几种处理异步配置的技术。

一种技术是使用工厂函数:

ts
MulterModule.registerAsync({
  useFactory: () => ({
    dest: './upload',
  }),
})

与其他 工厂提供商 一样,我们的工厂函数可以是 async,并且可以通过 inject 注入依赖项。

ts
MulterModule.registerAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => ({
    dest: configService.get<string>('MULTER_DEST'),
  }),
  inject: [ConfigService],
})

或者,您可以使用类而不是工厂来配置MulterModule,如下所示:

ts
MulterModule.registerAsync({
  useClass: MulterConfigService,
})

上面的构造在MulterModule内实例化了MulterConfigService,并使用它来创建所需的选项对象。请注意,在此示例中,MulterConfigService必须实现MulterOptionsFactory接口,如下所示。MulterModule将在所提供类的实例化对象上调用createMulterOptions()方法。

ts
@Injectable()
class MulterConfigService implements MulterOptionsFactory {
  createMulterOptions(): MulterModuleOptions {
    return {
      dest: './upload',
    }
  }
}

如果您想重用现有的选项提供程序,而不是在MulterModule内创建私有副本,请使用useExisting语法。

ts
MulterModule.registerAsync({
  imports: [ConfigModule],
  useExisting: ConfigService,
})

示例

此处 提供了一个可用的示例。