简体   繁体   English

使用 Multer 提交表单时如何防止图像保存

[英]How to Prevent an Image from Saving When Submitting Form Using Multer

I am working on a Nodejs project and I am trying to use multer to store images locally.我正在开发一个 Nodejs 项目,我正在尝试使用 multer 在本地存储图像。 I have made my form in HTML and was able to get everything working as it should.我已经在 HTML 中制作了我的表格,并且能够让一切正常工作。 When images are saved, they are stored in an uploads folder i created.保存图像后,它们将存储在我创建的上传文件夹中。 However, I am running into the issue of images from the form being stored in an uploads folder, even when there are errors such as empty fields that cause a redirection to the form page.但是,我遇到了将表单中的图像存储在上传文件夹中的问题,即使存在诸如导致重定向到表单页面的空字段之类的错误。 Is there anyway to prevent the image from saving unless the form is properly completed?除非表格正确填写,否则是否有阻止图像保存的方法? Heres the link to my repo: https://github.com/halsheik/RecipeWarehouse.git .这是我的仓库的链接: https://github.com/halsheik/RecipeWarehouse.git Below are the edits made to add multer into project.以下是将 multer 添加到项目中所做的编辑。

// Modules required to run the application
const express = require('express');
const multer = require('multer');
const crypto = require('crypto');
const path = require('path');
const { ensureAuthenticated } = require('../config/auth');

// Creates 'mini app'
const router = express.Router();

// Models
const Recipe = require('../models/Recipe'); // Recipe Model

// Set up storage engine
const storage = multer.diskStorage({
    destination: function(req, file, callback){
        callback(null, 'public/uploads');
    },

    filename: function(req, file, callback){
        crypto.pseudoRandomBytes(16, function(err, raw) {
            if (err) return callback(err);
          
            callback(null, raw.toString('hex') + path.extname(file.originalname));
        });
    }
});

const upload = multer({
    storage: storage
});

// My Recipes
router.get('/myRecipes', ensureAuthenticated, function(req, res){
    Recipe.find({}, function(err, recipes){
        if(err){
          console.log(err);
        } else {
          res.render('./home/myRecipes', {
            recipes: recipes,
            ingredients: recipes.ingredients,
            directions: recipes.directions
          });
        }
      });
});

// Create Recipe Page
router.get('/createRecipe', ensureAuthenticated, function(req, res){
    res.render('./home/createRecipe');
});

// Create Recipe
router.post('/createRecipe', upload.single('recipeImage'), ensureAuthenticated, function(req, res){
    const { recipeName, ingredients, directions } = req.body;
    let errors = [];

    // Checks that all fields are not empty
    if(!recipeName || !ingredients || !directions){
        errors.push({ msg: 'Please fill in all fields.' });
    }

    // Checks that an image is uploaded
    if(!req.file){
        errors.push({ msg: 'Please add an image of your recipe' });
    }

    // Checks for any errors and prevents recipe creation if any
    if(errors.length > 0){
        console.log(errors);
        res.render('./home/createRecipe', {
            errors,
            recipeName,
            ingredients,
            directions
        });
    } else {
        // Create a new 'Recipe' using our model
        const newRecipe = new Recipe({
            recipeName: recipeName,
            author: req.user._id,
            ingredients: ingredients,
            directions: directions,
        }); 

        // Saves recipe to mongoDB database
        newRecipe.save().then(function(){
            res.redirect('/recipes/myRecipes');
        }).catch(function(err){
            console.log(err);
        });
    }

});

module.exports = router;
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Homemade</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div class="newRecipeContainer">
        <form action="/recipes/createRecipe" method="POST" enctype="multipart/form-data">
            <div class="recipeNameContainer">
                <label class="recipeNameLabel">Title</label>
                <input type="text" name="recipeName">
            </div>

            <div class="recipeImage">
                <input type="file" accept="image/*" name="recipeImage" onchange="validateImageFile(this);"/> 
            </div>

            <div class="ingredientsContainer">
                <button class="addIngredientButton" type="button" @click="addIngredientForm">Add Another Ingredient</button>
        
                <div class="allIngredients" v-for="(ingredient, ingredientIndex) in ingredients">
                    <label class="ingredient">{{ ingredientIndex + 1 }}.)</label>
                    <input type="text" name="ingredients" v-model="ingredient.ingredient">
                    
                    <button class="deleteIngredientButton" type="button" v-if="ingredientIndex > 0" @click="deleteIngredientForm(ingredientIndex)">Delete Ingredient</button>
                </div>
            </div>

            <div class="directionsContainer">
                <button class="addDirectionButton" type="button" @click="addDirectionForm">Add Another Direction</button>
        
                <div class="allDirections" v-for="(direction, directionIndex) in directions">
                    <label class="direction">{{ directionIndex + 1 }}.)</label>
                    <input type="text" name="directions" v-model="direction.direction">
                    
                    <button class="deleteDirectionButton" type="button" v-if="directionIndex > 0" @click="deleteDirectionForm(directionIndex)">Delete Direction</button>
                </div>
            </div>

            <button class="createRecipeButton" type="submit">Create Recipe</button>
        </form>
    </div>

    <script src="/controls/newRecipeControl.js"></script>
