我应该如何批量上传到 s3 并通过最终回调从 nodeJS 网络服务器插入到 MongoDB?

Posted

技术标签:

【中文标题】我应该如何批量上传到 s3 并通过最终回调从 nodeJS 网络服务器插入到 MongoDB?【英文标题】:How should I batch upload to s3 and insert to MongoDB from nodeJS webserver with a final callback? 【发布时间】:2016-04-03 10:14:19 【问题描述】:

我有一个网络服务器,它接受来自客户端的图像,处理它们,将它们上传到 S3,将 url 批量插入到我的 mongoDB,最后将 json 结果发送回客户端。

使用单个图像的工作原理如下:

router.post("/upload", function(req, res)
    var form = new multiparty.Form();

    form.parse(req,function(err,fields,files)
        s3.upload(
                Key: filename,
                Bucket: bucketname,
                ACL: "public-read",
                Body: fs.createReadStream(filepath)
         , function(err, data)
            if(err) //error handle

            Model.collection.insert("name": "name", "url" : data.Location, function(err, result)
                if(err) //error handle
                res.json(result: result)
            )
        )
    )
)

这非常有效,因为我只是将文件数据上传到 s3 -> 完成后,将 s3 的输出(url)插入数据库 -> 完成后,将 mongo 的结果作为 jsonarray 发送到客户端。

问题是 - 我的客户端 html 将多个 type=file 输入与相同的 name=images 除外,因此我可以在我的表单解析器中以 images[i] 访问它们。上述算法重复images.length 次。当我必须将 jsonarray 结果返回给客户端时,问题就出现了,因为我必须等待所有异步 S3 上传-> mongo 插入完成,而且我无法确定回调将如何以及在哪里完成这项工作.

我尝试过的如下:

遍历图像,首先将它们上传到 S3,使用生成的 url ([data.Location]) 填充数组。将它们批量插入到 mongoDB 并在回调中将 jsonarray 结果返回给客户端。这不起作用,因为 mongo insert 不等待 S3 上传。 遍历图像,每次迭代上传并插入图像到 S3 和 mongoDB。 if (currentIndex = images.length),返回 jsonarray 结果。这不准确,因为我不知道哪些图像会最后结束(不同大小)。

我应该如何设计算法来批量上传s3,批量插入mongo,将包括s3 urls,文件名等的结果作为jsonarray返回给客户端?

谢谢,提前!

【问题讨论】:

【参考方案1】:

我通常用 Promises 解决这类问题,见:Bluebird。

然后您可以使用Promise.all() 在 S3 上进行批量上传,一旦您获得该回调,您可以批量插入 Mongo,完成后运行最终回调。或者,你可以做一个批处理来做这两个事情:upload->insert to mongo,当所有这些都完成后,返回最终回调。这将取决于您的服务器以及您要一次上传多少文件。您还可以使用 Promise.map() 并将 concurrency 选项设置为您想要运行的任何并发任务。

伪代码示例:

假设getFilesuploadFileuploadToMongo 返回一个Promise 对象。

var maxConcurrency =  10;

getFiles()
    .map(function(file)
        return uploadFile(file)
            .then(uploadToMongo) 
    ,concurrency: maxConcurrency)
    .then(function()
            return finalCallback();
    ).catch(handleError);

例如,如何手动 "promisify* S3:

function uploadMyFile(filename, filepath, bucketname) 
    return new Promise(function(resolve, reject)
        s3.upload(
            Key: filename,
            Bucket: bucketname,
            ACL: "public-read",
            Body: fs.createReadStream(filepath)
        , function(err, data)
            //This err will get to the "catch" statement.
            if (err) return reject(err);
            // Handle success and eventually call:
            return resolve(data);
        );
    );

你可以这样使用:

uploadMyFile
        .then(handleSuccess)
        .catch(handleFailure);

一切都好漂亮!

【讨论】:

嗨。事实上,我确实尝试过蓝鸟,但我没有让它运作良好。我跟着我的代码与this guide 相同。代码是过时的还是什么? var s3 = bluebird.promisifyAll(new aws.S3()); 似乎没有进行任何更改,bluebird.all(uploadToS3, function(data)uploadToMongo) 也没有等待 s3 上传完成。 经过大量的尝试和尝试,我达到了拥有bluebird.promisify(uploadToS3())bluebird.all(mapOfUploadToS3(), function()rest) 的地图的地步。它确实上传到 S3,但现在它永远不会到达其余功能。我怀疑我需要某种机制来让 Promise 知道 uploadToS3() 已经完成了它的工作?我在正确的轨道上吗? 糟糕,抱歉,那是bluebird.join(bluebird.all(mapOfUploadToS3()),function(data)rest); 抱歉,回复的冗长。非常感谢,提前! 我添加了一个关于如何手动完成的示例,我通常这样做是因为我有更多的控制权,我可以记录事情,添加更多的逻辑等等。 谢谢。您的帮助确实帮助我实现了承诺!欣赏它。【参考方案2】:

如果您无法让 Promise 起作用,您可以将调用的状态存储在本地变量中。您只需将调用分解为 2 个函数,一次上传和批量上传。

这是脏代码,但你应该能明白:

router.post("/upload", function(req, res)
    var form = new multiparty.Form();

    form.parse(req,function(err,fields,files)
        if (err)
            cb(err);
         else 
            bulkUpload(files, fields, function(err, result)
                if (err)
                    cb(err);
                 else 
                    res.json(result:result);
                
            )
        
    )
)

function singleUpload(file, field, cb)
    s3.upload(
            Key: filename,
            Bucket: bucketname,
            ACL: "public-read",
            Body: fs.createReadStream(filepath)
     , function(err, data)
        if(err)
            cb(err);
         else 
            Model.collection.insert("name": "name", "url" : data.Location, function(err, result)
                if(err) 
                    cb(err);
                 else 
                    cb(null, result);
                
            )
        
    )


function bulkUpload (files, fields, cb) 
    var count     = files.length;
    var successes = 0;
    var errors    = 0;

    for (i=0;i<files.length;i++) 
        singleUpload(files[i], fields[i], function (err, res) 
            if (err) 
                errors++
                //do something with the error?
             else 
                successes++
                //do something with the result?
            

            //when you have worked through all of the files, call the final callback
            if ((successes + errors) >= count) 
                cb(
                    null, 
                    
                        successes:successes,
                        errors:errors
                    
                )
            
        );
    

这不是我推荐的方法,但是另一个用户已经建议了 promises。我认为另一种方法会更有帮助。

祝你好运!

【讨论】:

以上是关于我应该如何批量上传到 s3 并通过最终回调从 nodeJS 网络服务器插入到 MongoDB?的主要内容,如果未能解决你的问题,请参考以下文章

将流数据上传到 Amazon S3

使用boto3批量上传图片到S3

将文件上传并压缩到s3

将批量数据从 s3 加载到 redshift

从客户端或服务器上传到 S3?

AWS S3 SDK:如何从多文件上载中的进度回调中获取文件名?