[英]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.