Sök…


Anmärkningar

CollectionFS-paketet har förvarats och avvecklats av sin författare; eftersom det inte finns något alternativt paket i Atmosphere eller Meteor-ekosystemet för att använda Mongos GridFS-funktionalitet, och koden fungerar fortfarande perfekt; Vi rekommenderar att du inte tar bort exemplet från StackOverflow-dokumentationen förrän någon annan GridFS-lösning kan dokumenteras som ersättning.

Ytterligare forskning
Filepicker.io Uppladdningar och bildkonvertering
Darios spara filmönster
Micha Roons filuppladdningsmönster
EventedMind File Upload-paket

Server / klient

Att ladda upp filer kan vara enkelt eller riktigt komplicerat, beroende på vad du vill göra. I allmänhet är det inte så svårt att överföra en fil i sig själv. Men det finns massor av kantfall kring bilagor, binära filer och liknande. Och den verkliga stickpunkten är horisontell skalning och skapa en lösning som fungerar när servern klonas en andra, tredje och nionde gång.

Låt oss börja med en grundläggande modell för server / klientuppladdning. Vi börjar med att lägga till ett filinmatningselement till dokumentobjektmodellen.

<template name="example">
  <input type=file />
</template>

Fäst sedan en händelse till ingångselementet i din styrenhet och ring en lokal Meteor-metod `` startFileTransfer '' för att initiera överföringen.

// client/example.js
Template.example.events({
  'change input': function(ev) {  
    _.each(ev.srcElement.files, function(file) {
      Meteor.startFileTransfer(file, file.name);
    });
  }
});

// client/save.js
/**
 * @blob (https://developer.mozilla.org/en-US/docs/DOM/Blob)
 * @name the file's name
 * @type the file's type: binary, text (https://developer.mozilla.org/en-US/docs/DOM/FileReader#Methods) 
 *
 * TODO Support other encodings: https://developer.mozilla.org/en-US/docs/DOM/FileReader#Methods
 * ArrayBuffer / DataURL (base64)
 */
Meteor.startFileTransfer = function(blob, name, path, type, callback) {
  var fileReader = new FileReader(),
    method, encoding = 'binary', type = type || 'binary';
  switch (type) {
    case 'text':
      // TODO Is this needed? If we're uploading content from file, yes, but if it's from an input/textarea I think not...
      method = 'readAsText';
      encoding = 'utf8';
      break;
    case 'binary': 
      method = 'readAsBinaryString';
      encoding = 'binary';
      break;
    default:
      method = 'readAsBinaryString';
      encoding = 'binary';
      break;
  }
  fileReader.onload = function(file) {
    Meteor.call('saveFileToDisk', file.srcElement.result, name, path, encoding, callback);
  }
  fileReader[method](blob);
}

Klienten kommer sedan att ringa metoden saveFileToDisk-servern, som gör den faktiska överföringen och lägger allt på disken.

// 
/**
 * TODO support other encodings:
 * http://stackoverflow.com/questions/7329128/how-to-write-binary-data-to-a-file-using-node-js
 */
