# 基于express+muter的文件上传
30 秒速记
- 标题里的
muter指的是multer,Express官方维护的multipart/form-data解析中间件 - 前端:
<form method="post" enctype="multipart/form-data">,或者FormData+fetch - 后端:
upload.single('logo')/upload.array('logo', 2)/upload.fields([...]),结果挂在req.file/req.files - 字段名必须前后端一致,不一致直接报
Unexpected field - 生产必配:
limits限大小和数量、fileFilter限类型、服务端生成文件名、错误中间件兜底
Express 自己不解析文件上传,express.json() 只管 JSON,表单传文件用的是 multipart/form-data 编码,要靠 multer 这类中间件把文件拆出来。 用法很简单:创建一个 upload 实例,按场景挂 single、array 或 fields,路由里从 req.file 或 req.files 拿文件信息。容易出问题的地方是字段名对不上、没限文件大小、错误没被接住,所以我会把 limits、fileFilter 和错误中间件当成标配。multer 1.x 有过几个拒绝服务漏洞,新项目直接用 2.x。
理解「基于express+muter的文件上传」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 middleware 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
面试官追问
追问 1前端用 JSON 提交,后端 req.body 里却没有文件,为什么?
JSON 里放不了文件内容,浏览器传文件要用 multipart/form-data,fetch 的话就是 body: formData,而且别手动设 Content-Type,让浏览器自己带上 boundary。后端没挂 multer 的话,文件一样拿不到。
追问 2上传接口返回 500,错误信息是 Unexpected field,怎么回事?
前端文件控件的 name 和 upload.single('xxx') 里的字段名不一样,或者 array 传的文件数超了上限。统一字段名;错误中间件里判断 err instanceof multer.MulterError,转成 400 加可读提示。
追问 3一个 2GB 的文件传上来,服务会怎样?怎么防?
默认不限大小,会一直往磁盘写,磁盘和带宽都被吃掉。配 limits: { fileSize: 5 * 1024 * 1024, files: 5 },超了 multer 直接中断并抛 LIMIT_FILE_SIZE;Nginx 前面再加一层 client_max_body_size。
追问 4multer 全局挂在 app.use 上好不好?
不好,只挂在需要上传的路由上。全局挂意味着所有接口都会尝试解析 multipart,攻击者可以往任意接口塞文件,临时文件还没人清理。
# 概览
30 秒速记
Node.js上传主流两个选择:multer(Express中间件)、formidable(不绑框架)- 底层都是解析
multipart/form-data请求体,用流式处理,不会把整个文件塞进内存 - 用了库只解决了「收到文件」,校验大小、类型、文件名、存储位置还是业务的事
Koa生态常用@koa/multer或koa-body,NestJS内置封装的就是multer
Node.js 做上传不用自己解析请求体,社区里 multer 和 formidable 都很成熟,Express 项目一般直接用 multer。 它们干的是同一件事:把 multipart/form-data 这种用分隔符拼起来的请求体拆开,文件流式写到磁盘或内存,普通字段放进 req.body。区别主要在接入方式,multer 是标准的 Express 中间件,formidable 是独立的解析器,哪个框架都能用。选完库只是开始,限制大小、过滤类型、生成安全的文件名这些都得自己配。
图片上传是web开发中经常用到的功能,node社区在这方面也有了相对完善的支持。
常用的开源组件有multer、formidable等,借助这两个开源组件,可以轻松搞定图片上传。
面试官追问
追问 1商品图片上传用了 multer,是不是就安全了?
不是,multer 默认什么都收。要自己配 fileFilter 只放图片类型,配 limits 限大小,敏感场景再读文件头的魔数确认真的是图片,mimetype 是客户端说了算的。
追问 2multer 和 formidable 怎么选?
Express 或 NestJS 项目用 multer,挂路由上就行;不用 Express、或者想自己控制解析流程(比如边收边传到对象存储),formidable 更灵活。两者性能差别不是选型重点。
追问 3需求从单张头像改成最多 9 张图,只把 single 改成 array 就够了吗?
还要定:单张和总大小上限、返回结构从对象变数组、第 5 张失败了前 4 张算不算。前端也得改成同一个字段名追加多个文件,后端读 req.files 而不是 req.file。
追问 4大文件上传 multer 扛得住吗?
几十兆以内没问题,它是流式写盘的。再大就该做分片上传和断点续传,或者让前端拿签名地址直传对象存储,别让业务服务器扛流量。
# 环境初始化
30 秒速记
- 依赖就两个:
npm install express multer(--save在npm 5之后已经是默认行为) - 示例结构:
app.js处理上传,form.html提供上传表单 - 读文件用
path.join(__dirname, 'form.html'),别依赖进程启动目录 - 把启动命令写进
package.json的scripts,本地和CI跑同一条
环境准备很简单,装 express 和 multer 两个包,然后一个 app.js 写服务端、一个 form.html 写上传表单。 命令里同一个包写两遍不会装两次,npm 会自动去重,package.json 里也只有一条。--save 从 npm 5 开始就是默认的,现在可以不写。有个小坑值得提前避开:读 form.html 用的是相对路径 ./form.html,它相对的是你在哪个目录执行 node,换个目录启动就找不到文件,用 path.join(__dirname, 'form.html') 最稳。
非常简单,一行命令。
npm install express multer multer --save
每个示例下面,都有下面两个文件
➜ upload-custom-filename git:(master) ✗ tree -L 1
.
├── app.js # 服务端代码,用来处理文件上传请求
├── form.html # 前端页面,用来上传文件
面试官追问
追问 1npm install express multer multer 装了两份 multer 吗?
没有,重复的包名会被去重,node_modules 里只有一份,package.json 里也只记一条。写重复了不报错而已。
追问 2在项目目录跑 node app.js 正常,在上一级跑 node demo/app.js 就报找不到 form.html,为什么?
fs.readFileSync('./form.html') 的相对路径是相对 process.cwd(),不是相对 app.js 所在目录。改成 path.join(__dirname, 'form.html') 就和启动位置无关了;multer 的 dest: 'upload/' 也是同一个问题。
追问 3要让同事一键复现这个上传示例,还缺什么?
缺依赖锁文件和启动脚本。把 "start": "node app.js" 写进 scripts,锁文件一起提交,端口写进 README 或者读环境变量,别人 npm ci && npm start 就能跑。
追问 4form.html 直接用 express.static 托管行不行?
完全可以,而且比每次请求 readFileSync 更好:app.use(express.static(path.join(__dirname, 'public'))),还自带缓存头。示例那样写只是为了少讲一个概念。
# 基础例子:单图上传
30 秒速记
- 后端:
multer({ dest: 'upload/' })+upload.single('logo') - 前端:
method="post"+enctype="multipart/form-data"+<input type="file" name="logo">,三个缺一不可 - 文件信息在
req.file,普通文本字段在req.body dest模式下文件名是随机32位十六进制,不带扩展名;目录不存在multer会自己建
