You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 00:07:06