如何使用 Multer 在 MERN Stack 应用程序中上传图像文件

Posted

技术标签:

【中文标题】如何使用 Multer 在 MERN Stack 应用程序中上传图像文件【英文标题】:How to upload image file in MERN Stack Application with Multer 【发布时间】:2021-08-26 11:13:45 【问题描述】:

我正在尝试在我的项目中使用 multer 将图像作为文件上传,但我不知道为什么我在 req.files 上看到的控制台后端的 onCreate 请求:[]; (空数组)。

这里是我创建元素的地方:

    const [imagesColors, setImagesColors] = useState([image: [], color: ''])
    
    const createProduct = (e) => 
        e.preventDefault ();
    
        const data = new FormData()
    
    data.append("name", name)
        data.append("description", description)
        data.append("processor", processor)
        data.append("ram", ram)
        data.append("storage", storage)
        data.append("imagesColors", imagesColors)
        data.append("price", price)
        data.append("type", type)
    
        console.log(data)
    
        fetch ('http://localhost:5000/products/create', 
          method: 'POST',
          body: data
        )...
    
    const handleInputChangeColor = (e, index) => 
        const  name, value  = e.target;
        const list = [...imagesColors];
        list[index][name] = value;
        setImagesColors(list);
      ;
    
      const handleInputChangeImage = (e, index) => 
        const name = e.target.name;
        const file = e.target.files;
        const list = [...imagesColors];
        list[index][name] = file;
        setImagesColors(list);
      ;
    
