在NodeJS中从URL获取图像并通过POST上传到另一个URL

4
在下面的代码片段中,我使用node-fetchform-data首先从远程URL检索图像文件,然后将其上传到S3存储桶(在不同的脚本中使用aws-sdkmulter):
import fetch from 'node-fetch';
import fs from 'fs';
import FormData from 'form-data';

const form = new FormData();

const processProfileImg = (imageURL, userID) => {
  fetch(imageURL, userID)
    .then((response) => {
      const dest = fs.createWriteStream(`./temp/${userID}.jpg`);
      response.body.pipe(dest);
    })
    .then((dest) => {
      form.append('profileImage', fs.createReadStream(`./temp/${userID}.jpg`));
      fetch(`https://www.schandillia.com/upload/profile-image?userID=${userID}`, { method: 'POST', body: form })
        .then(response => response.json())
        .then(json => console.log(json));
    });
};

export default processProfileImg;

问题是,在使用form-data函数进行POST之前,需要先将文件在检索时存储到本地。有没有完全绕过这个步骤的方法?我不想将文件保存在本地,我只想从远程URL中获取它并将其POST到上传路由,而不创建本地文件。
更新:稍微修改片段以实现Fransebas(第一个答案)的建议并避免异步问题后,我遇到了新问题:保存在本地的图像很好,但上传到S3的副本被部分切断!
附加代码:处理POST上传的路由https://www.schandillia.com/upload/profile-image如下,并且当我使用Postman上传文件时,它运行良好。
import dotenv from 'dotenv';
import express from 'express';
import aws from 'aws-sdk';
import multerS3 from 'multer-s3';
import multer from 'multer';
import path from 'path';

dotenv.config();
const router = express.Router();

// Set up S3
const s3 = new aws.S3({
  accessKeyId: process.env.IAM_ACCESS_KEY_ID,
  secretAccessKey: process.env.IAM_SECRET_ACCESS_KEY,
});

const checkFileType = (file, cb) => {
  // Allowed ext
  const filetypes = /jpeg|jpg/;
  // Check ext
  const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
  // Check mime
  const mimetype = filetypes.test(file.mimetype);
  if (mimetype && extname) {
    return cb(null, true);
  }
  return cb('Error: JPEG Only!');
};

// Single Upload
const profileImgUpload = multer({
  storage: multerS3({
    s3,
    contentType: multerS3.AUTO_CONTENT_TYPE,
    bucket: `${process.env.S3_BUCKET_NAME}/w`,
    acl: 'public-read',
    key(req, file, cb) {
      cb(null, req.query.userID + path.extname(file.originalname));
    },
  }),
  limits: { fileSize: 2000000 }, // In bytes: 2000000 bytes = 2 MB
  fileFilter(req, file, cb) {
    checkFileType(file, cb);
  },
}).single('profileImage');

router.post('/profile-image', (req, res) => {
  profileImgUpload(req, res, (error) => {
    if (error) {
      console.log('errors', error);
      res.json({ error });
    } else if (req.file === undefined) {
      // If File not found
      console.log('Error: No File Selected!');
      res.json('Error: No File Selected');
    } else {
      // If Success
      const imageName = req.file.key;
      const imageLocation = req.file.location;
      // Save the file name into database into profile model
      res.json({
        image: imageName,
        location: imageLocation,
      });
    }
  });
});
// End of single profile upload

// We export the router so that the server.js file can pick it up
module.exports = router;


1
它必须以某种方式本地存储。您可以将其存储在内存中以消除文件IO,但仅限于此。这个问题讨论了使用multer来做一些看似类似于您正在做的事情 - Heretic Monkey
2个回答

4
我没有使用过这种特定的发送数据方式(我更喜欢使用Ajax),但通过查看您的示例,我认为您可以跳过将图像保存在本地的步骤。如果您看到fs.createReadStream创建了一个读取流,请寻找从您获得的内容创建读取流的方法。
另外,我认为你应该将发送代码放在then里面,这样你就不会遇到异步问题。例如,如果您的发送数据代码位于then中,则可以使用response.body来创建流。
你差不多做到了,但仍然在使用文件,我认为你可以用类似于这样的东西存档
import fetch from 'node-fetch';
import fs from 'fs';
import FormData from 'form-data';

const form = new FormData();

const processProfileImg = (imageURL, userID) => {
  fetch(imageURL, userID)
    .then((response) => {
      // Use response.body directly, it contains the image right?
      form.append('profileImage', response.body);
      fetch(`https://www.schandillia.com/upload/profile-image?userID=${userID}`, { method: 'POST', body: form })
        .then(response => response.json())
        .then(json => console.log(json));
    });
};

export default processProfileImg;

如果我理解fetch文档的正确,那么response.body已经是一个流。

请看我的更新。我按照你的建议将上传代码包装在.then()中。但现在上传到S3的图像被部分裁剪了。 - TheLearner
抱歉,找不到任何可以执行“fileSteamFromBody”占位符所需功能的内容。目前为止,只要上传顺利进行,我并不介意在本地保存文件副本。即使是这样,我也很难应对。 - TheLearner
1
如果我正确理解这份文档 https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams ,那么body已经是一个可读流,请查看它。 - Fransebas

2
这对我很有效:
const axios = require('axios')
const FormData = require('form-data');

//Get image
let imageResponse = await axios({
    url: imageUrl,
    method: 'GET',
    responseType: 'arraybuffer'
})

//Create form data
const form = new FormData()
form.append('image', imageResponse.data, {
    contentType: 'image/jpeg',
    name: 'image',
    filename: 'imageFileName.jpg'
})

//Submit form
let result = await axios({
    url: serverUrl, 
    method: "POST",
    data: form, 
    headers: { "Content-Type": `multipart/form-data; boundary=${form._boundary}` }
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接