如何在Multer上传文件前用Joi校验multipart/form-data请求?
问题:Multer上传文件前校验字段,阻止无效请求的文件上传
我正尝试通过Postman向Node.js后端服务上传文件,同时附带email、username等额外字段。已通过Multer实现文件上传至云端的功能,但希望在额外字段缺失时,阻止Multer将文件上传至云端。尝试用Joi校验请求Schema,但遇到了问题。
第一次尝试的代码(校验不生效)
const upload = multer({ storage: new MulterGoogleCloudStorage({ autoRetry: true, }) }); function validateRequest(req: any, res: any, next: () => void) { const validator = metadataValidator.validate(req.body); if (validator.error) { return res.status(400).send("Missing properties."); } next(); } router.post('/upload', validateRequest, upload.array("files"), (req, res) => { ... })
此时因为Multer尚未解析form-data,req.body为空,导致Joi校验无法生效。
第二次尝试的代码(校验生效但文件已上传)
调整中间件顺序后,Multer先解析并上传文件,再执行校验:
router.post('/upload', upload.array("files"), validateRequest, (req, res) => { ... })
此时校验可以正常工作,但即使校验失败,文件已经被上传至云端,不符合需求。
当前问题
- 期望结果:发送缺少必填字段的请求时,请求直接失败,文件不会被上传。
- 实际结果:无论校验是否失败,Multer都会将文件上传至云端。
此前尝试过相关解决方案但未解决问题。
解决方案
方法1:内存暂存文件,校验通过后再上传至云端
先将文件暂存到内存,执行字段校验,校验通过后再上传到谷歌云存储,失败则直接丢弃内存中的文件:
// 初始化内存存储的Multer,仅将文件暂存到内存 const memoryUpload = multer({ storage: multer.memoryStorage() }); // 字段校验中间件 function validateRequest(req: any, res: any, next: () => void) { const validator = metadataValidator.validate(req.body); if (validator.error) { // 校验失败,直接返回,内存文件会被自动回收 return res.status(400).send("Missing properties."); } next(); } // 自定义上传到谷歌云的中间件 async function uploadToGCS(req: any, res: any, next: () => void) { try { const gcsStorage = new MulterGoogleCloudStorage({ autoRetry: true }); // 遍历内存中的文件,逐个上传到云端 for (const file of req.files) { await new Promise((resolve, reject) => { gcsStorage._handleFile(req, file, (err) => { err ? reject(err) : resolve(null); }); }); } next(); } catch (err) { res.status(500).send("File upload failed."); } } // 路由顺序:内存解析文件 -> 字段校验 -> 云端上传 -> 业务逻辑 router.post('/upload', memoryUpload.array("files"), validateRequest, uploadToGCS, (req, res) => { // 后续业务处理 res.status(200).send("Files uploaded successfully."); })
方法2:利用Multer的fileFilter实现异步校验
通过fileFilter在文件上传前解析并校验字段,校验不通过则拒绝文件上传:
const busboy = require('busboy'); const upload = multer({ storage: new MulterGoogleCloudStorage({ autoRetry: true }), fileFilter: (req, file, cb) => { // 使用busboy先解析form-data中的字段 const bb = busboy({ headers: req.headers }); let requestBody: any = {}; bb.on('field', (fieldName, value) => { requestBody[fieldName] = value; }); bb.on('finish', () => { req.body = requestBody; // 执行Joi校验 const validator = metadataValidator.validate(requestBody); if (validator.error) { // 校验失败,拒绝文件上传 cb(new Error("Missing properties."), false); } else { // 校验通过,允许上传 cb(null, true); } }); req.pipe(bb); } }); // 路由直接使用该上传中间件 router.post('/upload', upload.array("files"), (req, res) => { // 业务逻辑处理 })
注意:该方法需注意busboy与Multer的兼容性,因为Multer本身基于busboy实现,手动解析可能存在流式处理的冲突,方法1的稳定性更高。
内容的提问来源于stack exchange,提问作者Itonroe
相关产品推荐
相关产品推荐

