Локальные изображения в массиве Multer, не сохраняемые в экспрессе или реакции - PullRequest
1 голос
/ 08 ноября 2019

У меня есть приложение MERN, и я пытаюсь сохранить файлы локально, однако, похоже, ничего не работает.

Я пытаюсь загрузить массив из нескольких images.

Вот то, что у меня есть код:

collection.js

const mongoose = require("mongoose");

let collectionSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  },
  description: {
    type: String,
    required: true
  },
  reference: {
    type: String,
    required: true
  },
  images: [{ filename: String, mimetype: String, data: Buffer }],
  price: {
    type: Number,
    required: true
  },
  year: Number,
  categoryName: {
    type: String,
    required: true
  },
  sold: {
    type: Boolean,
    default: false
  }
});

collectionSchema.virtual("category", {
  ref: "category",
  localField: "name",
  foreignField: "categoryName",
  justOne: false
});

module.exports = mongoose.model("collection", collectionSchema);

collectionController.js POST метод

/* POST create a collection */
module.exports.createCollection = [
  ...validations,
  (req, res) => {
    // Get validation errors from the request
    const errors = validationResult(req);
    // Return the errors
    if (!errors.isEmpty()) {
      return res.status(422).json({ error: errors.array() });
    }

    Collections.findOne({ _id: req.body.id }).then(function(collection) {
      Collections.create({
        title: req.body.title,
        description: req.body.description,
        reference: req.body.reference,
        images: req.body.images,
        price: req.body.price,
        year: req.body.year,
        categoryName: req.body.categoryName,
        sold: req.body.sold
      })
        .then(function(collection) {
          res.status(201).json(collection);
        })
        .catch(function(error) {
          console.error(error);
          res.status(500).send(error);
        });
    });
  }
];

маршруты

const express = require("express");
const router = express.Router();
const passport = require("passport");
const collection = require("../controllers/collectionsController");
const multer = require("multer");
const GridFsStorage = require("multer-gridfs-storage");
const crypto = require("crypto");

const authenticate = passport.authenticate("jwt", { session: false });

// Create storage engine
const storage = new GridFsStorage({
  url: process.env.MONGODB_URI,
  file: (req, file) => {
    return new Promise((resolve, reject) => {
      crypto.randomBytes(16, (err, buf) => {
        if (err) {
          return reject(err);
        }
        const filename = file.originalname;
        const fileInfo = {
          filename: filename,
          bucketName: "uploads"
        };
        resolve(fileInfo);
      });
    });
  }
});

const upload = multer({ storage });

router.get("/", collection.index);
router.get("/:id", collection.getCollection);

// CRUD routes
router.post(
  "/create",
  authenticate,
  upload.single("images"),
  collection.createCollection
);
router.put("/:id", authenticate, collection.updateCollection);
router.delete("/:id", authenticate, collection.deleteCollection);

module.exports = router;

create component

import React, { Component } from "react";
import axios from "axios";
import swal from "sweetalert";

import AuthService from "../../Auth/AuthService";
import withAuth from "../../Auth/withAuth";
const Auth = new AuthService();

class Create extends Component {
  constructor() {
    super();
    this.state = {
      title: "",
      description: "",
      reference: "",
      images: [],
      price: "",
      year: "",
      categoryName: "",
      categorys: [],
      sold: false,
      titleErr: "",
      descriptionErr: "",
      referenceErr: "",
      imagesErr: "",
      priceErr: "",
      yearErr: "",
      categoryErr: ""
    };
  }

  validate = () => {
    let titleErr = "";
    let descriptionErr = "";
    let referenceErr = "";
    let imagesErr = "";
    let priceErr = "";
    let yearErr = "";
    let categoryNameErr = "";

    // Title validation
    if (!this.state.title) {
      titleErr = "Please enter a title";
    }

    // Description validation
    if (!this.state.description) {
      descriptionErr = "Please enter a description";
    }

    // Reference validation
    if (!this.state.reference) {
      referenceErr = "Please enter a reference";
    }

    // Images validation
    if (!this.state.images) {
      imagesErr = "You must have at least one image";
    }

    // Price validation
    if (!this.state.price) {
      priceErr = "Please enter a price";
    }

    // Year validation
    if (!this.state.year) {
      yearErr = "Please enter a year";
    }

    // Category validation
    if (!this.state.categoryName) {
      categoryNameErr = "Please enter a category";
    }

    // Render validations
    if (
      titleErr ||
      descriptionErr ||
      referenceErr ||
      imagesErr ||
      priceErr ||
      yearErr ||
      categoryNameErr
    ) {
      this.setState({
        titleErr,
        descriptionErr,
        referenceErr,
        imagesErr,
        priceErr,
        yearErr,
        categoryNameErr
      });
      return false;
    }

    return true;
  };

