经验分享
NestJS实现文件上传
# 开发背景任何一个成熟的网站都少不了文件上传,我的个人博客也不例外,为了方便修改头像、上传文章封面,所以来开发这么一个文件上传的接口。# 开始开发## 安装依赖这里我们需要设置静态资源
2023-04-16 09:54:19
9

开发背景

任何一个成熟的网站都少不了文件上传,我的个人博客也不例外,为了方便修改头像、上传文章封面,所以来开发这么一个文件上传的接口。

开始开发

安装依赖

这里我们需要设置静态资源目录,让NestJS知道我们要上传到哪个目录下。

npm i @nestjs/serve-static @nestjs/platform-express

依赖配置

在我们的modules中进行静态资源目录的配置,这里我放在app.module.ts中。

// ...
import { ServeStaticModule } from "@nestjs/serve-static";
import { join } from "path";

@Module({
  imports: [
    ServeStaticModule.forRoot({
      rootPath: join(__dirname, "../", "public"),
    }),
  ],
  //...
})
export class ApiModule {}

首先引入静态资源模型path.join

Module注解中imports引入静态资源模型,并进行配置,使用join方法组合路径设置静态资源的根目录(rootPath)为src/public目录下。

Controller实现

首先定义Controller骨架,我这里目前封装有一个基类BaseController,所以UploadController就继承这个BaseController,并且先引入UploadService,稍后再来定义。

import { Controller } from "@nestjs/common";
import { BaseControlle } from "../core/base.controller";
import { UploadService } from "../service/upload.service";

@Controller("upload")
export class UploadController extends BaseControlle {
  constructor(private readonly uploadService: UploadService) {
    super();
  }
}

中定义文件上传的方法,因为该接口是post请求,所以先引入Post注解,在接口方法uploadLocal内将操作抛给service去处理。

import { Controller, Post } from "@nestjs/common";
import { BaseControlle } from "../core/base.controller";
import { UploadService } from "../service/upload.service";

@Post("local")
uploadLocal() {
  return this.uploadService.uploadLocal();
}

抛给service时一定要将请求参数一同抛过去,里面含有上传的文件信息,需要引入UploadedFiles注解拿到参数request

import { 
  // ...
  UseInterceptors
} from "@nestjs/common";

// ...
uploadLocal(@UploadedFiles() request) {
  return this.uploadService.uploadLocal(request);
}

此时如果我们想设置上传的文件数量修改上传name添加额外的参数时,需要引入nestjs的拦截器UseInterceptors和文件拦截器FileFieldsInterceptor,并进行设置。

import { 
  // ...
  UseInterceptors
} from "@nestjs/common";

// ...
@UseInterceptors(FileFieldsInterceptor([
  { name: "file", maxCount: 1 },
  { name: "custom", }
]))
uploadLocal(@UploadedFiles() request) {
  return this.uploadService.uploadLocal(request);
}

可以通过修改namemaxCount修改上传name和设置上传的文件数量

添加额外参数只需要在FileFieldsInterceptor方法的数组参数中扩充即可。

这样我们上传文件的Controller就已经实现了。

Service实现

在实现Controller时,我们事先定义了UploadService,现在就来实现这个ServiceService的骨架我就不写了,这里主要是来实现uploadService.uploadLocal方法。

import * as fs from "fs";
//...

async uploadLocal(request) {
  const file = request.file[0];
  
  try {
    fs.readdirSync(`./public/uploads`);
  } catch (err) {
    fs.mkdirSync(`./public/uploads`);
  }
  
  const now = this.Moment().format("YYYYMMDD");
  
  try {
    fs.readdirSync(`./public/uploads/${now}`);
  } catch (err) {
    fs.mkdirSync(`./public/uploads/${now}`);
  }
  
  fs.writeFileSync(
    `./public/uploads/${now}/${file.originalname}`,
    file.buffer,
  );
  
  // 数据返回
  return {
    code:0,
    data:{
      fileUrl: `uploads/${now}/${file.originalname}`,
    }
  }
}

接下来介绍一下上述代码实现了什么?

  1. 引入fs模块用来读写文件
  2. 因为我这里只上传一个文件,所以读取request.file[0]拿到第一个文件
  3. 尝试读取public下是否有uploads目录,如果没有则创建
  4. 定义当日的时间并格式化为YYYYMMDD
  5. 尝试读取public/uploads下是否有当日的目录,如果没有则创建
  6. 将文件写入到当日的目录中
  7. 将本地路径返回

最终存放的路径如:public/uploads/20230416/demo.png

到此为止,一个简易的文件上传就实现了。

结语

我是一名前端程序员,但不止于前端,如果文章哪里写的有些欠妥,欢迎评论指正,大家一起学习,一起进步~