likes
comments
collection
share

NestJS17-File upload

作者站长头像
站长
· 阅读数 10

处理文件上传,Nest提供了基于express的multer包的内置模块。Multer处理multipart/form-data类型的数据,它主要通过HTTPPOST请求来上传文件。这个模块完全可以配置并且您可以调整它的行为来符合您的项目需求。

警告

Multer不能处理不支持multipart/form-data形式的数据 并且,这个包也不能和FastifyAdapter一起使用

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

$ npm i -D @types/multer

当这个包安装后,我们现在可以使用Express.Multer.File类型(您可以通过这个方法来导入:import { Express } from 'express'

基本例子

上传单个文件,简单的将Fileintreceptor()拦截器绑定到路由句柄并且使用@UploadedFile()装饰器从request中取得file文件。

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

注意

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

FileInterceptor()装饰器有2个参数

  • fieldName:提供包含文件的HTML表单中字段名称的字符串
  • options:MulterOptions类型的可选对象。和multer构造器中使用的是同一个对象(更多

警告

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

文件检查

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

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

Nest提供了内置管道来处理共通用例并促进/规范新增。这个管道叫做ParseFilePipe,如下:

@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执行。我将会讨论验证的接口,但是值得注意的是这个管道还有2个附加可选参数:

参数说明
errorHttpStatusCodeHTTP状态码将会因为任何的验证失败而被抛出
exceptionFactory一个工厂它将会收到异常消息并返回一个异常

现在,回到FileValidator接口。为了让管道结合验证,您需要既使用内置实现又要提供您自己定义的FileValidator。看下例子:

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

  /**
   * 指示根据构造函数中传递的选项,是否应将此文件视为有效文件。
   * @param file请求对象中的文件
   */
  abstract isValid(file?: any): boolean | Promise<boolean>;

  /**
   * 在验证失败时生成错误消息。
   * @param file请求对象中的文件
   */
  abstract buildErrorMessage(file: any): string;
}

注意

FileValdator接口通过它的isValid方法提供异步验证。要利用类型安全性,还可以将file参数为Express.Multer.File类型,以防您使用express(默认)作为驱动程序。

FileValidator是一个通常的类它访问文件对象并且根据服务端提供的参数验证它。Nest有2个内置FileValidator实现您能在您的项目中使用:

  • MaxFileSizeValidator - 验证是否取得文件的大小小于提供的值(用bytes比较)
  • FileTypeValidator - 验证是否文件的mime-type类型符合提供的值

警告

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

为了明白这些如何能够结合上文FileParsePipe一起使用,我们将使用上一个示例中呈现的代码片段的修改版本:

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

注意

如果验证器的数量大幅增加或它们的选项使文件变得混乱,您可以将这个数组定义在一个单独的文件中,然后将其作为一个名为fileValidators的命名常量导入到这里

最终,您可以使用特殊的ParseFilePipeBuilder类它允许您组合和构建您的验证器。通过如下所示的方式使用它,您可以避免手动实例化每个验证器,并直接传递它们的选项:

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

文件数组

为了上传文件数组(验证单个项目名),使用FileInterceptor()装饰器。这个装饰器有2个参数。

  • uploadedFields:一个对象数组,每个对象需要一个字符串值的项目名的name属性,一个可选maxCount属性。
  • options:可选的MulterOptions对象。

当使用FileFieldsInterceptor(),从@UploadedFiles()装饰器的request中取得文件(files)。

@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中提取文件。

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

没有文件

要接受multipart/form-data但不允许上传任何文件,请使用NoFilesInterceptor。这会将多部分数据设置为请求主体的属性。如果请求中包含文件,它们将引发一个BadRequestException异常。

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

默认参数

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

MulterModule.register({
  dest: './upload',
});

提示

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

异步配置

当您需要异步设定MulterModules参数来代替静态的配置,使用registerAsync()方法。和大多数动态模块一样,Nest提供了几个技术来处理异步配置

一个技术就是使用工厂模式:

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

和其他的factory providers一样,我们的工厂函数可以是异步的能够通过inject注入依赖

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

或者,您可以使用类来代替工厂用于配置MulterModule,如下:

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

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

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

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

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