为临时导出设置保留策略

CloudflareBeginner
立即练习

介绍

导出服务应删除临时下载文件,但不能删除需要保留的文档。你将在一个全新的私有存储桶中,按前缀应用过期规则、存储类别转换规则和未完成上传清理规则,然后检查实际的策略和对象元数据。

请先完成对象管理和分片上传清理。此新 VM 使用 Node.js 22.22.0、Wrangler 4.131.1 和 AWS SDK 3.888.0。R2 必须已启用,并且你需要有权限配置新存储桶。请阅读 生命周期行为定价,包括 Infrequent Access 的最短存储时长和检索费用。本实验中的测试对象会保留在 Standard 存储类别中,并在本次会话期间显式删除。验收检查的是已应用的规则和当前元数据,而不是几天后的删除或转换结果。不需要域名。

创建私有文档存储桶

在此步骤中,为此 VM 授权并创建一个临时存储桶。设备授权用于确认你的学习账号。R2 存储桶管理使用另一个仅限该账号的 API token。

启动 Bash,以便使用下面的命令语法,然后进入准备好的项目目录并检查工具版本。保持此终端打开,这样资源名称变量会继续有效:

bash
cd /home/labex/project/r2-lab
export PATH="$PWD/.tools/node-v22.22.0-linux-x64/bin:$PATH"
node --version
npx wrangler --version

在你自己的浏览器中授权显示的设备代码。授予同意前,确认学习账号以及请求的账号读取和用户读取权限:

npx wrangler login --device --browser=false --scopes account:read user:read
npx wrangler whoami --json

确认输出中的 loggedIn: true。即使列表中只有一个账号,也要读取账号名称。将下面的 YOUR_ACCOUNT_ID 替换为该账号实际的 32 位 ID。openssl rand -hex 6 会生成 12 个随机十六进制字符,因此本实验不会与之前的运行结果发生冲突。下面的 here-document 会写入标准配置文件;Shell 会将变量值替换到文件中。

ACCOUNT_ID=YOUR_ACCOUNT_ID
RUN_ID=$(openssl rand -hex 6)
NAME="labex-c05-r07-$RUN_ID"
BUCKET="$NAME-docs"
cat > wrangler.jsonc <<JSON
{"name":"$NAME","account_id":"$ACCOUNT_ID","compatibility_date":"2026-07-30","r2_buckets":[{"binding":"DOCUMENTS","bucket_name":"$BUCKET"}]}
JSON

要管理存储桶,请打开 Cloudflare 个人资料中的 API Tokens 页面,并创建一个以本实验命名的自定义 token。授予 Account → Workers R2 Storage → Edit 权限,并将 Account Resources 限制为你保存的那个学习账号。设置较短的过期时间。不要包含其他账号或无关权限。此管理令牌用于管理存储桶,包括创建和删除。本步骤稍后会创建单独的对象令牌,将其权限限定为此存储桶,供 S3 SDK 操作对象。

将 token 复制一次到此隐藏 VM 提示符中。umask 077 会限制文件权限,使其仅对你的用户可读;read -s 会隐藏输入内容。该文件使用 Wrangler 的标准 token 变量,并且会被 Git 排除。

umask 077
read -r -s -p 'R2 management API token: ' R2_MANAGEMENT_TOKEN; printf '\n'
printf 'CLOUDFLARE_API_TOKEN=%s\n' "$R2_MANAGEMENT_TOKEN" > .env.management
unset R2_MANAGEMENT_TOKEN

仅在 R2 管理命令中使用 --env-file=.env.management;普通的 whoami 命令仍会检查此 VM 的设备授权。

--env-file 放在每条 Wrangler 命令的末尾,避免它的文件参数列表把命令名称也当作参数。创建每个存储桶后,如果 Wrangler 询问是否向配置添加绑定,请输入 n 并按 Enter。配置中已经包含所需的绑定。

npx wrangler r2 bucket create "$BUCKET" --env-file=.env.management

列出你的存储桶并找到刚刚生成的准确名称。其他存储桶属于其他工作内容,不要操作它们。

npx wrangler r2 bucket list --env-file=.env.management

在 Dashboard 中打开 Storage & databases → R2 → Overview,选择这个准确的存储桶,并检查其空对象列表。在该存储桶的设置中,保持公共开发 URL 和自定义域名禁用。Dashboard 中显示的存储桶名称可以确认资源身份;后面的下载检查则用于证明存储的字节内容。

