夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Node基础

  • 《MySQL》学习笔记

  • Midway

  • Nest

    • 开篇词
    • 学习理由
    • nest概念扫盲
    • 快速掌握 nestcli
    • 5种http数据传输方式
    • IoC 解决了什么痛点问题?
    • 如何调试 Nest 项目
    • Provider注入对象
    • 全局模块和生命周期
    • AOP 架构有什么好处?
    • 一网打尽 Nest 全部装饰器
    • Nest如何自定义装饰器
    • Metadata和Reflector
    • ExecutionContext切换上下文
    • Module和Provider的循环依赖处理
    • 如何创建动态模块
    • Nest和Express,fastify
    • Nest的Middleware
    • RxJS和Interceptor
    • 内置Pipe和自定义Pipe
    • ValidationPipe验证post请求参数
    • 如何自定义 Exception Filter
    • 图解串一串 Nest 核心概念
    • 接口如何实现多版本共存
    • Express如何使用multer实现文件上传
      • multer
      • cors
      • 多文件上传
        • 限制文件数量
        • 多字段限制文件数量
        • 获取所有文件字段
        • 修改保存的文件名
      • 总结
    • Nest使用multer实现文件上传
    • 图书管理系统
    • 大文件分片上传
    • 最完美的 OSS 上传方案
    • Nest里如何打印日志
    • 为什么Node里要用Winston打印日志
    • Nest 集成日志框架 Winston
    • 通过Desktop学Docker也太简单了
    • 你的第一个 Dockerfile
  • 其他

  • 服务端
  • Nest
神说要有光
2025-03-10
目录

Express如何使用multer实现文件上传

# multer

Nest 的文件上传是基于 Express 的中间件 multer 实现的,所以在学习 Nest 文件上传之前,我们先学习下 multer 包的使用。

新建目录,npm init -y 创建 package.json

然后安装 express 和 multer 还有 cors 包:

npm install express multer cors

# cors

这个 cors 包是处理跨域 header 的。

然后创建 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('/aaa', upload.single('aaa'), function (req, res, next) {
  console.log('req.file', req.file);
  console.log('req.body', req.body);
})

app.listen(3333);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

app.use 使用中间件 cors 来处理跨域。

用 multer 处理文件上传,指定保存目录为 uploads/。

然后新建这样一个 index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
    <input id="fileInput" type="file"/>
    <script>
        const fileInput = document.querySelector('#fileInput');

        async function formData() {
            const data = new FormData();
            data.set('name','光');
            data.set('age', 20);
            data.set('aaa', fileInput.files[0]);

            const res = await axios.post('http://localhost:3333/aaa', data);
            console.log(res);
        }

        fileInput.onchange = formData;
    </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

通过 FormData + axios 上传文件,指定内容的传输格式 content-type 为 multipart/form-data。

(这里 axios 会自动根据内容指定 content-type,不需要手动指定)

然后用 node 把 server 跑起来,并且用 http-server 把静态服务跑起来:

浏览器访问下:

这时候在 devtools 可以看到 aaa 请求的 body 是多个 boundary 分隔的格式:

而分隔符是在 Content-Type 指定的:

这就是 form-data 的传输格式。

然后去服务端看看:

req.file 可以拿到文件字段,其余非文件字段在 req.body。

并且服务端多了 uploads 目录,下面就保存着我们上传的文件:

# 多文件上传

单文件上传我们会了,那多文件上传呢?

再添加一个路由处理多文件的上传:

bbb 路由通过 array 方法来取上传的文件,并且指定最大数量的限制。

上传的文件通过 req.files 来取。

然后前端这样传:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
    <input id="fileInput" type="file" multiple/>
    <script>
        const fileInput = document.querySelector('#fileInput');

        async function formData2() {
            const data = new FormData();
            data.set('name','光');
            data.set('age', 20);
            [...fileInput.files].forEach(item => {
                data.append('bbb', item)
            })

            const res = await axios.post('http://localhost:3333/bbb', data);
            console.log(res);
        }

        fileInput.onchange = formData2;
    </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

input 标签添加 multiple 属性允许多选。

onchange 的时候取出每个 file,通过 append 方法添加到 bbb 字段。

(这里fileInput.files 是一个伪数组,要转成数组才能用 forEach 方法)

这样 bbb 实际上就保存着上传的多个文件了。

我们传下试试:

node 服务的 req.files 接收到了多个文件:

并且 uploads 目录下也多了俩文件:

这就是多文件上传。

# 限制文件数量

