跳到主要内容

文件上传

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

注意

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

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

npm i -D @types/multer

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

基本示例

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

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

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

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

  1. fieldName:一个字符串,提供 HTML 表单中保存文件的字段的名称。
  2. options:类型为 MulterOptions 的可选对象。这与 multer 构造函数使用的相同对象(更多详细信息请查看这里)。
注意

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

文件验证

通常情况下,验证传入文件的元数据,比如文件大小或文件 MIME 类型,可能是很有用的。 为此,您可以创建自己的 Pipe 并将其绑定到用 UploadedFile 装饰器注释的参数上。 下面的示例演示了如何实现一个基本的文件大小验证器 Pipe:

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: [
// ... 这里设置文件验证器实例的集合
]
})
)
file: Express.Multer.File,
) {
return {
body,
file: file.buffer.toString(),
};
}

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

  • errorHttpStatusCode:在任何验证器失败时抛出的 HTTP 状态码。默认为 400(BAD REQUEST)。
  • 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;
}
备注

FileValidator 接口通过其 isValid 函数支持异步验证。 为了利用类型安全性,如果您正在使用默认的 express 驱动程序, 则还可以将file参数类型化为 Express.Multer.File

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

  • MaxFileSizeValidator - 检查给定文件的大小是否小于提供的值(以字节为单位)
  • FileTypeValidator - 检查给定文件的 MIME 类型是否与给定值匹配。
注意

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

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

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

如果验证器的数量大幅增加或它们的选项使文件杂乱不堪,您可以在一个单独的文件中定义此数组,并在此处作为命名常量导入。

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

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

文件数组

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

  1. fieldName:如上所述
  2. maxCount:定义接受的文件的最大数量的可选数字
  3. options:可选的 MulterOptions 对象,如上所述

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

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

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

多个文件

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

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

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

@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 包中导出。

异步配置

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

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

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

与其他工厂提供程序一样, 我们的工厂函数可以是async,并且可以通过 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,
});

例子

此处提供了一个工作示例。