</body>
</html>

Thanks for any help!谢谢你的帮助!

I had the same problem with this for a school project I did a month back.对于一个月前做的一个学校项目,我遇到了同样的问题。 I solved it by using multers memory storage and then persisting it myself using the buffer that multer gives.我通过使用 multer memory 存储解决了这个问题,然后使用 multer 提供的缓冲区自己保存它。 a bit of a dumb workaround, but it did the trick for me, and since you seem to have the same problem as I did, it will work for you too.有点愚蠢的解决方法,但它对我有用,而且由于你似乎和我有同样的问题,它也对你有用。

check out their documentation on how to use it.查看他们关于如何使用它的文档。 also check out how to write the buffer to a file with fs module.还可以查看如何使用 fs 模块将缓冲区写入文件。

EDIT:编辑:

Ok, I've found the code:好的,我找到了代码:

export const validateRequest = (req, res, next, schema, fileExpected = false) => {
    const options = { abortEarly: false, allowUnknown: true, stripUnknown: true };
    const { error, value } = schema.validate(req.body, options);
    const validationErrors = [];

    if (fileExpected && req.file === undefined) validationErrors.push('"prod_image" is requiered.');
    if (error) error.details.forEach(x => validationErrors.push(x.message));
    
    if (validationErrors.length > 0) {
        res.status(400).json(validationErrors);
    } else {
        req.body = value;
        next();
    }
};

since multer populates req.file and req.body at the same time, and since it needs to run before joi to handle the multipart/form-data, this is how I validate the reqest.由于 multer 同时填充 req.file 和 req.body,并且由于它需要在 joi 之前运行以处理多部分/表单数据,这就是我验证请求的方式。 After this, all that is left is to persist the file to disk.在此之后,剩下的就是将文件持久化到磁盘。 I did it like so:我是这样做的:

import fs from 'fs';
import path from 'path';
import multer from 'multer';
import { randomBytes } from 'crypto';
import { srcPath } from './../settings';


const storage = multer.memoryStorage();

const fileFilter = (req, file, cb) => {
    const ext = path.extname(file.originalname);
    if (ext !== '.jpg' && ext !== '.png') return cb(new Error('Invalid image extension.'));
    cb(null, true);
};

export const upload = multer({storage: storage, fileFilter: fileFilter });

export const persistImage = (file, cb) => {
    const ext = path.extname(file.originalname);
    const newName = randomBytes(16).toString('hex') + ext;
    const imagesFolderPath = srcPath + '/productImages/';
    const finalPath = path.join(imagesFolderPath, newName);
    fs.writeFile(finalPath, file.buffer, (err) => cb(err, newName));
};

export const removeImage = (imageName, cb) => {
    const imagesFolderPath = srcPath + '/productImages/';
    const finalPath = path.join(imagesFolderPath, imageName);
    fs.unlink(finalPath, (err) => cb(err));
};

The removeImage function is needed if saving data to the database fails.如果将数据保存到数据库失败,则需要 removeImage function。 This is a really bad solution in my opinion, but it was a requirement for the class.在我看来,这是一个非常糟糕的解决方案,但它是 class 的要求。 My professor considers saving images in the database evil.我的教授认为将图像保存在数据库中是邪恶的。 In a real scenario you would want to save them to something like Azures blob storage or something akin to that.在实际场景中,您可能希望将它们保存到 Azures blob 存储或类似的东西。 That would be ideal, but my project needed the files to be saved in the project folder, soooooo.....那将是理想的,但是我的项目需要将文件保存在项目文件夹中,sooooo .....

Many things can go wrong when doing it like this.这样做时,很多事情都会出错。 Hope this helps, cheers.希望这会有所帮助,干杯。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM