【问题标题】:Uploading photographs using MEAN.js使用 MEAN.js 上传照片
【发布时间】:2016-05-15 17:36:50
【问题描述】:

我正在使用this tutorial,它基于 MEAN 堆栈。 我想在服务器上存储用户可以使用 MongoDB 上传的照片。

包括:Angular directive to upload files

create-spot.client.view.html

<div data-ng-controller="SpotsCreateController">
    <form class="form-signin" data-ng-submit="create(picFile)" novalidate>
        <label>Upload an image</label>
        <input type="file" id="articleimage" ng-model="picFile" ng-file-select="" ng-file-change="generateThumb(picFile[0], $files)" multiple name="file" accept="image/*">
        <img ng-show="picFile[0].dataUrl != null" ng-src="{{picFile[0].dataUrl}}" class="img-thumbnail" height="50" width="100">
        <span class="progress" ng-show="picFile[0].progress >= 0">      
            <div style="width:{{picFile[0].progress}}%" ng-bind="picFile[0].progress + '%'" class="ng-binding"></div>
        </span> 
        <span ng-show="picFile[0].result">Upload Successful</span>

        <input type="submit" class="btn btn-lg btn-primary btn-block" ng-click="uploadPic(picFile)">

        <div data-ng-show="error">
            <strong data-ng-bind="error"></strong>
        </div>
    </form>
</div>

view-spot.client.view.html

<div data-ng-controller="SpotsViewController">
    <section data-ng-init="findOne()">
        <img ng-src="data:image/jpeg;base64,{{spot.image}}" id="image-id" width="200" height="200"/>
    </section>
</div>

application.js

var app = angular.module('newApp', ['ngAnimate', 'ngCookies', 'ngResource', 'ngRoute', 'ngSanitize', 'ngTouch', 'ui.bootstrap', 'users', 'spots']);

spots.create.client.controller.js

