上传资源
multer 是一个基于 Express 的中间件,用于处理 multipart/form-data
格式的数据,主要用于上传文件。
NestJS 内置了 multer,可以使用 @nestjs/platform-express 包中导出的 FileInterceptor
、FilesInterceptor
等拦截器来使用 multer 的功能。
npm i multer
、npm i @types/multer -D
nest g res user --no-spec
- 配置 module 文件
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { MulterModule } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { extname, join } from 'path';
// 配置文件上传
const multerOptions: MulterOptions = {
// 配置文件的存储
storage: diskStorage({
// 存储地址
destination: join(__dirname, '../../../public/notification-attachment'),
// 存储名称
filename: (_req, file, callback) => {
const suffix = extname(file.originalname); // 获取文件后缀
const docName = new Date().getTime(); // 自定义文件名
return callback(null, `${docName}${suffix}`);
},
}),
// 过滤存储的文件
fileFilter: (_req, file, callback) => {
// multer 默认使用 latin1 编码来解析文件名, 而 latin1 编码不支持中文字符, 所以会出现中文名乱码的现象
// 这里将文件名从 latin1 编码转换为 Buffer 对象, 再用 toString('utf8') 将 Buffer 对象转换为 utf8 编码的字符串
// utf8 是一种支持多国语言的编码方式, 这样就可以保证文件名的中文字符不会被错误解析
file.originalname = Buffer.from(file.originalname, 'latin1').toString(
'utf8',
);
callback(null, true);
},
// 限制文件大小
limits: {
// 限制文件大小为 10 MB
fileSize: 10 * 1024 * 1024, // 默认无限制
// 限制文件名长度为 50 bytes
fieldNameSize: 50, // 默认 100 bytes
},
};
@Module({
imports: [
MulterModule.register(multerOptions),
// 如需异步配置, 可使用 MulterModule.registerAsync
],
controllers: [UserController],
})
export class UserModule {}
- 配置 controller 文件
上传单个文件时,使用拦截器 FileInterceptor('参数名')
,FileInterceptor
会将上传的文件注入到控制器方法的参数中,你可以用 @UploadedFile()
装饰器来获取它
import {
Controller,
Post,
UseInterceptors,
UploadedFile,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express/multer';
@Controller('user')
export class UserController {
@Post('album')
@UseInterceptors(FileInterceptor('picture')) // 使用拦截器 FileInterceptor
upload(@UploadedFile() file: Express.Multer.File /* 获取上传的文件 */ ) {
return { file };
}
}
- 使用 Apifox 模拟前端上传图片
前端 POST 请求携带的参数名要与后端拦截器 FileInterceptor('参数名')
中的 '参数名'
一样
- 在 dist 目录下就可以预览到前端上传的文件啦
访问已上传的资源
- 配置静态资源路径
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express/interfaces';
import { join } from 'path';
import { AppModule } from './app.module';
async function bootstrap() {
// 显示配置 NestExpressApplication 类型, 以获取更好的语法提示
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useStaticAssets(join(__dirname, './images')); // 配置静态资源目录
await app.listen(3000);
}
bootstrap();
到 dist 目录下复制上传的图片文件名 XXX
,打开 http://127.0.0.1:3000/XXX
即可访问上传的图片
- 配置静态资源虚拟路径
app.useStaticAssets(join(__dirname, './images'), {
prefix: '/static', // 配置静态资源虚拟路径
});
现在需要打开 http://127.0.0.1:3000/static/XXX
才能访问上传的图片
下载资源
直接下载
使用 Express 的方法 res.download(path[, filename])
:
path
是一个字符串,表示文件的绝对或相对路径;
filename
是一个可选的字符串,表示建议的文件名,如果省略,则使用 path
的基本名称;
- 配置 controller 文件
import { Controller, Get, Res } from '@nestjs/common';
import { Response } from 'express';
import { join } from 'path';
@Controller('user')
export class UserController {
@Get('album')
getImg(@Res() res: Response) {
const url = join(__dirname, '../images/1676791106510.png'); // 正常开发中 url 应该是从数据库中的
res.download(url); // 将指定路径的文件作为附件传输给浏览器
}
}
现在访问 http://127.0.0.1:3000/user/album 即可下载图片文件
- 配置前端下载:
<template>
<el-button @click="download('/upload/export')">下载图片资源</el-button>
</template>
<script lang="ts" setup>
const download = (url: string) => {
window.open(url);
};
</script>
文件流下载
compressing 用于压缩和解压缩文件和流,支持 gzip、deflate、zip、tar、tgz、tbz2 等格式。
npm i compressing
- 配置 controller 文件
import { Controller, Get, Res } from '@nestjs/common';
import { zip } from 'compressing';
import { Response } from 'express';
import { join } from 'path';
@Controller('user')
export class UserController {
@Get('photo')
getImg(@Res() res: Response) {
const url = join(__dirname, '../images/1676791106510.png'); // 正常开发中 url 应该是从数据库中的
const targetStream = new zip.Stream(); // 创建一个可读的压缩流, 可以将任何数据压缩成 zip 格式
targetStream.addEntry(url); // 向压缩流中添加文件
res.setHeader('Content-Type', 'application/octet-stream'); // 设置文件格式为 '流'
res.setHeader(
'Content-Disposition', // 设置文件以什么方式呈现
'attachment; filename=superman',
// attachment 表示文件应该被下载到本地; filename=superman 表示下载文件的文件名
);
targetStream.pipe(res); // 将压缩流输出给响应流
}
}
现在访问 http://127.0.0.1:3000/user/photo 即可下载 “流” 文件
- 前端解析并下载 “流” 文件
<template>
<el-button @click="downloadByStream('/upload/stream')">下载流文件</el-button>
</template>
<script lang="ts" setup>
const downloadByStream = async (url: string) => {
const res = await fetch(url).then(res => res.arrayBuffer());
// 注意: 前端接收的是流文件, 这里需要返回 res.arrayBuffer() 而不是 res.json()
// 如果是使用 axios, 则需要设置 responseType 为 ArrayBuffer / Blob
const blob = new Blob([res]); // 将流文件转成 blob 形式
const imgUrl = URL.createObjectURL(blob); // 通过 blob 生成可访问的链接
const a = document.createElement('a');
a.href = imgUrl;
a.download = 'superman.zip';
a.click();
};
</script>