文件上传与设备摄像头采集:hackathon-starter 的 Multer 实战教程
【免费下载链接】hackathon-starterA boilerplate for Node.js web applications项目地址: https://gitcode.com/gh_mirrors/ha/hackathon-starter
在 Node.js 项目里实现文件上传,Multer 是最常用的中间件方案。hackathon-starter 作为一套完整的 Node.js Web 应用脚手架,内置了两个 Multer 实战案例:一个是经典的/api/upload磁盘文件上传,另一个是/ai/llm-camera设备摄像头采集图片并直接送给视觉大模型分析。本文带你读懂这两条完整链路,从表单、中间件到错误处理,一次学会 Multer 的两种典型用法。
为什么选 Multer 处理 Node.js 文件上传?
浏览器提交的文件表单使用multipart/form-data编码,Express 默认并不解析这种请求体。Multer 的工作就是把它解析出来,并把文件落到两个地方之一:
- 磁盘存储:
multer({ dest: '...' }),文件写入指定目录,适合"存文件、给下载"的场景 - 内存存储:
multer.memoryStorage(),文件内容以 Buffer 放在req.file.buffer,适合"上传后立即处理、不落盘"的场景
hackathon-starter 的两个案例恰好各用了一种,非常适合对照学习。依赖在 package.json 中声明,版本为multer ^2.4.0,README.md 也把它列为核心功能之一。
经典文件上传:1MB 限制的磁盘存储示例
打开/api/upload页面,你看到的是 views/api/upload.pug 定义的标准表单:
- 表单声明
enctype='multipart/form-data'(文件上传的必备属性) - 隐藏字段
_csrf携带 CSRF 令牌,防跨站伪造 - 文件输入框的字段名是
myFile
真正干活的是 controllers/api.js 中的uploadMiddleware:
const upload = multer({ dest: path.join(__dirname, '../uploads'), // 存到 uploads/ 目录 limits: { fileSize: 1024 * 1024 * 1 }, // 单文件上限 1MB }); upload.single('myFile')(req, res, (err) => { if (err) req.multerError = err; // 错误先挂到 req 上 next(); });这里有个值得学习的设计:中间件不直接终止请求,而是把 Multer 的错误记录到req.multerError,交给后续处理器决定怎么提示用户。
上传失败提示与超大文件错误的优雅处理
postFileUpload(controllers/api.js)展示了完整的错误分支:
- 文件超过 1MB → Multer 抛出
LIMIT_FILE_SIZE错误 → 页面显示"File size is too large" - 其他 Multer 错误 → 透出原始错误信息
- 上传成功 → 绿色成功提示,并 302 重定向回上传页
每个分支都先调用req.session.save(...)再重定向。这是一个容易被忽略的细节:如果用 Redis 或 Mongo 等异步会话存储,不先保存会话,重定向后新请求读不到 flash 消息,提示就会"丢闪"。
这套行为的正确性由端到端测试覆盖,见 test/e2e-nokey/upload.e2e.test.js:小文件上传成功、内容一致;超过 1MB 报错提示;恰好接近 1MB 仍可上传;文本/JSON/CSV/XML 多种类型均可通过。
设备摄像头采集:Multer 内存存储的进阶玩法
第二个案例更有意思:/ai/llm-camera页面让浏览器调用设备摄像头拍照,把图片直接喂给视觉大模型分析。前端逻辑在 views/ai/llm-camera.pug:
navigator.mediaDevices.enumerateDevices()枚举设备上的摄像头getUserMedia()获取视频流,在<video>上实时预览- 点击"Capture"时用
<canvas>抓取一帧,转成 JPEG Blob - 用
FormData把图片以字段名imagePOST 到/ai/llm-camera
后端则用 Multer 的内存存储接住图片(controllers/ai.js):
multer({ storage: multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024 }, // 10MB 上限 }).single('image');为什么用内存而不是磁盘?因为这张照片不需要保存——它马上会被转成 Base64 Data URL 送给 Groq 视觉模型(controllers/ai.js),落盘反而浪费 IO,还可能留下用户隐私数据。内存用完即弃,是这类"过路图片"的最佳实践。
中间件顺序与安全防护细节
在 app.js 和路由注册处,有两处值得注意的约定:
- Multer 必须跑在 Lusca CSRF 校验之前:
/api/upload和/ai/llm-camera两条路径在全局 CSRF 中间件里被放行,改由各自的 POST 路由末端再挂lusca({ csrf: true })单独校验,因为 multipart 请求体解析和 CSRF 校验的顺序容易冲突。 - 限流保护:两个 POST 路由都挂了
strictLimiter,防止上传接口被滥用。 - 页面级路由(GET)统一通过
lusca({ csrf: true })保护,表单里的_csrf隐藏字段与之配套。
这套"上传接口 + 限流 + CSRF"的组合拳,可以直接照搬到你自己的项目里。
关键文件速查清单
| 文件 | 作用 |
|---|---|
| controllers/api.js | 磁盘上传:页面、中间件、成功/失败处理 |
| views/api/upload.pug | 上传表单(multipart + CSRF) |
| controllers/ai.js | 内存上传 + Base64 + 视觉分析 |
| views/ai/llm-camera.pug | 摄像头枚举、预览、拍照前端逻辑 |
| app.js | 路由注册与中间件顺序 |
| test/e2e-nokey/upload.e2e.test.js | 上传功能的端到端测试 |
小结
通过 hackathon-starter 的两个案例,你可以把 Multer 的用法浓缩成三条经验:
- 要存盘就用
dest,要现处理就用memoryStorage,按文件的生命周期选型 limits.fileSize一定要设,并把LIMIT_FILE_SIZE等错误翻译成用户能看懂的提示- 上传接口记得组合限流与 CSRF 防护,中间件顺序(Multer 在前、CSRF 校验在后)别搞反
照着 AGENTS.md 和 AGENTS.md 中的功能速查表,你可以快速定位这两个功能的完整上下文,动手改造出自己的上传能力。🚀
【免费下载链接】hackathon-starterA boilerplate for Node.js web applications项目地址: https://gitcode.com/gh_mirrors/ha/hackathon-starter
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考