英文:
Upload files with React formData and Fetch
问题
问题
在我的应用程序中,用户可以修改他的描述和头像。为此,我创建了一个名为 updateUser
的控制器和一个在后端使用的 multer
中间件,还有一个在前端使用的 PUT 请求。然而,虽然我成功地修改了用户的描述,但当我尝试上传文件时,我得到了以下的 500 内部服务器错误
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>
MulterError: Unexpected field<br>
at wrappedFileFilter (C:\Users\barba\Desktop\Groupomania\backend\node_modules\multer\index.js:40:19)<br>
at Multipart.<anonymous >(C:\Users\barba\Desktop\Groupomania\backend\node_modules\multer\lib\make-middleware.js:107:7)<br>
at Multipart.emit (node:events:513:28)<br>
at HeaderParser.cb (C:\Users\barba\Desktop\Groupomania\backend\node_modules\busboy\lib\types\multipart.js:358:14)<br>
at HeaderParser.push (C:\Users\barba\Desktop\Groupomania\backend\node_modules\busboy\lib\types\multipart.js:162:20)<br>
at SBMH.ssCb [as _cb] (C:\Users\barba\Desktop\Groupomania\backend\node_modules\busboy\lib\types\multipart.js:394:37)<br>
at feed (C:\Users\barba\Desktop\Groupomania\backend\node_modules\streamsearch\lib\sbmh.js:248:10)<br>
at SBMH.push (C:\Users\barba\Desktop\Groupomania\backend\node_modules\streamsearch\lib\sbmh.js:104:16)<br>
at Multipart._write (C:\Users\barba\Desktop\Groupomania\backend\node_modules\busboy\lib\types\multipart.js:567:19)<br> at writeOrBuffer (node:internal/streams/writable:392:12)
</pre>
</body>
</html>
我尝试了各种方法,包括将我的 fetch API 更改为 axios,但没有什么真正有效。我还尝试查看其他人的 React 应用程序中它们是如何做的(后端和前端),但我没有看到任何关键的区别...
我也检查了我的 用户 PUT 路由 是否有 multer
中间件。
最后,我尝试用以下内容替换了 控制器 中的 user_pictureURL
:
${req.protocol}://${req.get('host')}/images/${req.file.filename}
--> ${req.file.filename}
代码
后端
首先是我的 server.js
和 app.js
:
server.js
:
const http = require('http');
const app = require('./app');
function normalizePort(val) {
const port = parseInt(val, 10);
if (isNaN(port)) {
return val;
}
if (port >= 0) {
return port;
}
return false;
};
const port = normalizePort('8080');
app.set('port', port);
function errorHandler(error) {
if (error.syscall !== 'listen') {
throw error;
}
const address = server.address();
const bind = typeof address === 'string' ? 'pipe ' + address : 'port: ' + port;
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges.');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use.');
process.exit(1);
break;
default:
throw error;
}
};
const server = http.createServer(app);
server.on('error', errorHandler);
server.on('listening', () => {
const address = server.address();
const bind = typeof address === 'string' ? 'pipe ' + address : 'port ' + port;
console.log('Listening on ' + bind);
});
server.listen(port);
app.js
:
const express = require('express');
const path = require('path');
const usersRoutes = require("./routes/user.routes");
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', '*');
res.setHeader('Access-Control-Allow-Methods', '*');
next();
});
app.use('/images', express.static(path.join(__dirname, 'images')));
app.use('/api/users', usersRoutes);
app.use('/images', express.static(path.join(__dirname, 'images')));
module.exports = app;
接下来是我的使用 Sequelize 创建的 用户数据库模型:
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class user extends Model {
static associate(models) {
}
}
user.init({
user_id: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
firstName: {
type: DataTypes.STRING,
allowNull: false
},
lastName: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
isEmail: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
user_pictureURL: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null
},
user_description: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: "Write your description here!"
}
}, {
sequelize,
modelName: 'user',
});
return user;
};
然后,是我的 用户控制器,特别是 updateUser
:
const { user } = require("../models");
const fs = require('fs');
let self = {};
self.updateUser = async (req, res) => {
try {
const userID = req.params.id;
const userExist = await user.findOne({ where: { user_id: userID } });
if (userExist) {
const userObject = req.file
? {
...fs.unlink(`images/${userExist.user_pictureURL}`, () => { }),
user
<details>
<summary>英文:</summary>
**Problem**
In my application, the user can modify his description and profile picture. To do that, I created a controller `updateUser` and a `multer` middleware in the **backend** and a PUT fetch in the **frontend**. However, while I can successfully modify the user's description, when I try to upload a file, I get the following `500 Internal Server Error`:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>
MulterError: Unexpected field<br>
&nbsp;&nbsp;at wrappedFileFilter (C:\Users\barba\Desktop\Groupomania\backend\node_modules\multer\index.js:40:19)<br>
&nbsp;&nbsp;at Multipart.&lt;anonymous &gt;(C:\Users\barba\Desktop\Groupomania\backend\node_modules\multer\lib\make-middleware.js:107:7)<br>
&nbsp;&nbsp;at Multipart.emit (node:events:513:28)<br>
&nbsp;&nbsp;at HeaderParser.cb (C:\Users\barba\Desktop\Groupomania\backend\node_modules\busboy\lib\types\multipart.js:358:14)<br>
&nbsp;&nbsp;at HeaderParser.push (C:\Users\barba\Desktop\Groupomania\backend\node_modules\busboy\lib\types\multipart.js:162:20)<br>
&nbsp;&nbsp;at SBMH.ssCb [as _cb] (C:\Users\barba\Desktop\Groupomania\backend\node_modules\busboy\lib\types\multipart.js:394:37)<br>
&nbsp;&nbsp;at feed (C:\Users\barba\Desktop\Groupomania\backend\node_modules\streamsearch\lib\sbmh.js:248:10)<br>
&nbsp;&nbsp;at SBMH.push (C:\Users\barba\Desktop\Groupomania\backend\node_modules\streamsearch\lib\sbmh.js:104:16)<br>
&nbsp;&nbsp;at Multipart._write (C:\Users\barba\Desktop\Groupomania\backend\node_modules\busboy\lib\types\multipart.js:567:19)<br>&nbsp;&nbsp;at writeOrBuffer (node:internal/streams/writable:392:12)
</pre>
</body>
</html>
I tried various things, including changing my fetch API with axios, but nothing really works. I also tried to check in other people's React apps how they do (backend and frontend), but I didn't manage to see any crucial difference...
I also did check that my **user put route** had the `multer` middleware.
Finally, I tried to replace the `user_pictureURL` from the **controller**:
`${req.protocol}://${req.get('host')}/images/${req.file.filename}` --> `${req.file.filename}`
**Code**
**Backend**
First are my `server.js` and `app.js`:
`server.js`:
const http = require('http');
const app = require('./app');
function normalizePort(val) {
const port = parseInt(val, 10);
if (isNaN(port)) {
return val;
}
if (port >= 0) {
return port;
}
return false;
};
const port = normalizePort('8080');
app.set('port', port);
function errorHandler(error) {
if (error.syscall !== 'listen') {
throw error;
}
const address = server.address();
const bind = typeof address === 'string' ? 'pipe ' + address : 'port: ' + port;
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges.');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use.');
process.exit(1);
break;
default:
throw error;
}
};
const server = http.createServer(app);
server.on('error', errorHandler);
server.on('listening', () => {
const address = server.address();
const bind = typeof address === 'string' ? 'pipe ' + address : 'port ' + port;
console.log('Listening on ' + bind);
});
server.listen(port);
`app.js`:
const express = require('express');
const path = require('path');
const usersRoutes = require("./routes/user.routes");
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', '*');
res.setHeader('Access-Control-Allow-Methods', '*');
next();
});
app.use('/images', express.static(path.join(__dirname, 'images')));
app.use('/api/users', usersRoutes);
app.use('/images', express.static(path.join(__dirname, 'images')));
module.exports = app;
Next is my **user database model** made with **sequelize**:
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class user extends Model {
static associate(models) {
}
}
user.init({
user_id: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
firstName: {
type: DataTypes.STRING,
allowNull: false
},
lastName: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
isEmail: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
user_pictureURL: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null
},
user_description: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: "Write your description here!"
}
}, {
sequelize,
modelName: 'user',
});
return user;
};
Then, it is my **user controllers** and more precisely the `updateUser`:
const { user } = require("../models");
const fs = require('fs');
let self = {};
self.updateUser = async (req, res) => {
try {
const userID = req.params.id;
const userExist = await user.findOne({ where: { user_id: userID } });
if (userExist) {
const userObject = req.file
? {
...fs.unlink(`images/${userExist.user_pictureURL}`, () => { }),
user_id: userID,
firstName: userExist.firstName,
lastName: userExist.lastName,
email: userExist.email,
password: userExist.password,
user_pictureURL: `${req.protocol}://${req.get('host')}/images/${req.file.filename}`,
user_description: req.body.user_description,
createdAt: userExist.createdAt,
updatedAt: Date.now()
}
: {
user_id: userID,
firstName: userExist.firstName,
lastName: userExist.lastName,
email: userExist.email,
password: userExist.password,
user_pictureURL: userExist.user_pictureURL,
user_description: req.body.user_description,
createdAt: userExist.createdAt,
updatedAt: Date.now()
};
const updatedUser = await user.update(userObject, { where: { user_id: userID } });
if (updatedUser[0] === 1) {
return res.status(200).json({
success: true,
message: `User with the id=${userID} has been updated!`,
data: updatedUser
});
} else {
return res.status(400).json({
success: false,
message: `User with the id=${userID} has not been updated!`,
data: updatedUser
});
}
} else {
return res.status(404).json({
success: false,
message: `User with the id=${userID} does not exist!`
});
}
} catch (error) {
return res.status(500).json({
success: false,
error: error,
message: "From userCtlr"
});
}
};
module.exports = self;
Finally, it's my `multer` middleware:
const multer = require("multer");
const MIME_TYPES = {
"image/jpg": "jpg",
"image/jpeg": "jpg",
"image/png": "png"
};
const fileFilter = (req, file, callback) => {
if (file.mimetype == "image/jpg" || file.mimetype == "image/jpeg" || file.mimetype == "image/png") {
return callback(null, true);
} else {
return callback(new Error("The file format is not supported!"), false);
}
};
const storage = multer.diskStorage({
destination: (req, file, callback) => {
callback(null, "images");
},
filename: (req, file, callback) => {
const name = file.originalname.split(" ").join("_");
const extension = MIME_TYPES[file.mimetype];
callback(null, name + Date.now() + "." + extension);
}
});
module.exports = multer({ storage, fileFilter }).single("images");
**Frontend**
Finally here's the frontend component. As I was not sure that the modals had any impact in the fetch I didn't delete them:
function Settings() {
const userId = sessionStorage.getItem("userId");
const token = sessionStorage.getItem("token");
let [userDescription, setUserDescription] = useState("");
const [pictureFile, setPictureFile] = useState(false);
let [myModals, setMyModals] = useState({
updateDescriptionModal: false,
uploadFileModal: false,
});
const getModalHandler = (modalName) => {
return {
isOpen: myModals[modalName],
open: () => setMyModals((state) => ({ ...state, [modalName]: true })),
close: () => setMyModals((state) => ({ ...state, [modalName]: false })),
};
};
const updateDescriptionModal = getModalHandler("updateDescriptionModal");
const uploadFileModal = getModalHandler("uploadFileModal");
const updateAvatar = (event) => {
if (event.target.files && event.target.files.length > 0) {
setPictureFile(event.target.files[0]);
}
};
const handleUserDescriptionUpdate = (event) => {
event.preventDefault();
const newUserData = new FormData();
newUserData.append("user_description", userDescription);
if (pictureFile) {
newUserData.append("user_pictureURL", pictureFile)
}
updateUser(newUserData);
};
const updateUser = async (body) => {
await fetch(`http://localhost:8080/api/users/${userId}`, {
method: "PUT",
body: body,
headers: {
'Authorization': `Bearer ${token}`
},
})
.then((response) => response.json())
.then((data) => {
if (data.success) {
if (pictureFile) {
uploadFileModal.close();
} else {
updateDescriptionModal.close();
}
} else {
alert(`${data.message}`);
}
})
.catch((error) => {
console.log(error.message);
});
};
return (
<div id="settingsContainer">
<button onClick={uploadFileModal.open}>Modify picture</button>
<button onClick={updateDescriptionModal.open}>Modify description</button>
<Dialog open={myModals.uploadFileModal}>
<DialogTitle>Delete account</DialogTitle>
<DialogContent>
<DialogContentText>
Please upload your <b>new avatar picture</b> here and <b>confirm</b> it!
</DialogContentText>
<input
type="file"
accept=".png, .jpeg, .jpg"
onChange={updateAvatar}
/>
</DialogContent>
<DialogActions>
<button onClick={uploadFileModal.close}>Cancel</button>
<button onClick={handleUserDescriptionUpdate}>Confirm</button>
</DialogActions>
</Dialog>
<Dialog open={myModals.updateDescriptionModal}>
<DialogTitle>Delete account</DialogTitle>
<DialogContent>
<DialogContentText>
Please write your <b>new description</b> here and <b>confirm</b> it!
</DialogContentText>
<TextField
fullWidth
type="text"
value={userDescription}
onChange={(event) => setUserDescription(event.target.value)}
/>
</DialogContent>
<DialogActions>
<button onClick={updateDescriptionModal.close}>Cancel</button>
<button onClick={handleUserDescriptionUpdate}>Confirm</button>
</DialogActions>
</Dialog>
</div>
);
}
I thank in advance anyone who will take the time to try to help me :).
</details>
# 答案1
**得分**: 1
确保前端使用的字段名称与后端期望的字段名称匹配。在你的情况下,字段名称应该是“user_pictureURL”,正如后端控制器代码中所指定的。检查开发者控制台的网络选项卡,以验证发送到后端的内容。在前端代码中,确保允许用户选择要上传的文件的表单或输入元素具有属性名称为“user_pictureURL”。类似于以下内容...
```js
<input type="file" name="user_pictureURL" accept=".png, .jpeg, .jpg" onChange={updateAvatar} />
在后端控制器代码中,确保 multer 中间件设置了正确的字段名称。更新 multer 配置以使用字段名称“user_pictureURL”。类似于以下内容...
const upload = multer({ storage }).single("user_pictureURL");
英文:
Make sure that the field name used in the frontend matches the field name expected by the backend. In your case, the field name should be "user_pictureURL" as specified in the backend controller code. Check developer consoles network tab to verify what is being sent to the backend. In the frontend code, make sure the form or input element that allows the user to select the file for uploading has the attribute name="user_pictureURL" Something like this...
<input type="file" name="user_pictureURL" accept=".png, .jpeg, .jpg" onChange={updateAvatar} />
In the backend controller code, make sure the multer middleware is set up with the correct field name. Update the multer configuration to use the field name "user_pictureURL". Something like this...
const upload = multer({ storage }).single("user_pictureURL")
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论