我有一个包含 Redux 和 AXIOS 的完整 MERN 堆栈项目。我使用 FormData 将图像上传到我的 Node 服务器,它有 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' }
最佳答案
我可以在你的代码中看到两个问题
首先来自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:
- busboy and connect-busboy
- multiparty and connect-multiparty
- formidable
- multer
所以 body-parser
不会填充 req.body
但是因为你已经在使用 multer
这里有一个关于如何填充的例子req.body
与 multipart/form-data
。
app.post('/', upload.none(), function (req, res, next) {
// req.body contains the text fields
})
但由于您需要文件,而上述方法无法正常工作,您可以使用 upload.any()
其次您的中间件注入(inject)顺序错误。
改变这个
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
的好处是它可以处理不同的操作系统路径系统,例如 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());
}
});
关于javascript - 部署时,FormData 不适用于使用 Redux 和 AXIOS 在 React 项目中上传文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54176118/