使用Node.js中的FineUploader上传文件会挂起服务器

时间:2013-04-03 14:54:51

标签: node.js express fine-uploader

我已经对这个bug进行了两周以上的故障排除,并且无法找到出错的地方。我使用FineUploader上传一个页面来上传多个文件。我有一个项目集合,每个都需要获得它自己的图像。所以我在请求参数,以确定图像对应的项目。我还有一个图片库和徽标的上传器。

我手动触发上传并在服务器上获取请求,然后设置req.on(data,function(){...})以上传文件。这永远不会被触发。

这是我的代码:

$(document).ready(function () {
    window.onSubmit = function (uploaderName) {
        return function (id, fileName) {
            console.log("Setting parameters...")
            window[uploaderName].setParams({
                farm: $('input[name=farm-name]').val()
            });
        }
    };

    window.logoUploader = new qq.FineUploader({
        element: $('#logo')[0],
        request: {
            endpoint: '/upload/farm/logo'
        },
        autoUpload: false,
        debug: true,
        validation: {
            allowedExtensions: ['jpeg', 'jpg', 'gif', 'png'],
            sizeLimit: 1024000 // 50 kB = 50 * 1024 bytes
        },
        text: {
            uploadButton: '<i class="icon-plus icon-white"></i> Select Logo'
        },
        callbacks: {
            onSubmit: onSubmit("logoUploader")
        }
    });

  window.shareLogoUploader = [];
  window.shareLogoUploader[0] = new qq.FineUploader({
    element: $('#share-logo-0')[0],
    request: {
      endpoint: '/upload/farm/share/gallery'
    },
    autoUpload: false,
    debug: true,
    validation: {
      allowedExtensions: ['jpeg', 'jpg', 'gif', 'png'],
      sizeLimit: 1024000 // 50 kB = 50 * 1024 bytes
    },
    text: {
      uploadButton: '<i class="icon-plus icon-white"></i> Select Logo'
    }
  });

    window.galleryUploader = new qq.FineUploader({
        element: $('#gallery')[0],
        request: {
            endpoint: '/upload/farm/gallery/photo'
        },
        autoUpload: false,
        debug: true,
        validation: {
            allowedExtensions: ['jpeg', 'jpg', 'gif', 'png'],
            sizeLimit: 2024000 // around 2mbs
        },
        text: {
            uploadButton: '<i class="icon-plus icon-white"></i> Pics'
        },
        callbacks: {
            onComplete: function (event, id, fileName, responseJSON) {
                if (responseJSON.success) {
                    console.log(responseJSON, fileName);
                    $('#thumbnails').append('<img src="/uploads/' + fileName + '" width="256px"><input type="hidden" name="photos[]" value="' + fileName + '">');
                }
            },
            onSubmit: onSubmit("galleryUploader")
        }
    });
});

// Shares
var shares = [];
var sharesGallery = [];

