如何使用 node.js 删除 s3 上的文件夹?

Posted

技术标签:

【中文标题】如何使用 node.js 删除 s3 上的文件夹?【英文标题】:How can I delete folder on s3 with node.js? 【发布时间】:2013-12-11 00:07:09 【问题描述】:

是的,我知道。 s3 存储上没有文件夹概念。但我真的很想用 node.js 从 s3 中删除一个特定的文件夹。我尝试了两种解决方案,但都没有奏效。 我的代码如下: 解决方案1: 直接删除文件夹。

var key='level/folder1/folder2/';
var strReturn;
        var params = Bucket: MyBucket;
        var s3 = new AWS.S3(params);
        s3.client.listObjects(
            Bucket: MyBucket,
            Key: key
        , function (err, data) 
            if(err)
                strReturn="\"status\":\"1\"";

            else
                strReturn=+"\"status\":\"0\"";
            
            res.send(returnJson);
            console.log('error:'+err+' data:'+JSON.stringify(data));
        );

其实我在folder2下有很多文件。如果我定义这样的键,我可以从 folder2 中删除单个文件: var key='level/folder1/folder2/file1.txt',但是当我删除一个文件夹时它不起作用(key='level/folder1/folder2/')。 解决方案2: 当我将此文件或文件夹上传到 s3 时,我尝试将过期设置为对象。代码如下:

s3.client.putObject(
                Bucket: Camera_Bucket,
                Key: key,
                            ACL:'public-read', 
                Expires: 60 
            

但它也没有。上传完成后,我检查了该文件的属性。它表明到期日期没有任何价值:

Expiry Date:none
Expiration Rule:N/A

【问题讨论】:

删除“文件夹”内的所有对象 我在“文件夹”中有很多文件。这就是我想删除文件夹的原因。如果我循环所有文件并删除它们,我将花费很长时间来完成它。 该文件夹仅作为共享该“文件夹”名称的一组路径存在。如果您删除所有对象,则“文件夹”将不再存在。如果您要删除大量文件,这可能需要一段时间。 rest API 有一个命令可以一次删除最多 1000 个文件,不确定 node.js api 是否暴露了这个。 当我将对象放到s3上时,如何设置单个对象的到期日期。当我按照 aws doc 描述的那样制作时,它不起作用。这很奇怪。 据我所知,过期适用于存储桶中的所有对象。您应该能够运行带有文件夹前缀的 listObjects,然后运行带有对象数组的 deleteObjects(至少在 php SDK 中)。两个电话的链接:docs.aws.amazon.com/AWSSDKforPHP/latest/#m=AmazonS3/…、docs.aws.amazon.com/AWSSDKforPHP/latest/#m=AmazonS3/… 【参考方案1】:

我喜欢列表对象然后删除方法,这是 aws cmd 行在幕后所做的。但我不想在删除它们之前等待列表(几秒钟)。所以我使用这个 1 步骤(背景)过程,我发现它稍微快一点。如果您真的想确认删除,您可以等待子进程,但我发现这大约需要 10 秒,所以我不打扰我只是触发并忘记并检查日志。与其他东西的整个 API 调用现在需要 1.5 秒,这对我的情况来说很好。

var CHILD = require("child_process").exec;
function removeImagesAndTheFolder(folder_name_str, callback)
            
            var cmd_str = "aws s3 rm s3://" 
                    + IMAGE_BUCKET_STR 
                    + "/" + folder_name_str
                    + "/ --recursive";
    
            if(process.env.NODE_ENV === "development")
                //When not on an EC2 with a role I use my profile    
                cmd_str += " " + "--profile " + LOCAL_CONFIG.PROFILE_STR;
            
            // In my situation I return early for the user. You could make them wait tho'.
            callback(null, "msg_str": "Check later that these images were actually removed.");
            //do not return yet still stuff to do   
            CHILD(cmd_str, function(error, stdout, stderr)
                if(error || stderr)
                    console.log("Problem removing this folder with a child process:" + stderr);
                else
                    console.log("Child process completed, here are the results", stdout);
                
            );
        

【讨论】:

【参考方案2】:

一种更简单的方法是获取该路径中的所有对象(键)并删除它们。在每次调用中获取 1000 个键和 s3 deleteObjects 也可以在每个请求中删除 1000 个键。递归执行以实现目标

用打字稿写的

/**
     * delete a folder recursively
     * @param bucket
     * @param path - without end /
     */
    deleteFolder(bucket: string, path: string) 
        return new Promise((resolve, reject) => 
            // get all keys and delete objects
            const getAndDelete = (ct: string = null) => 
                this.s3
                    .listObjectsV2(
                        Bucket: bucket,
                        MaxKeys: 1000,
                        ContinuationToken: ct,
                        Prefix: path + "/",
                        Delimiter: "",
                    )
                    .promise()
                    .then(async (data) => 
                        // params for delete operation
                        let params = 
                            Bucket: bucket,
                            Delete:  Objects: [] ,
                        ;
                        // add keys to Delete Object
                        data.Contents.forEach((content) => 
                            params.Delete.Objects.push( Key: content.Key );
                        );
                        // delete all keys
                        await this.s3.deleteObjects(params).promise();
                        // check if ct is present
                        if (data.NextContinuationToken) getAndDelete(data.NextContinuationToken);
                        else resolve(true);
                    )
                    .catch((err) => reject(err));
            ;

            // init call
            getAndDelete();
        );
    

【讨论】:

【参考方案3】:

您可以使用 aws-sdk 模块来删除文件夹。因为只有在文件夹为空时才能删除,所以应该先删除里面的文件。我正在这样做:

function emptyBucket(bucketName,callback)
  var params = 
    Bucket: bucketName,
    Prefix: 'folder/'
  ;

  s3.listObjects(params, function(err, data) 
    if (err) return callback(err);

    if (data.Contents.length == 0) callback();

    params = Bucket: bucketName;
    params.Delete = Objects:[];
    
    data.Contents.forEach(function(content) 
      params.Delete.Objects.push(Key: content.Key);
    );

    s3.deleteObjects(params, function(err, data) 
      if (err) return callback(err);
      if (data.IsTruncated) 
        emptyBucket(bucketName, callback);
       else 
        callback();
      
    );
  );

【讨论】:

它被限制为 1000 个对象。所以你应该添加一些逻辑以防对象超过 1000。 s3.deleteObjects返回的数据中没有data.Contents参数。我觉得应该改成data.Deleted 同意,data.Contents 未定义。应该是data.Deleted 另外,if (data.Contents.length == 0) callback(); 行需要更改为 if (data.Contents.length == 0) return callback(); 以停止尝试删除任何内容(否则 AWS 返回错误并且您收到错误回调) 我宁愿使用data.Contents.IsTruncated 而不是data.Contents.length == 1000 来隐藏“内部”1000 var...【参考方案4】:

listObjectsV2 仅列出带有当前目录前缀而不带有子文件夹前缀的文件。如果你想递归删除带有子文件夹的文件夹,源代码如下:https://github.com/tagspaces/tagspaces/blob/develop/app/services/objectstore-io.ts#L838

  deleteDirectoryPromise = async (path: string): Promise<Object> => 
    const prefixes = await this.getDirectoryPrefixes(path);

    if (prefixes.length > 0) 
      const deleteParams = 
        Bucket: this.config.bucketName,
        Delete:  Objects: prefixes 
      ;

      return this.objectStore.deleteObjects(deleteParams).promise();
    
    return this.objectStore
      .deleteObject(
        Bucket: this.config.bucketName,
        Key: path
      )
      .promise();
  ;

  /**
   * get recursively all aws directory prefixes
   * @param path
   */
  getDirectoryPrefixes = async (path: string): Promise<any[]> => 
    const prefixes = [];
    const promises = [];
    const listParams = 
      Bucket: this.config.bucketName,
      Prefix: path,
      Delimiter: '/'
    ;
    const listedObjects = await this.objectStore
      .listObjectsV2(listParams)
      .promise();

    if (
      listedObjects.Contents.length > 0 ||
      listedObjects.CommonPrefixes.length > 0
    ) 
      listedObjects.Contents.forEach(( Key ) => 
        prefixes.push( Key );
      );

      listedObjects.CommonPrefixes.forEach(( Prefix ) => 
        prefixes.push( Key: Prefix );
        promises.push(this.getDirectoryPrefixes(Prefix));
      );
      // if (listedObjects.IsTruncated) await this.deleteDirectoryPromise(path);
    
    const subPrefixes = await Promise.all(promises);
    subPrefixes.map(arrPrefixes => 
      arrPrefixes.map(prefix => 
        prefixes.push(prefix);
      );
    );
    return prefixes;
  ;

【讨论】:

【参考方案5】:

接受的答案在打字稿中使用时会引发错误,它是deleteParams中的对象数组。我通过以下方式修改代码使其工作。我对 Typescript 很陌生,但至少它现在可以工作了。

 async function emptyS3Directory(prefix: string) 
  const listParams = 
    Bucket: "bucketName",
    Prefix: prefix, // ex. path/to/folder
  ;

  const listedObjects = await s3.listObjectsV2(listParams).promise();

  if (listedObjects.Contents.length === 0) return;

  const deleteParams = 
    Bucket: bucketName,
    Delete:  Objects: [] as any ,
  ;

  listedObjects.Contents.forEach((content: any) => 
    deleteParams.Delete.Objects.push( Key: content.Key );
  );

  await s3.deleteObjects(deleteParams).promise();

  if (listedObjects.IsTruncated) await emptyS3Directory(prefix);

【讨论】:

【参考方案6】:

你可以试试这个:

import  s3DeleteDir  from '@zvs001/s3-utils'
import  S3  from 'aws-sdk'

const s3Client = new S3() 

await s3DeleteDir(s3Client, 
  Bucket: 'my-bucket',
  Prefix: `folder/`,
)

【讨论】:

【参考方案7】:

您可以像删除文件一样删除空文件夹。要删除 AWS S3 上的非空文件夹,您需要先将其清空,方法是删除其中的所有文件和文件夹。文件夹为空后,您可以将其作为常规文件删除。这同样适用于桶删除。我们已经在这个名为 Commandeer 的应用程序中实现了它,因此您可以从 GUI 中完成它。

【讨论】:

【参考方案8】:

根据 Emi 的回答,我制作了一个 npm 包,所以你不要' 不需要自己编写代码。代码也是用打字稿写的。

见https://github.com/bingtimren/s3-commons/blob/master/src/lib/deleteRecursive.ts

【讨论】:

【参考方案9】:

这是 ES7 中的一个实现,带有 async 函数并使用 listObjectsV2(修改后的 List Objects API):

async function emptyS3Directory(bucket, dir) 
    const listParams = 
        Bucket: bucket,
        Prefix: dir
    ;

    const listedObjects = await s3.listObjectsV2(listParams).promise();

    if (listedObjects.Contents.length === 0) return;

    const deleteParams = 
        Bucket: bucket,
        Delete:  Objects: [] 
    ;

    listedObjects.Contents.forEach(( Key ) => 
        deleteParams.Delete.Objects.push( Key );
    );

    await s3.deleteObjects(deleteParams).promise();

    if (listedObjects.IsTruncated) await emptyS3Directory(bucket, dir);

称呼它:

await emptyS3Directory(process.env.S3_BUCKET, 'images/')

【讨论】:

工作就像一个魅力:) 我认为应该是if (listedObjects.IsTruncated) await emptyS3Directory(bucket, dir);而不是if (listedObjects.Contents.IsTruncated) await emptyS3Directory(bucket, dir); @CSharper 正如isTruncated 的文档所说:Set to false if all of the results were returned. Set to true if more keys are available to return. If the number of results exceeds that specified by MaxKeys, all of the results might not be returned. 干得好!请注意,这种递归有一个缺点:它增加了每次迭代的调用堆栈深度。我建议用while(true) ... if (!listedObjects.IsTruncated) break; 包裹函数体 将此代码与@RonKlein 修改一起使用——它有效。我同意,这里的递归会消耗更多的内存。【参考方案10】:

根据接受的答案,我创建了 promise 返回函数,所以你可以链接它。

function emptyBucket(bucketName)
    let currentData;
    let params = 
        Bucket: bucketName,
        Prefix: 'folder/'
    ;

    return S3.listObjects(params).promise().then(data => 
        if (data.Contents.length === 0) 
            throw new Error('List of objects empty.');
        

        currentData = data;

        params = Bucket: bucketName;
        params.Delete = Objects:[];

        currentData.Contents.forEach(content => 
            params.Delete.Objects.push(Key: content.Key);
        );

        return S3.deleteObjects(params).promise();
    ).then(() => 
        if (currentData.Contents.length === 1000) 
            emptyBucket(bucketName, callback);
         else 
            return true;
        
    );

【讨论】:

以上是关于如何使用 node.js 删除 s3 上的文件夹?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我在 s3 上的访问被拒绝(使用适用于 Node.js 的 aws-sdk)?

如何使用 node.js、Express 和 knox 将文件从浏览器上传到 Amazon S3? [关闭]

如何读取大文件并将其上传到 s3?

Amazon S3 和 Cloudfront 上的 Node.js 服务器

如何删除或清除 S3 上的旧文件?

Node js - 在上传时配置 aws s3 图像