那如果传了超过 2 个文件呢?

会报错。

我们添加一个错误处理中间件:

在 express 里,约定有 4 个参数的中间件为错误处理中间件。

一旦某个中间件出了错,express 就会向后找错误处理中间件来调用,如果没有,那就用默认错误处理中间件,返回 500 响应。

我们多传几个文件:

可以看到服务端打印了报错:

这样我们只要返回对应的响应就好了:

app.post('/bbb', upload.array('bbb', 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 MulterError && err.code === 'LIMIT_UNEXPECTED_FILE') {
        res.status(400).end('Too many files uploaded');
    }
})
1
2
3
4
5
6
7
8

这样再次上传超过 2 个文件,就会收到服务端的 400 的响应,提示文件上传过多:

# 多字段限制文件数量

更复杂一点的情况,如果多个字段都会上传文件,而且限制也都不同呢?

那可以这样处理:

app.post('/ccc', upload.fields([
    { name: 'aaa', maxCount: 3 },
    { name: 'bbb', maxCount: 2 }
]), function (req, res, next) {
    console.log('req.files', req.files);
    console.log('req.body', req.body);
})
1
2
3
4
5
6
7

通过 fields 方法指定每个字段的名字和最大数量,然后接收到请求后通过 req.files['xxx'] 来取对应的文件信息。

其他非文件字段,同样是通过 req.body 来取。

前端写下对应的代码:

async function formData3() {
    const data = new FormData();
    data.set('name','光');
    data.set('age', 20);
    data.append('aaa', fileInput.files[0]);
    data.append('aaa', fileInput.files[1]);
    data.append('bbb', fileInput.files[2]);
    data.append('bbb', fileInput.files[3]);

    const res = await axios.post('http://localhost:3333/ccc', data);
    console.log(res);
}
1
2
3
4
5
6
7
8
9
10
11
12

这里本来要写两个 file input 分别上传 aaa、bbb 的文件,这里为了测试方便简化了下。

浏览器里再次上传:

服务端收到了 aaa 和 bbb 的文件:

# 获取所有文件字段

此外,如果我们并不知道有哪些字段是 file 呢?

这时候可以用 any 来接收:

app.post('/ddd', upload.any(), function(req, res, next) {
    console.log('req.files', req.files);
    console.log('req.body', req.body);
});
1
2
3
4

改下前端代码,这次设置 aaa、bbb、ccc、ddd 4 个 file 字段:

async function formData4() {
    const data = new FormData();
    data.set('name','光');
    data.set('age', 20);
    data.set('aaa', fileInput.files[0]);
    data.set('bbb', fileInput.files[1]);
    data.set('ccc', fileInput.files[2]);
    data.set('ddd', fileInput.files[3]);

    const res = await axios.post('http://localhost:3333/ddd', data);
    console.log(res);
}
1
2
3
4
5
6
7
8
9
10
11
12

再次上传 4 个文件,可以看到服务端接收到了:

只不过这时候不是 key、value 的形式了,需要自己遍历数组来查找。

# 修改保存的文件名

还有一个问题,如何修改保存的文件名呢?

之前是通过 dest 指定了保存的目录:

现在这样写:

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)
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

自己指定怎么存储,multer.distkStorage 是磁盘存储,通过 destination、filename 的参数分别指定保存的目录和文件名。

这里要先创建下用到的目录,然后再返回它的路径。

file 对象就是之前打印的那种:

我们用时间戳 Date.now() 加上Math.random() 乘以 10 的 9 次方,然后取整,之后加上原来的文件名。

测试下:

然后浏览器再次上传:

就可以看到目录和文件名都修改了:

案例代码在小册仓库 (opens new window)。

# 总结

express 的 multer 包是用来处理 multipart/form-data 格式的文件上传请求的。

通过 single 方法处理单个字段的单个文件,array 方法处理单个字段的多个文件,fields 方法处理多个字段的文件,any 处理任意数量字段的文件,分别用 req.file 和 req.files 来取解析出的文件。

其余非文件字段不会处理,还是通过 req.body 来取。

类似文件数量过多等错误,会抛出对应的 error 对象,在错误处理中间件里处理并返回对应的响应就好了。

Nest 的文件上传就是通过 multer 包实现的,下节我们来学下 Nest 里怎么使用 multer。

编辑 (opens new window)
上次更新: 2025/5/14 16:47:16
接口如何实现多版本共存
Nest使用multer实现文件上传

← 接口如何实现多版本共存 Nest使用multer实现文件上传→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式