Skip to content

文件上传

单文件上传#

在Nest中,上传单个文件的功能由FileInterceptor()拦截器和@UploadFile()装饰器共同实现。当FileInterceptor绑定到控制器方法时,它负责拦截请求中包含的文件,并将这些文件保存到指定的位置。而@UploadFile()装饰器用于从请求中提取已上传的文件。

@Post('upload')
@UseInterceptors(FileInterceptor('file', { dest: './uploads' }))
uploadFile(@UploadedFile() file: Express.Multer.File, @Body() body) {
console.log(file);
return {
message: '上传成功',
file: file.filename,
};
}

注意:

FileInterceptor在包@nestjs/platform-express中,

Express.Multer.File是上传文件的类型,不过会报类型错误,需要引入相关的类型文件

pnpm add @types/multer -D

通过apifox操作之后:

image-20250127010710155

除了在拦截器上直接指定文件上传后的保存位置之外,Multer中间件提供了文件存储引擎,我们可以在app.module.ts中通过动态模块进行配置

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MulterModule } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { extname } from 'path';
@Module({
imports: [
......
MulterModule.register({
storage: diskStorage({
destination: './uploads',
filename: (req, file, cb) => {
const randomName = Array(32)
.fill(null)
.map(() => Math.round(Math.random() * 16).toString(16))
.join('');
const filename = `${randomName}${extname(file.originalname)}`;
console.log(filename);
cb(null, filename);
},
}),
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

其中

Array(32)
.fill(null)
.map(() => Math.round(Math.random() * 16).toString(16))
.join('')

生成了一个由 32 个十六进制字符组成的随机字符串

Array(32).fill(null);
// [null, null, null, ..., null] (长度为 32)
.map(() => Math.round(Math.random() * 16).toString(16))
对数组的每个元素执行 map 函数生成随机的十六进制字符
Math.random() 生成 0 1 的随机浮点数
Math.random() * 16 将随机数扩展到 0 16 的范围
Math.round(...) 将数值四舍五入为整数0 16 之间的整数)。
.toString(16) 将整数转换为十六进制字符0-9 a-f)。

extname函数需要从path中引入

多文件上传#

UploadedFile 换成 UploadedFiles,把FileInterceptor换成FilesInterceptor,都是多加一个 s就可以了

@Post('uploads1')
@UseInterceptors(
FilesInterceptor('files', 3, {
dest: 'uploads',
}),
)
uploadFiles1(
@UploadedFiles() files: Array<Express.Multer.File>,
@Body() body,
) {
console.log('files', files);
return {
message: '上传成功',
files,
};
}

image-20250131121748637

不过稍微注意的是,上传多文件并不会自动帮你创建文件夹。

除了上面的方式之外,还能以下面的方式传递多文件

@Post('uploads2')
@UseInterceptors(
FileFieldsInterceptor([
{ name: 'files1', maxCount: 3 },
{ name: 'files2', maxCount: 3 },
]),
)
uploadFiles2(
@UploadedFiles()
files: { file1?: Express.Multer.File[]; file2?: Express.Multer.File[] },
@Body() body,
) {
console.log(files);
return {
message: '上传成功',
files,
};
}

image-20250131122019629

通过FileFieldsInterceptor分别处理多个文件的字段

任意字段文件上传#

如果并不知道有哪些字段是 file,可以用 AnyFilesInterceptor

@Post('anyUploads')
@UseInterceptors(
AnyFilesInterceptor({
dest: 'uploads',
}),
)
uploadAnyFiles(
@UploadedFiles() files: Array<Express.Multer.File>,
@Body() body,
) {
console.log('files', files);
return {
message: '上传成功',
files,
};
}

image-20250131123253953

这样同样识别出了所有 file字段

而且,AnyFilesInterceptor还有storage字段,我们可以直接单独的处理文件存储,比如:

import * as multer from 'multer'
import { extname } from 'path'
const storage = multer.diskStorage({
destination: './uploads',
filename: (req, file, cb) => {
const randomName = Array(32)
.fill(null)
.map(() => Math.round(Math.random() * 16).toString(16))
.join('')
const filename = `${randomName}${extname(file.originalname)}`
console.log(filename)
cb(null, filename)
},
})
export { storage }

直接在AnyFilesInterceptor中引入:

@Post('anyUploads')
@UseInterceptors(
AnyFilesInterceptor({
// dest: 'uploads',
storage: storage,
}),
)
uploadAnyFiles(
@UploadedFiles() files: Array<Express.Multer.File>,
@Body() body,
) {
console.log('files', files);
return {
message: '上传成功',
files,
};
}

上传限制#

要对上传的文件做一些限制,比如文件大小、类型等,这部分就可以利用我们讲过的 pipe 处理,比如我们完全可以自己创建一个Pipe

nest g pipe fileValidation --no-spec --flat

在里面做一些简单处理

import {
ArgumentMetadata,
HttpException,
HttpStatus,
Injectable,
PipeTransform,
} from '@nestjs/common'
@Injectable()
export class FileValidationPipe implements PipeTransform {
transform(value: Express.Multer.File, metadata: ArgumentMetadata) {
if (value.size > 10 * 1024) {
console.log(value)
throw new HttpException('文件上传大小不能超过10k', HttpStatus.BAD_REQUEST)
}
return value
}
}

在controller中应用:

@Post('upload')
@UseInterceptors(FileInterceptor('file'))
uploadFile(
@UploadedFile(FileValidationPipe) file: Express.Multer.File,
@Body() body,
) {
console.log(file);
return {
message: '上传成功',
file: file.filename,
};
}

image-20250131130624645

像文件大小、类型的校验这种逻辑太过常见,我们完全可以用Nest已经封装好的

@Post('upload')
@UseInterceptors(FileInterceptor('file'))
uploadFile(
@UploadedFile(
new ParseFilePipe({
validators: [
new MaxFileSizeValidator({ maxSize: 1000 }),
new FileTypeValidator({ fileType: 'image/jpeg' }),
],
exceptionFactory: (err) => {
console.log(err);
throw new HttpException(
'文件大小或类型不正确,上传失败---' + err,
400,
);
},
}),
)
file: Express.Multer.File,
@Body() body,
) {
console.log(file);
return {
message: '上传成功',
file: file.filename,
};
}

其中,MaxFileSizeValidator 是校验文件大小、FileTypeValidator 是校验文件类型,我们也可以自己实现这样的 validator,只要继承 FileValidator 就可以

import { FileValidator } from '@nestjs/common'
export class MyFileValidator extends FileValidator {
constructor(options: Record<string, any>) {
super(options)
}
isValid(file: Express.Multer.File): boolean | Promise<boolean> {
if (file.size > 10 * 1024) {
return false
} else if (file.mimetype !== 'image/jpeg') {
return false
}
return true
}
buildErrorMessage(file: Express.Multer.File): string {
if (file.size > 10 * 1024) {
return `文件 ---${file.originalname}--- 大小超出 10k`
} else if (file.mimetype !== 'image/jpeg') {
return `文件 ---${file.originalname}--- 类型不是 image/jpeg`
}
}
}

在controller中使用

@Post('upload')
@UseInterceptors(FileInterceptor('file'))
uploadFile(
@UploadedFile(
new ParseFilePipe({
validators: [new MyFileValidator({})],
exceptionFactory: (err) => {
console.log(err);
throw new HttpException(err, 400);
},
}),
)
file: Express.Multer.File,
@Body() body,
) {
console.log(file);
return {
message: '上传成功',
file: file.filename,
};
}