node.js 使用 Mongoose、Express 和 AngularJS 上传图片

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30166907/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 18:45:31  来源:igfitidea点击:

Uploading images with Mongoose, Express and AngularJS

angularjsnode.jsmongodbexpressmongoose

提问by Daimz

I know this has been asked many times before, and I have read almost all I could find about the subject, namely:

我知道这之前已经被问过很多次了,我已经阅读了几乎所有我能找到的关于这个主题的内容,即:

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

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

Uploading images using Node.js, Express, and Mongoose

使用 Node.js、Express 和 Mongoose 上传图片

Those are the best I have found so far. My problem is tho that they still aren't very clear, there is very little documentation online at all about this and the discussion seems aimed at people who are much more advanced than I am.

这些是迄今为止我发现的最好的。我的问题是他们仍然不是很清楚,关于这个的在线文档很少,而且讨论似乎是针对比我更先进的人。

So with that I would really love it if someone could please walk me though how to upload images using Mongoose, Express & AngularJS. I am actually using the MEAN fullstack. (this generator to be precise – https://github.com/DaftMonk/generator-angular-fullstack)

因此,如果有人可以教我如何使用 Mongoose、Express 和 AngularJS 上传图像,我会非常喜欢它。我实际上使用的是 MEAN 全栈。(这个生成器准确地说 - 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();
            });
        }

  }]);

I want to add the Images ID reference to project.images.thumbnailbut I want to store all the information inside an Image Object using the following Schema:

我想添加图像 ID 引用,project.images.thumbnail但我想使用以下架构将所有信息存储在图像对象中:

'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);

I have also added the following https://github.com/nervgh/angular-file-uploadto my bower packages.

我还将以下https://github.com/nervgh/angular-file-upload添加到我的凉亭包中。

As I say I just can't figure out how to tie it all together. And I'm not even sure if what I am trying to do is the correct way either.

正如我所说,我只是不知道如何将它们联系在一起。而且我什至不确定我正在尝试做的是否是正确的方法。

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

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

UPDATE:

更新:

Here is what I now have, I have added some comments detailing how I would like it to work, unfortunately I still haven't managed to get this working, I can't even get the image to start uploading, never mind uploading to S3. Sorry to be a pain but I am just finding this particularly confusing, which surprises me.

这是我现在拥有的,我添加了一些评论,详细说明了我希望它如何工作,不幸的是我仍然没有设法让它工作,我什至无法开始上传图像,更不用说上传到 S3 . 很抱歉让我感到痛苦,但我只是觉得这特别令人困惑,这让我感到惊讶。

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

客户端/应用程序/人/添加/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.jsI would like to store all image information here and use this to populate profileImagein people controller.

server/api/image/image.model.js我想在这里存储所有图像信息并使用它来填充profileImage人员控制器。

'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

客户端/应用程序/人/添加/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

服务器/api/上传/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

服务器/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

服务器/配置/环境/development.js

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

回答by Michael J. Calkins

All of this code is straight out of a project that depends heavily on this for large file uploads and images. Definitely checkout https://github.com/nervgh/angular-file-upload

所有这些代码都直接来自一个项目,该项目在大文件上传和图像方面严重依赖于此。绝对结帐https://github.com/nervgh/angular-file-upload

In my view somewhere:

在我看来,某处:

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

Using the module angularFileUploadI then have in my controller:

使用angularFileUpload我然后在我的控制器中的模块:

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

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

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

When the user clicks upload this gets executed where I send the file to be uploaded:

当用户点击上传时,它会在我发送要上传的文件的地方执行:

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

This request is sent to a controller that looks something like this:

这个请求被发送到一个看起来像这样的控制器:

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

server/components/randomString/index.js

服务器/组件/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

server/config/environment/development.js

enter image description here

在此处输入图片说明

server/api/upload/upload.controller.js

server/api/upload/upload.controller.js

enter image description here

在此处输入图片说明

回答by SUNDARRAJAN K

This is the way i used MEAN.JSfor file upload.

这是我使用MEAN.JS进行文件上传的方式。

Model

模型

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

Server Controller

服务器控制器

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

Client Controller

客户端控制器

$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

html

The ng-file-selectis used to get the file from the client.

NG-文件选择用于获取来自客户端的文件。

This works fine for me. Hope this helps.

