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

经Nginx代理后Express.js使用Multer上传文件失败问题排查

MERN应用VPS部署后图片上传400 Bad Request问题排查

问题概述

已将MERN(MongoDB、Express.js、React.js、Node.js)应用部署至VPS并通过Nginx提供服务,本地开发环境可正常完成带图片上传的产品添加操作,但生产环境执行相同操作时出现400 Bad Request错误。

PM2错误日志

Image upload error: [Error: ENOENT: no such file or directory, open 'public/uploads/products/image-1710418885258.png'] code: 'ENOENT', syscall: 'open', path: 'public/uploads/products/image-1710418885258.png'

相关配置

Nginx配置

server {
  listen 80;
  server_name ;

  location / {
    root /var/www/vital/Client;
    index  index.html index.htm;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
    try_files $uri $uri/ /index.html;
  }

  location /api/v1/ {
    client_max_body_size 500M;
    proxy_pass http://(vps-ip-address):3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  }
}

Multer配置

const multerStorage = multer.diskStorage({
  filename: (req, file, cb) => {
    cb(
      null,
      file.fieldname + "-" + Date.now() + path.extname(file.originalname)
    );
  },
  destination: (req, file, cb) => {
    cb(null, "/public/uploads/products");
  },
});

const upload = multer({
  storage: multerStorage,
});

exports.uploadProductImage = (req, res, next) => {
  upload.single("image")(req, res, (err) => {
    if (err) {
      console.error("Image upload error:", err);
      return res.status(400).json({
        status: "error",
        message: "Image upload failed",
      });
    }
    next();
  });
};

exports.createProduct = catchAsync(async (req, res) => {
  const productData = {
    name: req.body.name,
    description: req.body.description,
    category: req.body.category,
    trending: req.body.trending,
    subCategory: req.body.subCategory || null,
    tags: req.body.tags,
    productImage: req.file.filename,
  };
  const newDoc = await Product.create(productData);
  setTimeout(() => {
    res.status(201).json({
      status: "success",
      data: {
        newDoc,
      },
    });
  }, 1000);
});

路由配置

app.use("/api/v1/products", productRouter);
router
  .route("/create-products")
  .post(productController.uploadProductImage, productController.createProduct);

排查与解决方向

  • 修复Multer路径配置:当前destination使用根目录下的/public/uploads/products,但生产环境项目通常部署在子目录(如/var/www/vital/Server),需改为基于项目根目录的动态绝对路径:

    const path = require('path');
    // ...
    destination: (req, file, cb) => {
      const uploadPath = path.join(__dirname, '../public/uploads/products');
      cb(null, uploadPath);
    },
    

    注意根据项目目录结构调整../的层级,确保指向正确的存储目录。

  • 确认目录存在并设置权限:

    1. 手动创建缺失的目录(若不存在):
      mkdir -p /var/www/vital/Server/public/uploads/products
      
    2. 给Node进程(PM2运行用户)赋予目录读写权限:
      # 若PM2以www-data用户运行
      sudo chown -R www-data:www-data /var/www/vital/Server/public/uploads/products
      sudo chmod -R 755 /var/www/vital/Server/public/uploads/products
      
  • 添加Nginx静态文件访问配置:上传的图片需要通过Nginx直接提供访问,在Nginx server块中添加:

    location /uploads/ {
      root /var/www/vital/Server/public;
      expires 30d;
      add_header Cache-Control "public, max-age=2592000";
    }
    

    配置完成后重启Nginx:sudo systemctl restart nginx

  • 验证路径一致性:在生产环境Node项目中添加临时代码打印实际路径,确认Multer使用的路径与VPS物理路径一致:

    const uploadPath = path.join(__dirname, '../public/uploads/products');
    console.log('Upload path:', uploadPath);
    

内容的提问来源于stack exchange,提问作者Abdulhamid Abdirahim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 07:55:59