部署时,FormData 不适用于使用 Redux 和 AXIOS 在 React 项目中上传文件

FormData doesn't work on upload files in React project with Redux and AXIOS when Its deployed

我有一个包含 Redux 和 AXIOS 的完整 MERN 堆栈项目。我使用 FormData 将图像上传到我的节点服务器,它有 multer,它在我的本地主机上工作得很好,即使 chrome 上的控制台说空? (FormData {})。当它 部署 时,我的 FormData 是空的。所以我在没有文件的情况下测试了我的 FormData(只是来自表单的输入值)并且它传递到服务器并将它放在 req.body.

我尝试添加配置我的 formData 但没有成功。

我做错了什么???

例如

config: { headers: { 'Content-Type': 'multipart/form-data' } }等.......

这是我的一些代码:

REACT 表单 JS

import React, { Component, Fragment } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import TextAreaFieldGroup from "../common/TextAreaFieldGroup";
import InputGroup from "../common/InputGroup";
import { addEventful, upload } from "../../actions/eventfulActions";

import Dropzone from "react-dropzone";

const imageMaxSize = 10000000
; //bytes
const acceptedFileTypes =
  "image/x-png, image/png, image/jpg, image/jpeg, image/gif";
const acceptedFileTypesArray = acceptedFileTypes.split(",").map(item => {
  return item.trim();
});

class EventfulForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      eventtitle: "",
      description: "",
      // comments:'',
      files: [],
      errors: {}
    };

    this.onChange = this.onChange.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
  }


  componentWillReceiveProps(newProps) {
    if (newProps.errors) {
      this.setState({ errors: newProps.errors });
    }
  }

  verifyFile(files){
    if(files && files.length > 0){
      const currentFile = files[0]
      const currentFileType = currentFile.type
      const currentFileSize = currentFile.size
      if(currentFileSize > imageMaxSize){
        alert("TOO MANY FILES")
        return false
      }
      if (!acceptedFileTypesArray.includes(currentFileType)) {
        alert("IMAGES ONLY")
        return false
      }
      return true

    }
  }
  onSubmit(e) {
    e.preventDefault();
    const { user } = this.props.auth;



    const formdata = new FormData();
    this.state.files.forEach((file, i) => {
      const newFile = { uri: file, type: "image/jpg" };
      formdata.append("file", file, file.name);
    });

    // const newEventful = {
    //   eventtitle: this.state.eventtitle,
    //   description: this.state.description,
    //   pictures: this.state.pictures,
    //   name: user.name
    // };

    formdata.append("eventtitle", this.state.eventtitle);
    formdata.append("description", this.state.description);
    formdata.append("name", user.name);

    this.props.addEventful(formdata);
    this.setState({ eventtitle: "" });
    this.setState({ description: "" });
    this.setState({ files: [] });
  }
  onChange(e) {
    this.setState({ [e.target.name]: e.target.value });
  }

  onDrop = (files, rejectedFiles) => {
    if(rejectedFiles && rejectedFiles.length > 0){
      console.log(rejectedFiles)
      this.verifyFile(rejectedFiles)
    }
    if (files && files.length > 0) {
      const isVerified = this.verifyFile(files)
      if(isVerified){
        console.log(files[0].name);
        const formdata = new FormData();
        files.map(file => {
          formdata.append("file", file, file.name);
        });
        // formdata.append("file", files[0], files[0].name);

        console.log(formdata);
        // this.props.upload(formdata);
        this.setState({
          files: files
        });
      }
    }
  };

  render() {
    const previewStyle = {
      display: "inline",
      width: 100,
      height: 100
    };
    const { errors, files } = this.state;

    return (
      <div className="post-form mb-3">
        <div className="card card-info">
          <div className="card-header bg-info text-white">Create an Event</div>
          <div className="card-body">
            <form onSubmit={this.onSubmit}>
              <div className="form-group">
                <InputGroup
                  placeholder="Create a event title"
                  name="eventtitle"
                  value={this.state.eventtitle}
                  onChange={this.onChange}
                  error={errors.eventtitle}
                />
                {files.length > 0 && (
                  <Fragment>
                    <h3>Files name</h3>
                    {files.map((picture, i) => (
                      <p key={i}>{picture.name}</p>
                    ))}
                  </Fragment>
                )}
                <Dropzone
                  onDrop={this.onDrop.bind(this)}
                  accept={acceptedFileTypes}
                  maxSize={imageMaxSize}
                >
                  <div>
                    drop images here, or click to select images to upload.
                  </div>
                </Dropzone>


                <TextAreaFieldGroup
                  placeholder="Description"
                  name="description"
                  value={this.state.description}
                  onChange={this.onChange}
                  error={errors.description}
                />
              </div>
              <button type="submit" className="btn btn-dark">
                Submit
              </button>
            </form>
          </div>
        </div>
      </div>
    );
  }
}