S3 兼容 API 允许使用标准存储 SDK 访问 R2。它使用单独的访问密钥对,而不是 Wrangler 的设备 token。在 R2 Overview 中,使用 Account Details → API Tokens → Manage,然后创建一个以本实验生成的资源名称命名的 User API token。选择 Object Read & Write,将其限制为这个新建的准确存储桶;如果表单提供短期过期选项,请选择该选项。不要选择所有存储桶,也不要选择 Admin 权限。在保存这次性密钥之前,请保留此 token 页面处于可用状态。

在 VM 中使用下面的 Bash 提示符。read -s 会隐藏输入内容;umask 077 会让凭据文件仅对你的用户可读。这些名称是 AWS SDK 使用的标准环境变量。分别将 Access Key ID 和 Secret Access Key 粘贴到对应提示符中,然后按 Enter。不要粘贴通用 API token 的值。

umask 077
read -r -s -p 'Access Key ID: ' AWS_ACCESS_KEY_ID; printf '\n'
read -r -s -p 'Secret Access Key: ' AWS_SECRET_ACCESS_KEY; printf '\n'
printf 'AWS_ACCESS_KEY_ID=%s\nAWS_SECRET_ACCESS_KEY=%s\n' "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY" > .env.s3
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY

写入一个可复用的标准 SDK 客户端。SDK 要求提供区域字符串;R2 使用 auto。读取现有配置可以确保 CLI 和 SDK 操作都指向同一个账号和存储桶。

