NestJs文件上传由浅入深

本文将使用 NestJs 与 Multer 搭建一个文件上传服务,并逐步添加文件本地保存、文件大小限制、文件类型限制功能。

准备工作

  1. 使用 nest new 命令新建一个名为'upload-example'的项目,并指定使用pnpm包管理器。

    bash 复制代码
    nest new upload-example -p pnpm
  2. 进入项目根目录,安装 Multer 的类型声明包。

    bash 复制代码
    pnpm i -D @types/multer
  3. 在项目根目录使用 nest generate 命令自动生成一个 upload 模块,选择 REST API 风格即可,下一步输入 y 就行。

    bash 复制代码
    nest g resource upload

简易实现

  1. 在 VsCode 中打开/src/upload下的upload.controller.ts文件,删除多余代码,只留下一个空类即可,如下:

  2. @nestjs/common中引入PostUseInterceptorsUploadedFile模块,从@nestjs/platform-express中引入FileInterceptor模块。

    typescript 复制代码
    import { Controller, Post, UseInterceptors, UploadedFile } from '@nestjs/common';
    import { UploadService } from './upload.service';
    import { FileInterceptor } from '@nestjs/platform-express';
  3. 在 UploadController 中添加 simple 方法,代码如下:

    typescript 复制代码
    @Controller('upload')
    export class UploadController {
      constructor(private readonly uploadService: UploadService) {}
    
      @Post()
      @UseInterceptors(FileInterceptor('file'))
      simple(@UploadedFile() file: Express.Multer.File) {
        console.log(file);
        return this.uploadService.create(file);
      }
    }
    • @Post()代表 post 方法。

    • @UseInterceptors(FileInterceptor('file'))代表使用FileInterceptor处理上传的form data里的 file 字段的数据,也可以不指定字段名,直接处理整个表单数据。

    • @UploadedFile()使用该装饰器从 request 中取出 file。

    • uploadService文件代码如下:

  4. 使用pnpm run start命令启动项目,然后使用任一类postman工具发送一个上传请求即可。本文使用的是apifox,如下图:

    注意:端口号换成你自己的,根目录下的 main.js 中可以查看,nest应用一般默认3000。请求方法类型选 post,参数放 body 中,格式选 form-data,参数名为 file(FileInterceptor('file')中设置的名字,如没有设置,则可以不写参数名)。

至此,我们就实现了一个简单的上传接口。我们可以看见发起请求后,控制台打印出了文件信息,但没有保存文件,接下来我们就来实现文件的保存等功能。

文件保存

FileInterceptor装饰器传入第二个参数{ dest: 'uploads/' },表示将文件保存到根目录下的 uploads 文件夹中,如果没有则会自动创建该文件夹。
使用apifox发送请求后(先重启服务),我们会发现根目录下的 uploads 文件夹中多了一个二进制文件,而我上传的是一个 png 图片。这是因为我们没有设置文件后缀名,下面我们来解决这个问题。

首先我们先从 multer 中引入 diskStorage 模块,然后再在 FileInterceptor 中配置相关参数,如下:

ts 复制代码
import {
  Controller,
  Post,
  UseInterceptors,
  UploadedFile
} from '@nestjs/common';
import { UploadService } from './upload.service';
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer'; // 新增代码

@Controller('upload')
export class UploadController {
  constructor(private readonly uploadService: UploadService) {}

  @Post()
  @UseInterceptors(
    FileInterceptor('file', {
      // 新增代码
      storage: diskStorage({
        destination: 'uploads/', // 设置文件保存位置为根目录下的 uploads 文件夹
        filename: (_req, file, cb) => {
          // 生成一个随机字符串
          const randomName = Array(32)
            .fill(null)
            .map(() => Math.round(Math.random() * 16).toString(16))
            .join('');
          // 设置文件名
          return cb(null, `${randomName}${file.originalname}`);
        }
      })
    })
  )
  simple(@UploadedFile() file: Express.Multer.File) {
    return this.uploadService.create(file);
  }
}

现在我们重启服务后,再发送上传图片请求,就可以在 uploads 文件夹下看见刚刚通过接口上传的图片文件了。

