如何使用Multer安全处理对象数组形式的多文件上传?
问题:Multer安全处理数组对象中的文件上传
场景与需求
我正在构建一个包含多个试题的考试Demo,每个试题的结构如下:
[{ image: an_image_file, audio: audio_file, text: just_text, another_text: text }]
当前问题
我无法用Multer处理这种嵌套在对象数组里的图片和音频文件。目前用upload.any()能实现功能,但这个方法存在安全隐患,希望找到更安全的处理方案。
前端代码
const onSubmit = async (data) => { const form = new FormData(); data.questions.forEach((question, index) => { form.append(`questions[${index}].img`, question.img); form.append(`questions[${index}].audio`, question.audio); form.append(`questions[${index}].text`, question.text); }); try { const result = await axios.post( 'http://localhost:1445/api/v1/exam', form ); console.log(result); } catch (err) {} };
后端代码
const multer = require('multer'); const compression = require('compression'); const cors = require('cors'); const path = require('path'); const fs = require('fs'); const date = new Date().getTime(); const app = express(); app.use(express.json()); app.use('*', cors()); app.use(compression()); const upload = multer({ limits: 100000000, storage: multer.diskStorage({ destination: (req, file, cb) => { console.log(__dirname); const dir = path.join(__dirname, '..', 'public'); if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } cb(null, dir); }, filename: (req, file, cb) => { console.log('body', req.body); cb(null, `${date}_${file.filename}`); }, }), }); const router = express.Router(); const api = router.post( '/exam/', upload.any(), (req, res, next) => { console.log(req.body); console.log(req.files); return res.json({ ok: 'ok' }); } );
安全处理方案
1. 限制允许的文件字段
用fileFilter配合字段名正则验证,替代upload.any(),只接受符合格式的字段:
const upload = multer({ limits: 10485760, // 缩小为10MB,按需调整 storage: multer.diskStorage({/* 保留原存储配置 */}), fileFilter: (req, file, cb) => { // 验证字段名是否符合questions[索引].img/audio格式 const validField = /^questions\[\d+\]\.(img|audio)$/.test(file.fieldname); if (!validField) { return cb(new Error('非法文件字段')); } cb(null, true); } });
2. 严格限制文件类型
在fileFilter中添加MIME类型验证,只允许指定的图片、音频格式:
fileFilter: (req, file, cb) => { // 先验证字段名 const validField = /^questions\[\d+\]\.(img|audio)$/.test(file.fieldname); if (!validField) { return cb(new Error('非法文件字段')); } // 根据字段后缀判断允许的文件类型 const allowedTypes = { img: ['image/jpeg', 'image/png', 'image/gif'], audio: ['audio/mpeg', 'audio/wav', 'audio/ogg'] }; const fieldType = file.fieldname.split('.')[1]; if (allowedTypes[fieldType].includes(file.mimetype)) { cb(null, true); } else { cb(new Error(`不允许的${fieldType}文件类型`)); } }
3. 完善文件命名逻辑
避免使用原始文件名,改用唯一标识符防止冲突和路径遍历:
const { v4: uuidv4 } = require('uuid'); // 需要安装uuid包 filename: (req, file, cb) => { const ext = path.extname(file.originalname); const uniqueName = `${date}_${uuidv4()}${ext}`; cb(null, uniqueName); }
4. 额外安全措施
- 验证请求中试题数量的合理性,防止恶意提交大量文件
- 给上传目录设置严格的文件权限(如只读)
- 考虑将文件存储到云存储服务,减少服务器本地存储的安全风险
内容的提问来源于stack exchange,提问作者Dang Kien
相关产品推荐
相关产品推荐

