Skip to content

文件上传

环境配置

向军大叔每晚八点在 抖音bilibli 直播

上传文件是网站开发中的常用操作,使用NestJs进行上传非常简单,本章我们来掌握这块知识。

我们会使用到 multer 扩展包实现上传,所以需要安装扩展包与类型支持。

pnpm add multer
pnpm add -D @types/multer

创建模块与控制器

nest g mo common
//flat参数指不创建子目录结构
nest g co common/upload --no-spec --flat

模块定义

上传配置可以放在控制器方法,也可以放在模块中,下面是在模块中定义。

import { Global, Module } from '@nestjs/common'
import { MulterModule } from '@nestjs/platform-express'
import { diskStorage } from 'multer'
import { extname } from 'path'
import { UploadController } from './upload.controller'
@Global()
@Module({
  imports: [
    MulterModule.registerAsync({
      useFactory() {
        return {
          storage: diskStorage({
            //文件储存位置
            destination: 'uploads',
            //文件名定制
            filename: (req, file, callback) => {
              const path = Date.now() + '-' + Math.round(Math.random() * 1e10) + extname(file.originalname)
              callback(null, path)
            },
          }),
        }
      },
    }),
  ],
  controllers: [UploadController],
})
export class CommonModule {}

基本使用

现在我们在控制器中体验文件上传

上传处理

下面在控制器使用

import { Controller, Post, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { FileInterceptor } from '@nestjs/platform-express'
@Controller('upload')
export class UploadController {
  @Post('image')
  @UseInterceptors(FileInterceptor('file'))
  image(@UploadedFile() file: Express.Multer.File) {
    return file
  }
}

下面使用postman等测试工作来测试上传是否成功,如果成功会返回以下结果。

  • 下面数据包含在data中是因为使用拦截器处理,请查看拦截器章节了解
{
  "fieldname": "file",
  "originalname": "xj-small.png",
  "encoding": "7bit",
  "mimetype": "image/png",
  "destination": "uploads",
  "filename": "1658807155785-7305320730.png",
  "path": "uploads/1658807155785-7305320730.png",
  "size": 40442
}

类型校验

下面示例只允许上传图片文件

export class UploadController {
  @Post('image')
  @UseInterceptors(
    FileInterceptor('file', {
      fileFilter(req: any, file: Express.Multer.File, callback: (error: Error | null, acceptFile: boolean) => void) {
        if (file.mimetype.includes('image')) {
          callback(new UnsupportedMediaTypeException('文件类型错误'), false)
        } else {
          callback(null, true)
        }
      },
    }),
  )
  ...
}

优化代码

如果上传分图片/文档等不同类型的上传处理,使用上面的方式需要在控制器的不同方法定义重复度很高的代码 。下面我们来简化这个过程。

装饰器聚合

我们将 @UseInterceptors(FileInterceptor('file')) 通过装饰器聚合来简化代码,下面在 ./upload.ts 文件中定义。

import { applyDecorators, UseInterceptors } from '@nestjs/common'
import { FileInterceptor } from '@nestjs/platform-express'
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface'

export function upload(fieldName = 'file', options: MulterOptions = {}) {
  return applyDecorators(UseInterceptors(FileInterceptor(fieldName, options)))
}

现在在控制器中就可以简化操作了

import { Controller, Post, UploadedFile } from '@nestjs/common'
import { UploadDecorator } from './decorator/upload.decorator'

@Controller('upload')
export class UploadController {
  @Post()
  @upload('file', {
      fileFilter(req: any, file: Express.Multer.File, callback: (error: Error | null, acceptFile: boolean) => void) {
        if (file.mimetype.includes('image')) {
          callback(new UnsupportedMediaTypeException('文件类型错误'), false)
        } else {
          callback(null, true)
        }
  })
  upload(@UploadedFile() file: Express.Multer.File) {
    return file
  }
}

抽离验证功能

下面对上传的文件类型验证进行抽离,修改 ./upload.ts 定义 fileMimetypeFilter 函数用于对上传类型进行验证。

export function fileMimetypeFilter(...mimes: string[]) {
  return (req: any, file: Express.Multer.File, callback: (error: Error | null, acceptFile: boolean) => void) => {
    if (mimes.some((mime) => file.mimetype.includes(mime))) {
      callback(null, true)
    } else {
      callback(new UnsupportedMediaTypeException('文件上传类型错误'), false)
    }
  }
}

现在可以在控制器中对上传文件进行验证了

import { Controller, Post, UploadedFile } from '@nestjs/common'
import { UploadDecorator,fileMimetypeFilter} from './upload'

@Controller('upload')
export class UploadController {
  @Post()
  @UploadDecorator('file', {fileFilter: fileMimetypeFilter('image')})
  upload(@UploadedFile() file: Express.Multer.File) {
    return file
  }
}

最终封装

经过上面的学习,现在应该对nest.js上传已经掌握了,下面我们进行整合,使用上传使用变得更方便 。

功能整合

下面我们修改 ./upload.ts 对图片上传与文档上传定义

import { applyDecorators, UnsupportedMediaTypeException, UseInterceptors } from '@nestjs/common'
import { FileInterceptor } from '@nestjs/platform-express'
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface'

//上传类型验证
export function filterFilter(type: string) {
  return (req: any, file: Express.Multer.File, callback: (error: Error | null, acceptFile: boolean) => void) => {
    if (!file.mimetype.includes(type)) {
      callback(new UnsupportedMediaTypeException('文件类型错误'), false)
    } else {
      callback(null, true)
    }
  }
}

//文件上传
export function Upload(field = 'file', options: MulterOptions) {
  return applyDecorators(UseInterceptors(FileInterceptor(field, options)))
}

//图片上传
export function Image(field = 'file') {
  return Upload(field, {
    //上传文件大小限制
    limits: Math.pow(1024, 2) * 2,
    fileFilter: filterFilter('image'),
  } as MulterOptions)
}

//文档上传
export function Document(field = 'file') {
  return Upload(field, {
    //上传文件大小限制
    limits: Math.pow(1024, 2) * 5,
    fileFilter: filterFilter('document'),
  } as MulterOptions)
}

实际使用

现在可以在控制器中方便的使用上传了

import { Controller, Post, UploadedFile } from '@nestjs/common'
import { image } from './upload'

@Controller('upload')
export class UploadController {
  @Post('image')
  @Image()
  image(@UploadedFile() file: Express.Multer.File) {
    return file
  }
}

文件访问

图片虽然上传成功了,但还不能通过域名访问。使用NestJs配置非常简单,在man.ts主文件中定义

import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { NestExpressApplication } from '@nestjs/platform-express'
import { join } from 'path'

async function bootstrap() {
	//传递类型NestExpressApplication
  const app = await NestFactory.create<NestExpressApplication>(AppModule)
  ...
  //静态资源访问
  app.useStaticAssets('uploads', {prefix: '/uploads'})
  await app.listen(3000)
}
bootstrap()

现在可以通过url正常访问图片了,如 http://localhost:3000/uploads/xxxx.jpeg