Meteor.methods({
  saveFileToDisk: function(blob, name, path, encoding) {
    var path = cleanPath(path), fs = __meteor_bootstrap__.require('fs'),
      name = cleanName(name || 'file'), encoding = encoding || 'binary',
      chroot = Meteor.chroot || 'public';
    // Clean up the path. Remove any initial and final '/' -we prefix them-,
    // any sort of attempt to go to the parent directory '..' and any empty directories in
    // between '/////' - which may happen after removing '..'
    path = chroot + (path ? '/' + path + '/' : '/');

    // TODO Add file existance checks, etc...
    fs.writeFile(path + name, blob, encoding, function(err) {
      if (err) {
        throw (new Meteor.Error(500, 'Failed to save file.', err));
      } else {
        console.log('The file ' + name + ' (' + encoding + ') was saved to ' + path);
      }
    }); 

    function cleanPath(str) {
      if (str) {
        return str.replace(/\.\./g,'').replace(/\/+/g,'').
          replace(/^\/+/,'').replace(/\/+$/,'');
      }
    }
    function cleanName(str) {
      return str.replace(/\.\./g,'').replace(/\//g,'');
    }
  }
});

Det är en typ av kalkbenen och det lämnar mycket att önska. Det är kanske bra för att ladda upp en CSV-fil eller något, men det handlar om det.

Dropzone (med järn: router)

Om vi vill ha något mer polerat, med en integrerad Dropzone UI och en REST-slutpunkt, måste vi börja lägga till anpassade REST-rutter och paket med UI-hjälpare.

Låt oss börja med att importera Iron Router och Dropzone.

 meteor add iron:router
 meteor add awatson1978:dropzone

Och konfigurera uppladdade url-rutten som anges i dropzone-hjälpen.

Router.map(function () {
    this.route('uploads', {
      where: 'server',
      action: function () {
        var fs = Npm.require('fs');
        var path = Npm.require('path');
        var self = this;

        ROOT_APP_PATH = fs.realpathSync('.');

        // dropzone.js stores the uploaded file in the /tmp directory, which we access
        fs.readFile(self.request.files.file.path, function (err, data) {

          // and then write the file to the uploads directory
          fs.writeFile(ROOT_APP_PATH + "/assets/app/uploads/" +self.request.files.file.name, data, 'binary', function (error, result) {
            if(error){
              console.error(error);
            }
            if(result){
              console.log('Success! ', result);
            }
          });
        });
      }
    });
  });

Häftigt! Vi har en filuppladdare med snazzy UI och en programmerbar REST-slutpunkt. Tyvärr skalar det inte särskilt bra.

Filepicker.io

För att skala saker måste vi sluta använda lokal lagring på vår server och börja använda antingen en dedicerad fillagringstjänst eller implementera ett horisontellt lagringslager. Det enklaste sättet att komma igång med skalbar fillagring är att använda en lösning som Filepicker.io, som stöder S3, Azure, Rackspace och Dropbox. loadpicker har varit ett populärt Filerpicker unipackage under en stund.

meteor add mrt:filepicker

Filepicker-mönstret är ganska annorlunda än de andra lösningarna, eftersom det verkligen handlar om tredje parts integration. Börja med att lägga till en filpickersingång, som du ser beror starkt på data- * -attribut, vilket är ett ganska ovanligt mönster i Meteor-appar.

<input type="filepicker"
  id="filepickerAttachment"
  data-fp-button-class="btn filepickerAttachment"
  data-fp-button-text="Add image" 
  data-fp-mimetypes="image/*"
  data-fp-container="modal"
  data-fp-maxsize="5000000" 
  data-fp-services="COMPUTER,IMAGE_SEARCH,URL,DROPBOX,GITHUB,GOOGLE_DRIVE,GMAIL">

Du vill också ställa in en API-nyckel, konstruera filprickarwidgeten, utlösa den och observera dess utgångar.

if(Meteor.isClient){
  Meteor.startup(function() {
    filepicker.setKey("YourFilepickerApiKey");
  });
  Template.yourTemplate.rendered = function(){
    filepicker.constructWidget($("#filepickerAttachment"));
  }
  Template.yourTemplate.events({
  'change #filepickerAttachment': function (evt) {
    console.log("Event: ", evt, evt.fpfile, "Generated image url:", evt.fpfile.url);
  });
});

CollectionFS

Men om du verkligen ser allvar med lagring och vill lagra miljoner bilder, kommer du att behöva utnyttja Mongos GridFS-infrastruktur och skapa dig ett lagringslager. För det kommer du att behöva det utmärkta CollectionFS-delsystemet.

Börja med att lägga till nödvändiga paket.

meteor add cfs:standard-packages
meteor add cfs:filesystem

Och lägga till ett filöverföringselement till din objektmodell.

<template name="yourTemplate">
    <input class="your-upload-class" type="file">
</template>

Lägg sedan till en händelsekontroller på klienten.

Template.yourTemplate.events({
    'change .your-upload-class': function(event, template) {
        FS.Utility.eachFile(event, function(file) {
            var yourFile = new FS.File(file);
            yourFile.creatorId = Meteor.userId(); // add custom data
            YourFileCollection.insert(yourFile, function (err, fileObj) {
                if (!err) {
                   // do callback stuff
                }
            });
        });
    }
});

Och definiera dina samlingar på din server:

YourFileCollection = new FS.Collection("yourFileCollection", {
    stores: [new FS.Store.FileSystem("yourFileCollection", {path: "~/meteor_uploads"})]
});
YourFileCollection.allow({
    insert: function (userId, doc) {
        return !!userId;
    },
    update: function (userId, doc) {
        return doc.creatorId == userId
    },
    download: function (userId, doc) {
        return doc.creatorId == userId
    }
});

Tack till Raz för detta utmärkta exempel. Du vill kolla in den kompletta CollectionFS-dokumentationen för mer information om vad allt CollectionFS kan göra.

Serveruppladdningar

Följande skript är för att ladda upp en fil från serverfilsystemet till servern. Mest för konfigurationsfiler och filtittare.

//https://forums.meteor.com/t/read-file-from-the-public-folder/4910/5

// Asynchronous Method.
Meteor.startup(function () {
    console.log('starting up');

    var fs = Npm.require('fs');
    // file originally saved as public/data/taxa.csv
    fs.readFile(process.cwd() + '/../web.browser/app/data/taxa.csv', 'utf8', function (err, data) {
        if (err) {
            console.log('Error: ' + err);
            return;
        }

        data = JSON.parse(data);
        console.log(data);
    });
});


// Synchronous Method.
Meteor.startup(function () {
    var fs = Npm.require('fs');
    // file originally saved as public/data/taxa.csv
    var data = fs.readFileSync(process.cwd() + '/../web.browser/app/data/taxa.csv', 'utf8');

    if (Icd10.find().count() === 0) {
        Icd10.insert({
            date:  new Date(),
            data:  JSON.parse(data)
        });
    }
});


Meteor.methods({
  parseCsvFile:function (){
    console.log('parseCsvFile');

    var fs = Npm.require('fs');
    // file originally saved as public/data/taxa.csv
    var data = fs.readFileSync(process.cwd() + '/../web.browser/app/data/taxa.csv', 'utf8');
    console.log('data', data);
  }
});


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow