档案上传(File Upload) 是一项很基本的功能,到处都可以看见它的踪影,如:某某社群网站的上传大头贴、某某影音网站上传影片等。
Nest 针对档案上传功能封装了一套名为 multer 的套件,它会处理格式为 multipart/form-data
的资料,在 Express 的应用程序上经常可以看到它的身影,是非常知名的套件。
虽然 Nest 将其包装成内建模组,但还是建议各位安装 multer 的型别定义档,透过 npm 来进行安装:
$ npm install @types/multer -D
接收单一档案的方式很简单,只要在特定路由下使用 FileInterceptor
并透过参数装饰器 @UploadedFile
来取得档案。其中,FileInterceptor
有两个参数可以带入,分别是:
fieldName
:档案在表单上对应的名称。options
:对应到 MulterOption
,详细内容可以参考 multer 官方文档。这边以 app.controller.ts
为例来实作单一档案上传:
import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
@Controller()
export class AppController {
@Post('/single')
@UseInterceptors(FileInterceptor('file'))
uploadSingleFile(@UploadedFile() file: Express.Multer.File) {
return file;
}
}
透过 Postman 进行测试,将一个档案名称为 nestjs_logo.svg
的图片上传,会收到该图片的相关讯息:
如果同一个栏位名称有一个以上的档案,要使用 FilesInterceptor
并透过参数装饰器 @UploadedFiles
来取得一个包含多个 Express.Multer.File
型别的阵列。
注意:这里是使用复数 Files 而不是单一档案上传所使用的
FileInterceptor
与@UploadedFile
。
FilesInterceptor
有三个参数可以带入,分别是:
fieldName
:档案在表单上对应的名称。maxCount
:配置可接受档案数量的上限,可以选择性填入。options
:对应到 MulterOption
。同样以 app.controller.ts
为例来实作单一栏位多档上传:
import { Controller, Post, UploadedFiles, UseInterceptors } from '@nestjs/common';
import { FilesInterceptor } from '@nestjs/platform-express';
@Controller()
export class AppController {
@Post('/multiple')
@UseInterceptors(FilesInterceptor('files'))
uploadMultipleFiles(@UploadedFiles() files: Express.Multer.File[]) {
return files.map(({ fieldname, originalname }) => ({ fieldname, originalname }));
}
}
透过 Postman 进行测试,将档案名称为 nestjs_logo.svg
与 nodejs_logo.png
的图片上传,会收到它们的栏位名称与档案名称:
假如表单有多个栏位并且有一个以上的栏位包含档案,要使用 FileFieldsInterceptor
并透过 @UploadedFiles
装饰器来取得一个以栏位名称作为 key
的物件,其值为 Express.Multer.File
型别的阵列。其中,FileFieldsInterceptor
有两个参数可以带入:
uploadedFields
:一个包含多个物件的阵列,物件需要拥有 name
属性来指定栏位的名称,亦可以给定 maxCount
来指定该栏位可接受的档案数量上限。options
:对应到 MulterOption
。同样以 app.controller.ts
为例来实作多栏位多档案上传:
import { Controller, Post, UploadedFiles, UseInterceptors } from '@nestjs/common';
import { FileFieldsInterceptor } from '@nestjs/platform-express';
@Controller()
export class AppController {
@Post('/multiple')
@UseInterceptors(FileFieldsInterceptor([
{ name: 'first' },
{ name: 'second' }
]))
uploadMultipleFiles(@UploadedFiles() files: { [x: string]: Express.Multer.File[] }) {
const { first, second } = files;
const list = [...first, ...second];
return list.map(({ fieldname, originalname }) => ({ fieldname, originalname }));
}
}
透过 Postman 进行测试,将档案名称为 nestjs_logo.svg
与 nodejs_logo.png
的图片上传,会收到它们的栏位名称与档案名称:
假如表单有多个栏位并且有一个以上的栏位包含档案,但不需要依照栏位名称做分类的话,可以直接使用 AnyFilesInterceptor
并透过 @UploadedFiles
装饰器来取得一个包含多个 Express.Multer.File
型别的阵列。其中,AnyFilesInterceptor
可以带入一个参数,即 options
。
同样以 app.controller.ts
为例来实作不分栏位多档上传:
import { Controller, Post, UploadedFiles, UseInterceptors } from '@nestjs/common';
import { AnyFilesInterceptor } from '@nestjs/platform-express';
@Controller()
export class AppController {
@Post('/multiple')
@UseInterceptors(AnyFilesInterceptor())
uploadMultipleFiles(@UploadedFiles() files: Express.Multer.File[]) {
return files.map(({ fieldname, originalname }) => ({ fieldname, originalname }));
}
}
透过 Postman 进行测试,将档案名称为 nestjs_logo.svg
与 nodejs_logo.png
的图片上传,会收到它们的栏位名称与档案名称:
上面每个功能都可以指定 MulterOption
的配置,假如有个配置是多数上传档案都会用到的,那每次都要个别配置实在太麻烦了,所以 Nest 有提供一个预设值的方法,大幅减少这种重复的操作,那该如何使用呢?只要导入 MulterModule
并调用 register
方法即可,该方法可接受之参数正是 MulterOption
。
这里以 app.module.ts
为例,假如我们希望把上传的档案存到名为 upload
的资料夹里,那就在 register
里面给定 dest
属性,并指定其值为 ./upload
:
import { Module } from '@nestjs/common';
import { MulterModule } from '@nestjs/platform-express';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
MulterModule.register({
dest: './upload'
})
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
我们沿用「预设 multer 设置」与「不分栏位之多个档案上传」的范例进行测试,透过 Postman 上传 nestjs_logo.svg
与 nodejs_logo.png
,会在专案目录下看到 upload
资料夹,里面含有以下内容:
奇怪,怎麽跟预期的不一样?是哪里出错了吗?其实这是因为 multer 不知道你储存的档案类型与名称要叫什麽,并没有给它一个明确的定义,所以才会看到没有副档名的随机名称档案,这里可以做个小实验,将这两个档案的档案名称与副档名改成预期的样子,就可以看到他们的原貌了:
但这并不是解决问题的好方法,我们会希望能够自动化去处理这件事情,那该怎麽做呢?这时候可以用 multer 提供的 diskStorage
来辅助我们去处理档案名称的问题。
diskStorage
是一个函式,我们可以透过指定 destination
来配置档案的存放位置、指定 filename
去处理档案名称,这两个属性的值皆为 函式,透过函式去处理的弹性比较大,毕竟给特定值并不适用在每个场景。
我们透过撰写一个 Helper Class 来实作这两个函式,在 src
资料夹下新增 core/helpers
资料夹,并添加 multer.helper.ts
,由於这两个函式有特定的参数,故我们的方法也需要遵循这些参数来设计,其包含了 Request
、Express/Multer.File
以及 (error: Error | null, destination: string) => void
的 Callback 函式,透过该 Callback 将处理好的结果返回给 multer:
import { Request } from 'express';
import { join } from 'path';
export class MulterHelper {
public static destination(
request: Request,
file: Express.Multer.File,
callback: (error: Error | null, destination: string) => void
): void {
callback(null, join(__dirname, '../../../upload/'));
}
public static filenameHandler(
request: Request,
file: Express.Multer.File,
callback: (error: Error | null, destination: string) => void
): void {
const { originalname } = file;
const timestamp = new Date().toISOString();
callback(null, `${timestamp}-${originalname}`);
}
}
接着,我们就来将这两个函式实装上去,修改 app.module.ts
的内容,将 register
物件参数中的 dest
换成 storage
,并配置 destination
与 filename
:
import { Module } from '@nestjs/common';
import { MulterModule } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { MulterHelper } from './core/helpers/multer.helper';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
MulterModule.register({
storage: diskStorage({
destination: MulterHelper.destination,
filename: MulterHelper.filenameHandler
})
})
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
最後,透过 Postman 进行测试,将 nestjs_logo.svg
与 nodejs_logo.png
上传,会在专案目录下的 upload
资料夹看到这两个档案:
multer 将档案上传功能简化成套用 Middleware 即可使用,Nest 更进一步进行包装,使其可以很轻易地在 Nest 中使用,让它的使用方式更符合 Nest 的设计原则,是非常好用且强大的套件。这里附上今天的懒人包:
multipart/form-data
的资料。FileInterceptor
并透过 @UploadedFile
来取得档案资料。FilesInterceptor
并透过 @UploadedFiles
来取得档案资料。FileFieldsInterceptor
并透过 @UploadedFiles
来取得档案资料。AnyFilesInterceptor
并透过 @UploadedFiles
来取得档案资料。MulterModule.register()
来配置 multer 预设值。storage
属性与 diskStorage
来实作档案储存。
>>: Day20 Plugin 从零开始到上架 - 取得授权码(iOS)
之前就有做过这件事情 当时搞定了之後想说,简单吗~~~就是搞个Dockerfile而已 结果好一阵子...
练习刻板面时常常会遇到形形色色的字体 但若不是电脑本身有下载该字体的话,即便设定了还是会以预设字体呈...
大家好!今天这篇主要是实作浏览器上的录音与录影功能,这边先列出几个会做到的目标 显示视讯画面与声音 ...
今天是讲座笔记,内容来自 PHP也有Day #60 composer & vite ,建议...
前言: 今天我们要来完成前面提到的Sidebar,我会从Navbar接着开始接着讲,那就让我们开始...