这是奥马尔,
我的问题是在我的前端(反应)中使用 fetch 发送
FormData
时:
const formData = new FormData();
formData.append('file', values.image);
formData.append('path', 'image');
formData.append('model', 'item');
formData.append(
'data',
JSON.stringify({
menu: menu._id.toString(),
item: values.item,
})
);
const req = new Request(`/api/v1/upload`, {
method: 'POST',
body: formData,
});
Request
构造函数与fetch完全相同,但它只是每次都会跳过response.json()
,Request类是我自己的(我构建的)
后端代码使用 multer 处理多部分表单数据。 后端与邮递员完美配合,因此使用邮递员发送请求效果很好,但使用 fetch as up 则不行。 对于后端代码,很简单,我使用 upload.single 和内存存储。 代码:
const storage = multer.memoryStorage();
const upload = multer({ storage, fileFilter });
exports.processImage = (req, res, next) => {
if (!req.file) return next();
req.file.filename =
req.modelController.createFileName(req, req.file) + '.jpeg';
sharp(req.file.buffer)
.toFormat('jpeg')
.toFile(
path.join(
`public/image/upload/${req.body.model.toLowerCase()}`,
req.file.filename
)
);
next();
};
再次,如果我使用邮递员,效果很好
邮递员请求图片 Postman 代码与我的代码非常相似:
const formdata = new FormData();
formdata.append("model", "item");
formdata.append("path", "image");
formdata.append("data", "{\"menu\": \"673e442bae0dd4717f4af4c1\", \"item\": \"673f76230fb7a35ddfc2cd57\"}");
formdata.append("file", fileInput.files[0], "/C:/Users/LENOVO/Downloads/Matrix-Cola-Carbonated-Drink.jpg");
const requestOptions = {
method: "POST",
headers: myHeaders,
body: formdata,
redirect: "follow"
};
fetch("http://127.0.0.1:5423/api/v1/upload/", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
即使使用 multer,它也会将主体接收为 null 对象,并且文件为未定义
网络选项卡显示以下内容 网络选项卡,请求负载 标题进展顺利,内容类型是 multipart/form-data。
我没有尝试很多事情,但是:
设置内容类型标题是由表单数据实例自动完成的
它是[目标文件]
我使用formik来处理表单。 但我确信该文件保存在values.image
使用 Request 的前端代码与 fetch 密切相关,因为 Request 对象被设计为与 Fetch API 一起使用。 Request 类本身并不执行实际的网络操作 - 它只是创建一个通常传递给 fetch 的 Request 对象。
出现问题的原因是您的后端在使用
fetch
时未正确接收多部分 FormData 负载。
该问题与 Fetch API 有关,因为您的自定义 Request 类可能会包装它。根本原因是 Content-Type 标头的处理以及 FormData 的传递方式。您可以通过确保正确使用 Request 类或 fetch 或切换到像 axios 这样的库以保持一致性来解决此问题。
axios.post
检查 FormData 并使用适当的边界设置 Content-Type 标头(多部分/表单数据格式的必需部分)。
这可确保服务器正确解释表单数据。
server.js
const express = require('express');
const cors = require('cors');
const multer = require('multer');
const sharp = require('sharp');
const path = require('path');
const fs = require('fs');
// Initialize the Express app
const app = express();
// Middleware for JSON parsing and CORS
app.use(express.json());
app.use(cors());
// Configure multer storage in memory
const storage = multer.memoryStorage();
const upload = multer({ storage });
// Ensure upload directories exist
const ensureUploadDirExists = (dirPath) => {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
};
// POST route to handle file uploads
app.post('/api/v1/upload', upload.single('file'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded.' });
}
const { path: uploadPath, model, data } = req.body;
// Parse and log the additional data
const parsedData = JSON.parse(data);
console.log('Parsed data:', parsedData);
// Ensure the directory exists
const uploadDir = path.join(__dirname, 'public', uploadPath, model.toLowerCase());
ensureUploadDirExists(uploadDir);
// Create a unique filename
const filename = `${Date.now()}-${model.toLowerCase()}.jpeg`;
// Process and save the image
await sharp(req.file.buffer)
.toFormat('jpeg')
.toFile(path.join(uploadDir, filename));
// Respond with the file path and other details
res.json({
success: true,
message: 'File uploaded successfully.',
filePath: `/public/${uploadPath}/${model.toLowerCase()}/${filename}`,
data: parsedData,
});
} catch (error) {
console.error('Error processing upload:', error);
res.status(500).json({ success: false, error: 'Failed to process upload.' });
}
});
// Server listening
const PORT = process.env.PORT || 5423;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
服务器依赖安装
npm install express cors multer sharp
在前端
App.js
import React, { useState } from 'react';
import axios from 'axios';
const App = () => {
const menu = { _id: '12345' };
const [imageFile, setImageFile] = useState(null); // File input
const [itemName, setItemName] = useState(''); // Item name input
const [responseMessage, setResponseMessage] = useState('');
// Handle file input change
const handleFileChange = (event) => {
setImageFile(event.target.files[0]);
};
const handleSubmit = async (event) => {
event.preventDefault();
if (!imageFile) {
setResponseMessage('Please select an image to upload.');
return;
}
const formData = new FormData();
formData.append('file', imageFile);
formData.append('path', 'image');
formData.append('model', 'item');
formData.append(
'data',
JSON.stringify({
menu: menu._id.toString(),
item: itemName,
})
);
try {
const response = await axios.post('http://localhost:5423/api/v1/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
console.log(response.data);
setResponseMessage(JSON.stringify(response.data, null, 2)); // Format JSON response
} catch (error) {
console.error('Error details:', error.response?.data || error.message);
setResponseMessage('Error: Could not get a response.');
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<div>
<label>
Upload Image:
<input type="file" onChange={handleFileChange} />
</label>
</div>
<div>
<label>
Item Name:
<input
type="text"
value={itemName}
onChange={(e) => setItemName(e.target.value)}
/>
</label>
</div>
<button type="submit">Submit</button>
</form>
{responseMessage && (
<div>
<h3>Server Response:</h3>
<pre>{responseMessage}</pre>
</div>
)}
</div>
);
};
export default App;
依赖安装
npm install axios
node server.js
npm start
Postman