  componentDidMount() {
    axios
      .get("/api/category")
      .then(res => this.setState({ categorys: res.data }))
      .catch(error => {
        console.log(error);
      });
  }

  onTitleChange(event) {
    this.setState({ title: event.target.value });
  }

  onDescriptionChange(event) {
    this.setState({ description: event.target.value });
  }

  onReferenceChange(event) {
    this.setState({ reference: event.target.value });
  }

  onImagesChange(event) {
    this.setState({ images: event.target.value });
  }

  onPriceChange(event) {
    this.setState({ price: event.target.value });
  }

  onYearChange(event) {
    this.setState({ year: event.target.value });
  }

  onCategoryNameChange(event) {
    this.setState({ categoryName: event.target.value });
  }

  onSoldChange(event) {
    this.setState({ sold: event.target.value });
  }

  onSubmit = e => {
    e.preventDefault();
    const isValid = this.validate();
    const {
      title,
      description,
      reference,
      images,
      price,
      year,
      categoryName,
      sold
    } = this.state;

    let config = {
      headers: { Authorization: "bearer " + Auth.getToken() }
    };

    let body = {
      title,
      description,
      reference,
      images,
      price,
      year,
      categoryName,
      sold
    };

    const file = document.getElementById("images").files;
    const formData = new FormData();
    formData.append(body, file);

    if (isValid) {
      axios
        .post("/api/collections/create", formData, config)
        .then(result => {
          swal({
            title: "Success",
            text: "You have created a collection",
            icon: "success",
            button: "OK"
          });
          this.props.history.push("/dashboard");
        })
        .catch(error => {
          swal({
            title: "Error",
            text: `${error}`,
            icon: "error",
            button: "Try again"
          });
        });
    }
  };

