Nest 文件上传与下载
在Web开发中,文件上传与下载功能是常见的需求,NestJS框架提供了简便的解决方案,支持多种场景下的文件处理。
1. 文件上传
NestJS 提供了基于 multer 中间件的内置模块来处理文件上传,它支持多种存储方式及灵活的配置。本例中,multer 的存储方式通过 diskStorage 配置,文件将存储在服务器的磁盘上,并且文件名带有时间戳以避免文件名冲突。
1.1. Multer 配置
在 file.ts 文件中,我们配置了 multer 使用磁盘存储方式来存储上传文件,文件会被保存在按日期命名的目录下,文件名则是根据当前时间戳和文件 MIME 类型生成的。
import { join } from 'path';
import { diskStorage } from 'multer';export default {root: join(__dirname, '../uploads'), // 上传文件的根目录storage: diskStorage({destination: join(__dirname, `../uploads/${new Date().toLocaleDateString()}`), // 按日期生成目录filename: (req, file, cb) => {const filename = `${new Date().getTime()}.${file.mimetype.split('/')[1]}`; // 根据时间戳和文件类型生成文件名return cb(null, filename);},}),
};
1.2. 文件上传实现
在 album.controller.ts 中,文件上传的具体实现通过 FileInterceptor 实现。通过 @UploadedFile() 装饰器提取上传的文件,并将文件信息传递给 AlbumService 进行处理。
import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { AlbumService } from './album.service';@Controller('album')
export class AlbumController {constructor(private readonly albumService: AlbumService) {}@Post()@UseInterceptors(FileInterceptor('file')) // 使用 FileInterceptor 拦截上传文件upload(@UploadedFile() file) {this.albumService.upload(file); // 将文件信息传递给服务层return true;}
}
-
FileInterceptor('file'):拦截HTTP POST请求中的文件,'file'为表单中上传字段的名称。
-
@UploadedFile():提取上传的文件信息。
-
albumService.upload():将文件信息传递给业务层处理。
1.3. 动态配置Multer存储策略
通过 MulterModule.registerAsync 实现了基于配置服务的动态加载,文件上传路径等配置信息可通过 nestjs-config 动态获取。
import { Module } from '@nestjs/common';
import { MulterModule } from '@nestjs/platform-express';
import { ConfigService } from 'nestjs-config';
import { AlbumController } from './album.controller';
import { AlbumService } from './album.service';@Module({imports: [MulterModule.registerAsync({useFactory: (config: ConfigService) => config.get('file'), // 动态加载配置inject: [ConfigService],}),],controllers: [AlbumController],providers: [AlbumService],
})
export class AlbumModule {}
通过 ConfigService 动态获取上传路径和文件存储策略,便于在不同环境下灵活调整。
2. 文件下载
文件下载有不同的方案实现:
-
将文件打包为 tar 或者 zip 然后下载。
-
通过流进行下载。
在本例中,实现了将所有上传文件打包成 tar 文件并提供下载的功能。
2.1. 实现文件打包与下载
在 AlbumService 中,我们使用了 compressing 库来将文件夹打包成 tar 文件并通过 tarStream 流的方式传递给客户端。
import { Injectable } from '@nestjs/common';
import { tar } from 'compressing'; // 使用 compressing 库进行文件打包
import { ConfigService } from 'nestjs-config';@Injectable()
export class AlbumService {constructor(private readonly configService: ConfigService) {}// 文件上传逻辑upload(file) {console.log(file);}// 文件下载逻辑async downloadAll() {const uploadDir = this.configService.get('file').root; // 获取文件上传目录const tarStream = new tar.Stream(); // 创建 tar 压缩流await tarStream.addEntry(uploadDir); // 将上传目录添加到压缩流中return { filename: 'hello-world.tar', tarStream };}
}
2.2. 实现文件下载控制器
在控制器中,通过 @Res() 响应对象设置HTTP头信息,将压缩包文件流传递给客户端,实现文件的下载。
import { Controller, Get, Res } from '@nestjs/common';
import { Response } from 'express';
import { AlbumService } from './album.service';@Controller('album')
export class AlbumController {constructor(private readonly albumService: AlbumService) {}@Get('export')async downloadAll(@Res() res: Response) {const { filename, tarStream } = await this.albumService.downloadAll(); // 获取文件名和压缩流res.setHeader('Content-Type', 'application/octet-stream'); // 设置文件类型res.setHeader('Content-Disposition', `attachment; filename=${filename}`); // 设置文件名tarStream.pipe(res); // 将压缩流传递给响应对象}
}
-
@Res():通过直接访问HTTP响应对象来设置下载文件的头部信息。
-
res.setHeader():设置响应头,使浏览器将响应作为文件下载处理。
-
tarStream.pipe(res):将文件压缩流直接写入响应,实现文件下载。
2.3. 文件流下载
通过 StreamableFile 类,可以将文件流传输给客户端,并且能够灵活地设置 Content-Type、Content-Disposition 等响应头信息,确保下载的文件具备合适的格式与名称。
我们将通过流式传输方式处理文件,并通过 StreamableFile 类将其返回给客户端。
import { Controller, Get, StreamableFile } from '@nestjs/common';
import { createReadStream } from 'fs';
import { join } from 'path';@Controller('album')
export class FileController {@Get('stream')getFile(): StreamableFile {const file = createReadStream(join(process.cwd(), 'package.json')); // 读取文件的流return new StreamableFile(file); // 使用 StreamableFile 返回文件流}
}
-
createReadStream():使用 Node.js 的 fs 模块创建文件的可读流。
-
StreamableFile:NestJS 提供的类,用于返回流文件。它会自动处理文件流的传输,确保下载流畅。
在流式下载中,我们可能需要设置响应头,比如指定文件的 Content-Type 或 Content-Disposition(文件名和下载方式)。
import { Controller, Get, StreamableFile, Res, Header } from '@nestjs/common';
import { createReadStream } from 'fs';
import { join } from 'path';
import { Response } from 'express';@Controller('file')
export class FileController {@Get('stream-with-header')@Header('Content-Type', 'application/json') // 设置Content-Type为JSON@Header('Content-Disposition', 'attachment; filename="package.json"') // 设置文件名getFileUsingHeader(): StreamableFile {const file = createReadStream(join(process.cwd(), 'package.json'));return new StreamableFile(file);}@Get('stream-direct')getFileWithResponse(@Res({ passthrough: true }) res: Response): StreamableFile {const file = createReadStream(join(process.cwd(), 'package.json'));res.set({'Content-Type': 'application/json', // 设置文件类型'Content-Disposition': 'attachment; filename="package.json"', // 设置文件下载名});return new StreamableFile(file);}
}
-
@Header():直接在控制器方法上设置响应头,例如 Content-Type 和 Content-Disposition。
-
res.set():通过 @Res() 访问 Express 响应对象,手动设置响应头。这里使用 passthrough: true,允许 StreamableFile 处理文件流,同时保留响应对象的控制。
通过引入 StreamableFile 类,我们增强了文件下载的功能,并且使其支持流式传输大文件。可以灵活设置文件的响应头、动态生成文件路径与文件名,并确保在处理大文件时能够有效利用内存。
流式下载非常适合在需要传输大文件(如媒体文件、压缩包等)的场景中使用。
3. 优化建议与扩展功能
3.1. 文件类型验证与大小限制
在文件上传中,可能需要对文件大小和文件类型进行限制。例如限制上传文件的大小不得超过1MB,并且只允许上传图片文件(如 jpeg,png 等)。可以结合 ParseFilePipe 来实现这些验证功能:
import { Controller, Post, UploadedFile, UseInterceptors, ParseFilePipe, MaxFileSizeValidator,FileTypeValidator } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { Express } from 'express';
import { AlbumService } from './album.service';@Controller('album')
export class AlbumController {constructor(private readonly albumService: AlbumService) {}@Post()@UseInterceptors(FileInterceptor('file'))uploadFileWithValidation(@UploadedFile(new ParseFilePipe({validators: [new MaxFileSizeValidator({ maxSize: 1024 * 1024 }), // 限制文件大小为1MBnew FileTypeValidator({ fileType: /jpeg|png/ }), // 仅允许jpeg和png类型],}),)file: Express.Multer.File,) {this.albumService.upload(file);return true;}
}
-
MaxFileSizeValidator:限制上传文件的最大大小,单位为字节。
-
FileTypeValidator:检查文件的 MIME 类型,确保仅允许指定类型的文件。
3.2. 异步配置Multer
在复杂的应用中,通常需要根据环境动态配置上传文件的路径或其他选项。可以使用 MulterModule.registerAsync() 来实现异步配置,如依赖环境变量或配置服务动态调整文件上传设置。
import { Module } from '@nestjs/common';
import { MulterModule } from '@nestjs/platform-express';
import { ConfigService } from 'nestjs-config';
import { AlbumController } from './album.controller';
import { AlbumService } from './album.service';@Module({imports: [MulterModule.registerAsync({useFactory: async (configService: ConfigService) => ({dest: configService.get<string>('UPLOAD_PATH'), // 根据配置或环境动态调整上传路径}),inject: [ConfigService],}),],controllers: [AlbumController],providers: [AlbumService],
})
export class AlbumModule {}
4. OSS 服务集成
在此基础上,我们还可以通过集成阿里云 OSS 服务,实现资源上传至 OSS 服务器等功能。
https://help.aliyun.com/zh/oss/developer-reference/node-js-1/?spm=a2c4g.11186623.0.0.6c04b9302Jll4G