在 Web 项目中,文件上传 是一个高频且核心的需求,例如:
- 用户头像、内容配图上传
- PDF / Word / 附件上传
- 需要为 Web、App、小程序等多端提供统一的文件上传接口
如果每个项目都从零开始设计一套上传方案,不仅效率低下,还可能因为考虑不周而引入安全隐患。
本文将基于 Node.js 生态,使用 Express + Multer,手把手搭建一套前端友好、可长期复用、可一键部署的文件上传服务,并提供 Web(Fetch API)与 uni-app 的完整对接示例。
一、Multer 中间件:文件上传的专用处理器
Multer 是一个用于处理 multipart/form-data 格式数据的 Node.js 中间件。在基于 Express 框架开发时,它是处理文件上传场景的事实标准。
它主要解决了三个问题:
- 接收并解析 来自浏览器或移动端应用上传的文件流。
- 控制存储,包括文件保存的目录和命名规则,防止文件覆盖。
- 前置校验,在上传阶段就能完成文件大小、类型等基础验证。
简单来说:
Multer = Express 生态中专为「文件上传」任务构建的基础设施层。
二、Multer 的核心使用方式
1. 配置存储引擎:决定文件存哪、叫什么
使用 diskStorage 方法定义文件的存储规则。
const storage = multer.diskStorage({
destination: './uploads/',
filename: function (req, file, cb) {
cb(
null,
file.fieldname +
'-' +
Date.now() +
path.extname(file.originalname)
)
}
})
参数说明:
destination:文件保存的目录。建议统一使用 uploads 这样的文件夹,便于管理。
filename:自定义文件名。这里采用“字段名-时间戳.后缀”的格式,能有效避免因同名导致的文件覆盖。
path.extname:用于提取并保留原始文件的后缀名。
2. 创建上传中间件:集成校验规则
基于配置好的存储引擎,创建最终的上传处理中间件。
const upload = multer({
storage: storage,
limits: {
fileSize: 10000000 // 文件大小限制为 10MB
},
fileFilter: function (req, file, cb) {
checkFileType(file, cb)
}
}).single('file') // 'file' 需与前端上传字段名保持一致
这一层配置至关重要,它负责:
- 文件大小控制:通过
limits 防止过大文件耗尽服务器资源。
- 文件类型白名单:通过
fileFilter 只允许指定类型的文件上传,提升安全性。
- 字段名匹配:
single('file') 表示处理单个文件,且字段名为 file。
三、一个完整可用的 Multer 中间件代码
将上述配置整合,得到一个可直接用于生产环境的中间件模块。
const multer = require('multer')
const path = require('path')
// 设置存储引擎
const storage = multer.diskStorage({
destination: './uploads/',
filename: function (req, file, cb) {
cb(
null,
file.fieldname +
'-' +
Date.now() +
path.extname(file.originalname)
)
}
})
// 初始化 upload 中间件
const upload = multer({
storage: storage,
limits: { fileSize: 10000000 }, // 10MB
fileFilter: function (req, file, cb) {
checkFileType(file, cb)
}
}).single('file')
// 文件类型校验函数
function checkFileType(file, cb) {
const filetypes = /jpeg|jpg|png|gif|pdf|doc|docx/
const extname = filetypes.test(
path.extname(file.originalname).toLowerCase()
)
const mimetype = filetypes.test(file.mimetype)
if (mimetype && extname) {
return cb(null, true)
} else {
cb('Error: Images, PDFs, and Documents Only!')
}
}
module.exports = upload
这个模块导出后,可以在任何路由中引入,用于处理文件上传请求。
四、构建文件上传与删除接口
有了上传中间件,接下来在 Express 路由中创建具体的接口。
1. 文件上传接口
const upload = require('../middleware/upload')
const codes = require('../config/codes')
// @desc 上传文件
// @route POST /api/files/upload
// @access Public
exports.uploadFile = (req, res, next) => {
upload(req, res, (err) => {
// 处理 Multer 中间件本身的错误(如文件过大、类型不符)
if (err) {
const error = new Error(err.message || err)
error.code = codes.INVALID_PARAMS
error.isOperational = true
return next(error)
}
// 检查是否有文件被上传
if (req.file === undefined) {
const error = new Error('Please select a file to upload.')
error.code = codes.MISSING_PARAMS
error.isOperational = true
return next(error)
}
// 构建返回给前端的数据
const fileData = {
filename: req.file.filename,
path: req.file.path,
size: req.file.size,
mimetype: req.file.mimetype,
url: `/uploads/${req.file.filename}`
}
res.cc(fileData, 'File uploaded successfully')
})
}
2. 文件删除接口
一个完整的服务通常也需要删除能力。
const fs = require('fs').promises
const path = require('path')
// @desc 删除文件
// @route DELETE /api/files/:filename
// @access Public
exports.deleteFile = async (req, res, next) => {
const filename = req.params.filename
// 基础安全校验,防止路径遍历攻击
if (!filename || filename.includes('..') || filename.includes(path.sep)) {
const error = new Error('Invalid filename.')
return next(error)
}
const filePath = path.join(__dirname, '..', 'uploads', filename)
await fs.unlink(filePath)
res.cc({ filename }, 'File deleted successfully')
}
五、前端(Web)如何对接上传接口?
在 Web 前端,使用经典的 fetch API 配合 FormData 对象即可轻松上传。
HTML 部分
<input type="file" id="fileInput" />
<button onclick="uploadFile()">上传文件</button>
<div id="result"></div>
JavaScript 部分
async function uploadFile() {
const fileInput = document.getElementById('fileInput')
if (fileInput.files.length === 0) {
alert('请先选择文件')
return
}
const file = fileInput.files[0]
const formData = new FormData()
formData.append('file', file) // 字段名 ‘file’ 需与后端配置一致
const response = await fetch('/api/files/upload', {
method: 'POST',
body: formData // 无需手动设置 Content-Type,浏览器会自动处理
})
const result = await response.json()
console.log(result)
}
六、uni-app 如何对接上传接口?
在 uni-app 等跨端框架中,可以使用其内置的上传 API。
uni.uploadFile({
url: '/api/files/upload',
filePath: tempFilePath, // 例如,从选择文件 API 获取的临时路径
name: 'file', // 字段名 ‘file’ 需与后端配置一致
success: (res) => {
const result = JSON.parse(res.data)
console.log(result)
}
})
总结
通过 Express 框架集成 Multer 中间件,我们快速构建了一个功能完备的文件上传服务。它具备文件类型校验、大小限制、安全存储等生产级特性,并提供了清晰的 Web 端与移动端对接示例。
这套方案代码清晰、职责分离,你可以直接将其集成到你的 Node.js 项目中,作为稳定可靠的后端文件处理基础设施。如果想了解更多实战项目架构与开发技巧,欢迎访问 云栈社区 与其他开发者交流讨论。