Node.js文件上传至MongoDB失败:封面图仅设默认值求助
我正在用Node.js实现文件上传功能,前端基于Nest.js发送文件请求。目前能在req.body中获取到文件相关数据,但上传功能无法正常运行——只有封面图会被设置为默认值,其余字段都能正常保存到MongoDB。以下是相关代码、创建帖子函数、控制台日志及前端请求截图:
数据模型与文件处理代码
const { Schema, validate } = require("clean-schema"); const PostModel = new Schema({ _id: { constant: true, value: generatePostId, onDelete: deleteAllBookmarks, onSuccess: handleSuccess, }, body: { required: true, validator: validatePostBody }, coverPicture: { default: { url: "", metadata: {} }, dependent: true, dependsOn: "_setCoverPicture", resolver: ({ context: { _setCoverPicture } }) => ({ url: _setCoverPicture.url, }), }, _setCoverPicture: { alias: "coverPicture", virtual: true, shouldInit: false, validator: validateFile, onFailure: onFailure("coverPicture"), sanitizer: sanitizeFile("_setCoverPicture", "coverPicture"), }, }).getModel(); const uploadImage = ({ pageId, path }) => { const filePath = getFilePath(TEMP_FILE_UPLOAD_DIR)(path); cutFile(path, `public/static/pages/${pageId}/${filePath}`); return { url: `/static/pages/${pageId}/${filePath}` }; }; function onFailure(fileField) { return ({ context }) => { const path = context[fileField].path; if (path) deleteFile(path); }; } function sanitizeFile(setterField, fileField) { return ({ context }) => { const { _id: pageId } = context; const file = context[setterField]; const oldFile = context[fileField]; const res = file?.unset ? { url: "" } : uploadImage({ pageId, path: file.path }); // 编辑时移除旧图片 if (oldFile?.url) removeImage({ url: oldFile.url }); return { url: String(res.url) }; }; } function validateFile(file) { if (file == "") return { valid: true, validated: { unset: true } }; if (!file || typeof file != "object") return FILE_ERROR; const _path = validateString("", { maxLength: 1000 })(file?.path); if (!_path.valid) return FILE_ERROR; const _size = validateNumber("", { range: { bounds: [0, MAX_FILE_SIZE_IN_BYTES] }, })(file?.size); if (!_size.valid) return FILE_ERROR; const path = String(_path.validated), size = Number(_size.validated); return { valid: true, validated: { path, size } }; } module.exports = { PostModel, resolveReactions, validateSetReaction };
创建帖子函数
const createPost = async (req, res) => { try { console.log(req.body); const isQuestion = req.body.isQuestion === "true"; const { data, error } = await PostModel.create({ ...req.body, isQuestion, createdBy: req.user._id, }); console.log(data); if (error) return res.status(400).json({ data: error, success: false }); const post = await postDb.create(data); res.status(200).json({ data: post, success: true }); } catch (error) { console.log("Error creating post:", error); res.status(500).json({ data: "An Error occured", success: false }); } };
相关截图
控制台日志:
前端请求:
问题排查与解决方案
1. 核心问题:文件未被正确解析
从控制台日志可以看到,req.body.coverPicture的值是"[object File]"——这说明后端没有正确解析multipart/form-data格式的文件,只是把前端传来的File对象转成了字符串,导致后续的文件校验和处理逻辑全部失效,最终封面图使用默认值。
2. 具体修复步骤
(1)配置后端文件解析中间件
如果是Nest.js后端,需使用FileInterceptor处理单文件上传:
import { Controller, Post, UseInterceptors, UploadedFile, Body, Req } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { extname } from 'path'; @Controller('posts') export class PostsController { @Post('create') @UseInterceptors(FileInterceptor('coverPicture', { storage: diskStorage({ destination: './temp', // 临时存储目录 filename: (req, file, cb) => { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); cb(null, file.fieldname + '-' + uniqueSuffix + extname(file.originalname)); }, }), })) async createPost( @UploadedFile() coverPicture: Express.Multer.File, @Body() body: any, @Req() req: any ) { // 将解析后的文件数据映射到虚拟字段_setCoverPicture req.body._setCoverPicture = { path: coverPicture.path, size: coverPicture.size }; // 调用原有的createPost逻辑 return createPost(req, res); } }
如果是Express后端,需配置multer中间件:
const multer = require('multer'); const upload = multer({ dest: './temp' }); // 临时存储目录 // 在路由中使用中间件 app.post('/posts/create', upload.single('coverPicture'), createPost);
(2)调整字段映射逻辑
PostModel中_setCoverPicture是处理文件的虚拟字段,需确保后端将解析后的文件数据传入_setCoverPicture而非coverPicture。如果前端字段名是coverPicture,可在解析后手动将数据赋值给req.body._setCoverPicture(如上述代码所示)。
(3)验证文件处理函数权限
确保uploadImage中的目标目录public/static/pages/${pageId}/${filePath}存在,且后端进程有读写该目录的权限。可在uploadImage中添加目录创建逻辑:
const fs = require('fs'); const path = require('path'); const uploadImage = ({ pageId, path }) => { const filePath = getFilePath(TEMP_FILE_UPLOAD_DIR)(path); const targetDir = path.join('public', 'static', 'pages', pageId); // 确保目标目录存在 if (!fs.existsSync(targetDir)) { fs.mkdirSync(targetDir, { recursive: true }); } cutFile(path, path.join(targetDir, filePath)); return { url: `/static/pages/${pageId}/${filePath}` }; };
(4)校验validateFile逻辑
当文件未被正确解析时,validateFile会返回FILE_ERROR,触发onFailure删除临时文件。需确保FILE_ERROR常量已正确定义,且错误处理逻辑符合预期。
内容的提问来源于stack exchange,提问作者Anon

