首页 > 解决方案 > 即使在使用 Multer 和 HTML 的 Nodejs 项目中表单提交失败时,图像也会保存

问题描述

我正在开发一个 Nodejs 项目,目前正试图弄清楚如何防止在表单提交失败时保存通过表单上传的图像(即由于空字段)。我查看了其他几篇其他帖子、Google 和 multer 文档,但无法弄清楚如何阻止上传发生。这是我的仓库的代码:https ://github.com/halsheik/RecipeWarehouse.git 。下面,我发布了任何相关代码。谢谢你的帮助。

// 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
          });
        }
      });
});

// My Recipes
router.get('/createRecipe', ensureAuthenticated, function(req, res){
    res.render('./home/createRecipe');
});

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

    // Checks that all fields are not empty
    if(!recipeName || !recipeDescription || !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){
        // Displays create Recipe form along with errors
        res.render('./home/createRecipe', {
            errors
        });
    } else {
        // Create a new 'Recipe' using our model
        const newRecipe = new Recipe({
            recipeName: recipeName,
            author: req.user._id,
            recipeImageFileName: req.file.filename,
            recipeDescription: recipeDescription,
            ingredients: ingredients,
            directions: directions,
        }); 

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

// Get Single Recipe
router.get('/:id', function(req, res){
    // Searches for a 'Recipe' with a unique 'id'
    Recipe.findById(req.params.id, function(err, recipe){
        if(err){
            throw err;
        }

        // Renders the Recipe in its own page with full information
        res.render('./home/recipe.ejs', {
            recipe: recipe
        });
    });
  });

// Delete recipe
router.delete('/:id', function(req, res){
    const query = {_id: req.params.id}
  
    Recipe.deleteOne(query, function(err){
        if(err){
          console.log(err);
          throw err;
        }
        
        res.send('Success');
    });
  });

module.exports = router;
<!DOCTYPE html>
<html lang="en">
    <head>
        <!-- Required meta tags -->
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Homemade</title>

        <!-- Required program scripts -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script src="https://code.jquery.com/jquery-3.5.1.js" integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc=" crossorigin="anonymous"></script>
        
        <!-- Style Sheets-->
        <link rel="stylesheet" href="/styles/navBarStyle.css">
        <link rel="stylesheet" href="/styles/myRecipesStyle.css">
        <link rel="stylesheet" href="/styles/createRecipeStyle.css">
    </head>
    <body>
        <!-- Background image -->
        <img id="background" src="/images/foodBackground.jpg" alt="">

        <div id="newRecipeContainer">
            <div id="closeButtonContainer">
                <div id="backButton"><a id="back" href="/recipes/myRecipes">&larr; My Recipes</a></div>
            </div>
            
            <form id="createRecipeForm" action="/recipes/createRecipe" method="POST" enctype="multipart/form-data">
                <label id="formSubHeading">Create Your Homemade Recipe</label>

                <div id="recipeNameContainer">
                    <label id="recipeNameLabel">Title</label>
                    <input id="recipeNameInput" type="text" name="recipeName">
                </div>

                <div id="recipeImage">
                    <label id="recipeImageLabel">Add An Image of Your Meal</label>
                    <input id="recipeImageInput" type="file" accept="image/*" name="recipeImage"/> 
                    <label id="recipeImageInputLabel" for="recipeImageInput" name="recipeImage">Choose A File</label>
                </div>

                <div id="recipeDescription">
                    <label id="recipeDescriptionLabel">Description</label>
                    <textarea id="recipeDescriptionInput" name="recipeDescription" cols="30" rows="10" maxlength="2000"></textarea>
                </div>

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

                <div class="directionsContainer">
                    <label id="directionsLabel">Directions</label>
                    <button id="addDirectionButton" type="button" @click="addDirectionForm">Add Another Direction</button>
            
                    <div class="allDirections" v-for="(direction, directionIndex) in directions">
                        <label class="directionLabel">{{ directionIndex + 1 }}.)</label>
                        <input class="directionInput"type="text" name="directions" v-model="direction.direction">
                        
                        <button class="deleteDirectionButton" type="button" v-if="directionIndex > 0" @click="deleteDirectionForm(directionIndex)">X</button>
                    </div>
                </div>
                
                <div id="createRecipeButtonContainer">
                    <button id="createRecipeButton" type="submit">Create Recipe</button>
                </div>
                
            </form>
        </div>

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

再次感谢任何帮助。

标签: htmlnode.jsexpressmulter

解决方案


我知道有两种方法可以停止上传

  1. 您可以使用fileFilter。它是传递给 multer 的选项之一。但它只能用于检查文件类型等,因为req.body它可能只包含出现在表单中文件之前的字段。
const upload = multer({
    storage: storage,
    fileFilter: (req, file, cb) => {

        // req.body is NOT reliable here 
        // it may only contain the some of the fields

        // To reject this file pass `false`, like so:
        cb(null, false)
 
        // To accept the file pass `true`, like so:
        cb(null, true)
 
        // You can always pass an error if something goes wrong:
        cb(new Error('I don\'t have a clue!'))
 
}
});
router.post('/createRecipe', ensureAuthenticated, upload.single('recipeImage'), function(req, res){

    if(!req.file){
        //the file was not uploaded
    }
    ...
}

我也调查了你的回购。您fileFilter作为选项传递给multer.diskStorage. 这没有任何作用。multer.diskStorage只接受 2 个选项destinationfilename. 将其传递multer

  1. 使用 MemoryStorage(默认或替代),仅在满足条件时才保存到磁盘

推荐阅读