繁体   English   中英

React NextJS 无法同时通过 nextjs api 上传文件 501 Not Implemented req.body undefined

[英]React NextJS unable to upload a file through nextjs api at the same time as my form data 501 Not Implemented req.body undefined

我有一个带有表单的 nextjs 应用程序,在该表单中,我为用户提供了在填写个人数据的同时上传文件的选项。 我正在使用 Multer,在我使用的下一个连接中间件中,它让我将 bodyParser 设置为 false。 当我收集其他数据时,我收到错误 501 无法实现说"error": "Sorry something Happened. Cannot destructure property 'firstName' of 'req.body' as it is undefined."

然后我求助于另一个名为 multiparty 的中间件,我在 nextjs api 的 addcv 路由中使用它。 我仍然遇到同样的错误。 如果有人能帮我解决这个问题,我将不胜感激。 我不明白为什么不能在上传表单数据的同时上传文件,因为我可以在普通的 nodejs 应用程序中毫无问题地执行此操作。

以下是表格的相关部分:

CVForm.jsx
import { useSubmitCV } from '../hooks/useSubmitCV';
function CVForm() {
  const [formData, setFormData] = useState({
    firstName: '',
    lastName: '',
    email: '',
    phone: '',
    cvfile: '',
    address: '',
    usEligible: '',
    city: '',
    state: '',
    zip: '',
    dob: '',
    description: '',
  });
  const [blockOneOpen, setBlockOneOpen] = useState(false);
  const [havecv, setHaveCv] = useState(false);
  const [haveNoCv, setHaveNoCv] = useState(false);
  const [buttonClicked, setButtonClicked] = useState(false);
  const [buttonText, setButtonText] = useState('Click Here To Start');

  const { submitcv, error, loading } = useSubmitCV();

  const onChange = (e) => {
    setFormData((prevState) => ({
      ...prevState,
      [e.target.name]: e.target.value,
    }));
  };

  const onFileChange = (e) => {
    if (!cvfile) {
      setFormData((prevState) => ({
        ...prevState,
        cvfile: null,
      }));
    } else {
      setFormData((prevState) => ({
        ...prevState,
        cvfile: e.target.files[0],
      }));
    }
  };
const cvSubmit = async (e) => {
    e.preventDefault();
    if (
      formData.firstName === '' ||
      formData.lastName === '' ||
      formData.email === '' ||
      formData.phone === ''
    ) {
      toast.error('All Fields are required');
    } else {
      await submitcv(formData);
    }
    console.log(formData);
    setButtonClicked(!buttonClicked);
    setButtonText('Start Here');
  };
// JSX PORTION 
<label className='block mb-6 mt-6 text-sm font-medium text-gray-900 dark:text-gray-300'                       
  htmlFor='file_input'>Upload Resume
</label>
<input
className='block w-full text-sm text-white bg-gray-50 rounded-lg border border-gray-300 cursor-pointer dark:text-white focus:outline-none dark:bg-[#00388d] dark:border-gray-600 dark:placeholder-white'
id='cvfile'
type='file'
filename='cvfile'
name='cvfile'
onChange={onFileChange}
accept='.doc,.docx,.pdf,.odf' />
   <div className='mb-6 mt-6'>
      <button type='submit'className='w-full px-2 py-4 text-white bg-[#00388d] rounded-md  focus:bg-indigo-600 focus:outline-none'>
      Send Resume
     </button>
</div>

使用提交钩子 useSubmitCV.js

import { useState } from 'react';
import { toast } from 'react-toastify';
import axios from 'axios';
import { useRouter } from 'next/router';

export const useSubmitCV = () => {
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(null);

  const router = useRouter();

  // const { dispatch } = useCVContext();

  const API_URL = '/api/cvs/addcv';

  const submitcv = async (formData) => {
    setLoading(true);
    setError(null);
    const form = new FormData();
    form.append('firstName', formData.firstName);
    form.append('lastName', formData.lastName);
    form.append('email', formData.email);
    form.append('phone', formData.phone);
    form.append('cvfile', formData.cvfile);
    form.append('fileUrl', formData.fileUrl);
    form.append('address', formData.address);
    form.append('city', formData.city);
    form.append('state', formData.state);
    form.append('zip', formData.zip);
    form.append('usEligible', formData.usEligible);
    form.append('dob', formData.dob);
    form.append('description', formData.description);

    const response = await axios.post(API_URL, form);
    if (response.data.message) {
      setError(response.data.message);
      toast.error(response.data.message);
      setLoading(false);
      return;
    }
    toast.success('Resume Successfully Submitted');
    setLoading(false);
    router.push('/');
  };
  return { submitcv, loading, error };
};

中间件文件 middleware.js

import nextConnect from 'next-connect';
import multiparty from 'multiparty';

const middleware = nextConnect();

middleware.use(async (req, res, next) => {
  const form = new multiparty.Form();

  await form.parse(req, function (err, fields, files) {
    req.body = fields;
    req.files = files;
    next();
  });
});

export default middleware;

最后是 nextjs 的 api 路由中的 addcv.js 路由

import nextConnect from 'next-connect';
import multer from 'multer';
import { v4 as uuidv4 } from 'uuid';
import fs from 'fs';
import path from 'path';
import middleware from '../../../middleware/middleware';

import { File, Web3Storage } from 'web3.storage';

import CV from '../../../models/cvModel';

const w3storage = new Web3Storage({ token: process.env.WEB3_STORAGE_TOKEN });

function checkFileType(file, cb) {
  // Allowed ext
  const filetypes = /doc|docx|pdf|odf/;
  // 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);
  } else {
    cb('Error: Documents Only!');
  }
}

