使用React中的formData和Fetch上传文件

huangapple go评论76阅读模式
英文:

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>
            &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>

我尝试了各种方法,包括将我的 fetch API 更改为 axios,但没有什么真正有效。我还尝试查看其他人的 React 应用程序中它们是如何做的(后端和前端),但我没有看到任何关键的区别...

我也检查了我的 用户 PUT 路由 是否有 multer 中间件。

最后,我尝试用以下内容替换了 控制器 中的 user_pictureURL

${req.protocol}://${req.get('host')}/images/${req.file.filename} --> ${req.file.filename}

代码

后端

首先是我的 server.jsapp.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&#39;s description, when I try to upload a file, I get the following `500 Internal Server Error`:

    &lt;!DOCTYPE html&gt;
    &lt;html lang=&quot;en&quot;&gt;
        &lt;head&gt;
            &lt;meta charset=&quot;utf-8&quot;&gt;
            &lt;title&gt;Error&lt;/title&gt;
        &lt;/head&gt;
        &lt;body&gt;
            &lt;pre&gt;
                MulterError: Unexpected field&lt;br&gt;
                &amp;nbsp;&amp;nbsp;at wrappedFileFilter (C:\Users\barba\Desktop\Groupomania\backend\node_modules\multer\index.js:40:19)&lt;br&gt;
                &amp;nbsp;&amp;nbsp;at Multipart.&amp;lt;anonymous &amp;gt;(C:\Users\barba\Desktop\Groupomania\backend\node_modules\multer\lib\make-middleware.js:107:7)&lt;br&gt;
                &amp;nbsp;&amp;nbsp;at Multipart.emit (node:events:513:28)&lt;br&gt;
                &amp;nbsp;&amp;nbsp;at HeaderParser.cb (C:\Users\barba\Desktop\Groupomania\backend\node_modules\busboy\lib\types\multipart.js:358:14)&lt;br&gt;
                &amp;nbsp;&amp;nbsp;at HeaderParser.push (C:\Users\barba\Desktop\Groupomania\backend\node_modules\busboy\lib\types\multipart.js:162:20)&lt;br&gt;
                &amp;nbsp;&amp;nbsp;at SBMH.ssCb [as _cb] (C:\Users\barba\Desktop\Groupomania\backend\node_modules\busboy\lib\types\multipart.js:394:37)&lt;br&gt;
                &amp;nbsp;&amp;nbsp;at feed (C:\Users\barba\Desktop\Groupomania\backend\node_modules\streamsearch\lib\sbmh.js:248:10)&lt;br&gt;
                &amp;nbsp;&amp;nbsp;at SBMH.push (C:\Users\barba\Desktop\Groupomania\backend\node_modules\streamsearch\lib\sbmh.js:104:16)&lt;br&gt;
                &amp;nbsp;&amp;nbsp;at Multipart._write (C:\Users\barba\Desktop\Groupomania\backend\node_modules\busboy\lib\types\multipart.js:567:19)&lt;br&gt;&amp;nbsp;&amp;nbsp;at writeOrBuffer (node:internal/streams/writable:392:12)
            &lt;/pre&gt;
        &lt;/body&gt;
    &lt;/html&gt;


I tried various things, including changing my fetch API with axios, but nothing really works. I also tried to check in other people&#39;s React apps how they do (backend and frontend), but I didn&#39;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(&#39;host&#39;)}/images/${req.file.filename}` --&gt; `${req.file.filename}`


**Code**
**Backend**

First are my `server.js` and `app.js`:

