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

如何使用Multer(搭配Express)存取嵌套对象数组中的上传图片

解决Express中嵌套对象数组的图片上传问题

你当前的核心问题是前端用JSON格式上传文件路径而非实际文件,JSON无法传输二进制文件,所以后端Multer根本接收不到文件,导致req.files为undefined。必须改用FormData处理文件上传,同时调整后端Multer配置解析动态字段。

一、前端代码修改

彻底替换原fetch逻辑,用FormData承载所有数据(包括文件和普通字段):

form.addEventListener("submit", (event) => {
    event.preventDefault();
    const formData = new FormData();

    // 添加普通字段
    formData.append('client', document.getElementById("client").value);
    formData.append('city', document.getElementById("city").value);

    // 遍历动态添加的spot,添加文件和地址
    for (let i = 0; i < spotIndex; i++) {
        // 获取文件对象(不是value的fake路径)
        const photo1File = document.getElementById(`photo_1_${i}`).files[0];
        const photo2File = document.getElementById(`photo_2_${i}`).files[0];
        const address = document.getElementById(`address_${i}`).value;

        if (photo1File) formData.append(`photo_1_${i}`, photo1File);
        if (photo2File) formData.append(`photo_2_${i}`, photo2File);
        formData.append(`address_${i}`, address);
    }

    // 发送请求:不要手动设置Content-Type,浏览器会自动添加正确的multipart/form-data及边界
    fetch("http://localhost:3334/admin/add", {
        method: "POST",
        body: formData,
    })
    .then(res => res.json())
    .then(data => console.log('上传成功:', data))
    .catch(err => console.error('上传失败:', err));
});

关键修改点:

  • 用FormData替代JSON,直接append文件对象(files[0])而非路径字符串
  • 不手动设置Content-Type,避免丢失multipart/form-data的边界标识,导致后端无法解析
  • 保留原动态字段命名规则(photo_1_0、address_0等),方便后端映射

二、后端代码修改

调整Multer配置和路由处理逻辑,解析动态文件字段并重组为目标结构:

const express = require('express');
const multer = require('multer');
const fs = require('fs');
const path = require('path');

const app = express();
// 注意:express.json()和express.urlencoded()不处理multipart/form-data,Multer会处理req.body和req.files
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 确保uploads目录存在,Multer不会自动创建
const uploadDir = path.join(__dirname, 'uploads');
if (!fs.existsSync(uploadDir)) {
    fs.mkdirSync(uploadDir);
}

// 修改文件名生成规则,避免同名文件覆盖
const storage = multer.diskStorage({
    destination: function (req, file, cb) {
        cb(null, uploadDir);
    },
    filename: function (req, file, cb) {
        // 用时间戳+原文件名,避免重复
        const uniqueName = `${Date.now()}-${file.originalname}`;
        cb(null, uniqueName);
    },
});

const upload = multer({ storage });

// 使用upload.any()接收所有文件字段(因为是动态添加的,无法提前固定字段名)
app.post("/admin/add", upload.any(), (req, res) => {
    // 重组req.body和req.files为目标结构
    const { client, city } = req.body;
    const spots = [];

    // 计算spot数量(假设address_0是必传字段)
    const spotCount = Object.keys(req.body).filter(key => key.startsWith('address_')).length;

    for (let i = 0; i < spotCount; i++) {
        // 找到对应索引的文件
        const photo1 = req.files.find(file => file.fieldname === `photo_1_${i}`);
        const photo2 = req.files.find(file => file.fieldname === `photo_2_${i}`);

        spots.push({
            address: req.body[`address_${i}`],
            // 保存文件的相对路径或完整路径,根据需求调整
            photo_1: photo1 ? `/uploads/${photo1.filename}` : null,
            photo_2: photo2 ? `/uploads/${photo2.filename}` : null
        });
    }

    // 此时你可以将client、city、spots存入数据库或做其他处理
    console.log('重组后的数据:', { client, city, spots });
    console.log('上传的文件:', req.files);

    res.json({ success: true, data: { client, city, spots } });
});

app.listen(3334, () => console.log('服务器运行在http://localhost:3334'));

关键修改点:

  • 使用upload.any()接收所有动态文件字段(如果能提前确定字段名,也可以用upload.fields(),但动态场景下upload.any()更灵活)
  • 手动创建uploads目录,避免Multer因目录不存在报错
  • 修改文件名规则,添加时间戳防止同名文件覆盖
  • 在路由回调中重组req.body和req.files为你需要的spots数组结构

三、额外注意事项

  • CORS配置:如果前端和后端端口不同,需要在Express中添加CORS中间件(npm install cors后,app.use(cors()))
  • 字段验证:建议在后端添加字段验证(如检查文件类型、大小,确保address必填等)
  • 文件清理:如果上传失败,需要考虑清理已保存的临时文件,避免垃圾文件堆积

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 09:57:11