首页 > 解决方案 > 使用 react.js node.js express.js REST API 将配置文件图片上传到 sqlite DB

问题描述

所以我试图通过 react.js 应用程序将配置文件图像上传到作为 REST API 运行的后端,使用 node.js express.js 和 sqlite 作为数据库。

但不知何故,我找不到任何教程对我有用。不过我一定很近。所以我现在拥有的是:

我的路线如下所示:
playerRoutes.js

const express = require('express');
const router = express.Router();
const multer = require('multer');
const uuidv4 = require('uuid/v4');

const upload_dir = './uploads';

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, upload_dir);
  },
  filename: (req, file, cb) => {
    cb(null, `${uuidv4()}-${file.filename.toLowerCase}`);
  }
});

const upload = multer({
  storage: storage,
  fileFilter: (req, file, cb) => {
    if (
      file.mimetype == 'image/png' ||
      file.mimetype == 'image/jpg' ||
      file.mimetype == 'image/jpeg'
    ) {
      cb(null, true);
    } else {
      cb(null, false);
      return cb(new Error('Only .png, .jpg and .jpeg format allowed!'));
    }
  }
});

const { playerController } = require('../controller');

router.get('/', playerController.getAllPlayer);
router.get('/:id', playerController.getSpecificPlayer);
router.post('/', upload.single('profileImg'), playerController.createPlayer);
router.put('/:id', playerController.updatePlayer);
router.delete('/:id', playerController.deletePlayer);

module.exports = router;

所以关键点是它的设置storage告诉在哪里上传+文件过滤器upload,对吧?

哪个route.post将`upload.single('profileImg'),对吗?该路线将包括我用于创建 Player 的控制器,可以在此处找到:

playerController.js

const { Player } = require('../models');

module.exports = {
  getAllPlayer(req, res) {
    return Player.findAll({
      attributes: ['id', 'name', 'nickname', 'profileImg']
    })
      .then(players => res.status(200).send(players))
      .catch(err => res.status(400).send(err));
  },
  getSpecificPlayer(req, res) {
    return Player.findByPk(req.params.id, {
      attributes: ['id', 'name', 'nickname', 'profileImg']
    }).then(player => {
      if (!player) {
        return res.status(404).send({
          message: `Player with id ${req.params.id} not found`
        });
      }
      return res.status(200).send(player);
    });
  },
  createPlayer(req, res) {
    console.log(req.file);
    return Player.create({
      name: req.body.name,
      nickname: req.body.nickname
    })
      .then(player => res.status(201).send(player))
      .catch(err => res.status(400).send(err));
  },
  updatePlayer(req, res) {
    return Player.findByPk(req.params.id, {
      attributes: ['id', 'name', 'nickname', 'profileImg']
    })
      .then(player => {
        if (!player) {
          return res.status(404).send({
            message: `Player with id ${req.params.id} not found`
          });
        }
        return player
          .update({
            name: req.body.name || player.name,
            nickname: req.body.nickname || player.nickname,
            profileImg: req.body.profileImg || player.profileImg
          })
          .then(() => res.status(200).send(player))
          .catch(err => res.status(400).send(err));
      })
      .catch(err => res.status(400).send(err));
  },
  deletePlayer(req, res) {
    return Player.findByPk(req.params.id, {
      attributes: ['id', 'name', 'nickname', 'profileImg']
    })
      .then(player => {
        if (!player) {
          return res.status(404).send({
            message: `Player with id ${req.params.id} not found`
          });
        }
        return player
          .destroy()
          .then(() => res.status(204).send())
          .catch(err => res.status(400).send(err));
      })
      .catch(err => res.status(400).send(err));
  }
};

现在,关于几个教程,我应该在console.log(req.file)使用帖子点击端点时看到其中的信息,对吧?

模型如下所示:

models/player.js(续集模型)

'use strict';
module.exports = (sequelize, DataTypes) => {
  const Player = sequelize.define(
    'Player',
    {
      name: {
        type: DataTypes.STRING,
        validate: { notEmpty: { msg: 'Player name cannot be empty' } }
      },
      nickname: {
        type: DataTypes.STRING
      },
      profileImg: {
        type: DataTypes.STRING
      }
    },
    {}
  );
  Player.associate = function(models) {
    // associations can be defined here
  };
  return Player;
};

所以现在到前端代码(react.js):

这是我在我的反应应用程序中加载的表单:

playerform.js

import React, { Component } from 'react';
import axios from 'axios';
import {
  FormControl,
  FormGroup,
  FormLabel,
  Form,
  Button
} from 'react-bootstrap';
import PropTypes from 'prop-types';

class PlayerForm extends Component {
  state = {
    name: '',
    nickname: '',
    profileImg: ''
  };

  handleSubmit = e => {
    e.preventDefault();

    axios
      .post(`${process.env.API_URL}/player`, JSON.stringify(this.state), {
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json'
        }
      })
      .then(() => this.props.onCreate())
      .catch(err => console.log(err));
  };

  onFileChange(e) {
    this.setState({ profileImg: e.target.files[0] });
  }

  handelChange = e => {
    this.setState({
      [e.target.name]: e.target.value
    });
  };

  render() {
    return (
      <div className="container">
        <Form onSubmit={this.handleSubmit}>
          <FormGroup>
            <FormLabel>Name</FormLabel>
            <FormControl
              type="text"
              name="name"
              placeholder="Enter player name"
              onChange={this.handelChange.bind(this)}
            />
          </FormGroup>
          <FormGroup>
            <FormLabel>Nickname</FormLabel>
            <FormControl
              type="text"
              name="nickname"
              placeholder="Enter player nickname"
              onChange={this.handelChange.bind(this)}
            />
          </FormGroup>
          <FormGroup>
            <FormLabel>Picture</FormLabel>
            <FormControl
              type="file"
              name="file"
              playerholder="Upload player picture"
              onChange={this.onFileChange.bind(this)}
            />
          </FormGroup>
          <Button variant="btn btn-primary" type="submit">
            Add
          </Button>
        </Form>
      </div>
    );
  }
}

PlayerForm.propTypes = {
  onCreate: PropTypes.func.isRequired
};

export default PlayerForm;

因此,当输入内容并点击Add按钮时,我的 api 声明 req.file 未定义,我无法找出原因。

谁能帮我深入分析错误?

标签: node.jsreactjsrestexpresssequelize.js

解决方案


multer您的预期内容与您实际从前端发布的内容略有不同。

如果您使用Postmanmultipart/form-data (或类似工具),请尝试向您的“创建播放器”端点发送 POST 请求。你可能会看到它有效。

但是,您的 axios 帖子'Content-Type': 'application/json'以 multer 包无法处理的格式发送数据。

尝试使用new FormData()此处讨论的构造如何在 axios 中设置多部分并做出反应?


推荐阅读