文件上传
为了处理文件上传,Nest 提供了一个基于 Express 的 multer 中间件包的内置模块。Multer 处理以 multipart/form-data 格式发送的数据,该格式主要用于通过 HTTP POST 请求上传文件。此模块是完全可配置的,你可以根据应用需求调整其行为。
警告
Multer 无法处理不是受支持的多部分格式(multipart/form-data)的数据。另外请注意,此包与 FastifyAdapter 不兼容。
为了获得更好的类型安全,让我们安装 Multer 的类型定义包:
$ npm i -D @types/multer安装此包后,我们就可以使用 Express.Multer.File 类型了(你可以通过以下方式导入此类型:import { Express } from 'express')。
基本示例
要上传单个文件,只需将 FileInterceptor() 拦截器绑定到路由处理程序,并使用 @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() 装饰器接受两个参数:
fieldName:一个字符串,提供 HTML 表单中包含文件的字段名称options:可选的MulterOptions类型对象。这与 multer 构造函数使用的对象相同(更多详情请参见此处)。
警告
FileInterceptor() 可能与 Google Firebase 等第三方云提供商不兼容。
文件验证
通常,验证传入文件的元数据(如文件大小或文件 MIME 类型)是很有用的。为此,你可以创建自己的管道(Pipe)并将其绑定到使用 UploadedFile 装饰器注解的参数上。以下示例演示了如何实现一个基本的文件大小验证管道:
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;
}
}可以结合 FileInterceptor 使用如下:
@Post('file')
@UseInterceptors(FileInterceptor('file'))
uploadFileAndValidate(@UploadedFile(
new FileSizeValidationPipe(),
// 可以在此处添加其他管道
) file: Express.Multer.File, ) {
return file;
}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) {}
abstract isValid(file?: any): boolean | Promise<boolean>;
abstract buildErrorMessage(file: any): string;
}提示
FileValidator 接口通过其 isValid 函数支持异步验证。为了利用类型安全,如果你使用 express(默认)作为驱动程序,还可以将 file 参数类型化为 Express.Multer.File。
FileValidator 是一个常规类,它可以访问文件对象,并根据客户端提供的选项对其进行验证。Nest 有两个内置的 FileValidator 实现,你可以在项目中使用:
MaxFileSizeValidator- 检查给定文件的大小是否小于提供的值(以bytes为单位)FileTypeValidator- 检查给定文件的 MIME 类型是否匹配给定的字符串或正则表达式。默认情况下,使用文件内容的魔术数字验证 MIME 类型
为了理解如何将这些与前面提到的 ParseFilePipe 结合使用,我们将使用前面示例的修改片段:
@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,提示
默认情况下文件是必需的,但你可以通过在 build 函数选项中添加 fileIsRequired: false 参数(与 errorHttpStatusCode 同级)使其变为可选。
文件数组
要上传文件数组(使用单个字段名标识),请使用 FilesInterceptor() 装饰器(注意装饰器名称中的复数 Files)。此装饰器接受三个参数:
fieldName:如上所述maxCount:可选数字,定义可接受的最大文件数量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() 装饰器。此装饰器接受两个参数:
uploadedFields:一个对象数组,每个对象指定一个必需的name属性(值为字符串类型的字段名,如上所述),以及一个可选的maxCount属性,如上所述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,
});你还可以向 registerAsync() 方法传递所谓的 extraProviders。这些提供者将与模块提供者合并。
MulterModule.registerAsync({
imports: [ConfigModule],
useClass: ConfigService,
extraProviders: [MyAdditionalProvider],
});当你需要为工厂函数或类构造函数提供额外的依赖时,这非常有用。
示例
可以在此处找到一个可运行的示例。