J'ai un projet de pile MERN complet avec Redux et AXIOS. J'ai utilisé FormData pour télécharger les images sur mon serveur de nœuds qui a le multer et cela fonctionne parfaitement bien sur mon hôte local même la console sur mon chrome dit vide? (FormData {}). Lorsqu'il est déployé , mon FormData est vide. J'ai donc testé mon FormData sans les fichiers (juste la valeur d'entrée des formulaires) et il passe au serveur et l'a sur req.body.
J'ai essayé d'ajouter la configuration de mon formData et n'a pas fonctionné.
Qu'est-ce que je fais mal ???
Par exemple
config: {headers: {'Content-Type': ' multipart / form-data '}}
etc .....
Voici quelques-uns de mes codes:
REACT Formulaire JS
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' }
Mon FormAction.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...................."); } ); } );
node.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 }) ); };
ERREURS / JOURNAUX sur mon PM2
0 | serveur | 2019-01-13 21:27 -07: 00: Le serveur est prêt à prendre des messages 0 | serveur | 2019-01-13 21:28 -07: 00: corps demandé !!!!! [Objet: null prototype] {} 0 | serveur | 2019-01-13 21:28 -07: 00: req.files !!!!! [] 0 | serveur | 2019-01-13 21:28 -07: 00: {[Erreur: ENOENT: pas de tel fichier ou répertoire, ouvrez '/ var / www / LCTW / uploads / file-1547440111023'] 0 | serveur | 2019-01-13 21:28 -07: 00: numéro d'erreur: -2, 0 | serveur | 2019-01-13 21:28 -07: 00: code: 'ENOENT', 0 | serveur | 13/01/2019 21:28 -07: 00: appel système: 'ouvert', 0 | serveur | 2019-01-13 21:28 -07: 00: chemin: '/ var / www / LCTW / uploads / file-1547440111023', 0 | serveur | 2019-01-13 21:28 -07: 00: storageErrors: []}
ici, mes fichiers req.body et req.files sont vides. MAIS
quand j'ai commenté des parties de fichiers sur mon node.js, req.body existe!
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);
4 Réponses :
config: { headers: { 'Content-Type': 'multipart/form-data' } }Le type de contenu multipart / form-data doit spécifier le paramètre
boundary
, que vous ne pouvez pas connaître à l'avance.Ne remplacez pas le type de contenu qui sera défini automatiquement par XHR / fetch.
J'ai essayé avec et sans. c'est pareil. dites-vous que je dois définir la limite?
Je peux voir deux problèmes dans votre code
D'abord à partir de la page npm de analyseur de corps
Cela ne gère pas les corps en plusieurs parties, en raison de leur complexité et typiquement grande nature. Pour les corps en plusieurs parties, vous pourriez être intéressé par les modules suivants:
- busboy et connect-busboy
- multipartie et connexion-multipartite
- formidable
- multer
Donc, body-parser
ne remplira pas le req.body
mais puisque vous utilisez déjà multer
voici un exemple sur comment remplir le req.body
avec le multipart/form-data
.
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()); } });
mais puisque vous avez besoin de fichiers et ce qui précède ne fonctionnera pas, vous pouvez utiliser upload.any()
Deuxièmement votre injection de middleware est dans le mauvais ordre.
Modifier ceci
global.rootPath = __dirname;
à
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 ); } );
Et au lieu de
router.post( "/", passport.authenticate("jwt", { session: false }), (req, res) => { upload(req, res, err => { //code } ); } );
faire
var upload = multer({ storage: storage })
MODIFIER 1
Ajouter dans app.js ou index.js ou le point de départ de votre application p>
var upload = multer({ storage: storage }).array("file");
global.rootPath
aura désormais le chemin complet de votre application. ex / usr / user / Desktop / myapp
en utilisant path, join (global.rootPath, "uploads")
vous donnera / usr / user / Desktop / myapp / uploads
.
La bonne chose d'utiliser path.join
est qu'il gère différents systèmes de chemin de système d'exploitation comme Windows et * nix
Utilisez toujours path.join
pour créer tous les chemins.
app.post('/', upload.none(), function (req, res, next) { // req.body contains the text fields })
Merci beaucoup. Cela fonctionne un peu, maintenant j'ai de nouvelles erreurs sur mes journaux pm2, lorsque j'essaye de télécharger une image. 0 | serveur | 2019-01-14 02:56 -07: 00: Erreur: ENOENT: aucun fichier ou répertoire de ce type, ouvrez '/ var / www / LCTW / uploads / file-1547459776817'
Comment se fait-il qu'il cherche ce fichier? n'est-il pas censé rechercher base64 ?
avec POST 500 ERROR
Est-ce quelque chose à voir avec mon var storage = multer.diskStorage ({destination: function (req, file, cb) {cb (null, __dirname + "../../../uploads"); / / vous indiquez où télécharger les fichiers,}, filename: function (req, file, cb) {cb (null, file.fieldname + "-" + Date.now ());}});
?
@ I.Y Eh bien, vous faites fs.readFileSync (file.path);
donc supposément si le fichier n'est pas là, vous devriez obtenir l'erreur ENOENT
. Je vous suggère de toujours utiliser path.join pour créer des chemins !!. Je vous suggère également de faire quelque chose comme global.rootPath = __dirname
à l'index.js et d'éviter d'utiliser ../../ ..
. Idéalement, cela devrait ressembler à path, join (__ dirname, global.rootPath, "uploads")
comme ça dans mon server.js? app.use (path.join (__ dirname, global.rootPath, "uploads"));
j'ai essayé d'avoir une mauvaise passerelle
Pas exactement. J'ai ajouté l'exemple dans la réponse. J'ai accidentellement fait une erreur de copier-coller dans le commentaire
son cb (null, path.join (global.rootPath, "uploads"))
son "." Pas vrai? eh bien, j'ai essayé les deux et il y a une erreur en disant path.js: 28 throw new TypeError ('Path must be a string. Received' + inspect (path));
J'ai utilisé avec succès FormData () dans mon propre code React pour télécharger des fichiers, mais pour une raison que je ne peux pas m'expliquer, les fichiers doivent être ajoutés en dernier. Je me demande si cela a à voir avec la réponse précédente mentionnant l'exigence d'un paramètre de limite et l'incapacité de le connaître jusqu'à ce que le téléchargement réel se produise.
Essayez d'abord d'ajouter vos données, puis les fichiers en dernier. Je voudrais également simplement essayer un seul fichier comme cas de test. Encore une fois, dernier.
Ajoutez-les d'abord:
this.state.files.forEach((file, i) => { const newFile = { uri: file, type: "image/jpg" }; formdata.append("file", file, file.name); });
Puis appelez ceci:
formdata.append("eventtitle", this.state.eventtitle); formdata.append("description", this.state.description); formdata.append("name", user.name);
J'espère que cela vous aidera. Pour mémoire, j'utilise également multer, mais j'ai eu le même problème en utilisant multer côté serveur. L'ajout de données avant les fichiers était ma solution requise.
Cordialement,
DB
lorsque vous utilisez multer, votre image est stockée dans la propriété file de votre requête, donc req.file, mais vous avez req.files. Je ne sais pas si c'est votre seul problème, car je vois que d'autres ont commenté stackOverflow, mais je pense que c'est également un problème. Faites un console.log (req.file) et assurez-vous que j'ai raison, mais j'ai juste utilisé multer dans mon code aussi et le mien fonctionne.