return (
...
    imagesColors.map((x, i) => 
                        return (
                          <div className="box">
                            <label htmlFor="file" className="file--Input--Container">
                              <input
                                type="file"
                                id="file"
                                multiple
                                name="image"
                                className="file--Input"
                                filename="imageFile"
                                placeholder="Product Image"
                                onChange=e => handleInputChangeImage(e, i)
                              />
                              <div className="file--Label--Container">
                                 <FaCloudUploadAlt className="upload--Icon"/> Upload Images
                              </div>
                            </label>
                            <select
                              onChange=e => handleInputChangeColor(e, i)
                              value=x.color
                              name="color"
                              defaultValue=""
                            >
                              <option selected value="">Color</option>
                              <option value='#4f5b66'>Space-gray</option>
                              <option value='#a7adba'>Silver</option>
                              <option value='#FFFFFF'>White</option>
                              <option value='#F63204'>Red</option>
                              <option value='#000000'>Black</option>
                              <option value='#0095CB'>Pacific-Blue</option>
                            </select>
                            <div className="btn-box">
                              imagesColors.length !== 1 && <button onClick=() => handleRemoveClick(i)>Remove</button>
                              imagesColors.length - 1 === i && <button onClick=handleAddClick>Add</button>
                            </div>
                          </div>
                        );
                      )

当我 console.log 状态 imagesColors 它返回:

这是在后端使用 multer 函数的 create 函数:

const imageStorage = multer.diskStorage(
    destination: (req, file, callback) => 
        callback(null, "../../shpJS/frontend/src/styles/images")
    ,
    filename: (req, file, callback) => 
        callback(null, file.name)
    
)

const uploads = multer(storage: imageStorage)

router.post('/create', uploads.array("imagesColors"), async (req, res) => 
    console.log(req, ' asd')
    try 
        const data = 
            name: req.body.name,
            description: req.body.description,
            processor: req.body.processor,
            ram: req.body.ram,
            storage: req.body.storage,
            imagesColors: req.files,
            price: req.body.price,
            type: req.body.type,
            likes: req.body.likes
        
        let product = await productService.create(data)
        res.status(201).json(product)
     catch (error) 
        res.status(500).json(error: error)
    
)

以及来自请求的控制台中的结果:

【问题讨论】:

看看 req.imagesColors 记录了什么。 @NithinKJoy [对象对象]。但是我该如何解决呢? 我在 2 天前用 multer 上传(文件和 json 数据一起)时遇到了同样的问题。所以我所做的是在前端将图像转换为 base64 并上传,然后在后端将 base64 转换为文件。你需要那个源代码吗? @NithinKJoy 是的,你能提供吗? 【参考方案1】:

要在前端使用 FormData API 发送多个文件,您必须逐个附加这些文件。您可以将它们附加到同一个字段,该字段将作为数组到达后端。如果您想随每个文件发送其他数据,请以相同的顺序将该数据附加到 不同的字段。

在你的情况下,它看起来像这样:

for (const imageAndColor of imagesColors) 
  data.append('images', imageAndColor.image);
  data.append('colors', imageAndColor.color);

然后在后端将uploads.array("imagesColors") 更改为uploads.array("images")。您的图像将在req.files 中,颜色在req.body.colors 中。保证两个数组的顺序——第一个图像是两个数组中的第一个元素,第二个图像是第二个元素,依此类推。

【讨论】:

谢谢伙计。这就是我要找的。差不多,但我认为这个命令在前端对我有很大帮助【参考方案2】:

您将imageColors 作为对象数组(不是文件数组)发送,其中包括图像文件列表和颜色。此外,您需要在表单数据键的末尾添加括号,否则将无法正确解析。

您可以在发送前映射 imageColors 对象:

data.append("imagesColors[]", imagesColors.map(i) => i.images[0])

如果您也想发送颜色名称,则必须将它们发送到另一个数组中:

data.append("colors[]", imagesColors.map(i) => i.color)

【讨论】:

所以没有办法在组合对象中发送它们? 也许你明白我为什么要在组合对象中制作它?所以在那之后我可以在前端制作一个变体。 实际上你可以在添加文件之前用颜色改变文件名。 imagesColors.forEach(i =&gt; data.append("imagesColors[]", i.images[0], i.color)。最后一个i.color 参数将替换文件名。 我收到 Multer 错误:MulterError: Unexpected field at wrappedFileFilter (\multer\index.js:40:19) uploads.array("imagesColors") 更改为uploads.any()【参考方案3】:

作为提出此问题的人,接受任何解决问题的方法(请参阅此问题的 cmets)。我正在回答一种不使用任何包(如 multer)上传文件的方法。

所以方法是先在前端将图片转成base64发送到后端,在后端将base64转回文件。

以下是将文件转换为 base64 的前端代码。

传递的参数images只不过是event.target.files

const [imagesArray,setImagesArray]=useState()

let imageToBase64=(images)=>  //passed parameter images here
    let imagesBase64=[]
    for(let image of images)
      const reader = new FileReader();

    reader.onload = () => 
      if (reader.readyState === 2) 
        let imageBase64=reader.result
        imagesBase64.push(imageBase64)
      
    ;
    reader.readAsDataURL(image);
  
  setImagesArray(imagesBase64);
  

设置前端以以下格式发送数据


    name: req.body.name,
    description: req.body.description,
    processor: req.body.processor,
    ram: req.body.ram,
    storage: req.body.storage,
    imagesColors: [base64 string,base64 string],//differnce here
    price: req.body.price,
    type: req.body.type,
    likes: req.body.likes

后端代码

将base64转换为文件(我相信你是在上传图片所以我使用了sharp包)

将此行放在根文件中(可能是 index.js 或 app.js) app.use(express.json(limit: '50mb'))

const sharp = require("sharp");
const FileType = require("file-type");


router.post('/create', async (req, res) => 
    
 async function convert(base64) 
  var buffer = Buffer.from(base64.split(";base64,").pop(), "base64");
  let ext = await FileType.fromBuffer(buffer);
  let newPath =
    `/public/any path/` +
    Date.now() +
    Math.random().toString().slice(2, 14) +
    "." +
    ext;

  await sharp(buffer)
    .resize(1920, 1080)
    .toFile(newPath)
    .catch(err => console.log(err));

   return "done"
;

//this converts all base64 to images.
//warning: never `console.log(req.body.imagesColors)` because base64 is very very long string and logging that can crash your terminal.

if (req.body.imagesColors.length > 0)
    for (let image of req.body.imagesColors)
      await convert(image)
)

【讨论】:

以上是关于如何使用 Multer 在 MERN Stack 应用程序中上传图像文件的主要内容,如果未能解决你的问题,请参考以下文章

MERN Stack - Express 和 React 在同一个端口上?

MERN Stack 为所有用户显示相同的数据

MERN-stack使用react-router用户身份验证而不使用redux

如何将图像上传到 Cloudinary - MERN 堆栈

如何使用 axios 进行 OAuth 登录

对像 MERN Stack 这样的 Web 和 api 解决方案进行身份验证和授权的最佳方式是啥?