web-dev-qa-db-fra.com

Téléchargement d'images avec Mongoose, Express et AngularJS

Je sais que cela a été posé plusieurs fois auparavant, et j'ai lu presque tout ce que je pouvais trouver sur le sujet, à savoir:

https://stackoverflow.com/a/25022437/1031184

Téléchargement d'images à l'aide de Node.js, Express et Mongoose

Ce sont les meilleurs que j'ai trouvés jusqu'à présent. Mon problème est qu'ils ne sont toujours pas très clairs, il y a très peu de documentation en ligne à ce sujet et la discussion semble s'adresser à des gens beaucoup plus avancés que moi.

Donc, avec cela, j'aimerais vraiment que quelqu'un me montre comment télécharger des images en utilisant Mongoose, Express & AngularJS. J'utilise en fait le fullstack MEAN. (ce générateur pour être précis - https://github.com/DaftMonk/generator-angular-fullstack )

AddController:

'use strict';

angular.module('lumicaApp')
  .controller('ProjectAddCtrl', ['$scope', '$location', '$log', 'projectsModel', 'users', 'types', function ($scope, $location, $log, projectsModel, users, types) {
    $scope.dismiss = function () {
      $scope.$dismiss();
    };

        $scope.users = users;
        $scope.types = types;

    $scope.project = {
            name: null,
            type: null,
            images: {
                thumbnail: null // I want to add the uploaded images _id here to reference with mongoose populate.
            },
            users: null
        };

        $scope.save = function () {
            $log.info($scope.project);
            projectsModel.post($scope.project).then(function (project) {
        $scope.$dismiss();
            });
        }

  }]);

Je veux ajouter la référence ID des images à project.images.thumbnail mais je veux stocker toutes les informations dans un objet image en utilisant le schéma suivant:

'use strict';

    var mongoose = require('mongoose'),
        Schema = mongoose.Schema;

    var ImageSchema = new Schema({
      fileName: String,
      url: String,
      contentType: String,
      size: String,
      dimensions: String
    });

    module.exports = mongoose.model('Image', ImageSchema);

J'ai également ajouté les éléments suivants https://github.com/nervgh/angular-file-upload à mes packages Bower.

Comme je l'ai dit, je n'arrive pas à comprendre comment lier tout cela ensemble. Et je ne sais même pas si ce que j'essaie de faire est la bonne manière non plus.

-------------------------------------------------- ------------------------ \

MISE À JOUR:

Voici ce que j'ai maintenant, j'ai ajouté quelques commentaires détaillant comment j'aimerais que cela fonctionne, malheureusement je n'ai toujours pas réussi à faire fonctionner cela, je ne peux même pas obtenir l'image pour commencer le téléchargement, peu importe le téléchargement sur S3 . Désolé d'être une douleur mais je trouve cela particulièrement déroutant, ce qui me surprend.

client/app/people/add/add.controller.js

'use strict';

angular.module('lumicaApp')
    .controller('AddPersonCtrl', ['$scope', '$http', '$location', '$window', '$log', 'Auth', 'FileUploader', 'projects', 'usersModel', function ($scope, $http, $location, $window, $log, Auth, FileUploader, projects, usersModel) {
        $scope.dismiss = function () {
            $scope.$dismiss();
        };

        $scope.newResource = {};

        // Upload Profile Image
        $scope.onUploadSelect = function($files) {
            $scope.newResource.newUploadName = $files[0].name;

            $http
                .post('/api/uploads', {
                    uploadName: newResource.newUploadName,
                    upload: newResource.newUpload
                })
                .success(function(data) {
                    newResource.upload = data; // To be saved later
                });
        };

        $log.info($scope.newResource);

        //Get Projects List
        $scope.projects = projects;

        //Register New User
        $scope.user = {};
        $scope.errors = {};


        $scope.register = function(form) {
            $scope.submitted = true;

            if(form.$valid) {
                Auth.createUser({
                    firstName: $scope.user.firstName,
                    lastName: $scope.user.lastName,
                    username: $scope.user.username,
                    profileImage: $scope.user.profileImage, // I want to add the _id reference for the image here to I can populate it with 'ImageSchema' using mongoose to get the image details(Name, URL, FileSize, ContentType, ETC)
                    assigned: {
                        teams: null,
                        projects: $scope.user.assigned.projects
                    },
                    email: $scope.user.email,
                    password: $scope.user.password
                })
                    .then( function() {
                        // Account created, redirect to home
                        //$location.path('/');
                        $scope.$dismiss();
                    })
                    .catch( function(err) {
                        err = err.data;
                        $scope.errors = {};

                        // Update validity of form fields that match the mongoose errors
                        angular.forEach(err.errors, function(error, field) {
                            form[field].$setValidity('mongoose', false);
                            $scope.errors[field] = error.message;
                        });
                    });
            }
        };

        $scope.loginOauth = function(provider) {
            $window.location.href = '/auth/' + provider;
        };

    }]);

server/api/image/image.model.js Je voudrais stocker toutes les informations sur l'image ici et l'utiliser pour remplir profileImage dans contrôleur de personnes.

'use strict';

    var mongoose = require('mongoose'),
        Schema = mongoose.Schema;

    var ImageSchema = new Schema({
      fileName: String,
      url: String, // Should store the URL of image on S3.
      contentType: String,
      size: String,
      dimensions: String
    });

    module.exports = mongoose.model('Image', ImageSchema);

client/app/people/add/add.jade

.modal-header
    h3.modal-title Add {{ title }}
.modal-body
    form(id="add-user" name='form', ng-submit='register(form)', novalidate='')
        .form-group(ng-class='{ "has-success": form.firstName.$valid && submitted,\
        "has-error": form.firstName.$invalid && submitted }')
            label First Name
            input.form-control(type='text', name='firstName', ng-model='user.firstName', required='')
            p.help-block(ng-show='form.firstName.$error.required && submitted')
                | First name is required

        .form-group(ng-class='{ "has-success": form.lastName.$valid && submitted,\
        "has-error": form.lastName.$invalid && submitted }')
            label Last Name
            input.form-control(type='text', name='lastName', ng-model='user.lastName', required='')
            p.help-block(ng-show='form.lastName.$error.required && submitted')
                | Last name is required

        .form-group(ng-class='{ "has-success": form.username.$valid && submitted,\
        "has-error": form.username.$invalid && submitted }')
            label Username
            input.form-control(type='text', name='username', ng-model='user.username', required='')
            p.help-block(ng-show='form.username.$error.required && submitted')
                | Last name is required

        // Upload Profile Picture Here
        .form-group
            label Profile Image
            input(type="file" ng-file-select="onUploadSelect($files)" ng-model="newResource.newUpload")

        .form-group(ng-class='{ "has-success": form.email.$valid && submitted,\
        "has-error": form.email.$invalid && submitted }')
            label Email
            input.form-control(type='email', name='email', ng-model='user.email', required='', mongoose-error='')
            p.help-block(ng-show='form.email.$error.email && submitted')
                | Doesn't look like a valid email.
            p.help-block(ng-show='form.email.$error.required && submitted')
                | What's your email address?
            p.help-block(ng-show='form.email.$error.mongoose')
                | {{ errors.email }}

        .form-group(ng-class='{ "has-success": form.password.$valid && submitted,\
        "has-error": form.password.$invalid && submitted }')
            label Password
            input.form-control(type='password', name='password', ng-model='user.password', ng-minlength='3', required='', mongoose-error='')
            p.help-block(ng-show='(form.password.$error.minlength || form.password.$error.required) && submitted')
                | Password must be at least 3 characters.
            p.help-block(ng-show='form.password.$error.mongoose')
                | {{ errors.password }}

        .form-group
            label Assign Project(s)
            br
            select(multiple ng-options="project._id as project.name for project in projects" ng-model="user.assigned.projects")
        button.btn.btn-primary(ng-submit='register(form)') Save

    pre(ng-bind="user | json")
.modal-footer
    button.btn.btn-primary(type="submit" form="add-user") Save
    button.btn.btn-warning(ng-click='dismiss()') Cancel

server/api/upload/index.js

'use strict';

var express = require('express');
var controller = require('./upload.controller');

var router = express.Router();

//router.get('/', controller.index);
//router.get('/:id', controller.show);
router.post('/', controller.create);
//router.put('/:id', controller.update);
//router.patch('/:id', controller.update);
//router.delete('/:id', controller.destroy);

module.exports = router;

server/api/upload/upload.controller.js

'use strict';

var _ = require('lodash');
//var Upload = require('./upload.model');
var aws = require('aws-sdk');
var config = require('../../config/environment');
var randomString = require('../../components/randomString');

// Creates a new upload in the DB.
exports.create = function(req, res) {
    var s3 = new aws.S3();
    var folder = randomString.generate(20); // I guess I do this because when the user downloads the file it will have the original file name.
    var matches = req.body.upload.match(/data:([A-Za-z-+\/].+);base64,(.+)/);

    if (matches === null || matches.length !== 3) {
        return handleError(res, 'Invalid input string');
    }

    var uploadBody = new Buffer(matches[2], 'base64');

    var params = {
        Bucket: config.aws.bucketName,
        Key: folder + '/' + req.body.uploadName,
        Body: uploadBody,
        ACL:'public-read'
    };

    s3.putObject(params, function(err, data) {
        if (err)
            console.log(err)
        else {
            console.log("Successfully uploaded data to my-uploads/" + folder + '/' + req.body.uploadName);
            return res.json({
                name: req.body.uploadName,
                bucket: config.aws.bucketName,
                key: folder
            });
        }
    });
};

function handleError(res, err) {
    return res.send(500, err);
}

server/config/environment/development.js

aws: {
        key: 'XXXXXXXXXXXX',
        secret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
        region: 'sydney',
        bucketName: 'my-uploads'
    }
18
Daimz

Tout ce code est directement issu d'un projet qui en dépend fortement pour les téléchargements de fichiers volumineux et les images. Certainement payer https://github.com/nervgh/angular-file-upload

À mon avis quelque part:

<div class="form-group">
  <label>File Upload</label>
  <input type="file" ng-file-select="onUploadSelect($files)" ng-model="newResource.newUpload">
</div>

Utilisation du module angularFileUpload J'ai alors dans mon contrôleur:

$scope.onUploadSelect = function($files) {
  $scope.newResource.newUploadName = $files[0].name;
};

https://github.com/nervgh/angular-file-upload

Lorsque l'utilisateur clique sur upload, cela s'exécute où j'envoie le fichier à télécharger:

$http
  .post('/api/uploads', {
    uploadName: newResource.newUploadName,
    upload: newResource.newUpload
  })
  .success(function(data) {
    newResource.upload = data; // To be saved later
  });

Cette demande est envoyée à un contrôleur qui ressemble à ceci:

'use strict';

var _ = require('lodash');
var aws = require('aws-sdk');
var config = require('../../config/environment');
var randomString = require('../../components/randomString');

// Creates a new upload in the DB.
exports.create = function(req, res) {
  var s3 = new aws.S3();
  var folder = randomString.generate(20); // I guess I do this because when the user downloads the file it will have the original file name.
  var matches = req.body.upload.match(/data:([A-Za-z-+\/].+);base64,(.+)/);

  if (matches === null || matches.length !== 3) {
    return handleError(res, 'Invalid input string');
  }

  var uploadBody = new Buffer(matches[2], 'base64');

  var params = {
    Bucket: config.aws.bucketName,
    Key: folder + '/' + req.body.uploadName,
    Body: uploadBody,
    ACL:'public-read'
  };

  s3.putObject(params, function(err, data) {
    if (err)
      console.log(err)
    else {
      console.log("Successfully uploaded data to csk3-uploads/" + folder + '/' + req.body.uploadName);
      return res.json({
        name: req.body.uploadName,
        bucket: config.aws.bucketName,
        key: folder
      });
    }
   });
};

function handleError(res, err) {
  return res.send(500, err);
}

serveur/composants/randomString/index.js

'use strict';

module.exports.generate = function(textLength) {
  textLength = textLength || 10;
  var text = '';
  var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

  for(var i = 0; i < textLength; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }

  return text;
};

enter image description here

server/config/environment/development.js

enter image description here

server/api/upload/upload.controller.js

enter image description here

11

C'est la façon dont j'ai utilisé MEAN.JS pour le téléchargement de fichiers.

Modèle

var UserSchema = new mongoose.Schema({
name:{type:String,required:true},
photo:Buffer  // Image
});

Contrôleur de serveur

var userPicture = function(req,res){             // Stores Picture for a user matching the ID.
user.findById(req.param('id'), function (err, user) {
    console.log(req.files) // File from Client
    if(req.files.file){   // If the Image exists
        var fs = require('node-fs');
        fs.readFile(req.files.file.path, function (dataErr, data) {
            if(data) {
                user.photo ='';
                user.photo = data;  // Assigns the image to the path.
                user.save(function (saveerr, saveuser) {
                    if (saveerr) {
                        throw saveerr;
                    }
                    res.json(HttpStatus.OK, saveuser);                        
                });
            }
        });
        return
    }
    res.json(HttpStatus.BAD_REQUEST,{error:"Error in file upload"});
});
};

Contrôleur client

$scope.saveuserImage =  function(){
    $scope.upload = $upload.upload({  // Using $upload
        url: '/user/'+$stateParams.id+'/userImage',  // Direct Server Call.
        method:'put',
        data:'',  // Where the image is going to be set.
        file: $scope.file
    }).progress(function (evt) {})
        .success(function () {
            var logo = new FileReader();  // FileReader.

            $scope.onAttachmentSelect = function(file){
                logo.onload = function (e) {
                    $scope.image = e.target.result;  // Assigns the image on the $scope variable.
                    $scope.logoName = file[0].name; // Assigns the file name.
                    $scope.$apply();
                };
                logo.readAsDataURL(file[0]);
                $scope.file = file[0];
                $scope.getFileData = file[0].name
            };
            location.reload();
            $scope.file = "";
            $scope.hideUpload = 'true'
        });
    $scope.getFileData = '';
 //        location.reload()
};

Html

ng-file-select est utilisé pour obtenir le fichier du client.

Cela fonctionne bien pour moi. J'espère que cela t'aides.

Note: J'ai utilisé la balise HTML au lieu de jade. Modifications appropriées applicables lors de l'utilisation de jade.

2
SUNDARRAJAN K

Pour autant que je puisse deviner, vous liez la méthode FileReader.onload() à l'intérieur de la fonction saveUserImage, puis la méthode onload ne sera jamais appelée car la fonction n'est jamais liée à la place, un utilisateur appelle la méthode saveUserImage avant de modifier l'image. Après cela, aucune image ne sera sélectionnée car la méthode onload() ne s'exécutera pas.

Essayez de coder Client Controller de cette façon

//This goes outside your method and will handle the file selection.This must be executed when your `input(type=file)` is created. Then we will use ng-init to bind it.

  $scope.onAttachmentSelect = function(){
        var logo = new FileReader();  // FileReader.
        logo.onload = function (event) {
        console.log("THE IMAGE HAS LOADED");
        var file = event.currentTarget.files[0]
        console.log("FILENAME:"+file.name);
        $scope.image = file; 
        $scope.logoName = file.name; // Assigns the file name.
           $scope.$apply();
           //Call save from here
           $scope.saveuserImage();
        };
        logo.readAsDataURL(file[0]);
        $scope.file = file[0];
       $scope.getFileData = file[0].name
            reader.readAsDataURL(file);
    };


//The save method is called from the onload function (when you add a new file)
$scope.saveuserImage =  function(){
    console.log("STARGING UPLOAD");
    $scope.upload = $upload.upload({  // Using $upload
        url: '/user/'+$stateParams.id+'/userImage',  
        method:'put'
        data:,   $scope.image
        file: $scope.file
    }).progress(function (evt) {})
        .success(function () {
            location.reload();
            $scope.file = "";
            $scope.hideUpload = 'true'
        });
    $scope.getFileData = '';
 //        location.reload()
};

