如何使用Meteor方法上传文件时避免阻塞

时间:2015-07-09 08:17:58

标签: meteor

我已经创建了一个Meteor方法来上传文件,它运行良好但是直到文件完全上传,我无法移动,所有订阅似乎等待上传完成...是否有办法避免这种情况?

以下是服务器上的代码:

Meteor.publish('product-photo', function (productId) {
    return Meteor.photos.find({productId: productId}, {limit: 1});
});

Meteor.methods({
    /**
     * Creates an photo
     * @param obj
     * @return {*}
     */
    createPhoto: function (obj) {
        check(obj, Object);

        // Filter attributes
        obj = filter(obj, [
            'name',
            'productId',
            'size',
            'type',
            'url'
        ]);

        // Check user
        if (!this.userId) {
            throw new Meteor.Error('not-connected');
        }

        // Check file name
        if (typeof obj.name !== 'string' || obj.name.length > 255) {
            throw new Meteor.Error('invalid-file-name');
        }

        // Check file type
        if (typeof obj.type !== 'string' || [
                'image/gif',
                'image/jpg',
                'image/jpeg',
                'image/png'
            ].indexOf(obj.type) === -1) {
            throw new Meteor.Error('invalid-file-type');
        }

        // Check file url
        if (typeof obj.url !== 'string' || obj.url.length < 1) {
            throw new Meteor.Error('invalid-file-url');
        }

        // Check file size
        if (typeof obj.size !== 'number' || obj.size <= 0) {
            throw new Meteor.Error('invalid-file-size');
        }

        // Check file max size
        if (obj.size > 1024 * 1024) {
            throw new Meteor.Error('file-too-large');
        }

        // Check if product exists
        if (!obj.productId || Meteor.products.find({_id: obj.productId}).count() !== 1) {
            throw new Meteor.Error('product-not-found');
        }

        // Limit the number of photos per user
        if (Meteor.photos.find({productId: obj.productId}).count() >= 3) {
            throw new Meteor.Error('max-photos-reached');
        }

        // Resize the photo if the data is in base64
        if (typeof obj.url === 'string' && obj.url.indexOf('data:') === 0) {
            obj.url = resizeImage(obj.url, 400, 400);
            obj.size = obj.url.length;
            obj.type = 'image/png';
        }

        // Add info
        obj.createdAt = new Date();
        obj.userId = this.userId;

        return Meteor.photos.insert(obj);
    }
});

客户端上的代码

Template.product.events({
 'change [name=photo]': function (ev) {
        var self = this;
        readFilesAsDataURL(ev, function (event, file) {
            var photo = {
                name: file.name,
                productId: self._id,
                size: file.size,
                type: file.type,
                url: event.target.result
            };

            Session.set('uploadingPhoto', true);

            // Save the file
            Meteor.call('createPhoto', photo, function (err, photoId) {
                Session.set('uploadingPhoto', false);

                if (err) {
                    displayError(err);
                } else {
                    notify(i18n("Transfert terminé pour {{name}}", photo));
                }
            });
        });
    }
 });

2 个答案:

答案 0 :(得分:0)

我终于找到了解决方案。

解释:我使用的代码阻止了订阅,因为它只使用一个方法调用将所有文件从第一个字节传输到最后一个字节,这导致阻塞线程(我想,保留给每个转发完成之前,服务器上的用户。

解决方案:我将文件拆分为大约8KB的块,并按块发送块,这样,每次块传输后线程或阻止订阅的任何内容都是免费的。

最后的工作解决方案就在那篇帖子上:How to write a file from an ArrayBuffer in JS

客户代码

// data comes from file.readAsArrayBuffer();
var total = data.byteLength;
var offset = 0;

var upload = function() {
  var length = 4096; // chunk size

  // adjust the last chunk size
  if (offset + length > total) {
     length = total - offset;
  }

  // I am using Uint8Array to create the chunk
  // because it can be passed to the Meteor.method natively
  var chunk = new Uint8Array(data, offset, length);

  if (offset < total) {
     // Send the chunk to the server and tell it what file to append to
     Meteor.call('uploadFileData', fileId, chunk, function (err, length) {
        if (!err) {
          offset += length;
          upload();
        }
     }
  }
};
upload();

服务器代码

var fs = Npm.require('fs');
var Future = Npm.require('fibers/future');

Meteor.methods({
  uploadFileData: function(fileId, chunk) {
    var fut = new Future();
    var path = '/uploads/' + fileId;

    // I tried that with no success
    chunk = String.fromCharCode.apply(null, chunk);

    // how to write the chunk that is an Uint8Array to the disk ?
    fs.appendFile(path, new Buffer(chunk), function (err) {
        if (err) {
          fut.throw(err);
        } else {
          fut.return(chunk.length);
        }
    });
    return fut.wait();
  }
});

答案 1 :(得分:0)

改善@ Karl的代码:

客户端

此函数将文件分成多个块并逐个发送到服务器。

  function uploadFile(file) {
    const reader = new FileReader();
    let _offset = 0;
    let _total = file.size;

    return new Promise((resolve, reject) => {

      function readChunk() {
        var length = 10 * 1024; // chunk size

        // adjust the last chunk size
        if (_offset + length > _total) {
          length = _total - _offset;
        }

        if (_offset < _total) {
          const slice = file.slice(_offset, _offset + length);
          reader.readAsArrayBuffer(slice);
        } else {
          // EOF
          setProgress(100);
          resolve(true);
        }
      }

      reader.onload = function readerOnload() {
        let buffer = new Uint8Array(reader.result) // convert to binary
        Meteor.call('fileUpload', file.name, buffer, _offset,
          (error, length) => {
            if (error) {
              console.log('Oops, unable to import!');
              return false;
            } else {
              _offset += length;
              readChunk();
            }
          }
        );
      };

      reader.onloadend = function readerOnloadend() {
        setProgress(100 * _offset / _total);
      };

      readChunk();
    });
  }

服务器

服务器然后在offset为零时写入文件,否则追加到其末尾,返回promise,因为我使用异步函数来写/附加以避免阻塞客户端。

if (Meteor.isServer) {
  var fs = require('fs');
  var Future = require('fibers/future');
}

Meteor.methods({
  // Upload file from client to server
  fileUpload(
    fileName: string,
    fileData: Uint8Array,
    offset: number) {
    check(fileName, String);
    check(fileData, Uint8Array);
    check(offset, Number);

    console.log(`[x] Received file ${fileName} data length: ${fileData.length}`);

    if (Meteor.isServer) {
      const fut = new Future();
      const filePath = '/tmp/' + fileName;
      const buffer = new Buffer(fileData);
      const jot = offset === 0 ? fs.writeFile : fs.appendFile;
      jot(filePath, buffer, 'binary', (err) => {
        if (err) {
          fut.throw(err);
        } else {
          fut.return(buffer.length);
        }
      });
      return fut.wait();
    }
  }
)};

用法

uploadFile(file)
      .then(() => {
        /* do your stuff */
      });