限制文件类型、大小

在 src 下新建一个 config/multerConfig.ts 文件,并添加以下代码:

ts 复制代码
import { extname } from 'path';
import { diskStorage } from 'multer';

export const multerConfig = {
  limits: {
    fileSize: 1024 * 1024 * 5
  },
  fileFilter: (_req, file: Express.Multer.File, cb) => {
    // 限制上传图片类型文件
    if (file.mimetype.match(/(jpg|jpeg|png|gif)$/)) {
      return cb(null, true);
    }

    return cb(null, false);
  },
  storage: diskStorage({
    destination: 'uploads/',
    filename: (_req, file, cb) => {
      const randomName = Array(32)
        .fill(null)
        .map(() => Math.round(Math.random() * 16).toString(16))
        .join('');
      // 获取文件后缀
      const suffix = extname(file.originalname);
      return cb(null, `${randomName}${suffix}`);
    }
  })
};

在 upload.controller.ts 文件引入该配置对象,即添加以下代码到该文件
import { multerConfig } from 'src/config/multerConfig';

然后修改 upload.controller.ts 文件中的 simple 方法的 FileInterceptor 配置,如下:

ts 复制代码
@Post()
@UseInterceptors(FileInterceptor('file', multerConfig))
simple(@UploadedFile() file: Express.Multer.File) {
  return this.uploadService.create(file);
}

最后在 upload.service.ts 文件中添加上传文件不符合的要求的响应信息,如下:

ts 复制代码
import { Injectable } from '@nestjs/common';

@Injectable()
export class UploadService {
  create(file: Express.Multer.File) {
    if (!file) {
      return {
        code: 400,
        message: 'Only image files are allowed!'
      };
    }

    return {
      code: 200,
      data: {
        url: `http://localhost:3000/static/${file.filename}`
      },
      message: 'success'
    };
  }
}

重启服务,分别上传图片与其他类型的文件,观察接口返回的响应数据。到这里我们就实现了文件类型与大小的限制,更多配置可以查看 multer 官方文档

支持访问已上传文件

前面我们实现了文件的上传,但我们必须要进入 nest 项目里才能查看,而不能通过 url 访问已上传的资源。接下来我们就来实现通过 url 访问已上传的资源。

其实我们只需要搭建一个静态资源服务,在main.js中添加几行代码即可实现,如下:

ts 复制代码
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express'; // 新增代码,引入模块

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  // 新增代码,设置静态资源路径与访问前缀
  app.useStaticAssets('uploads', {
    prefix: '/static/'
  });
  await app.listen(3000);
}
bootstrap();

现在我们上传一个图片后,即可通过接口返回的 url 访问该图片。 项目仓库

相关推荐
刀法如飞1 小时前
Go数组去重的20种实现方式,AI时代解决问题的不同思路
后端·算法·go
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题】【Java基础篇】第30题:JDK动态代理和CGLIB动态代理有什么区别
java·开发语言·后端·面试·代理模式
言萧凡_CookieBoty1 小时前
AI 编程省 Token 实战:从 Spec、上下文工程到模型分层的降本策略
前端·ai编程
swipe1 小时前
别再把 AI 聊天做成纯文本:从 agui 这个前后端项目,拆解“可感知工具调用”的流式 AI UI
后端·langchain·llm
GetcharZp1 小时前
GitHub 爆火!纯 Go 编写的文件同步神器 Syncthing,凭什么成为程序员的标配?
后端
hERS EOUS2 小时前
SpringBoot 使用 spring.profiles.active 来区分不同环境配置
spring boot·后端·spring
DFT计算杂谈2 小时前
wannier90 参数详解大全
java·前端·css·html·css3
LucianaiB2 小时前
我用飞书多维表做了一个 AI 活动推荐智能体:每天自动催我别错过截止日期!
后端
铁皮饭盒3 小时前
第2课:5分钟!用 Trae AI 生成你的第一个后端服务(Bunjs + Elysia)
前端·后端·全栈
金銀銅鐵3 小时前
[git] 浅解 git reset 命令
git·后端