  render() {
    const {
      title,
      description,
      reference,
      images,
      price,
      year,
      categoryName,
      sold
    } = this.state;

    return (
      <>
        <div className='block md:flex md:flex-column h-full'>
          <div className='p-12 w-full text-center text-gray-800'>
            <h1 className='title mb-10'>Create a collection</h1>

            <form
              className='w-full m-auto max-w-lg'
              onSubmit={this.onSubmit}
              encType='multipart/form-data'
            >
              <div className='flex flex-wrap mb-4'>
                <label htmlFor='title'>Title:</label>
                <input
                  type='text'
                  name='title'
                  value={title}
                  onChange={this.onTitleChange.bind(this)}
                  placeholder='Title'
                />

                <p className='mt-2 text-red-500 text-xs'>
                  {this.state.titleErr}
                </p>
              </div>

              <div className='flex flex-wrap'>
                <label htmlFor='description'>Description:</label>
                <textarea
                  type='text'
                  name='description'
                  className='h-64'
                  value={description}
                  onChange={this.onDescriptionChange.bind(this)}
                  placeholder='Content'
                ></textarea>

                <p className='mb-4 text-red-500 text-xs'>
                  {this.state.descriptionErr}
                </p>
              </div>

              <div className='flex flex-wrap mb-4'>
                <label htmlFor='reference'>Reference:</label>
                <input
                  type='text'
                  name='reference'
                  value={reference}
                  onChange={this.onReferenceChange.bind(this)}
                  placeholder='reference'
                />

                <p className='mt-2 text-red-500 text-xs'>
                  {this.state.referenceErr}
                </p>
              </div>

              <div className='flex flex-wrap mb-4'>
                <label htmlFor='images'>images:</label>
                <input
                  type='file'
                  multiple
                  name='images'
                  value={images}
                  onChange={this.onImagesChange.bind(this)}
                  id='images'
                ></input>

                <p className='mt-2 text-red-500 text-xs'>
                  {this.state.imagesErr}
                </p>
              </div>

              <div className='flex flex-wrap mb-4'>
                <label htmlFor='price'>Price:</label>
                <input
                  type='number'
                  name='price'
                  value={price}
                  onChange={this.onPriceChange.bind(this)}
                  placeholder='price'
                />

                <p className='mt-2 text-red-500 text-xs'>
                  {this.state.priceErr}
                </p>
              </div>

              <div className='flex flex-wrap mb-4'>
                <label htmlFor='year'>Year:</label>
                <input
                  type='number'
                  name='year'
                  value={year}
                  onChange={this.onYearChange.bind(this)}
                  placeholder='Year'
                />

                <p className='mt-2 text-red-500 text-xs'>
                  {this.state.yearErr}
                </p>
              </div>

              <div className='flex flex-col mb-4'>
                <label htmlFor='categoryName'>Category</label>

                <div className='relative'>
                  <select
                    name='categoryName'
                    value={categoryName}
                    onChange={this.onCategoryNameChange.bind(this)}
                  >
                    <option>N/A</option>
                    {this.state.categorys.map(category => (
                      <option key={category._id} value={category.name}>
                        {category.name}
                      </option>
                    ))}
                  </select>

                  <div className='pointer-events-none absolute inset-y-0 right-0 flex items-center px-2'>
                    <svg
                      className='fill-current h-4 w-4'
                      xmlns='http://www.w3.org/2000/svg'
                      viewBox='0 0 20 20'
                    >
                      <path d='M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z' />
                    </svg>
                  </div>
                </div>
              </div>

              <div className='flex flex-col mb-2'>
                <label htmlFor='sold'>Sold?</label>

                <div className='relative'>
                  <select
                    name='sold'
                    value={sold}
                    onChange={this.onSoldChange.bind(this)}
                  >
                    <option value='false'>No</option>
                    <option value='true'>Yes</option>
                  </select>

                  <div className='pointer-events-none absolute inset-y-0 right-0 flex items-center px-2'>
                    <svg
                      className='fill-current h-4 w-4'
                      xmlns='http://www.w3.org/2000/svg'
                      viewBox='0 0 20 20'
                    >
                      <path d='M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z' />
                    </svg>
                  </div>
                </div>
              </div>

              <div className='flex'>
                <button type='submit' className='btn w-full'>
                  Submit
                </button>
              </div>
            </form>
          </div>
        </div>
      </>
    );
  }
}

export default withAuth(Create);

Если я тестирую создание коллекции в почтальоне, я получаю следующее

Любая помощь в этом была бы великолепной, как это былоя весь день везу меня на стену, пытаясь даже сохранить базовые локальные изображения, не говоря уже о S3.

Теперь я получаю

1 Ответ

0 голосов
/ 08 ноября 2019

РЕДАКТИРОВАТЬ: я понял, что вы используете upload.single("images") вместо upload.array("images") ... https://github.com/expressjs/multer#arrayfieldname-maxcount

Я создал очень простую суть, надеюсь, это поможет вам: https://gist.github.com/danikane/740fd36b100bebfc21620181936b8b53

Прежде всего, ваша форма multipart / form-data? Получаете ли вы файлы и сохраняются ли они в файловой системе в папке назначения, которую вы определили с указанным вами именем файла? Сначала обойдите логику сохранения MongoDB, упростите и логику мультитера - например, пропустите функцию имени файла. Я не совсем понял, где сохранить не удается.

Если вы хотите сохранить файлы в MongoDB, у вас есть 2 варианта:

1) Используйте GridFS для хранения файла (для файлов, которые могутпревышает 16 МБ)

https://docs.mongodb.com/manual/core/gridfs/

https://www.settletom.com/blog/uploading-images-to-mongodb-with-multer

2) Сохранять буфер входящего файла (с параметром MemoryStorage или fs.readFile загруженного файла)

https://github.com/expressjs/multer#file-information

Похоже, вы пытаетесь смешивать вещи. :)

Опять же, вы можете использовать файловую систему для хранения файлов и просто сохранить путь и свойства файла в массиве images в вашем документе MongoDB.

Youможно также попробовать express-fileupload , но я сомневаюсь, что ваша проблема действительно с multer. Как только вы все сделаете правильно, AWS S3 будет очень легким делом.

ИМХО MongoDB - отличный универсал, мне это нравится, но для этой задачи могут быть более подходящие варианты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...