const storage = multer.memoryStorage({
  destination: (req, file, cb) => {
    cb(null, path.join(process.cwd(), 'public', 'uploads'));
  },
  filename: (req, file, cb) => {
    cb(null, uuidv4() + '-' + file.originalname);
  },
  fileFilter: function (_req, file, cb) {
    checkFileType(file, cb);
  },
});

const maxSize = 3 * 1000 * 1000;

const upload = multer({ storage: storage, limits: { fileSize: maxSize } });

const createCV = nextConnect({
  onError(error, req, res) {
    res
      .status(501)
      .json({ error: `Sorry something Happened! ${error.message}` });
  },
  onNoMatch(req, res) {
    res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
  },
})
  .use(middleware)
  .use(upload.single('cvfile'))
  .post((req, res) => {
    // @desc Create a new cv
    // @route POST /api/cvs/addcv
    // @access Public

    const {
      firstName,
      lastName,
      email,
      phone,
      cvfile,
      address,
      city,
      state,
      zip,
      usEligible,
      dob,
      description,
    } = req.body;

    const fileUrl = req.file.filename;

    console.log(fileUrl);
    const bytes = fs.readFileSync(`${process.env.UPLOAD_PATH}/${fileUrl}`);
    const newFile = new File([bytes], fileUrl);
    const cid = w3storage.put([newFile]);
    const filePath = path.join(process.cwd(), 'public', 'uploads', fileName);
    if (fs.existsSync(filePath)) {
      fs.unlinkSync(filePath);
    }
    const cv = CV.create({
      firstName,
      lastName,
      email,
      phone,
      cvfile,
      address,
      city,
      state,
      zip,
      usEligible,
      dob,
      description,
      fileUrl: `https://${cid}.ipfs.dweb.link/${fileName}`,
    });

    res.status(201).json(cv);
    console.log(req.body);
    // res.status(201).json({ body: req.body, file: req.file });
  });

export default createCV;

export const config = {
  api: {
    bodyParser: false, // Disallow body parsing, consume as stream
  },
};

同样,如果有人知道如何做到这一点而不必在 nextjs api 路由系统之外进行 go,我一定会很感激。 先感谢您。

编辑::我运行了一个调试器,并在 useSubmit.js 中提交后从表单值中得到以下信息在此处输入图像描述

这将 cvfile 的值显示为 cv.docx,这是正确的,但对于 fileUrl 的值(这很重要:))设置为未定义。 不好。 更糟糕的是,因为我是 Next 新手,而且在这方面有点绿,我不知道如何解决它。

编辑 2::在调试器上; 我的代码在这段代码之后死了:

const response = await axios.post(API_URL, form, {
    headers: {
      'Content-Type': 'multipart/formdata',
    },
  });

最后,我不得不简化 multer 并添加文件功能。 我删除了多方中间件并坚持使用 nextConnect。 这是修改后的代码:

import nextConnect from 'next-connect';
import multer from 'multer';
import path from 'path';
import connectDB from '../../../config/db';

import { File, Web3Storage } from 'web3.storage';

import CV from '../../../models/cvModel';

export const config = {
  api: {
    bodyParser: false,
  },
};

const w3storage = new Web3Storage({ token: process.env.WEB3_STORAGE_TOKEN });

function checkFileType(file, cb) {
  // Allowed ext
  const filetypes = /doc|docx|pdf|odf/;
  // 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);
  } else {
    cb('Error: Documents Only!');
  }
}

const storage = multer.memoryStorage({
  fileFilter: function (_req, file, cb) {
    checkFileType(file, cb);
  },
});

const maxSize = 3 * 1000 * 1000;

const upload = multer({ storage: storage, limits: { fileSize: maxSize } });

const createCV = nextConnect({
  onError(error, req, res) {
    res
      .status(501)
      .json({ error: `Sorry something Happened! ${error.message}` });
  },
  onNoMatch(req, res) {
    res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
  },
})
  .use(upload.single('cvfile'))
  .post(async (req, res) => {
    // @desc Create a new cv
    // @route POST /api/cvs/addcv
    // @access Public
    await connectDB();

    try {
      const {
        firstName,
        lastName,
        email,
        phone,
        address,
        city,
        state,
        zip,
        usEligible,
        dob,
        description,
      } = req.body;

      console.log(req.body, req.file);

      const newFile = new File([req.file.buffer], req.file.originalname);
      const cid = await w3storage.put([newFile]);

      const cv = await CV.create({
        firstName,
        lastName,
        email,
        phone,
        address,
        city,
        state,
        zip,
        usEligible,
        dob,
        description,
        fileUrl: `https://${cid}.ipfs.dweb.link/${req.file.originalname}`,
      });

      return res.status(201).json(cv);

      // res.status(201).json({ body: req.body, file: req.file });
    } catch (error) {
      console.log(error);
      return res.status(400).json({ error: error.message });
    }
  });

export default createCV;

如您所见,由于我将文件存储在 web3 存储中,因此不再需要使用 diskStorage,因此已经删除了很多代码。 我还从此代码中删除了 cvfile 变量以及 model,因为它不需要。 我希望这可以帮助其他遇到此特定问题的人。

如果您阅读多方的自述文件,我引用:

使用 content-type multipart/form-data解析 http 请求,也称为文件上传。

(我喜欢 package 的创意名称。)您的表单需要发送multipart/form-data (您使用的是application/x-www-form-urlencoded (默认值))。

useSubmitCV.js更改 axios 请求以包含自定义内容类型:

// ...
await axios.post(API_URL, form, {
  headers: {
    'Content-Type': 'multipart/formdata'
  }
});

这应该允许多方接收您的数据。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM