【问题标题】:How to avoid blockin while uploading file using Meteor method使用 Meteor 方法上传文件时如何避免阻塞
【发布时间】:2015-09-27 11:56:00
【问题描述】:

我创建了一个 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));
                }
            });
        });
    }
 });

【问题讨论】:

标签: meteor


【解决方案1】:

改进@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();
    });
  }

服务器

当偏移量为零时,服务器然后写入文件,否则追加到文件末尾,返回一个承诺,因为我使用异步函数写入/追加以避免阻塞客户端。

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

【讨论】:

    【解决方案2】:

    我自己终于找到了解决办法。

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

    解决方案:我将文件分成大约 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();
      }
    });
    

    【讨论】:

      猜你喜欢
      • 2021-09-24
      • 1970-01-01
      • 2022-10-17
      • 1970-01-01
      • 1970-01-01
      • 2013-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多