angular.module('spots').controller('SpotsCreateController', ['$scope', '$timeout', 'Authentication', 'Spots', '$location'
function($scope, $timeout, Authentication, Spots, $location) {
    $scope.authentication = Authentication;
    $scope.fileReaderSupported = window.FileReader !== null;

    $scope.create = function(picFile) {
        var spot = new Spots({
            title: this.title,
            description: this.description,
            image: null
        });
        spot.$save(function(response) {
            $location.path('spots/' + response._id);
        }, function(errorResponse) {
            $scope.error = errorResponse.data.message;
        });
    };

    $scope.doTimeout = function(file) {
        $timeout( function() {
            var fileReader = new FileReader();
            fileReader.readAsDataURL(file);
            fileReader.onload = function(e) {
                $timeout(function() {
                    file.dataUrl = e.target.result;
                });
            };
        });
    };
    $scope.generateThumb = function(file) {
        if (file) {
            if ($scope.fileReaderSupported && file.type.indexOf('image') > -1) {
                $scope.doTimeout(file);
            }
        }
    };

spot.server.model.js

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

var SpotSchema = new Schema({
    ...
    image: {
        type: String,
        default: '',
        required: false
    }
});

mongoose.model('Spot', SpotSchema);

spots.server.routes.js

var multiparty = require('connect-multiparty'),
    multipartyMiddleware = multiparty();

module.exports = function(app) {
    app.route('/api/spots')
        .get(spots.list)
        .post(users.requiresLogin, multipartyMiddleware, spots.create);

    app.route('/api/spots/:spotId')
        .get(spots.read)
        .put(users.requiresLogin, spots.hasAuthorization, spots.update)
        .delete(users.requiresLogin, spots.hasAuthorization, spots.delete);

    app.param('spotId', spots.spotByID);
};

spots.server.controller.js

var mongoose = require('mongoose'),
    fs = require('fs'),
    Spot = mongoose.model('Spot');

exports.create = function(req, res) {
    if (req.files.file) {
        var file = req.files.file;
    }

    var spot = new Spot(req.body);
    spot.creator = req.user;

    fs.readFile(file.path, function (err,original_data) {
        if (err) {
            return res.status(400).send({
                message: getErrorMessage(err)
            });
        }
        var base64Image = original_data.toString('base64');
        fs.unlink(file.path, function (err) {
            if (err) {
                console.log('failed to delete ' + file.path);
            } else {
                console.log('successfully deleted ' + file.path);
            }
        });
        spot.image = base64Image;
        spot.save(function(err) {
            if (err) {
                return res.status(400).send({
                    message: getErrorMessage(err)
                });
            } else {
                res.json(spot);
            }
        });
    });
};

我做错了什么?请考虑我想限制文件大小,我认为使用base64 是一个好的开始。问题是照片没有存储在数据库中,因为控制器不能与其余部分一起工作。

【问题讨论】:

  • 到底是什么问题?

标签: javascript angularjs node.js mongodb ng-file-upload


【解决方案1】:

您遇到的具体问题是什么,出了什么问题?

对于 Express 后端应用程序,我通常使用 multer 中间件来处理文件上传。此外,我创建了单独的路由/控制器来处理文件,而不是在保存父对象的同时尝试处理它们。这使我可以很好地分离逻辑,而不必担心文件上传失败时不会保存父对象。你可以使用 ng-file-upload 的 JS API 在 Angular 中处理。

Express 中的示例路线(我们这里有一个带有“徽标”图像的“俱乐部”):

router.post(
  '/logo',
  ensureAuthenticated, ensureAdmin,
  logoCtrl.upload,
  logoCtrl.save
);
router.get(
  '/:clubId/logo.*',
  logoCtrl.stream
);

示例控制器方法:

let multer = require('multer');
module.exports = {

  /**
   * Upload logo
   */
  save(req, res, next) {

    //Get club and file
    let club = req.user.club;
    let file = req.file;

    //Update
    club.logo = {
      data: file.buffer,
      mimeType: file.mimetype
    };

    //Save
    club.save()
      .then(() => {
        res.end();
      })
      .catch(next);
  },

  /**
   * Stream logo
   */
  stream(req, res, next) {
    let club = req.club;
    res.contentType(club.logo.mimeType);
    res.send(club.logo.data);
  },

  /**
   * Upload middleware
   */
  upload(req, res, next) {

    //Create upload middleware
    let upload = multer({
      storage: multer.memoryStorage(),
      limits: {
        fileSize: 50000000
      }
    }).single('logo');

    //Use middleware
    upload(req, res, next);
  }
};

如您所见,使用 multer 非常简单,您真正需要的只是一个用于上传徽标的路由,带有两个控制器方法,一个用于注册 multer 中间件并处理文件,另一个用于将其保存到MongoDB(在这种情况下附加到请求中的俱乐部)。

只需确保 ng-file-upload 使用与 multer 期望的相同的字段名称来上传文件。在上面的例子中是“logo”。如果您不确定,请检查您的客户端应用发送到服务器的请求,并确保服务器应用期望相同的字段名称。

如果您还有其他问题,请告诉我。

【讨论】:

    【解决方案2】:

    您可以使用formidablegridfs-stream

    //controller
    var mongoose = require('mongoose'),
      fs = require('fs'),
      Spot = mongoose.model('Spot');
    
    exports.create = function(req, res) {
      handleRequest(req, function(err, spot) {
        if(err) {
          return res.status(400).send({
            message: getErrorMessage(err)
          });
        }
    
        res.json(spot);
    
      });
    };
    
    function handleRequest(req) {
    
      var spot = new Spot(req.body);
      spot.creator = req.user;
    
      var formidable = require('formidable');
    
      var form = new formidable.IncomingForm();
    
      form.parse(req, function(err, fields, files) {
        if (err) {
          return done(err);
        }
        var file = files.qqfile;
        if (file && file.name && file.name.trim !== '') {
          if (file.size > 5000000) {
            message = 'file is too large';
          }
          if (!file.type) {
            message = 'file is not an image';
          }
          if (file.type.indexOf('image/') !== 0) {
            message = 'file is not an image type';
          }
        }
        if (message) {
          logger.info('Uploading failed', file, message);
          return done(message);
        }
    
        uploadFile(mongoose.connection, 'Pictures', files.qqfile, require('uuid').v1(), function(err) {
          if (err) {
            return done(err);
          }
          if (!data) return done(false, null);
    
          if (typeof data === 'string') {
            data = JSON.parse(data);
          }
          logger.info('[PHOTOS]', 'Uploaded', data.filename);
          photo = {
            unique_id: token,
            name: file.name,
            contentType: file.type
          };
    
          spot.photos = spot.photos || [];
          spot.photos.push(photo);
          spot.markModified('photos');
          spot.save(done);
    
        });
      });
    }
    
    function uploadFile(DB, className, data, token, callback) {
      var grid = require('gridfs-stream');
      var gfs = grid(DB.db, mongoose.mongo);
    
      var writestream = gfs.createWriteStream({
        filename: token,
        root: className
      });
      writestream.on('close', function (file) {
        return callback(null, file);
      });
    
      if (data.path) {
        var fs = require('fs');
        if (!fs.existsSync(data.path)) {
          return callback(false);
        }
        var pipe = false;
        if (data.pipe) {
          pipe = data.pipe;
        } else {
          var fs = require('fs');
          if (!fs.existsSync(data.path)) {
            return callback(false);
          }
          var rs = fs.createReadStream(data.path);
          pipe = rs.pipe.bind(rs);
        }
        return pipe(writestream);
      } else {
        logger.error('[PHOTO] no path', data);
      }
      callback(false);
    }
    

    【讨论】:

    • 感谢您的回答。我有点迷茫,因为我不知道如何将它包含在我的文件中,因为您的解决方案完全不同(不同的模块,...)
    • 你好! :) 其他文件真的不需要调整吗?
    • 在 Chrome 中运行它并检查您的网络选项卡显示的内容...您应该在标题中看到 Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryTBxXOJDciMbRAhmD 和在有效负载中看到 ------WebKitFormBoundaryTBxXOJDciMbRAhmD Content-Disposition: form-data; name="qqfile"; filename="somefile.gif" Content-Type: image/gif 之类的内容...
    • 当我点击按钮时,没有任何反应,Chrome 告诉我状态为待处理。然后,我的控制台出现错误Reference Error: generateFilename is not defined
    • 啊,是的,你必须包含一个函数来生成一个唯一的文件名……试试require('uuid').v1()
    猜你喜欢
    • 2014-12-10
    • 2010-11-13
    • 1970-01-01
    • 2016-02-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多