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 测试文件上传和下载功能:
- 单文件上传
POST http://localhost:3000/file/upload
Content-Type: multipart/form-data
file: [选择文件]
- 多文件上传
POST http://localhost:3000/file/upload-multiple
Content-Type: multipart/form-data
files: [选择多个文件]
- 文件下载
GET http://localhost:3000/file/filename.jpg
11. 实现文件管理功能
- 创建文件实体
// 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;
}
- 更新文件服务
// 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);
}
}
- 更新文件控制器
// 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. 常见问题
-
文件上传失败
- 检查文件大小限制
- 验证文件类型
- 确保上传目录存在且有写入权限
-
文件下载失败
- 检查文件路径是否正确
- 验证文件是否存在
- 确保有读取权限
-
文件管理问题
- 定期清理未使用的文件
- 实现文件备份机制
- 考虑使用云存储服务
14. 下一步
在下一篇文章中,我们将:
- 实现 WebSocket 服务
- 实现实时消息推送
- 实现聊天功能
- 实现通知功能