cat > storage.mjs <<'JS'
import { S3Client } from "@aws-sdk/client-s3";
import { readFileSync } from "node:fs";
const config = JSON.parse(readFileSync("wrangler.jsonc", "utf8"));
export const Bucket = config.r2_buckets[0].bucket_name;
export const s3 = new S3Client({
  region: "auto",
  endpoint: `https://${config.account_id}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
  }
});
JS

应用按前缀限定的生命周期规则

在此步骤中,配置一个 生命周期策略,即 R2 会随着对象存放时间增长而应用的一组存储操作。临时导出文件应过期,而需要保留的手册应不受这些规则影响。存储类别转换会改变计费和访问类别,但不会删除对象。

此临时存储桶将使用两条规则:temporary/ 前缀下的对象在两天后过期,该前缀下未完成的上传在一天后中止;archive/ 前缀下的对象在 30 天后转换为 Infrequent Access。没有规则会匹配 retained/

API 使用秒表示时间:一天是 86,400 秒。使用带引号的 here-document 写入策略,以保留其中的 JSON:

cat > lifecycle.json <<'JSON'
{
  "rules": [
    {
      "id": "temporary-exports",
      "enabled": true,
      "conditions": {
        "prefix": "temporary/"
      },
      "deleteObjectsTransition": {
        "condition": {
          "type": "Age",
          "maxAge": 172800
        }
      },
      "abortMultipartUploadsTransition": {
        "condition": {
          "type": "Age",
          "maxAge": 86400
        }
      }
    },
    {
      "id": "archive-transition",
      "enabled": true,
      "conditions": {
        "prefix": "archive/"
      },
      "storageClassTransitions": [
        {
          "condition": {
            "type": "Age",
            "maxAge": 2592000
          },
          "storageClass": "InfrequentAccess"
        }
      ]
    }
  ]
}
JSON
npx wrangler r2 bucket lifecycle set "$BUCKET" --file lifecycle.json --env-file=.env.management
npx wrangler r2 bucket lifecycle list "$BUCKET" --env-file=.env.management

set 命令会替换整个策略,因此请确认操作对象只有这个新建的实验存储桶。确认输出中包含准确的两个前缀、启用状态和时间。不要将此替换操作应用到现有的应用存储桶。在 Dashboard 中打开同一存储桶的 Settings → Object Lifecycle Rules,检查操作内容,但不要修改它们。

费用边界: Infrequent Access 会产生检索费用,并有最短存储时长。本实验只配置未来的转换,并在清理时删除新建的 Standard 对象。你不会等待 30 天,不会强制执行转换,也不能声称转换已经实际发生。

此真实 Dashboard 截图展示已配置的未来操作:2 天后删除 temporary/ 对象,1 天后中止该前缀下未完成的上传,30 天后将 archive/ 对象转为 Infrequent Access。截图不表示等待时间已经结束或操作已经执行。下一步检查对象的当前元数据;retained/ 不属于这两个前缀。

已启用的前缀生命周期规则

检查新应用规则的过期元数据

在此步骤中,在应用策略之后上传新对象。R2 文档说明,新对象会在 x-amz-expiration 中反映适用的过期信息;现有对象在规则变更后可能需要更长时间才会反映新规则。SDK 会将此标头公开为 Expiration

cat > seed.mjs <<'JS'
import { PutObjectCommand, HeadObjectCommand } from "@aws-sdk/client-s3";
import { readFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
for (const Key of ["temporary/export.txt", "archive/export.txt"]) {
  await s3.send(new PutObjectCommand({ Bucket, Key, Body: readFileSync("document.txt"), ContentType: "text/plain" }));
}
await s3.send(new PutObjectCommand({ Bucket, Key: "retained/handbook.txt", Body: readFileSync("retained.txt"), ContentType: "text/plain" }));
for (const Key of ["temporary/export.txt", "archive/export.txt", "retained/handbook.txt"]) {
  const head = await s3.send(new HeadObjectCommand({ Bucket, Key }));
  console.log({ key: Key, expiration: head.Expiration || "none", storageClass: head.StorageClass || "STANDARD" });
}
JS
node --env-file=.env.s3 seed.mjs

确认 temporary/export.txt 有过期日期,保留的手册没有删除过期信息,并且新建的归档对象仍使用 Standard 存储类别。未来转换由远程规则证明,而不是由当前的 IA 存储类别证明。如果预期的新对象过期元数据缺失,请检查已应用的前缀和策略;不要将其称为成功的过期检查。

下载保留的对象,并比较其原始字节内容:

npx wrangler r2 object get "$BUCKET/retained/handbook.txt" --remote --file retained-download.txt --env-file=.env.management
cmp retained.txt retained-download.txt

策略读取成功且保留对象可读取,即可证明本实验范围内的结果。实际的生命周期删除是异步操作,可能在名义过期时间之后发生;本实验不会评估数小时后的删除事件。

显式清空实验存储

在此步骤中,立即删除现在创建的三个测试对象,而不是依赖它们未来的生命周期操作。本实验没有创建未完成的上传,但仍要列出该清单:仅查看对象列表无法证明存储桶中没有未完成的分片。

cat > empty.mjs <<'JS'
import { DeleteObjectCommand, ListObjectsV2Command, ListMultipartUploadsCommand } from "@aws-sdk/client-s3";
import { s3, Bucket } from "./storage.mjs";
for (const Key of ["temporary/export.txt", "archive/export.txt", "retained/handbook.txt"]) await s3.send(new DeleteObjectCommand({ Bucket, Key }));
const objects = await s3.send(new ListObjectsV2Command({ Bucket }));
const uploads = await s3.send(new ListMultipartUploadsCommand({ Bucket }));
console.log("Objects:", objects.Contents || []);
console.log("Incomplete uploads:", uploads.Uploads || []);
JS
node --env-file=.env.s3 empty.mjs

确认对象数组和未完成上传数组都为空。如果你在实验过程中创建了分片上传会话,请使用上一个实验中的中止操作,并提供那个确切的自有对象键和上传 ID,然后重复执行这两个只读列表操作。绝不要静默忽略列表请求失败。

删除空存储桶及其策略

在此步骤中,确认对象和上传检查通过后,删除你创建的存储桶。策略属于存储桶配置,会随存储桶一起消失。

npx wrangler r2 bucket delete "$BUCKET" --env-file=.env.management
npx wrangler r2 bucket list --env-file=.env.management

确认生成的准确名称。要求在一次成功的已认证列表中找不到该名称。撤销管理凭据前,先运行平台删除检查。

撤销实验凭据并退出登录

在此步骤中,关闭本实验留下的访问权限。在 R2 API Tokens 页面中,只撤销以本实验命名的对象 token。在个人资料的 API Tokens 页面中,撤销为本实验创建的独立 R2 管理 token。删除存储桶不会撤销 token,Wrangler logout 也不会撤销 S3 凭据。

撤销后,删除本地凭据文件并退出此 VM:

rm .env.s3 .env.management
npx wrangler logout

检查结构化身份信息。退出登录后出现非零状态码是预期行为:

npx wrangler whoami --json || true

确认 loggedIn: false;保留普通的 Dashboard 登录状态。平台会检查本地凭据文件是否已删除以及 Wrangler 是否已退出登录。两个 token 的撤销是本实验中的手动 Dashboard 检查点,不会根据文件删除操作推断其完成情况。

总结

应用按范围限定的过期规则、未来存储转换规则和分片上传清理规则,检查当前元数据,保留指定数据并显式完成清理。