NestJS 入门教程(四):文件上传和下载功能的实现

1. 前言

在上一篇文章中,我们实现了用户认证和授权功能。本文将介绍如何实现文件上传和下载功能,包括单文件上传、多文件上传、文件下载等。

2. 安装依赖

首先安装文件处理相关的依赖:

pnpm add @nestjs/platform-express multer @types/multer

3. 创建文件模块

使用 NestJS CLI 创建文件模块:

nest g module file
nest g controller file
nest g service file

4. 配置文件上传

src/modules/file/config/multer.config.ts 中创建 Multer 配置:

import { diskStorage } from 'multer';
import { extname } from 'path';
import { v4 as uuid } from 'uuid';

export const multerConfig = {
  storage: diskStorage({
    destination: './uploads',
    filename: (req, file, callback) => {
      const fileExtName = extname(file.originalname);
      const fileName = `${uuid()}${fileExtName}`;
      callback(null, fileName);
    },
  }),
  fileFilter: (req, file, callback) => {
    if (!file.originalname.match(/\.(jpg|jpeg|png|gif|pdf|doc|docx|xls|xlsx)$/)) {
      return callback(new Error('Only image, PDF and document files are allowed!'), false);
    }
    callback(null, true);
  },
  limits: {
    fileSize: 5 * 1024 * 1024, // 5MB
  },
};

5. 实现文件服务

src/modules/file/file.service.ts 中实现文件服务:

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { join } from 'path';
import { existsSync, mkdirSync } from 'fs';

@Injectable()
export class FileService {
  constructor(private configService: ConfigService) {
    const uploadPath = join(process.cwd(), 'uploads');
    if (!existsSync(uploadPath)) {
      mkdirSync(uploadPath);
    }
  }

  getFileUrl(filename: string): string {
    const baseUrl = this.configService.get('BASE_URL');
    return `${baseUrl}/uploads/${filename}`;
  }

  getFilePath(filename: string): string {
    return join(process.cwd(), 'uploads', filename);
  }
}

6. 实现文件控制器

src/modules/file/file.controller.ts 中实现文件控制器:

import { Controller, Post, UploadedFile, UseInterceptors, Get, Param, Res, UseGuards } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { FileService } from './file.service';
import { Response } from 'express';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { multerConfig } from './config/multer.config';

@Controller('file')
@UseGuards(JwtAuthGuard)
export class FileController {
  constructor(private readonly fileService: FileService) {}

  @Post('upload')
  @UseInterceptors(FileInterceptor('file', multerConfig))
  uploadFile(@UploadedFile() file: Express.Multer.File) {
    return {
      filename: file.filename,
      originalname: file.originalname,
      size: file.size,
      mimetype: file.mimetype,
      url: this.fileService.getFileUrl(file.filename),
    };
  }

  @Get(':filename')
  async getFile(@Param('filename') filename: string, @Res() res: Response) {
    const filePath = this.fileService.getFilePath(filename);
    res.sendFile(filePath);
  }
}

7. 更新文件模块

src/modules/file/file.module.ts 中更新模块配置:

import { Module } from '@nestjs/common';
import { FileController } from './file.controller';
import { FileService } from './file.service';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [ConfigModule],
  controllers: [FileController],
  providers: [FileService],
  exports: [FileService],
})
export class FileModule {}

8. 实现多文件上传

src/modules/file/file.controller.ts 中添加多文件上传方法:

import { Controller, Post, UploadedFiles, UseInterceptors, UseGuards } from '@nestjs/common';
import { FilesInterceptor } from '@nestjs/platform-express';
import { FileService } from './file.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { multerConfig } from './config/multer.config';

@Controller('file')
@UseGuards(JwtAuthGuard)
export class FileController {
  // ... 其他代码 ...

  @Post('upload-multiple')
  @UseInterceptors(FilesInterceptor('files', 10, multerConfig))
  uploadMultipleFiles(@UploadedFiles() files: Array<Express.Multer.File>) {
    return files.map(file => ({
      filename: file.filename,
      originalname: file.originalname,
      size: file.size,
      mimetype: file.mimetype,
      url: this.fileService.getFileUrl(file.filename),
    }));
  }
}

9. 更新环境变量

.env 文件中添加基础 URL:

BASE_URL=http://localhost:3000

10. 测试文件上传和下载

使用 Postman 测试文件上传和下载功能:

  1. 单文件上传
POST http://localhost:3000/file/upload
Content-Type: multipart/form-data

file: [选择文件]
  1. 多文件上传
POST http://localhost:3000/file/upload-multiple
Content-Type: multipart/form-data

files: [选择多个文件]
  1. 文件下载
GET http://localhost:3000/file/filename.jpg

11. 实现文件管理功能

  1. 创建文件实体
// src/modules/file/entities/file.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';

@Entity('files')
export class File {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  filename: string;

  @Column()
  originalname: string;

  @Column()
  mimetype: string;

  @Column()
  size: number;

  @Column()
  url: string;

  @CreateDateColumn()
  createdAt: Date;
}
  1. 更新文件服务
// src/modules/file/file.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { File } from './entities/file.entity';
import { ConfigService } from '@nestjs/config';
import { join } from 'path';
import { existsSync, mkdirSync, unlinkSync } from 'fs';

@Injectable()
export class FileService {
  constructor(
    @InjectRepository(File)
    private fileRepository: Repository<File>,
    private configService: ConfigService,
  ) {
    const uploadPath = join(process.cwd(), 'uploads');
    if (!existsSync(uploadPath)) {
      mkdirSync(uploadPath);
    }
  }

  async create(file: Express.Multer.File) {
    const fileEntity = this.fileRepository.create({
      filename: file.filename,
      originalname: file.originalname,
      mimetype: file.mimetype,
      size: file.size,
      url: this.getFileUrl(file.filename),
    });
    return this.fileRepository.save(fileEntity);
  }

  async findAll() {
    return this.fileRepository.find();
  }

  async findOne(id: number) {
    return this.fileRepository.findOne({ where: { id } });
  }

  async remove(id: number) {
    const file = await this.findOne(id);
    if (file) {
      const filePath = this.getFilePath(file.filename);
      if (existsSync(filePath)) {
        unlinkSync(filePath);
      }
      await this.fileRepository.delete(id);
    }
  }

  getFileUrl(filename: string): string {
    const baseUrl = this.configService.get('BASE_URL');
    return `${baseUrl}/uploads/${filename}`;
  }

  getFilePath(filename: string): string {
    return join(process.cwd(), 'uploads', filename);
  }
}
  1. 更新文件控制器
// src/modules/file/file.controller.ts
import { Controller, Post, UploadedFile, UseInterceptors, Get, Param, Res, UseGuards, Delete } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { FileService } from './file.service';
import { Response } from 'express';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { multerConfig } from './config/multer.config';

@Controller('file')
@UseGuards(JwtAuthGuard)
export class FileController {
  constructor(private readonly fileService: FileService) {}

  @Post('upload')
  @UseInterceptors(FileInterceptor('file', multerConfig))
  async uploadFile(@UploadedFile() file: Express.Multer.File) {
    return this.fileService.create(file);
  }

  @Get()
  findAll() {
    return this.fileService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.fileService.findOne(+id);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.fileService.remove(+id);
  }

  @Get('download/:filename')
  async getFile(@Param('filename') filename: string, @Res() res: Response) {
    const filePath = this.fileService.getFilePath(filename);
    res.download(filePath);
  }
}

12. 总结

本文介绍了如何:

  • 实现单文件上传
  • 实现多文件上传
  • 实现文件下载
  • 实现文件管理功能

在下一篇文章中,我们将介绍如何实现 WebSocket 实时通信功能。

13. 常见问题

  1. 文件上传失败

    • 检查文件大小限制
    • 验证文件类型
    • 确保上传目录存在且有写入权限
  2. 文件下载失败

    • 检查文件路径是否正确
    • 验证文件是否存在
    • 确保有读取权限
  3. 文件管理问题

    • 定期清理未使用的文件
    • 实现文件备份机制
    • 考虑使用云存储服务

14. 下一步

在下一篇文章中,我们将:

  • 实现 WebSocket 服务
  • 实现实时消息推送
  • 实现聊天功能
  • 实现通知功能
### 文件上传功能实现NestJS实现文件上传功能通常涉及以下几个方面: #### 1. 安装依赖库 为了支持 `multipart/form-data` 类型的数据解析,需要安装 `@nestjs/platform-express` `multer` 库。可以通过以下命令完成安装: ```bash npm install @nestjs/platform-express multer --save ``` #### 2. 配置 MulterModule 通过配置 `MulterModule` 来定义文件存储路径命名规则。以下是基于引用[^2]的一个示例配置: ```typescript import { Module } from '@nestjs/common'; import { UploadController } from './upload.controller'; import { UploadService } from './upload.service'; import { MulterModule } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { extname, join } from 'path'; @Module({ imports: [ MulterModule.register({ storage: diskStorage({ destination: join(__dirname, '../uploadFiles'), // 设置文件保存目录 filename: (req, file, cb) => { const randomName = Array(32).fill(null).map(() => Math.floor(Math.random() * 16).toString(16)).join(''); cb(null, `${randomName}${extname(file.originalname)}`); // 使用随机名称防止重复 }, }), }), ], controllers: [UploadController], providers: [UploadService], }) export class UploadModule {} ``` 此部分代码设置了文件存储位置以及自动生成唯一文件名的功能。 #### 3. 控制器中的文件接收逻辑 控制器负责处理前端发送过来的文件请求。下面是一个完整的例子,展示了如何结合 `FileInterceptor` 实现文件上传[^1]: ```typescript import { Controller, Post, UseInterceptors, UploadedFile, Body } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { ApiOperation, ApiConsumes } from '@nestjs/swagger'; import { CreateFileManageDto } from './dto/create-file-manage.dto'; import { FileManageService } from './file-manage.service'; @Controller('files') export class FileController { constructor(private readonly fileManageService: FileManageService) {} @Post('upload') @ApiOperation({ summary: '上传文件' }) @ApiConsumes('multipart/form-data') // 声明接受 multipart/form-data 数据类型 @UseInterceptors(FileInterceptor('file')) upload(@UploadedFile() file, @Body() fileInfo: CreateFileManageDto) { console.log(file); console.log(fileInfo); return this.fileManageService.create(fileInfo); } } ``` 这段代码实现文件上传的核心逻辑,并且集成了 Swagger 文档的支持[^3]。 #### 4. 处理异常情况 如果希望捕获并优雅地处理可能发生的错误,则可以引入全局异常过滤器来增强系统的健壮性[^4]: ```typescript // main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { HttpExceptionFilter } from './filters/http-exception.filter'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 添加全局异常过滤器 app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(3000); } bootstrap(); ``` 这样当发生任何未被捕获的 HTTP 错误时,都会被该过滤器拦截并返回标准化响应给客户端。 --- ### 总结 以上就是在 NestJS实现文件上传的主要流程,涵盖了从环境搭建到具体编码实践的过程。需要注意的是实际开发过程中还应考虑安全性因素比如验证文件大小、类型等限制条件以保护服务器资源免受恶意攻击影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值