为什么我尝试使用 multer 上传文件时 req.file "undefined"?

Why is req.file "undefined" for me when trying to upload a file using multer?

我正在尝试允许用户使用表单上传文件,然后应该在我的控制器文件中使用 multer 处理图像。出于某种原因,当我使用 upload.single('foobar') 时,它返回为“未定义”,并搞砸了我的应用程序的其余部分。具体来说,当我 运行 代码时,我在 createTour.js 文件中的错误处理程序 returns 出现一条警告,内容为“无法读取未定义的 属性 'imageCover'”。任何帮助,将不胜感激。如果有帮助,here's the GitHub.

控制器文件代码(tourController.js):

const multerStorage = multer.memoryStorage();

const multerFilter = (req, file, cb) => {
  if (file.mimetype.startsWith('image')) {
    cb(null, true);
  } else {
    cb(new AppError('Not an image! Please upload images only.', 400), false);
  }
};

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

exports.uploadTourImages = upload.single('imageCover');

exports.resizeTourImages = catchAsync(async (req, res, next) => {
  console.log(req.file);

  req.body.imageCover = `tour-${req.params.id}-${Date.now()}-cover.jpeg`;
  await sharp(req.file.imageCover[0].buffer)
    .resize(2000, 1333)
    .toFormat('jpeg')
    .jpeg({ quality: 90 })
    .toFile(`public/img/tours/${req.body.imageCover}`);

  next();
});

这是前端的表单,用Jade写的(create.pug):

extends base
block content
    main.main
        .create-form
            h2.heading-secondary.ma-bt-lg Create a tour, baby!
            form.form.form--create
                .form__group
                    label.form__label(for='name') Tour name
                    input#name.form__input(type='text', placeholder='you@example.com', required)
                .form__group
                    label.form__label(for='duration') Tour duration
                    input#duration.form__input(type='number', placeholder='you@example.com', required)
                .form__group
                    label.form__label(for='maxGroupSize') Max group size
                    input#maxGroupSize.form__input(type='number', placeholder='you@example.com', required)
                .form__group
                    label.form__label(for='difficulty') Difficulty
                    input#difficulty.form__input(type='text', placeholder='you@example.com', required)
                .form__group
                    label.form__label(for='price') Price
                    input#price.form__input(type='number', placeholder='you@example.com', required)
                .form__group
                    label.form__label(for='startLocation') Starting Location
                    input#startLocation.form__input(type='text', placeholder='you@example.com')
                .form__group
                    label.form__label(for='summary') Summary
                    input#summary.form__input(type='text', placeholder='you@example.com', required)
                .form__group
                    label.form__label(for='description') Detailed Description
                    input#description.form__input(type='text', placeholder='you@example.com', required)
                .form__group
                     input.form__upload(type='file', accept='image/*', id='imageCover', name='imageCover')
                     label(for='imageCover') Choose image cover
                .form__group
                    button.btn.btn--green Submit

这是处理表单数据的客户端 JS 代码,将其发布到 API (createTour.js):

import axios from 'axios';
import { showAlert } from './alerts';

export const createTour = async (
  name,
  duration,
  maxGroupSize,
  difficulty,
  price,
  startLocation,
  summary,
  description,
  imageCover
) => {
  try {
    const startLocation = {
      type: 'Point',
      coordinates: [-80.185942, 25.774772],
      address: '47 Bowman Lane, Kings Park, NY 11754',
      description: 'New Yorkkkkkkkk'
    };

    const res = await axios({
      method: 'POST',
      url: 'http://127.0.0.1:8000/api/v1/tours',
      data: {
        name,
        duration,
        maxGroupSize,
        difficulty,
        price,
        startLocation,
        summary,
        description,
        imageCover
      }
    });

    if (res.data.status === 'success') {
      showAlert('success', 'NEW TOUR CREATED!');
      window.setTimeout(() => {
        location.assign('/');
      }, 1500);
    }
  } catch (err) {
    showAlert('error', err.response.data.message);
  }
};

最后,不确定是否有帮助,但这是路由器文件代码(tourRoutes.js):

router
  .route('/')
  .get(tourController.getAllTours)
  .post(
    authController.protect,
    authController.restrictTo('user', 'admin', 'lead-guide'),
    tourController.uploadTourImages,
    tourController.resizeTourImages,
    tourController.createTour
  );

由于您使用的是 multer.single(),上传的文件将作为对象填充在 req.file 下(参见 https://github.com/expressjs/multer#singlefieldname)。但是,您在这一行中将其作为数组访问:

await sharp(req.file.imageCover[0].buffer)

将其更改为

await sharp(req.file.imageCover.buffer)

应该可以解决这个问题。

同样在客户端,您似乎实际上并未进行 form-data 上传。您需要将其更改为:

const formData = new FormData();

formData.set('imageCover', imageCover);
// add some data from the input fields
formData.set('name', name); // add the other remaining
axios.post('http://127.0.0.1:8000/api/v1/tours', formData, {
    headers: {
        'content-type': 'multipart/form-data'
    }
})