EventfulForm.propTypes = {
  addEventful: PropTypes.func.isRequired,
  auth: PropTypes.object.isRequired,
  errors: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
  auth: state.auth,
  errors: state.errors,
  eventful: state.files
});

export default connect(
  mapStateToProps,
  { addEventful, upload }
)(EventfulForm);

我的FormAction.js

import axios from "axios";

import {
  ADD_EVENTFUL,
  GET_ERRORS,
  ADD_LIKE,
  REMOVE_LIKE,
  GET_EVENTFUL,
  GET_EVENTFULS,
  DELETE_EVENTFUL,
  CLEAR_ERRORS,
  EVENTFUL_LOADING,
  UPLOAD_FILES
} from "./types";

const config = {
  onUploadProgress: progressEvent =>
    console.log(
      "Upload Progress" +
        Math.round((progressEvent.loaded / progressEvent.total) * 100) +
        "%"
    )
};
// Add eventful
export const addEventful = eventfulData => dispatch => {
  dispatch(clearErrors());
  // .post("/api/eventfuls", eventfulData, config)

  axios({
    method: 'post',
    url: '/api/eventfuls',
    data: eventfulData,
    config: { headers: { 'Content-Type': 'multipart/form-data' } }

  }).then(res =>
      dispatch({
        type: ADD_EVENTFUL,
        payload: res.data
      })
    )
    .catch(err =>
      dispatch({
        type: GET_ERRORS,
        payload: err.response.data
      })
    );
};

node.js

const express = require("express");
const router = express.Router();
const mongoose = require("mongoose");
const passport = require("passport");
const bodyParser = require("body-parser");

// Eventful model
const Eventful = require("../../models/Eventful");
const User = require("../../models/User");

// Validation
const validateEventfulInput = require("../../validation/eventful");
const validateCommentInput = require("../../validation/comment");

var multer = require("multer");

var fs = require("fs");
var path = require("path");

var btoa = require("btoa");

router.use(
  bodyParser.urlencoded({
    extended: false
  })
);
router.use(bodyParser.json());

var storage = multer.diskStorage({
  destination: function(req, file, cb) {
    cb(null, __dirname + "../../../uploads"); //you tell where to upload the files,
  },
  filename: function(req, file, cb) {
    cb(null, file.fieldname + "-" + Date.now());
  }
});

var upload = multer({
  storage: storage
}).array("file");

router.use((request, response, next) => {
  response.header("Access-Control-Allow-Origin", "*");
  response.header(
    "Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS"
  );
  response.header("Access-Control-Allow-Headers", "Content-Type");
  next();
});

// @route   POST api/eventfuls
// @desc    Create eventful
// @access  Private
router.post(
  "/",
  passport.authenticate("jwt", { session: false }),
  (req, res) => {
    upload(req, res, err => {
      console.log("req.body!!!!!", req.body);
      const { errors, isValid } = validateEventfulInput(req.body);

      // Check Validation
      if (!isValid) {
        console.log(errors);
        // If any errors, send 400 with errors object
        return res.status(400).json(errors);
      }

      console.log("req.files!!!!!", req.files);
      if (err) {
        console.log(err);
        res.status(404).json({
          uploadFailed: "Upload failed"
        });
      } else {
        let newArr = [];

        for (let file of req.files) {
          let fileReadSync = fs.readFileSync(file.path);
          let item = {};
          item.image = {};
          item.image.data = fileReadSync;
          item.image.contentType = "img/png";
          newArr.push(item);

          fs.unlink(file.path, function(err) {
            if (err) {
              console.log("error deleting image", file.path);
            } else {
              console.log("deleted image", file.path);
            }
          });
        }
        for (var i = 0; i < newArr.length; i++) {
          var base64 = btoa(
            new Uint8Array(newArr[i].image.data).reduce(
              (data, byte) => data + String.fromCharCode(byte),
              ""
            )
          );
          newArr[i].image.data = base64;
        }

        console.log("33333333333333333333", newArr);

        const newEventful = new Eventful({
          title: req.body.eventtitle,
          description: req.body.description,
          pictures: newArr,
          user: req.user.id,
          name: req.user.name
        });

        newEventful.save().then(eventful => res.json(eventful));
      }
      console.log("skipped....................");
    }
  );
  }
);

我的 PM2 上的错误/日志

