Multer 使用数据创建新文件夹

Posted

技术标签:

【中文标题】Multer 使用数据创建新文件夹【英文标题】:Multer create new folder with data 【发布时间】:2016-04-08 00:29:36 【问题描述】:

我使用multer。

问题 1

当我在app.js中加入下面的sn-p

app.use(multer(
        dest: './uploads'
    
).single('file'));

它会在根文件夹下创建一个新文件夹,我的问题是关于这个新文件夹的生命周期,什么时候会被删除? 100次调用后文件夹的大小可以是多少?

问题 2

如果我不想限制文件大小,我应该在配置中放入什么?

app.use(multer(
    dest: './public/profile/img/',
    limits: 
        fieldNameSize: 50,
        files: 1,
        fields: 5,
        fileSize: 1024 * 1024
    ,

更新

我的应用是这样构建的

app.js 文件包含

    app.use(multer(
            dest: './uploads'
        
    ).single('file'));

app.use('/', routes, function (req, res, next) 
    next();
);

路由文件如下所示

appRouter
    .post('*', function (req, res) 
        handler.dispatch(req, res)
    )
    .get('*', function (req, res) 
        handler.dispatch(req, res)
    )

在第三个文件中,我使用如下解压缩方式

update: function (req, res) 
  var filePath = path.join(req.file.destination, req.file.filename);
            var unzipper = new Unzipper(filePath);
            unzipper.on("extract", function () 
                console.log("Finished extracting");
                res.sendStatus(200);
            );
            unzipper.on('progress', function (fileIndex, fileCount) 
                console.log('Extracted file ' + (fileIndex + 1) + ' of ' + fileCount);
            );
            unzipper.on('list', function (files) 
                console.log('The archive contains:');
                console.log(files);
            );

            unzipper.on('error', function (err) 
                console.log('Caught an error', err);
            );

            unzipper.extract(
                path: "./"
            );
        

以下是我的节点应用程序的结构,有人可以建议如何以及在哪里(哪个文件)它的推荐使用 Raf 代码向文件添加 dateTime,我可以添加排序...

【问题讨论】:

您能否更好地解释第一个问题?我以前用过 Multer。 不限制文件大小是个坏主意;您基本上是在要求 DoS 攻击。 【参考方案1】:

我会尝试用一个真实的例子来回答你的问题,至少你可以从中学到一些东西。如果您希望删除除最近上传之外的所有内容,那么您需要编写某种逻辑来区分哪些上传是最近的,哪些是旧的。下面我描述,我将如何解决这个问题,可能并不完美,但我就是这样做的。

文件夹永远不会被自动删除,除非您手动或以编程方式将其删除。

100 次调用的文件夹大小假设在每次调用中您上传 x 大小的文件将是 x 乘以 100

您不想限制文件上传,不要提供限制配置,但建议指定文件上传限制。

您显然可以将 multer 附加到应用程序或创建它的实例并将其传递给路由。我更喜欢第二种方法:

多重配置

var storage = multer.diskStorage(
  destination: function (req, file, cb) 
    cb(null, 'uploads/')
  ,
  filename: function (req, file, cb) 
    var filename = file.originalname;
    var fileExtension = filename.split(".")[1];
    cb(null, Date.now() + "." + fileExtension);
  
); 

如上图,我不让multer给上传的文件起一个随机的名字。我所做的是,获取文件名,去掉其扩展名,然后使用Date.now(),这将为我提供当前时间戳,并附加上传的文件扩展名。如果我进行六次上传,它们会显示如下(我的大部分上传都是 .jpg,取自文件名)。

上传的最终结果(时间戳会有所不同)

1453414099665.jpg (oldest) 
1453414746114.JPG
1453414748991.jpg
1453414751662.jpg
1453414754815.jpg (most recent)

我将上面的 storage 附加到 multer 的实例中,如下所示:

var upload = multer(storage: storage);

现在我可以将upload 传递给处理文件上传的路由,如下所示:

如下所示将上传附加到路由

//simple route to return upload form, simple jade file
app.get('/upload', function(req, res)
  res.render('upload');
);

//this route processes the upload request, see below upload.single('file') 
//is the passed multer
app.post('/upload', upload.single('file'),  function(req,res)
  res.status(204).end();
);

假设您一直在上传,然后在某个时候您想列出上传目录中的所有文件。路线如下:

列出上传目录中的所有文件

//lists all files in the uploads directory and return it to browser as json response
app.get('/listAllFiles', function(req, res) 
  //reading directory in synchronous way
  var files = fs.readdirSync('./uploads');
  res.json(files);
);

你想删除上传目录中的所有文件,路径如下:

删除上传目录中的所有文件

//delete all files in the upload direcotry asynchronously
app.get('/deleteAllFiles', function(req, res) 
  fs.readdir('./uploads', function(err, items) 
    items.forEach(function(file) 
        fs.unlink('./uploads/' + file);
        console.log('Deleted ' + file);
    );
    res.status(204).end();
  );
);

如果您希望同步删除所有文件,则必须调用 readdir 的同步版本 (readdirSync) 和取消链接 (unlinkSync)

var filenames = fs.readdirSync('./uploads');

filenames.forEach(function(file) 
  fs.unlinkSync('./uploads/' + file);
);

现在您要删除除最近上传的文件之外的所有文件。好吧,我已经将所有文件名设置为时间戳。所以我会做如下事情:

删除除最近的文件以外的所有文件(最近的文件是以最近的时间戳作为文件名的文件)。

//delets all file asynchronously except the most recent in which case the file
//with name being the latest timestamp is skipped.
app.get('/deleteAllExceptMostRecent', function(req, res) 
  console.log('/deleteAllFilesExceptMostRecent');
  fs.readdir('./uploads', function(err, items) 
    //sort the array of files names in reverse, so we have most recent file on top
    items.reverse();
    var flag = true;

    items.forEach(function(file) 
      //skip deletion of most recent file. if condition executed onces only.
      if(flag) 
        flag = false;
       else 
        fs.unlink('./uploads/' + file);
        console.log('Deleted ' + file);
      
    );
  );
  res.status(204).end();
);

我没有在我的示例中添加任何限制,但建议这样做。默认文件大小限制为无限大,如果您不将其包含在 prod 环境中,那么您将容易受到 cmets 中所示的 DoS 攻击。

要使上述文件操作正常工作,您需要加载

var fs = require('fs'); 

关于您的第二点,只需跳过限制属性,默认限制将是无穷大。

出于演示目的,我在一个工作的 nodejs 应用程序中设置了上述内容,见下文:

app.js

var express = require('express');
var multer = require('multer');
var bodyParser = require('body-parser');
var path = require('path');
var fs = require('fs');

var app = new express();
app.use(bodyParser.json());

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

var storage = multer.diskStorage(
  destination: function (req, file, cb) 
    cb(null, 'uploads/')
  ,
  filename: function (req, file, cb) 
    /* if you need to retain the original filename, then use filename and append to it date
     * if you don't need original filename but, just extension, then append extension at the
     * end of current timestamp. If you don't need extenion then just use Date.now() which
     */
    var filename = file.originalname;
    var fileExtension = filename.split(".")[1];

    cb(null, Date.now() + "." + fileExtension);
  
)

var upload = multer(storage: storage);

//get upload form
app.get('/upload', function(req, res)
  res.render('upload');
);

//process upload
app.post('/upload', upload.single('file'),  function(req,res)
  res.status(204).end();
);

//lists all files in the uploads directory.
app.get('/listAllFiles', function(req, res) 
  var files = fs.readdirSync('./uploads');
  res.json(files);
);

//delete all files in the upload direcotry asynchronously
app.get('/deleteAllFiles', function(req, res) 
  fs.readdir('./uploads', function(err, items) 
    items.forEach(function(file) 
        fs.unlink('./uploads/' + file);
        console.log('Deleted ' + file);
    );
    res.status(204).end();
  );
);

//delets all file asynchronously except the most recent in which case the file
//with name being the latest timestamp is skipped.
app.get('/deleteAllExceptMostRecent', function(req, res) 
  console.log('/deleteAllFilesExceptMostRecent');
  fs.readdir('./uploads', function(err, items) 
    items.reverse();
    var flag = true;

    items.forEach(function(file) 
      if(flag) 
        flag = false;
       else 
        fs.unlink('./uploads/' + file);
        console.log('Deleted ' + file);
      
    );
  );
  res.status(204).end();
);

//delete all files of a direcotry in synchronous way
app.get('/deleteAllSync', function(req, res) 
  var filenames = fs.readdirSync('./uploads');

  filenames.forEach(function(file) 
    fs.unlinkSync('./uploads/' + file);
  );
);

//delete all files except most recent in synchronous way
app.get('/deleteAllSyncExceptMostRecent', function(req, res) 
  var filenames = fs.readdirSync('./uploads');
  filenames.reverse();
  var flag = true;
  filenames.forEach(function(file) 
    if(flag) 
      flag = false;
    else
      fs.unlinkSync('./uploads/' + file);
  );
);

var port = 3000;
app.listen( port, function() console.log('listening on port '+port);  );

views/upload.jade

html
  head
    title
  body
    form(method="post",enctype="multipart/form-data",action="/upload")
      p
        input(type="file",name="file")
      p
        input(type="submit")

【讨论】:

感谢 Raf 1+,请查看我的更新存储)?非常感谢! 你能帮忙吗? 你可以把你的代码 app.use(multer(dest: './uploads').single('file')); 改成一些东西app.use(upload); 在此之上,您必须复制/粘贴我在答案中显示的上传定义。这是一种方法,基本上你告诉应用程序使用一个包含存储定义的 multer 对象。另一种可能更简单的方法是注释掉您自己的 app.use(multer 然后在 router.post 上方复制/粘贴存储和 multer 定义并将其作为第二个参数传递给路由器app.post('/upload', upload.single('file'), function(req,res) 谢谢我需要使用第一个选项,但不确定如何将 app.use(upload) 与我已有的 (app.use(multer(des....)) 结合起来请举个例子,我现在试试?谢谢! app.use(upload) 替换 app.use(multer(dest....)) 他们是一样的毕竟,在您的方法中,您在 app.use 中定义 multer 但是,在我的方法中,我单独定义 multer 然后将其传递给 app.use (显然,您必须将存储和上传定义放在 app.use(upload) 之上它可以工作。【参考方案2】:

(1) 当您进行以下调用时,您是在告诉 multer 将上传的文件放入名为 uploads 的目录中。因此,如果您的应用启动时该目录尚不存在,它将为您创建该目录。

app.use(multer(
        dest: './uploads'
    
).single('file'));

就该目录的生命周期而言,只要您不删除它,它就会一直存在,并且仍然要求 multer 将其用作目标。上传的文件正在那里添加,因此其内容的大小取决于上传的内容。

(2) 至于限制文件大小,根据文档的默认设置是Infinity。所以没有限制,除非你设置一个。

但是,不了解您的应用程序,我仍然强烈建议设置一个限制,即使您需要它相当高。完全取消大小限制可能会导致大问题。


编辑 如果您想在上传新文件时删除./uploads 的内容,Node 提供了一种删除文件的方法:fs.unlink。另见SO about removing a file in node

在您的上传处理程序中,检查./uploadsunlink 的内容,该文件不是当前请求中使用的文件。当前上传见req.file

【讨论】:

谢谢,但是你能建议在几个文件被保存在那里之后如何删除这个文件夹,也许是保留新的请求并删除旧的......或者最好在我已经开始做的时候限制文件大小... 你的意思是要在一定的时间限制后删除./uploads/内的旧文件?还是在./uploads/ 目录下所有文件的总大小超过一定限制后删除旧文件?您可能不想删除./uploads/ 目录本身,对吧?因为那样你会丢失所有通过 multer 上传和处理的文件。 我想删除文件夹中的所有内容,但要保留文件夹本身和最后一个调用输入,如何完成? 以下答案提供了一种删除目录中最新文件的方法:***.com/a/34862475/1671886。【参考方案3】:

阙1)您可以决定文件夹的生命周期。

    如果您想将文件存储在服务器上并在没有文件大小限制的情况下随时取回,您需要更大的存储空间

    如果您使用像 Amazon S3 之类的第三方进行存储,则在本地存储没有意义,只要将其上传到服务器,您就可以从本地存储中删除。 您可以在发送响应时使用after Hook。

    app.use(function (req, res, next) 
    function afterResponse() 
        res.removeListener('finish', afterRequest);
        res.removeListener('close', afterRequest);
    
        // Delete files from multer 
        // you can delete older one from upload folder as you see new one came here.
    
    
    res.on('finish', afterResponse);
    res.on('close', afterResponse);
    
    // action before request
    // eventually calling `next()`
     );
    

Que 2)默认是无限的。

【讨论】:

以上是关于Multer 使用数据创建新文件夹的主要内容,如果未能解决你的问题,请参考以下文章

在使用 Multer 将文件上传到文件夹之前删除和创建文件夹

multer创建文件夹,如果不存在

尽管创建了文件对象,但 Multer req.file 始终未定义

反应节点通过 axios 传递文件与 multer 错误 500

使用 fs 在 multer 中重命名上传的图像

multer 解析文件上传