$(function () {
    $('section#shares button#add-share').click(function (e) {
        e.preventDefault();

        var title = $('section#shares .control-group input[name=title]');
        var size = $('section#shares .control-group select[name=size]');
        var type = $('section#shares .control-group select[name=type]');
        var price = $('section#shares .control-group input[name=price]');
        var amount = $('section#shares .control-group input[name=amount]');
        var unit = $('section#shares .control-group select[name=unit]');
        var discount_3 = $('input[name=discount-3]').val();
        var discount_6 = $('input[name=discount-6]').val();
        var discount_12 = $('input[name=discount-12]').val();
        var errors = $('section#shares .control-group#errors');
        var delivery_period = $('section#shares .control-group input[name=delivery_period]');
        var delivery_period_es = $('section#shares .control-group input[name=delivery_period_es]');
        var billing_period = $('section#shares .control-group input[name=billing_period]');
        var billing_period_es = $('section#shares .control-group input[name=billing_period_es]');
        var logoUploader = window.shareLogoUploader[shares.length];
        var logoButtonId = "share-logo-" + shares.length;

            shares.push({
                title: title.val(),
                currency: $('select[name=currency]').children(':selected').val(),
                size: size.children(':selected').val(),
                type: type.children(':selected').val(),
                price: price.val() * 100.0,
                unit: unit.children(':selected').val(),
                amount: amount.val(),
                discounts: {
                    '3': [discount_3, price.val() * discount_3 / 100.0],
                    '6': [discount_6, price.val() * discount_6 / 100.0],
                    '12': [discount_12, price.val() * discount_12 / 100.0]
                },
                delivery_period: delivery_period.val(),
                delivery_period_es: delivery_period_es.val(),
                billing_period: billing_period.val(),
                billing_period_es: billing_period_es.val()
            });

            var newLogoButtonId = "share-logo-" + shares.length;

            sharesGallery.push({
                name: type.children(':selected').val() + "-" + size.children(':selected').val(),
                logoUploader: logoUploader
            })
        $("#" + logoButtonId).css('display', 'none');
        $("#" + logoButtonId).parent().append('<div id="' + newLogoButtonId + '"></div>');

        window.shareLogoUploader[shares.length] = new qq.FineUploader({
            element: $("#" + newLogoButtonId)[0],
            request: {
                endpoint: '/upload/farm/share/gallery'
            },
            autoUpload: false,
            debug: true,
            validation: {
                allowedExtensions: ['jpeg', 'jpg', 'gif', 'png'],
                sizeLimit: 1024000 // 50 kB = 50 * 1024 bytes
            },
            text: {
                uploadButton: '<i class="icon-plus icon-white"></i> Select Logo'
            }
        });

        console.log(shares);

        return;
    }
});

这会正确触发上传,我在服务器上获取请求并以这种方式处理它,然后服务器挂起等待从未到来的数据,re req.on Data naver被触发。这是服务器端代码:

var fs = require('fs'), util = require('util'), uuid = require('node-uuid');

var uploadpath = __dirname + "/../public/uploads/";

var slugify = exports.slugify = function(value) {
    return value.toLowerCase().replace(/-+/g, '').replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
};
// Route that takes the post upload request and sends the server response
exports.upload = function(req, res) {
    winston.info("Upload parameters", [req.route.params[0], req.query.farm]);
    uploadpath = __dirname + "/../public/uploads/";
    if(!fs.existsSync(uploadpath)) {
        fs.mkdirSync(uploadpath);
    }
    switch(req.route.params[0]) {
        case 'farm/logo':
            uploadpath += "../farms/" + slugify(req.query.farm);
            if(!fs.existsSync(uploadpath)) {
                fs.mkdirSync(uploadpath);
            }
            uploadpath += "/" + "logo." + req.header('x-file-name').split(".").pop();
            break;

        case 'farm/gallery/photo':
            uploadpath += "../farms/" + slugify(req.query.farm);
            if(!fs.existsSync(uploadpath)) {
                fs.mkdirSync(uploadpath);
            }
            uploadpath += "/gallery";
            if(!fs.existsSync(uploadpath)) {
                fs.mkdirSync(uploadpath);
            }
            var fname = req.header('x-file-name');
            uploadpath += "/" + fname;
            break;

        case 'farm/share/gallery':
            uploadpath += "../farms/" + slugify(req.query.farm);
            if(!fs.existsSync(uploadpath)) {
                fs.mkdirSync(uploadpath);
            }
            uploadpath += "/shares";
            if(!fs.existsSync(uploadpath)) {
                fs.mkdirSync(uploadpath);
            }
            uploadpath += "/" + slugify(req.query.share);
            if(!fs.existsSync(uploadpath)) {
                fs.mkdirSync(uploadpath);
            }
            uploadpath += "/gallery";
            if(!fs.existsSync(uploadpath)) {
                fs.mkdirSync(uploadpath);
            }
            var fname = req.header('x-file-name');
            uploadpath += "/" + fname;
            break;
        case 'courier/license':
        case 'courier/registration':
            uploadpath += "../couriers/" + slugify(req.query.courier);
            if(!fs.existsSync(uploadpath)) {
                fs.mkdirSync(uploadpath);
            }
            var fname = req.header('x-file-name');
            uploadpath += "/" + fname;
            break;

        default:
            var fname = req.header('x-file-name');
            uploadpath += fname;
            break;
    }

    winston.info("Uploading to ", uploadpath);

    uploadFile(req, uploadpath, function(data) {
        if(data.success)
            res.send(JSON.stringify(data), {
                'Content-Type' : 'text/plain'
            }, 200);
        else
            res.send(JSON.stringify(data), {
                'Content-Type' : 'text/plain'
            }, 404);
    });
}
// Mainfunction to recieve and process the file upload data asynchronously
var uploadFile = function(req, targetfile, callback) {

    // Moves the uploaded file from temp directory to it's destination
    // and calls the callback with the JSON-data that could be returned.
    var moveToDestination = function(sourcefile, targetfile) {
        moveFile(sourcefile, targetfile, function(err) {
            if(!err)
                callback({
                    success : true
                });
            else
                callback({
                    success : false,
                    error : err
                });
        });
    };
    // Direct async xhr stream data upload, yeah baby.
    if(req.xhr) {

        // Be sure you can write to '/tmp/'
        var tmpfile = '/tmp/' + uuid.v1();

        // Open a temporary writestream
        var ws = fs.createWriteStream(tmpfile);
        ws.on('error', function(err) {
            console.log("uploadFile() - req.xhr - could not open writestream.");
            callback({
                success : false,
                error : "Sorry, could not open writestream."
            });
        });
        ws.on('close', function(err) {
            moveToDestination(tmpfile, targetfile);
        });
        // Writing filedata into writestream
        req.on('data', function(data) {
            ws.write(data);
        });
        req.on('end', function() {
            ws.end();
        });
    }

    // Old form-based upload
    else {
        moveToDestination(req.files.qqfile.path, targetfile);
    }
};
// Moves a file asynchronously over partition borders
var moveFile = function(source, dest, callback) {
    var is = fs.createReadStream(source)

    is.on('error', function(err) {
        console.log('moveFile() - Could not open readstream.', err);
        callback('Sorry, could not open readstream.')
    });
    is.on('end', function() {
        fs.unlinkSync(source);
        callback();
    });
    var os = fs.createWriteStream(dest);
    os.on('error', function(err) {
        console.log('moveFile() - Could not open writestream.', err);
        callback('Sorry, could not open writestream.');
    });

    is.pipe(os);
}

有人能指出我正确的方向,为每一堆文件使用不同的参数上传文件吗?

提前谢谢。

3 个答案:

答案 0 :(得分:3)

好吧,如果有人(像我一样)需要一个体面的简单例子来实现他们的node.js / express应用程序的Fine Uploader - 我终于能够让它运行起来。事实证明,我为自己制造的所有事情都比必要的要困难得多,而Fine Uploader服务器示例并没有说清楚现在使用较新版本的Express是多么容易(这是使用express@3.1.1)。

app.use(express.bodyParser({uploadDir: __dirname + '/uploads'}));

app.get('/upload', localQuery, function(request, response) {
    //This serves up our 
    response.render('index');

});

app.post('/uploadhander', function (request, response, next) {

    var fileName = request.files.qqfile.name
    var savePath = __dirname + '/process/';

    //after upload, rename the file, then respond to fineuploader to tell it of success
    fs.rename(request.files.qqfile.path, savePath + fileName, function(err) {
    if (err != null) {
        response.send(JSON.stringify({success: false, error: err}), {'Content-Type': 'text/plain'}, 404);
    } else {
        response.send(JSON.stringify({success: true}), {'Content-Type': 'text/plain'}, 200);
    }
    });

});

以下是处理Fine Uploader选项的/ upload的jade模板(您的选项显然会有所不同):

  div.span12
    h4 Upload a show file
    #jquery-wrapped-fine-uploader
      script(type='text/javascript')
        jQuery(document).ready(function() {
          jQuery('#jquery-wrapped-fine-uploader').fineUploader({
            debug: false,
            request: {
              endpoint: '/uploadhander',
              params: {
                hostname: "#{locals.query.hostname}",
                hostport: "#{locals.query.hostport}",
                bucket: "#{locals.query.bucket}",
                nid: "#{locals.query.nid}",
                title: "#{locals.query.title}",
              },
              chunking: {
                enabled: true,
              },
              resume: {
                enabled: true,
              },
            }
          });
        });

我应该指出,我还没有测试过简历功能,但我可以验证是否正在处理多个上传,代码比发布的示例少得多。希望这有助于其他人!

答案 1 :(得分:0)

看起来您正在使用Node示例,或者可能是Fine Uploader Github项目的服务器目录中的Node示例的修改版本。该示例尚未更新以处理多部分编码请求,Fine Uploader现在默认为所有浏览器发送。

我对Node没有太多经验,但如果您使用的是Express,似乎您可以/应该使用req.files来获取与请求关联的文件。

我将在Widen/fine-uploader-server repo中打开一个案例,提醒我更新节点示例。

答案 2 :(得分:0)

multer与express.js一起使用会更有用,更简单 我的服务器端代码看起来像这样

var multer  = require('multer');

var upload = multer({
    dest : path.join(__dirname , '../public/uploads/')
});

router.post('/upload', function (req, res) {

    upload(req, res, function (err) {

        if (err) 
            response.send(JSON.stringify({success: false, error: err}), {'Content-Type': 'text/plain'}, 404);
        else
            res.send(JSON.stringify({success: true}), {'Content-Type': 'text/plain'}, 200);

    });


});

module.exports = router;