0|server | 2019-01-13 21:27 -07:00: Server is ready to take messages 0|server | 2019-01-13 21:28 -07:00: req.body!!!!! [Object: null prototype] {} 0|server | 2019-01-13 21:28 -07:00: req.files!!!!! [] 0|server | 2019-01-13 21:28 -07:00: { [Error: ENOENT: no such file or directory, open '/var/www/LCTW/uploads/file-1547440111023'] 0|server | 2019-01-13 21:28 -07:00: errno: -2, 0|server | 2019-01-13 21:28 -07:00: code: 'ENOENT', 0|server | 2019-01-13 21:28 -07:00: syscall: 'open', 0|server | 2019-01-13 21:28 -07:00: path: '/var/www/LCTW/uploads/file-1547440111023', 0|server | 2019-01-13 21:28 -07:00: storageErrors: [] }

这里我的 req.body 和 req.files 是空的。 但是

当我在 node.js 上注释掉文件部分时,req.body 存在!

0|server   | 2019-01-13 21:40 -07:00: req.body!!!!! [Object: null prototype] {
0|server   | 2019-01-13 21:40 -07:00:   eventtitle: 'asdfas',
0|server   | 2019-01-13 21:40 -07:00:   description: 'asdfads',
0|server   | 2019-01-13 21:40 -07:00:   name: 'In Soo Yang' }
config: { headers: { 'Content-Type': 'multipart/form-data' } } 

multipart/form-data内容类型必须指定boundary参数,你无法提前知道。

不要覆盖将由 XHR/fetch 自动设置的 Content-Type。

我发现你的代码有两个问题

第一个 来自 body-parser

的 npm 页面

This does not handle multipart bodies, due to their complex and typically large nature. For multipart bodies, you may be interested in the following modules:

  1. busboy and connect-busboy
  2. multiparty and connect-multiparty
  3. formidable
  4. multer

所以 body-parser 不会填充 req.body 但由于您已经在使用 multer 这里有一个关于如何使用 [=21= 填充 req.body 的示例].

app.post('/', upload.none(), function (req, res, next) {
  // req.body contains the text fields
})

但由于您需要文件,而上述方法不起作用,您可以使用 upload.any()

其次你的中间件注入顺序错误。

改变这个

var upload = multer({
  storage: storage
}).array("file");

var upload = multer({
  storage: storage
})

而不是

router.post(
  "/",
  passport.authenticate("jwt", { session: false }),
  (req, res) => {
    upload(req, res, err => {

     //code

    }
  );
  }
);

router.post(
  "/",
  passport.authenticate("jwt", { session: false }),
  upload.array("file"), //or upload.any()
  (req, res) => {

    //....code
    //now req.body sould work
    //file should be at req.files

  );
  }
);

编辑 1

在app.js或index.js或您应用程序的起点添加

global.rootPath = __dirname;

global.rootPath 现在将拥有您的应用程序的完整路径。前 /usr/user/Desktop/myapp 使用 path,join(global.rootPath, "uploads") 会给你 /usr/user/Desktop/myapp/uploads。 使用 path.join 的好处是它可以处理不同的 OS 路径系统,例如 Windows 和 *nix

始终使用 path.join 创建所有路径。

var storage = multer.diskStorage({
  destination: function(req, file, cb) {
    cb(null, path.join(global.rootPath, "uploads")); //you tell where to upload the files,
  },
  filename: function(req, file, cb) {
    cb(null, file.fieldname + "-" + Date.now());
  }
});

我已经成功地在我自己的React代码中使用FormData()上传文件,但是由于某些我无法解释的原因,需要在最后附加文件。我想知道这是否与先前提到边界参数要求以及在实际上传发生之前无法知道它有关。

尝试先附加数据,最后再附加文件。我也会尝试将单个文件作为测试用例。再次,最后。

首先附加这些:

formdata.append("eventtitle", this.state.eventtitle);
formdata.append("description", this.state.description);
formdata.append("name", user.name);

然后调用这个:

this.state.files.forEach((file, i) => {
  const newFile = { uri: file, type: "image/jpg" };
  formdata.append("file", file, file.name);
});

希望对您有所帮助。作为记录,我也使用 multer,但在服务器端使用 multer 时遇到了同样的问题。在文件之前附加数据是我需要的修复。

此致,

数据库

当您使用 multer 时,您的图像存储在您请求的文件 属性 中,因此 req.file,但您有 req.files。不确定这是否是您唯一的问题,因为我看到其他人对 Whosebug 发表了评论,但我认为这也是一个问题。做一个 console.log(req.file) 并确保我是对的,但我只是在我的代码中使用了 multer 并且我的作品。

我想在这里补充的一件事是你不能 console.log 到 FormData Object。

例如:-

var formData = new FormData();
formData.append('username', 'Rahul');
formData.append('username', 'Yogesh');

console.log(formData) // empty:- FormData {}

但该数据将发送至 API。您可以在 API Headers.

中查看该数据