`server.js`:

    const http = require(&#39;http&#39;);
    const app = require(&#39;./app&#39;);
    
    function normalizePort(val) {
        const port = parseInt(val, 10);
        if (isNaN(port)) {
            return val;
        }
        if (port &gt;= 0) {
            return port;
        }
        return false;
    };
    
    const port = normalizePort(&#39;8080&#39;);
    app.set(&#39;port&#39;, port);
    
    function errorHandler(error) {
        if (error.syscall !== &#39;listen&#39;) {
            throw error;
        }
        const address = server.address();
        const bind = typeof address === &#39;string&#39; ? &#39;pipe &#39; + address : &#39;port: &#39; + port;
        switch (error.code) {
            case &#39;EACCES&#39;:
                console.error(bind + &#39; requires elevated privileges.&#39;);
                process.exit(1);
                break;
            case &#39;EADDRINUSE&#39;:
                console.error(bind + &#39; is already in use.&#39;);
                process.exit(1);
                break;
            default:
                throw error;
        }
    };
    
    const server = http.createServer(app);
    
    server.on(&#39;error&#39;, errorHandler);
    server.on(&#39;listening&#39;, () =&gt; {
        const address = server.address();
        const bind = typeof address === &#39;string&#39; ? &#39;pipe &#39; + address : &#39;port &#39; + port;
        console.log(&#39;Listening on &#39; + bind);
    });
    server.listen(port);


`app.js`:

    const express = require(&#39;express&#39;);
    const path = require(&#39;path&#39;);
    const usersRoutes = require(&quot;./routes/user.routes&quot;);
    
    const app = express();
    
    app.use(express.json());
    app.use(express.urlencoded({ extended: true }));
    
    app.use((req, res, next) =&gt; {
      res.setHeader(&#39;Access-Control-Allow-Origin&#39;, &#39;*&#39;);
      res.setHeader(&#39;Access-Control-Allow-Headers&#39;, &#39;*&#39;);
      res.setHeader(&#39;Access-Control-Allow-Methods&#39;, &#39;*&#39;);
      next();
    });
    
    app.use(&#39;/images&#39;, express.static(path.join(__dirname, &#39;images&#39;)));
    
    app.use(&#39;/api/users&#39;, usersRoutes);
    app.use(&#39;/images&#39;, express.static(path.join(__dirname, &#39;images&#39;)));
    
    module.exports = app;

Next is my **user database model** made with **sequelize**:

    &#39;use strict&#39;;
    
    const { Model } = require(&#39;sequelize&#39;);
    
    module.exports = (sequelize, DataTypes) =&gt; {
      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: &quot;Write your description here!&quot;
        }
      }, {
        sequelize,
        modelName: &#39;user&#39;,
      });
    
      return user;
    };



Then, it is my **user controllers** and more precisely the `updateUser`:

    const { user } = require(&quot;../models&quot;);
    const fs = require(&#39;fs&#39;);
    
    let self = {};
    
    self.updateUser = async (req, res) =&gt; {
        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}`, () =&gt; { }),
                        user_id: userID,
                        firstName: userExist.firstName,
                        lastName: userExist.lastName,
                        email: userExist.email,
                        password: userExist.password,
                        user_pictureURL: `${req.protocol}://${req.get(&#39;host&#39;)}/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: &quot;From userCtlr&quot;
            });
        }
    };
    
    module.exports = self;

Finally, it&#39;s my `multer` middleware:

    const multer = require(&quot;multer&quot;);
    
    const MIME_TYPES = {
        &quot;image/jpg&quot;: &quot;jpg&quot;,
        &quot;image/jpeg&quot;: &quot;jpg&quot;,
        &quot;image/png&quot;: &quot;png&quot;
    };
    
    const fileFilter = (req, file, callback) =&gt; {
        if (file.mimetype == &quot;image/jpg&quot; || file.mimetype == &quot;image/jpeg&quot; || file.mimetype == &quot;image/png&quot;) {
            return callback(null, true);
        } else {
            return callback(new Error(&quot;The file format is not supported!&quot;), false);
        }
    };
    
    const storage = multer.diskStorage({
        destination: (req, file, callback) =&gt; {
            callback(null, &quot;images&quot;);
        },
        filename: (req, file, callback) =&gt; {
            const name = file.originalname.split(&quot; &quot;).join(&quot;_&quot;);
            const extension = MIME_TYPES[file.mimetype];
            callback(null, name + Date.now() + &quot;.&quot; + extension);
        }
    });
    
    module.exports = multer({ storage, fileFilter }).single(&quot;images&quot;);


**Frontend**
Finally here&#39;s the frontend component. As I was not sure that the modals had any impact in the fetch I didn&#39;t delete them:

    function Settings() {
      const userId = sessionStorage.getItem(&quot;userId&quot;);
      const token = sessionStorage.getItem(&quot;token&quot;);
    
      let [userDescription, setUserDescription] = useState(&quot;&quot;);
      const [pictureFile, setPictureFile] = useState(false);
    
      let [myModals, setMyModals] = useState({
        updateDescriptionModal: false,
        uploadFileModal: false,
      });
    
      const getModalHandler = (modalName) =&gt; {
        return {
          isOpen: myModals[modalName],
          open: () =&gt; setMyModals((state) =&gt; ({ ...state, [modalName]: true })),
          close: () =&gt; setMyModals((state) =&gt; ({ ...state, [modalName]: false })),
        };
      };
    
      const updateDescriptionModal = getModalHandler(&quot;updateDescriptionModal&quot;);
      const uploadFileModal = getModalHandler(&quot;uploadFileModal&quot;);
    
      const updateAvatar = (event) =&gt; {
        if (event.target.files &amp;&amp; event.target.files.length &gt; 0) {
          setPictureFile(event.target.files[0]);
        }
      };
    
      const handleUserDescriptionUpdate = (event) =&gt; {
        event.preventDefault();
        const newUserData = new FormData();
        newUserData.append(&quot;user_description&quot;, userDescription);
        if (pictureFile) {
          newUserData.append(&quot;user_pictureURL&quot;, pictureFile)
        }
        updateUser(newUserData);
      };
    
      const updateUser = async (body) =&gt; {
        await fetch(`http://localhost:8080/api/users/${userId}`, {
          method: &quot;PUT&quot;,
          body: body,
          headers: {
            &#39;Authorization&#39;: `Bearer ${token}`
          },
        })
          .then((response) =&gt; response.json())
          .then((data) =&gt; {
            if (data.success) {
              if (pictureFile) {
                uploadFileModal.close();
              } else {
                updateDescriptionModal.close();
              }
            } else {
              alert(`${data.message}`);
            }
          })
          .catch((error) =&gt; {
            console.log(error.message);
          });
      };
    
      return (
        &lt;div id=&quot;settingsContainer&quot;&gt;
    
        &lt;button onClick={uploadFileModal.open}&gt;Modify picture&lt;/button&gt;
        &lt;button onClick={updateDescriptionModal.open}&gt;Modify description&lt;/button&gt;
    
    
          &lt;Dialog open={myModals.uploadFileModal}&gt;
            &lt;DialogTitle&gt;Delete account&lt;/DialogTitle&gt;
    
            &lt;DialogContent&gt;
              &lt;DialogContentText&gt;
                Please upload your &lt;b&gt;new avatar picture&lt;/b&gt; here and &lt;b&gt;confirm&lt;/b&gt; it!
              &lt;/DialogContentText&gt;
    
              &lt;input
                type=&quot;file&quot;
                accept=&quot;.png, .jpeg, .jpg&quot;
                onChange={updateAvatar}
              /&gt;
    
            &lt;/DialogContent&gt;
    
            &lt;DialogActions&gt;
              &lt;button onClick={uploadFileModal.close}&gt;Cancel&lt;/button&gt;
              &lt;button onClick={handleUserDescriptionUpdate}&gt;Confirm&lt;/button&gt;
            &lt;/DialogActions&gt;
    
          &lt;/Dialog&gt;
    
          &lt;Dialog open={myModals.updateDescriptionModal}&gt;
            &lt;DialogTitle&gt;Delete account&lt;/DialogTitle&gt;
    
            &lt;DialogContent&gt;
              &lt;DialogContentText&gt;
                Please write your &lt;b&gt;new description&lt;/b&gt; here and &lt;b&gt;confirm&lt;/b&gt; it!
              &lt;/DialogContentText&gt;
    
              &lt;TextField
                fullWidth
                type=&quot;text&quot;
                value={userDescription}
                onChange={(event) =&gt; setUserDescription(event.target.value)}
              /&gt;
    
            &lt;/DialogContent&gt;
    
            &lt;DialogActions&gt;
              &lt;button onClick={updateDescriptionModal.close}&gt;Cancel&lt;/button&gt;
              &lt;button onClick={handleUserDescriptionUpdate}&gt;Confirm&lt;/button&gt;
            &lt;/DialogActions&gt;
    
          &lt;/Dialog&gt;
    
        &lt;/div&gt;
      );
    }

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...

&lt;input type=&quot;file&quot; name=&quot;user_pictureURL&quot; accept=&quot;.png, .jpeg, .jpg&quot; onChange={updateAvatar} /&gt;

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(&quot;user_pictureURL&quot;)

huangapple
  • 本文由 发表于 2023年7月11日 02:43:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/76656495.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定