Le HTML.

//There is the ng-init call to binding function onAttachmentSelect
<div class="form-group">
  <label>File Upload</label>
  <input type="file" ng-init="onAttachmentSelect" ng-model="newResource.newUpload">
</div>

J'espère que cet indice peut vous aider

MODIFIER *

Je vais essayer de vous expliquer les différentes étapes que vous devez suivre pour vérifier votre code:

1.- Votre entrée [type = fichier] s'affiche-t-elle? Si affiché, veuillez sélectionner une image

2.- Votre entrée appelle-t-elle l'onload lorsque l'image sélectionnée a changé? (un console.log devrait être imprimé avec ma version de code)

3.- S'il a été appelé. Effectuez les opérations dont vous avez besoin avant d'envoyer, à l'intérieur de la méthode onload (si possible)

4.- Lorsque cette méthode a fini de faire les changements souhaités. Informez avec ng-model ou comme vous le souhaitez, une variable dans l'objet que vous avez préparé à télécharger, avec la chaîne base64 générée dans la méthode onload.

En arrivant à ce point, n'oubliez pas de vérifier que:

Comme de très grandes images peuvent être envoyées via json avec base64, il est très important de ne pas oublier de modifier la taille json minimale dans Express.js pour que votre application empêche les rejets. Cela se fait, par exemple dans votre serveur/app.js comme ceci:

var bodyParser = require('body-parser');
app.use(bodyParser.json({limit: '50mb'}));
app.use(bodyParser.urlencoded({limit: '50mb'}));

Rappelez-vous également que la méthode reader.readAsDataURL(file) vous donnera une chaîne base64 qui pourrait agir comme src de l'image. Vous n'avez pas besoin de plus que cela. Cette base64 est ce que vous pouvez économiser en mangouste. Ensuite, vous pouvez utiliser ng-model pour envoyer une variable contenant la base64 dans le formulaire avec le bouton "soumettre".

Ensuite, dans le point de terminaison Express.js qui gérera votre formulaire, vous pourrez décoder la chaîne base64 dans un fichier ou enregistrer la base64 directement sur mangouste (le stockage d'images dans la base de données n'est pas très recommandé si beaucoup d'images est en cours de chargement, ou de gros fichiers souhaités, car la requête mongoDB sera très lente).

J'espère que vous pourrez résoudre ces problèmes. Si vous avez encore des doutes, veuillez commenter et je vais essayer de vous aider

Je suis également un noob utilisant MEANJS, et voici comment je l'ai fait fonctionner en utilisant ng-flow + FileReader:

Entrée HTML:

<div flow-init 
        flow-files-added="processFiles($files)"
        flow-files-submitted="$flow.upload()" 
        test-chunks="false">
        <!-- flow-file-error="someHandlerMethod( $file, $message, $flow )"     ! need to implement-->
        <div class="drop" flow-drop ng-class="dropClass">
            <span class="btn btn-default" flow-btn>Upload File</span>
            <span class="btn btn-default" flow-btn flow-directory ng-show="$flow.supportDirectory">Upload Folder</span>
            <b>OR</b>
            Drag And Drop your file here
        </div>

manette:

    $scope.uploadedImage = 0;

    // PREPARE FILE FOR UPLOAD
    $scope.processFiles = function(flow){
        var reader = new FileReader();
        reader.onload = function(event) {
            $scope.uploadedImage = event.target.result;
        };
        reader.onerror = function(event) {
            console.error('File could not be read! Code ' + event.target.error.code);
        };
        reader.readAsDataURL(flow[0].file);
    };

Et côté serveur, la variable du modèle recevant la valeur de uploadImage est simplement de type chaîne.

Le récupérer depuis le serveur n'a nécessité aucune conversion:

<img src={{result.picture}} class="pic-image" alt="Pic"/>

Il suffit maintenant de savoir quoi faire avec les gros fichiers ...

0
Daniel