Express 中的文件上传
Nest 的文件上传是基于 Express 中间件 multer 实现的,所以需要先了解下 multer 包的使用。
npm init -y # 创建一个 package.json
npm install express multer cors # 安装依赖包
cors 是处理跨域用的。
创建 index.js 文件,并写入内容
const express = require("express");
const multer = require("multer");
const cors = require("cors");
const app = express();
app.use(cors());
const upload = multer({ dest: "uploads/" });
app.post("/files", upload.single("myfiles"), function (req, res, next) {
console.log("req.file", req.file);
console.log("req.body", req.body);
});
app.listen(8888, () => {
console.log("服务启动,地址: http://localhost:8888");
});
启动服务,运行 node index.js
。
新建 index.html
,用于写前端的文件上传交互
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input id="fileControll" type="file" />
</body>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
<script>
const fileInput = document.querySelector("#fileControll");
fileInput.addEventListener("change", async (event) => {
const file = event.target.files[0];
const data = new FormData();
data.append("myfiles", file);
const res = await axios.post("http://localhost:8888/files", data);
console.log(res);
});
</script>
</html>
此时点击文件上传,可以查看终端的打印和文件目录
多文件上传
新增一个路由,来实现多文件上传
更改前端的交互
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input id="fileControll" type="file" multiple /> // 标识为多选
</body>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
<script>
const fileInput = document.querySelector("#fileControll");
fileInput.addEventListener("change", async (event) => {
const files = event.target.files;
const data = new FormData();
// files 是一个 FileList 伪数组,需转成数组
Array.from(files).forEach((file) => data.append("multfiles", file));
const res = await axios.post("http://localhost:8888/mult-files", data);
});
</script>
</html>
限制文件上传数量
express 中,约定有 4 个参数的中间件为错误处理中间件。
一旦某个中间件出了错,express 就会向后找错误处理中间件来调用,如果没有,那就用默认错误处理中间件,返回 500 响应。
多字段多文件上传
当存在多个字段都会上传文件,而且限制也不同时,可以采用 upload.fields([...])
app.post(
"/mult-files-fields",
upload.fields([
{ name: "a", maxCount: 3 },
{ name: "b", maxCount: 2 },
]), // 这里配置成数组
function (req, res, next) {
console.log("req.files", req.files);
console.log("req.body", req.body);
},
function (err, req, res, next) {
if (err instanceof multer.MulterError && err.code === "LIMIT_UNEXPECTED_FILE") {
res.status(400).end("超出文件最大上传数量");
}
}
);
通过 fields
方法指定每个字段的名字和最大数量,然后接收到请求后通过 req.files['a']
、req.files['b']
这种格式来取对应的文件信息
不确定的文件字段名
当不确定,或者不限制文件的上传名时,可以使用 upload.any()
app.post("/mult-files-any", upload.any(), function (req, res, next) {
console.log("req.files", req.files);
console.log("req.body", req.body);
});
此时的 files 就不是以 key value 格式传输的,而是把字段名添加到每一项的 fieldname
中,需要自行取值
更改上传文件的文件名
express 内置存储的文件名,仅仅是一串随机 uuid,如果想让文件变得更有规律和可读性,可以自定义文件名。
const fs = require("fs");
const path = require("path");
const storage = multer.diskStorage({
destination: function (req, file, cb) {
try {
fs.mkdirSync(path.join(process.cwd(), "my-uploads"));
} catch (e) {}
cb(null, path.join(process.cwd(), "my-uploads"));
},
filename: function (req, file, cb) {
const uniqueSuffix =
Date.now() +
"-" +
Math.round(Math.random() * 1e9) +
"-" +
file.originalname;
cb(null, file.fieldname + "-" + uniqueSuffix);
},
});
const upload = multer({ storage })
此时的文件名就不能使用 dest
直接指定了。multer.distkStorage
是磁盘存储,通过 destination
、filename
的参数分别指定保存的目录和文件名。
两个文件是不同的字段名,分别为 a
b
,后边是时间戳+随机数,最后面就是文件的源文件名。
Nest 中的文件上传
在了解完 express 上传之后,就可以使用 Nest 来进行使用了,因为 Nest 本身支持的就是 express 的文件上传,若要使用 fastify 的文件上传需进行封装,是社区实现的。
安装 multer
的类型包
pnpm i @types/multer -D
访问静态资源
pnpm i @nestjs/serve-static
// app.module.ts
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'client'), // 标识静态资源在 client 文件夹下
}),
// ...
],
controllers: [AppController],
providers: [AppService],
})
// client/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input type="file" id="fileControll" />
</body>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
<script>
const fileInput = document.querySelector('#fileControll');
fileInput.addEventListener('change', async (event) => {
const file = event.target.files[0];
const data = new FormData();
data.append('filename', file);
const res = await axios.post('/person/upload', data);
console.log(res);
});
</script>
</html>
单文件上传
在 controller 中进行文件接收,字段名和接口名与前端保持一致
@Post('upload')
@UseInterceptors(FileInterceptor('filename', { dest: 'uploads' }))
uploadFile(@UploadedFile() file: Express.Multer.File, @Body() body) {
return { file, body };
}
此时上传文件,前端拿到的结果就是
后台的指定文件夹中也新增了一个文件
多文件上传和限制上传数量
将 FileInterceptor
换成 FilesInterceptor
,区别就是多加了个 s,此时第二个参数是上传的数量限制,第三个参数才和 FileInterceptor
一样是指定的上传目录。
同时,controller 入参的装饰器 @UploadedFile()
要改为 @UploadedFiles()
,也是加个 s
@Post('upload')
@UseInterceptors(FilesInterceptor('filename', 3, { dest: 'uploads' }))
uploadFile(@UploadedFiles() file: Express.Multer.File[], @Body() body) {
return { file, body };
}
以上是文件上传了两个,在 controller 中,制定了文件最大的上传数量 3,此时若上传四个
直接就是请求报错。
多文件多字段上传
和 express 中是类似的,不过要把 controller 上的装饰器 @FilesInterceptor
换成 @FileFieldsInterceptor
。
@Post('upload')
@UseInterceptors(
FileFieldsInterceptor(
[
{ name: 'a', maxCount: 1 },
{ name: 'b', maxCount: 2 },
],
{ dest: 'uploads' },
),
)
uploadFile(@UploadedFiles() files: Express.Multer.File[], @Body() body) {
return { files, body };
}
后台接收到的 files 就是这样子的
和 express 完全一样
不确定的文件字段名
当可以使用随机字段名上传文件时,可以使用 AnyFilesInterceptor
@Post('upload')
@UseInterceptors(AnyFilesInterceptor({ dest: 'uploads' }))
uploadFile(@UploadedFiles() files: Express.Multer.File[], @Body() body) {
return { files, body };
}
此时,任意的文件字段名,都会被 Nest 承认。
更改上传文件的文件名
Nest 也同样可以在上传时更改文件的存储文件名。
// storage.ts
import * as multer from 'multer';
import fs from 'fs';
import path from 'path';
const storage = multer.diskStorage({
destination: function (req, file, cb) {
try {
fs.mkdirSync(path.join(process.cwd(), 'my-uploads'));
} catch (e) {}
cb(null, path.join(process.cwd(), 'my-uploads'));
},
filename: function (req, file, cb) {
const uniqueSuffix =
Date.now() +
'-' +
Math.round(Math.random() * 1e9) +
'-' +
file.originalname;
cb(null, file.fieldname + '-' + uniqueSuffix);
},
});
export { storage };
在 controller 中使用
限制文件大小和类型(ParseFilePipe)
Nest 内置的管道 ParseFilePipe 就是专门做文件的校验的
MaxFileSizeValidator
是校验文件大小,FileTypeValidator
是校验文件类型。
既然是 Pipe,当然也提供了自定义错误和状态码的能力
此外,
FileTypeValidator
还支持正则匹配:new FileTypeValidator( { fileType : /^image\/(jpg|jpeg|png|gif)$/ } )
自定义文件上传的校验
只要继承 FileValidator
,就可以实现自定义的 validator
import { FileValidator } from '@nestjs/common';
export class MyFileSizeValidator extends FileValidator {
constructor(options) {
super(options);
}
isValid(file: Express.Multer.File): boolean | Promise<boolean> {
if (file.size > 1000000) {
return false;
}
return true;
}
buildErrorMessage(file: Express.Multer.File): string {
return `文件 ${file.originalname} 大小超出 1000k`;
}
}
标签:files,文件,系列,req,Nestjs,file,const,上传
From: https://www.cnblogs.com/jsonq/p/18065735