有没有办法安排删除 Firebase 存储上的文件?

Is there a way to schedule a deletion of a file on Firebase Storage?

我正在尝试构建一个允许用户上传和下载一些文件的 ReactJS 应用程序。我的应用程序中已经具有 Firebase 存储上传和下载功能。问题是我想安排一个任务,例如每次用户上传他的文件时,它会在 10 分钟内删除一个文件。

有什么建议吗?

Firebase 中没有为此内置任何内容,但 Cloud Storage(底层存储机制)有一种称为 object lifecycle management 的东西,其中:

you can assign a lifecycle management configuration to a bucket. The configuration contains a set of rules which apply to current and future objects in the bucket. When an object meets the criteria of one of the rules, Cloud Storage automatically performs a specified action on the object.

如果删除规则真的像你说的那么稳定,那我就去看看。

如果规则更复杂,我通常会从 Cloud Functions 开始,因为我可以编写 code that run periodically 来进行清理。

如果您正在为此寻找可靠的服务器端解决方案,您可以使用 Scheduled Cloud Function

这是基于节点的云函数的实现:

import * as admin from "firebase-admin";
import * as functions from "firebase-functions";

admin.initializeApp();

/**
 * Scheduled Cloud Function that deletes files older than 10 minutes,
 * checking every 5 minutes.
 *
 * Files must be prefixed with "tmpUserUploads" to be checked by this
 * function. If a file has a temporary or event-based hold on it, it
 * will be skipped over by this function.
 *
 * @author samthecodingman [MIT License]
 * @see 
 *
 * @example
 * "tmpUserUploads/someFile.png" // -> checked
 * "tmpUserUploads/users/someUserId/someFile.png" // -> checked
 * "profilePictures/someUserId/profile@1x.png" // -> not checked
 */
export cleanupTemporaryFiles =
functions.pubsub.schedule('every 5 minutes').onRun(async (context) => {
  // variables for tracking statistics
  let processedCount = 0, disposedCount = 0, skippedCount = 0, erroredCount = 0, totalCount = 0;
  const errorCountsByCode = {};
  
  try {
    const bucket = admin.storage().bucket();

    // Query for all files starting with "tmpUserUploads"
    const [filesArray] = await bucket.getFiles({
      prefix: "tmpUserUploads"
    });
    
    totalCount = filesArray.length;

    // variables with our settings to be reused below
    const TIMESTAMP_TEN_MINUTES_AGO = Date.now() - 600000;
    const DELETE_OPTIONS = { ignoreNotFound: true };

    // If this number is regularly large, consider shortening the interval above
    console.log("Found ${totalCount} files that need to be checked.");

    // Process purge of each file as applicable keeping track of the results
    const deleteOldFileResults = await Promise.all(
      filesArray.map(async (file) => {
        let metadata;
        try {
          // get the metadata for this file object
          [metadata] = await file.getMetadata();

          // pull out the bits we need
          const { temporaryHold, eventBasedHold, timeCreated } = metadata;

          // held files should not be deleted (will throw an error if you try)
          const activeHold = temporaryHold || eventBasedHold;

          // dispose when not held and older than 10 minutes
          const dispose = !activeHold && timeCreated < TIMESTAMP_TEN_MINUTES_AGO;

          if (dispose) {
            await file.delete(DELETE_OPTIONS);
            disposedCount++;
          } else if (activeHold) {
            skippedCount++;
          }
          
          processedCount++;

          return { file, metadata, disposed: dispose, skipped: activeHold };
        } catch (error) {
          // trap the error so other files still attempt to be deleted

          erroredCount++;
          processedCount++;

          const code = deleteResult.error.code || "unknown";
          const errorCountForCode = (errorsByCode[code] || 0) + 1;

          // Consider crashing function if same error code is encountered many times
          // if (errorCountForCode > 10) {
          //   throw new Error(`Error code "${code}" has been encountered more than 10 times`);
          // }

          errorCountsByCode[code] = errorCountForCode;

          return { file, metadata, disposed: false, skipped: true, error };
        }
      })
    );

    // Assemble an informative log message

    const skippedLogMessage = skippedCount === 0
      ? "No files had active holds"
      : `${skippedCount} of these were skipped due to active holds`;
    const errorLogMessage = erroredCount === 0
      ? "no errors were encountered"
      : `${erroredCount} were skipped due to errors (Error code breakdown: ${JSON.stringify(errorCountsByCode)})`;

    console.log(`${disposedCount}/${totalCount} temporary files were purged. ${skippedLogMessage} and ${errorLogMessage}.`);
  } catch (error) {
    const stats = JSON.stringify({
      disposed: disposedCount,
      skipped: skippedCount,
      errored: erroredCount,
      errorCodeCounts: errorCountsByCode
    });
    
    console.error(`Critical failure: ${error.message}. Encounted after processing ${processedCount}/${totalCount} files: ${stats}`);
  }
})