这对我来说很好用。希望这可以帮助。

Note: I have used HTMLtag instead of jade. Suitable changes applicable while using jade.

注意:我使用了HTML标记而不是jade。使用玉石时适用的适当变化。

回答by Alejandro Teixeira Mu?oz

As far as I can guess, you are binding the FileReader.onload()method inside the saveUserImage function, then the onload method will be never called as the function is never binded instead a user calls saveUserImage method before editing the image. After that, no image will be selected as the onload()method will not execute.

据我猜测,您正在将FileReader.onload()方法绑定到saveUserImage 函数内,然后 onload 方法将永远不会被调用,因为该函数永远不会被绑定,而是用户在编辑图像之前调用 saveUserImage 方法。之后,将不会选择任何图像,因为该onload()方法将不会执行。

Try coding Client Controllerit this way

尝试以这种方式对客户端控制器进行编码

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

The HTML.

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>

Hope this clue may help you

希望这个线索可以帮助你

EDIT*

编辑*

Will try to explain you the different Steps you must follow to check your code:

将尝试向您解释检查代码必须遵循的不同步骤:

1.- Is your input[type=file] showing? If showing, please select an image

2.- Is your input calling the onload when the image selected has changed? (a console.log should be printed with my code version)

3.- If it has been called. Make the operations you need before sending, inside the onload method (if possible)

4.- When this method has finished doing desired changes. Inform with ng-model or however you want, a variable in the object you prepared to upload, with the base64 string generated in the onload method.

1.- 您的 input[type=file] 显示了吗?如果显示,请选择图片

2.- 当所选图像发生变化时,您的输入是否调用了 onload?(应该用我的代码版本打印一个 console.log)

3.- 如果它已被调用。在发送之前,在 onload 方法中进行您需要的操作(如果可能)

4.- 当此方法完成所需的更改时。使用 ng-model 或您想要的任何方式通知您准备上传的对象中的变量,以及在 onload 方法中生成的 base64 字符串。

When arriving this point, remember checking that:

到达这一点时,请记住检查:

As very big images could be sent over json with base64, it′s very important to remember changing the minimum json size in Express.js for your app to prevent rejects. This is done, for example in your server/app.js as this:

由于使用 base64 可以通过 json 发送非常大的图像,因此记住更改 Express.js 中为您的应用程序更改最小 json 大小以防止拒绝非常重要。这是完成的,例如在您的 server/app.js 中,如下所示:

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

Remember also that the method reader.readAsDataURL(file)will give you a base64 string that could act as src of the image. You don′t need more than this. This base64 is what you can save in mongoose. Then, you can use ng-model to send a variable containing the base64 in the form with the "submit" button.

还请记住,该方法reader.readAsDataURL(file)将为您提供一个 base64 字符串,该字符串可以充当图像的 src。你不需要更多。这个 base64 是你可以保存在猫鼬中的。然后,您可以使用 ng-model 在带有“提交”按钮的表单中发送包含 base64 的变量。

Then, in the Express.js endpoint that will handle your form, you will be able to decode the base64 string to a file, or to save the base64 directly on mongoose (storing images in the db is not much recommended if a lot of images is being to be loaded, or big ones desired, as the mongoDB query will be very slow).

然后,在将处理您的表单的 Express.js 端点中,您将能够将 base64 字符串解码为文件,或者将 base64 直接保存在 mongoose 上(如果图像很多,建议不要将图像存储在 db 中)正在加载,或者需要大的,因为 mongoDB 查询会很慢)。

Hope you can solve with those indications. If you still have some doubts, please comment and I′ll try to help

希望你能解决这些迹象。如果您还有疑问,请发表评论,我会尽力提供帮助

回答by Daniel

I'm also a noob using MEANJS, and this is how I made it work using ng-flow + FileReader:

我也是一个使用 MEANJS 的菜鸟,这就是我使用 ng-flow + FileReader 让它工作的方式:

HTML input:

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>

controller:

控制器:

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

And on the server side the variable on the model receiving the value of uploadedImage is just of type string.

而在服务器端,模型上接收 uploadImage 值的变量只是字符串类型。

Fetching it back from the server didn't require any conversion:

从服务器取回它不需要任何转换:

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

Now just need to find out what to do with big files...

现在只需要找出如何处理大文件......