我即将废弃这个当前的NodeJS项目,因为我已经做了一些简单的事情,比如创建多分辨率的图像副本并为其中一个加水印。我花了几天时间与Node争夺这项工作,这是荒谬的,因为Ruby中的相同代码只有几行而且需要花费几分钟才能完成。然而,公司想要它在Node中,所以我们走了。
使用NodeJS,Express,Multer和GraphicsMagick我正在尝试拍摄用户上传的高清图像并创建多个较小的副本,最后的缩略图被加水印。这本身就相当容易,但是当我开始进行数百次快速上传的负载测试时,我发现尽管上传了原始图像,但我还是丢失了大约20%的复制图像。我对正在发生的事情的猜测是,当写入磁盘时,复制功能被中断,因为下一次上传过程中,JavaScript正在重写文件名或其他内容。
正如您在下面的代码中所看到的,每次调用post处理程序时我都会创建一个新对象,我假设它会封装方法变量并阻止任何被覆盖的内容。老实说,我不知道JavaScript在这里做了什么。
我的测试方法是一个自定义Bash脚本,它通过Curl发布文件。我已经在Ubuntu Xenial和Debian上测试了这个,结果相同。下面的脚本只上传了20张图片,但问题仍然存在,它预计会返回40张图片,但会返回36左右:
#!/bin/bash
#simulates multiple image posts with different parameters
for run in {1..2}
do
FNAME=("imageOne" "imageTwo" "imageThree" "imageFour" "imageFive" "imageSix" "imageSeven" "imageSeven" "imageEight" "imageNine" "imageTen")
USER=("userOne" "userTwo" "userThree" "userFour" "userFive" "userSix" "userSeven" "userSeven" "userEight" "userNine" "userTen")
for item in {0..9}
do
URL="http://localhost:3000/api/images/${USER[$item]}/${FNAME[$item]}"
curl -i -F "image=@sample.png;type=image/png" $URL
# works when sleep called
# sleep 1
done
done
正如在评论中所说,当在每次上传之前插入间隔时,代码完美地工作,这意味着问题是由异步执行引起的。也许我的回调不会等到确认正在写的文件?
以下是主要方法。 Upload方法使用Multer上传用户图像。 FindSize获取水印和用户图像并查找尺寸以供以后处理,Watermark创建原始文件的副本。我已经为了这个演示删除了所有其他功能,例如创建水印并调整大小到不同的分辨率,因为它仍然会遇到同样的问题。
"use strict";
var multer = require('multer'),
fs = require('fs'),
gm = require('gm');
function ProcessFile(req,res){
var fname,
fmime;
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './uploads/');
},
filename: function (req, file, cb) {
fmime = '.' + file.mimetype.substr(6,file.mimetype.length);
fname = req.params.user + '-' + Date.now() + '-' + req.params.filename;
cb(null, fname + fmime);
}
});
// multer option that controls which files are uploaded
var fileFilter = function(req,file,cb)
{
// remove non-raster-image filetypes
var mimetypes = ['gif','jpeg', 'png', 'bmp'];
for (var mime in mimetypes)
{
if (file.mimetype == 'image/' + mimetypes[mime])
{
// accept file
cb(null,true);
return;
}
}
// reject file
cb(null,false);
};
var upload = multer({storage:storage, fileFilter:fileFilter, limits:{fileSize:1000000}}).single('image');
var findSize = function(call){
var yPos = 0,
xPos = 0,
self = this,
width = 0,
height = 0;
// get width of watermark image
function getWidth(c) {
gm('watermark.png')
.size(function (err, size) {
width = size.width;
height = size.height;
c(); // getMidPoint callback
});
}
// finds horizontal mid point of image and calls main function
function getMidPoint(c) {
gm('./uploads/' + fname + fmime)
.size(function (err, size) {
if (err) {
console.log(err)
}
else {
yPos = size.height / 2;
xPos = size.width / 2;
c();
}
});
}
getWidth(function c(){
getMidPoint(function c(){
call(yPos, xPos, width, height);
});
});
};
var watermark = function(yPos, xPos, width, height, call){
// set watermark width to 60% of image width
var markwidth = (xPos*2)* 0.6,
xPlacement = xPos-(markwidth/2),
yPlacement = yPos-(height/2),
aspectHeight = (yPos*2)/(xPos*2) * markwidth,
self = this;
aspectHeight = Math.round(aspectHeight);
// does nothing but create a copy for demo purposes
gm('./uploads/'+fname + fmime)
// write original image
.write('./uploads/'+fname+'-'+markwidth+'x'+aspectHeight + fmime, function (err) {
if (err){console.log(err)}
else
{
call();
}
});
};
this.run = function(req,res){
upload(req,res, function(err){
if(!err) {
findSize(function call(yPos, xPos, width, height) {
watermark(yPos, xPos, width, height, function call() {
console.log('complete');
});
});
}
});
}
}
var p = new ProcessFile();
module.exports = p;
以下是路线的代码。图像处理方法作为变量' imageHandler'。
导入app.post('/api/images/:user/:filename', function(req,res){
imageHandler.run(req,res);
res.end();
});
我仍然试图理解服务器上异步,单线程,事件驱动语言的好处,除非在特殊情况下,这对我来说显然是荒谬的。
无论如何,如果有人可以提供这方面的帮助,我将非常感激,否则我会废弃这个并回到铁轨上。