TUTORIAL

Cloudflare R2 对象存储开通与 API 管理完全教程

返回教程中心
  • 共 35 步
  • 进阶
  • 30

Cloudflare R2 对象存储开通与 API 管理完全教程

从注册开通到 API 管理的完整中文教程。控制台按中文界面讲解,关键接口附可直接改用的示例代码。

项 值
难度 入门 → 进阶
接口 S3 兼容
需绑卡 仅身份验证
配套示例 Python / Node.js / 命令行

对象存储是网站、备份与 AI 数据的地基。Cloudflare R2 把这套能力用 S3 兼容接口交到你手上:每月免费 10 GB 存储、100 万次写入、1000 万次读取,出站流量分文不收。本教程从注册开通、创建存储桶、签发 API 令牌,一路写到用代码和命令行上传下载,并处理好公开访问、跨域与临时链接。看完这一篇,你就能把文件稳稳放进云端。

STEPS

操作步骤

照着做即可。做完一步就勾上,勾过的步骤会变灰,下次回来还是这个进度。

0 / 35 步已完成

  1. R2 是什么,凭什么选它开通前的准备清单在控制台开通 R2创建第一个存储桶签发 API 令牌(核心一步)四种方式验证连通性公开访问:r2.dev 与自定义域名配置跨域 CORS临时授权:预签名 URL生命周期规则与自动清理常见报错速查表安全清单与命令速查

  2. 先建立正确的概念,后面才不会踩坑。
    对象存储(Object Storage)和普通服务器磁盘不是一回事。它没有「文件夹路径」的概念,只有桶(Bucket)和对象(Object)两个概念:桶是一个命名空间,对象是躺在桶里的那一份份文件,通过一个字符串键(Key)来取。你把图片、视频、日志、备份包、模型权重放进去,然后用 HTTP 或 S3 接口按需取用。
    R2 是 Cloudflare 提供的对象存储服务,兼容 Amazon S3 的 API。真正的差异点只有一个,但足够致命:出站流量(Egress)不收费。S3 按每 GB 约 0.09 美元收取流量费,一个下载量大的站点,光流量费一年就能把存储费吃掉几倍。R2 把这一项直接归零,并且把它铺在全球 CDN 节点上。

    图 1 R2 的读写通道总览。写入侧最终都收敛到同一个桶,读取侧要不要走 CDN,取决于你有没有绑定自定义域名。

    1. R2 官方定价按存储量 + 两类操作次数计费,流量费为零。操作分两类:会改变状态、偏贵的叫 Class A(写入、列举、列举桶、复制对象、分片上传等),读多写少的叫 Class B(下载、查询元数据、查询桶位置等)。删除对象是免费的。
      计费项标准存储 Standard低频访问 Infrequent Access说明存储$0.015 / GB-月$0.01 / GB-月GB-月 = 按天取峰值后月内平均Class A 操作$4.50 / 百万次$9.00 / 百万次上传、列举、复制等写类操作Class B 操作$0.36 / 百万次$0.90 / 百万次下载、HEAD 查询等读类操作数据取回免费$0.01 / GB低频访问读取时额外收出站流量免费免费所有存储类别都不收流量费最低存储时长无30 天提前删除仍按 30 天计费
      免费额度按月计算,只适用于标准存储,低频访问不享受免费额度:
      额度每月可用10 GB存储 / 月 GB-month100 万Class A 操作 / 月 写、列举、复制1000 万Class B 操作 / 月 下载、查询¥0出站流量 不限量

      图 2 两张柱状图说明同一件事:R2 在存储和小操作上只是略便宜,真正的差距在出站流量那一栏——它是零。
      说明:关于计费取整
      Cloudflare 按最小计费单位向上取整。你做了 1,000,001 次 A 类操作,就按 200 万次计费;存了 1.1 GB-月,按 2 GB-月算。个人用量基本碰不到,但如果你在做成本估算,别按理想值算。
      要点:未授权的请求不计费
      如果请求方没有权限,Cloudflare 返回 401 Unauthorized,这类请求不会计入你的操作次数。也就是说密钥泄露被人乱刷,官方文档明确说明不会因此向你收费——但仍要第一时间吊销重发。

  3. 三样东西,提前备好后面不返工。
    一个 Cloudflare 账号——注册邮箱即可。若你的域名已托管在 Cloudflare,后面的自定义域名环节会顺很多。 一张支持国际支付的信用卡(Visa / Mastercard,或绑定国内储蓄卡的 PayPal)。这是身份验证,不是立即扣费:通常会有一笔约 1 美元的预授权,验证通过后原路退回。没有外币卡可用虚拟信用卡平台,但优先用自己长期在用的双币卡,避免后续超额时扣款失败导致服务受限。 一个打算用来访问文件的域名(可选,但强烈建议)。要启用自定义域名,域名必须已作为 zone 添加进同一个 Cloudflare 账号;域名不在 Cloudflare 的话,需要先做一次 CNAME 部分接入。
    关于本教程的界面语言:正文按 Cloudflare 控制台的中文界面描述,括号内附英文原名,方便对照官方文档或在英文界面下定位。不同版本的译文可能略有出入,以实际显示为准。

  4. R2 是独立订阅项,需要在控制台走一次开通流程。
    登录 Cloudflare 控制台。左侧菜单找到 存储和数据库(Storage & databases)→ 进入 R2 对象存储(R2 Object Storage)。首次进入会看到 R2 总览页(Overview),页面提示尚未订阅。按提示点击添加 R2 订阅,填入付款方式完成结账。回到 R2 总览页,此时应能看到「存储桶」列表区域和「管理 R2 API 令牌」入口。
    路径:存储和数据库 → R2 对象存储 → 总览 Overview

    图 3 左侧导航里的「存储和数据库 → R2 对象存储」。首次进入需要先订阅;开通后「创建存储桶」和「管理 R2 API 令牌」两个按钮才会出现。
    注意:别把账户 ID 当成密钥
    账户 ID(32 位十六进制)不是机密,可以出现在代码、URL、日志里。它只是拼 S3 端点域名用的——https://<账户ID>.r2.cloudflarestorage.com。真正的机密只有后面那对密钥。

  5. 桶是所有文件的容器,命名规则要先看明白。
    在 R2 总览页(概览 Overview)点击 创建存储桶(Create bucket)。填写存储桶名称:只能用小写英文字母、数字和连字符,不能有大写、空格、下划线、中文;且全网唯一——所有 Cloudflare 用户共用一套命名空间,名字撞了只能换(提示 Already taken)。名字要能表达用途,比如 hztuan-blog-images。选择位置提示(Location Hint):写「自动」(Automatic)即可,它只是告诉 Cloudflare 你的访问者主要在哪,不是硬性存储位置。选择默认存储类别:选标准(Standard)。只有标准存储享受每月免费额度,低频访问的 A/B 操作单价更高,还有 30 天最低存储时长和取回费。点击 创建存储桶,等页面跳转到桶详情页。

    图 4 创建存储桶表单。三个字段里只有「存储桶名称」需要动脑,位置和存储类别按推荐值走即可。

    1. 标签用途对象 Objects浏览、上传、下载、删除文件。可直接拖拽上传,适合小批量人工操作。设置 Settings公共访问、自定义域名、CORS、生命周期规则都在这里,也是本教程后续章节的主战场。指标 Metrics存储用量、操作次数、出站流量随时间变化,用来看免费额度还剩多少。

    2. 如果偏好终端,Wrangler 不用 API 密钥,直接用你的 Cloudflare 账号登录。
      # 安装并登录(登录会打开浏览器授权)
      npm i -D wrangler
      npx wrangler login

      # 创建、查看、删除存储桶
      npx wrangler r2 bucket create hztuan-blog-images
      npx wrangler r2 bucket list
      npx wrangler r2 bucket delete hztuan-blog-images

      # 上传 / 下载单个对象
      npx wrangler r2 object put hztuan-blog-images/hello.txt --file ./hello.txt
      npx wrangler r2 object get hztuan-blog-images/hello.txt --file ./downloaded.txt

      如果提示选择账号,说明这个登录态下有多个 Cloudflare 账号,按提示选一个即可。

  6. 所有代码都要靠这对密钥跟 R2 说话。它只显示一次。
    R2 提供两类令牌,务必分清:
    对象读取和写入 Object Read & Write管理 / 行政 Admin Read & Write能干什么读写桶里的对象:上传、下载、删除文件管理桶本身:创建/删除桶、改生命周期、改 CORS、查用量适用绝大多数场景(应用读写文件)只在需要程序化管理桶时签,且范围要收窄工作方式产出 Access Key ID + Secret Access Key,供 S3 兼容 SDK 使用用于调用 Cloudflare REST API / Wrangler 类的桶级操作
    重要:最容易踩的坑
    很多教程里的报错 InvalidAccessKeyId,根源是拿了 Cloudflare 的通用 API 令牌(在「我的个人资料 → API 令牌」里创建的那种)去连 R2。R2 只认自己签发的 R2 API 令牌。两者长得像,但不是一回事。

    1. 回到 R2 对象存储 总览页,点击 管理 R2 API 令牌(Manage R2 API Tokens)。点击 创建 API 令牌(Create API token)。如果你要的是管理权限,这里会区分「创建用户 API 令牌」和「创建账户 API 令牌」,按需选。填写令牌名称,写清楚用途和归属,例如 R2-hztuan-blog-images-prod。以后要吊销、轮换,一眼就知道动的是哪一把。权限:选 对象读取和写入(Object Read & Write)。指定存储桶:选「应用到特定存储桶」(Apply to specific buckets only),然后只勾选你刚建的那个桶。这一步是权限收敛的关键——一个桶一把密钥,出事只影响一个桶。TTL / 有效期:长期运行的程序设「无过期」;测试用途设一个较短期限,到期自动失效更安全。点击 创建 API 令牌。

      图 5 令牌配置界面。右半边说明这一步的价值:勾选「应用到特定存储桶」之后,这把密钥就再也碰不到其他桶了。

    2. 创建成功后,页面会一次性展示三项内容。这是唯一一次能读到 Secret Access Key 的机会,关掉页面就只能重新签发一把。

      图 6 创建成功页。三项内容里只有密钥是一次性的,其余随时可查。这一页建议立刻截图前两项存进密码管理器。
      要点:立刻可以做的三件事
      把 Access Key ID / Secret Access Key 存进密码管理器或系统的环境变量。
      在 R2 总览页「账户详细信息」里复制账户 ID,拼出完整的 S3 端点。
      在代码里用环境变量读取,不要把密钥字面量写进源码。
      # 临时在当前终端设置(关闭终端即失效)
      export R2_ACCESS_KEY_ID="8f2a1c4b9d7e6f0a3b5c8d1e2f4a7b6c"
      export R2_SECRET_ACCESS_KEY="粘贴你的 Secret Access Key"
      export R2_ENDPOINT="https://<账户ID>.r2.cloudflarestorage.com"
      export R2_BUCKET="hztuan-blog-images"

      Windows PowerShell 用 $env:R2_ACCESS_KEY_ID = "…" 的写法,语法不带 export。

  7. 密钥拿到手,先跑通一次上传下载,再往上搭业务。

    1. 进入桶的 对象 Objects 标签,直接拖一个 txt 或图片上去。看到文件出现在列表里,就说明存储桶本身正常。

    2. 已有 S3 使用经验的话,AWS CLI 迁移成本最低。所有命令都要带 --endpoint-url,区域填 auto。
      # 配置一个独立 profile:region 必须写 auto,R2 不用它但 SDK 强制要求
      aws configure --profile r2
      # AWS Access Key ID → 你的 Access Key ID
      # AWS Secret Access Key → 你的 Secret Access Key
      # Default region name → auto

      # 上传 / 下载 / 列举
      aws s3 cp ./photo.png s3://$R2_BUCKET/photo.png \
      --profile r2 --endpoint-url "$R2_ENDPOINT" --acl public-read
      aws s3 ls s3://$R2_BUCKET/ --profile r2 --endpoint-url "$R2_ENDPOINT"
      aws s3 cp s3://$R2_BUCKET/photo.png ./back.png \
      --profile r2 --endpoint-url "$R2_ENDPOINT"

      # 整个目录同步(迁移历史文件时常用)
      aws s3 sync ./uploads s3://$R2_BUCKET/uploads/ \
      --profile r2 --endpoint-url "$R2_ENDPOINT" --cache-control "max-age=31536000,public"

      注意:关于 --acl public-read
      R2 不支持 ACL 机制,这个参数会被忽略或报错。R2 的公开访问只靠两种方式实现:桶设置里开启 r2.dev,或绑定自定义域名。不要试图在 SDK 里逐个对象设置 ACL。

    3. pip install boto3

      import os
      import boto3

      s3 = boto3.client(
      service_name="s3",
      endpoint_url=os.environ["R2_ENDPOINT"], # https://<账户ID>.r2.cloudflarestorage.com
      aws_access_key_id=os.environ["R2_ACCESS_KEY_ID"],
      aws_secret_access_key=os.environ["R2_SECRET_ACCESS_KEY"],
      region_name="auto", # boto3 强制要求,实际不使用
      )

      # 上传
      s3.upload_file("./photo.png", "hztuan-blog-images", "2026/10/photo.png")

      # 下载
      s3.download_file("hztuan-blog-images", "2026/10/photo.png", "./back.png")

      # 列举
      for obj in s3.list_objects_v2(Bucket="hztuan-blog-images").get("Contents", []):
      print(obj["Key"], obj["Size"])

      # 删除(DeleteObject 本身不计费)
      s3.delete_object(Bucket="hztuan-blog-images", Key="2026/10/photo.png")

    4. npm install @aws-sdk/client-s3

      import { S3Client, PutObjectCommand, GetObjectCommand, ListObjectsV2Command }
      from "@aws-sdk/client-s3";

      const s3 = new S3Client({
      region: "auto", // 必填,R2 不使用
      endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
      credentials: {
      accessKeyId: process.env.R2_ACCESS_KEY_ID,
      secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
      },
      });

      // 上传
      await s3.send(new PutObjectCommand({
      Bucket: "hztuan-blog-images",
      Key: "2026/10/photo.png",
      Body: "Hello, R2!",
      ContentType: "image/png",
      }));

      // 下载并转成字符串
      const res = await s3.send(new GetObjectCommand({
      Bucket: "hztuan-blog-images", Key: "2026/10/photo.png",
      }));
      console.log("内容:", await res.Body.transformToString());

      // 列举
      const list = await s3.send(new ListObjectsV2Command({
      Bucket: "hztuan-blog-images",
      }));
      console.log(list.Contents?.map(o => o.Key));

  8. 桶默认是私有的。要让外面访问,必须显式打开其中一种。

    1. 进入桶的 设置 Settings 标签。找到 公共开发 URL(Public Development URL)区域,点击 启用(Enable)。弹出「允许公开访问?」确认框时,手动输入 allow(不是点按钮,是打字),再点 允许。回到设置页确认「公共 URL 访问」显示为 允许(Allowed)。

      图 7 桶设置页里的两个公开访问入口。右下角是启用 r2.dev 时的确认框——需要手动输入 allow 才会放行。

    2. 确认你的域名已经作为 zone 添加进同一个 Cloudflare 账号(如果还没托管,先做一次 CNAME 部分接入)。进入桶的 设置 Settings → 找到 自定义域(Custom Domains)→ 点击 添加。输入想用的子域名,例如 img.hztuan.com,点 继续。页面会展示即将新增的 DNS 记录,确认后点 连接域(Connect Domain)。域名已在 Cloudflare 时,DNS 记录会自动创建。状态从正在初始化(Initializing)变为有效(Active)需要几分钟,刷新页面查看。若卡住,点桶名旁边的 … → 重试连接。
      重要:两条硬性禁止
      不要把域名的 CNAME 指向 *.r2.dev。官方明确说明这是不支持的访问路径,可靠性和性能都没有保证。
      用了 WAF 或 Cloudflare Access 之后,必须把 r2.dev 关掉。否则你的桶仍然能通过 r2.dev 被匿名访问,你的防护等于白做。
      对比项自定义域名r2.dev 公共开发 URL适用场景生产开发、演示、临时测试需要域名是,且须在同一 Cloudflare 账号否速率限制无有(Cloudflare 托管子域)缓存加速可配智能分层缓存不可用WAF / 访问控制可配(含 HMAC 校验)不可用Bot 管理可配不可用支持预签名 URL不支持不支持(预签名只在 S3 端点上有效)
      补充:公开桶在子域根路径下不能列出桶内文件清单,只能按确切的对象键访问。匿名访问者不能通过打开 https://img.hztuan.com/ 来浏览整个桶。

  9. 浏览器要从你的网页直接读写 R2 文件,就必须有 CORS 策略。
    CORS(跨源资源共享)是浏览器的一套同源限制机制:网页 A 默认不能读取网页 B 的资源,除非 B 明确授权。R2 里的文件如果要给外部网站引用(做图床、做附件直传),这一步不做,浏览器控制台会直接报跨域错误——而且服务器返回 200,文件也确实传上去了,只是前端读不到结果,这种「明明成功却报错」的现象十有八九就是 CORS 缺失。

    1. 公开桶 + CORS:桶设为公开,浏览器可读(下载)任何对象。适合放在公开博客上的图片。预签名 URL + CORS:桶保持私有,只有拿到那条限时链接的人能操作。浏览器侧依然要配 CORS——预签名解决了鉴权,没解决同源限制。

    2. 进入桶的 设置 Settings → CORS 策略(CORS Policy)→ 添加 CORS 策略。切到 JSON 标签页,把策略粘进去(不是填表单,是直接写 JSON)。点 保存。策略会显示在设置页上。
      [
      {
      "AllowedOrigins": ["https://www.hztuan.com", "http://localhost:3000"],
      "AllowedMethods": ["GET", "PUT", "HEAD"],
      "AllowedHeaders": ["Content-Type", "x-amz-*"],
      "ExposeHeaders": ["ETag"],
      "MaxAgeSeconds": 3600
      }
      ]

      图 8 左边是策略编辑区(JSON 模式),右边是浏览器实际的两段式请求过程。改完策略后如果内容有缓存,记得清一次缓存。

    3. 字段对应的响应头怎么填AllowedOriginsAccess-Control-Allow-Origin只能是 * 或 协议://主机[:端口]。不能带路径,带斜杠结尾就非法。多端口要逐个列出。AllowedMethodsAccess-Control-Allow-Methods按实际用到的写:GET/HEAD(读)、PUT(传)、DELETE(删)。AllowedHeadersAccess-Control-Allow-Headers客户端会带的自定义头都要列,比如 Content-Type、校验和、缓存控制、自定义元数据。ExposeHeadersAccess-Control-Expose-Headers想让 JS 读到 ETag、Content-Length、cf-cache-status 就写上。MaxAgeSeconds预检结果缓存填 3600 之类,减少预检往返。浏览器自身上限可能只有 2 小时。

    4. 别猜,打开浏览器开发者工具的网络面板,看那条被拦的请求,逐项核对:
      AllowedOrigins 里有没有这条请求的 Origin(完全一致,带端口)?AllowedMethods 里有没有被拦的那个方法?AllowedHeaders 里有没有客户端实际发出的头?
      注意:用 curl 测不出来这件事
      R2 只在请求带合法 Origin 头时才返回 CORS 响应头。直接 curl 裸请求,或者不带 Origin 去测,都会「看起来没有 CORS 头」——那是正常现象,不是配置错误。另外,如果桶已经走自定义域名在跑流量,改完 CORS 后已缓存的旧文件不会立刻带上新头,需要手动清一次缓存。
      也可以用 Wrangler 从命令行配置(适合批量环境):
      # cors.json
      {
      "rules": [
      {
      "allowed": {
      "origins": ["https://www.hztuan.com"],
      "methods": ["GET"]
      }
      }
      ]
      }

      # 应用并确认
      npx wrangler r2 bucket cors set hztuan-blog-images --file cors.json
      npx wrangler r2 bucket cors list hztuan-blog-images

  10. 让客户端直接上传到 R2,密钥全程不落地。
    如果让浏览器直接上传大文件,标准做法是:文件不经过你的服务器。客户端先向你的后端要一条预签名 URL(Presigned URL),后端用密钥签好这条链接(含签名、权限、过期时间)返回给前端,前端拿到后直接 PUT 到 R2。好处很实在:大文件不进你的服务器,不占带宽,也不暴露密钥。
    预签名 URL 本质是一个持有者令牌:谁拿到它,谁就能在有效期内执行那一个操作。三个要素:
    资源标识:账户 ID + 桶名 + 对象键允许的操作:GET/PUT/HEAD/DELETE。不支持 HTML 表单式的 POST 分片上传有效期:1 秒 ~ 7 天(604,800 秒)

    图 9 签名是本地计算出来的,不需要向 R2 发起请求。签好后前端拿着链接直传,你的服务器完全不接触文件内容。

    1. npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

      import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
      import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

      const S3 = new S3Client({
      region: "auto",
      endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
      credentials: {
      accessKeyId: process.env.R2_ACCESS_KEY_ID,
      secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
      },
      });

      // 上传用:写死 ContentType,就把上传类型限制住了
      const putUrl = await getSignedUrl(
      S3,
      new PutObjectCommand({
      Bucket: "hztuan-blog-images",
      Key: "uploads/2026/photo.png",
      ContentType: "image/png",
      }),
      { expiresIn: 3600 }, // 1 小时
      );

      // 下载用:限时分享链接
      const getUrl = await getSignedUrl(
      S3,
      new GetObjectCommand({ Bucket: "hztuan-blog-images", Key: "2026/10/report.pdf" }),
      { expiresIn: 600 }, // 10 分钟
      );

      res.json({ uploadUrl: putUrl, downloadUrl: getUrl });

    2. import os, boto3

      s3 = boto3.client(
      service_name="s3",
      endpoint_url=os.environ["R2_ENDPOINT"],
      aws_access_key_id=os.environ["R2_ACCESS_KEY_ID"],
      aws_secret_access_key=os.environ["R2_SECRET_ACCESS_KEY"],
      region_name="auto",
      )

      url = s3.generate_presigned_url(
      ClientMethod="put_object",
      Params={"Bucket": "hztuan-blog-images", "Key": "uploads/a.png", "ContentType": "image/png"},
      ExpiresIn=3600,
      )

    3. # 用 PUT 预签名链接上传
      curl -X PUT "https://hztuan-blog-images.<账户ID>.r2.cloudflarestorage.com/uploads/a.png?X-Amz-..." \
      -H "Content-Type: image/png" --data-binary @a.png

      # 用 GET 预签名链接下载
      curl -O "https://hztuan-blog-images.<账户ID>.r2.cloudflarestorage.com/photos/cat.png?X-Amz-..."

      // 前端:拿到链接直接传,全程不碰密钥
      const { uploadUrl } = await (await fetch("/api/r2/presign", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ key: "uploads/a.png", type: "image/png" }),
      })).json();

      await fetch(uploadUrl, {
      method: "PUT",
      headers: { "Content-Type": "image/png" }, // 必须与签名时一致
      body: file,
      });

    4. 重要:1. Content-Type 必须完全一致
      签名会把 Content-Type 算进去。客户端发了不一样的类型,直接 403 / SignatureDoesNotMatch。这是「签名明明没错却上传失败」的头号原因。
      重要:2. 只能在 S3 端点上用
      预签名 URL 走的是 <账户ID>.r2.cloudflarestorage.com,对自定义域名无效。如果你需要「用自定义域名访问 + 还要鉴权」,那是另一个方案:WAF 的 HMAC 校验功能(需 Pro 或以上套餐)。
      重要:3. 链接就是凭证,别乱发
      有效期内任何持有该链接的人都能执行那个操作。同一条链接在过期前可以重复使用。敏感操作请把有效期压到分钟级。
      重要:4. 过期后前端读不到错误详情
      预签名过期返回 403 ExpiredRequest,而 R2 不会在过期响应里带 CORS 头,所以浏览器 JS 连错误信息都读不出来。解决办法是在过期前刷新链接,或者让请求先经过你自己的服务器。
      签名参数(X-Amz-Algorithm、X-Amz-Credential、X-Amz-Date、X-Amz-Expires、X-Amz-Signature)任何一处被改动,都会返回 403 / SignatureDoesNotMatch。

  11. 让桶自己处理过期数据和存储类别降级,不用写定时脚本。
    生命周期规则是一组作用于对象全生命周期的动作。最常用的两个场景:临时上传目录(tmp/)里的文件 7 天后自动删;日志和备份超过 90 天转到低频访问或直接删除。
    进入桶的 设置 Settings。找到 对象生命周期规则(Object Lifecycle Rules)→ 添加规则。填写规则:可指定前缀(只对某些键生效)、过期天数、是否转换存储类别。点 保存更改。

    图 10 左侧是规则表单,右侧是对象在桶里的完整轨迹。删除发生后对象就不再计费,但删除动作通常在规则生效后 24 小时内执行。
    说明:几条行为细节
    对象通常在 x-amz-expiration 时间点后的 24 小时内被移除,不是准点删除。
    新规则对刚上传的对象立即生效,但存量对象会有延迟,对象多时可能超过 24 小时。迁移期间可能仍看到旧规则的痕迹。
    桶自带一条默认规则:未完成的分片上传在发起 7 天后自动中止,不用自己配。
    标准 → 低频访问的转换会产生一次 Class A 操作费,别在高频访问的文件上乱设。
    规则上限 1000 条;修改生命周期属于桶级操作,需要带 Workers R2 Storage Write 权限组的令牌。
    # Wrangler 管理生命周期
    npx wrangler r2 bucket lifecycle add hztuan-blog-images
    npx wrangler r2 bucket lifecycle list hztuan-blog-images
    npx wrangler r2 bucket lifecycle remove hztuan-blog-images --id "规则ID"

    # 用整份 JSON 覆盖式配置(便于纳入版本管理)
    npx wrangler r2 bucket lifecycle set hztuan-blog-images --file lifecycle.json

  12. 按报错信息反查,通常三分钟内能定位。
    现象 / 错误码真正的原因怎么修InvalidAccessKeyId用了 Cloudflare 通用 API 令牌,或把账户 ID 当成了 Access Key去 R2 总览页重新签发 R2 API 令牌;确认 Access Key ID 是一串十六进制,不是 32 位账户 IDSignatureDoesNotMatch / 403密钥错、Content-Type 与签名时不一致、或改动了 URL 里的签名参数核对 Content-Type;重新生成链接;不要手动编辑带签名的 URL公开 URL 返回 403自定义域名没连上、对象键写错,或 r2.dev 开关没打开检查桶设置里「公共 URL 访问」是否为「允许」;确认域名状态已变为「有效」403 ExpiredRequest预签名 URL 已过期刷新链接;敏感操作把有效期压到分钟级上传成功但前端报错CORS 策略缺失或 AllowedOrigins 写错按 第 8 章的方法在网络面板里逐项核对预签名 URL 在自定义域名上 404预签名只支持 S3 API 端点改用 <账户ID>.r2.cloudflarestorage.com,或用 WAF HMAC 校验(Pro 起)代码报 region 错region 填了 ap-east-1 之类真实区域码一律改成 auto控制台报 Already taken桶名全网已被占用加随机后缀,如 hztuan-images-8f2a自定义域名一直「正在初始化」DNS 未生效;或域名不在同一账号;或域名是企业套餐且被 zone hold 锁住等 DNS 传播;用 dig CNAME 核对;企业套餐需先释放 zone hold改了 CORS 但响应头没变旧内容还在缓存里对该主机名执行一次缓存清除代码报缺少 --acl 相关的错照搬了 S3 的 ACL 用法R2 不支持 ACL,删掉相关参数,改用公共桶 / 自定义域名 / 预签名

  13. 收尾部分。打印出来贴在手边也行。

    1. 密钥只存在环境变量或密钥管理服务里,仓库里没有;提交历史里也搜不到(git log -p | grep -i secret)。 每个桶一把独立令牌,权限限定「对象读取和写入 + 仅此桶」。 对外一律用自定义域名,r2.dev 只在本地调试时开着,用完就关。 需要鉴权的下载走预签名 URL,有效期压到实际需要的时长。 前端不做任何鉴权,关键操作用后端接口转发,密钥不出服务器。 临时目录(tmp/、用户上传中转)配了生命周期规则自动清理。 有定期备份需求的桶,配置了生命周期转低频访问,而不是让标准存储无限增长。 能定期查一次「指标」页,看 Class A / B 操作量有没有异常暴涨。

    2. # 桶
      npx wrangler r2 bucket create <BUCKET>
      npx wrangler r2 bucket list
      npx wrangler r2 bucket delete <BUCKET>

      # 对象
      npx wrangler r2 object put <BUCKET>/<KEY> --file ./local.txt
      npx wrangler r2 object get <BUCKET>/<KEY> --file ./out.txt
      npx wrangler r2 object delete <BUCKET>/<KEY>

      # CORS
      npx wrangler r2 bucket cors set <BUCKET> --file cors.json
      npx wrangler r2 bucket cors list <BUCKET>

      # 生命周期
      npx wrangler r2 bucket lifecycle add <BUCKET>
      npx wrangler r2 bucket lifecycle list <BUCKET>
      npx wrangler r2 bucket lifecycle set <BUCKET> --file lifecycle.json
      npx wrangler r2 bucket lifecycle remove <BUCKET> --id <RULE_ID>

    3. 用途桶设置访问方式个人图床 / 附件标准存储;开 CORS(GET);tmp/ 加 7 天清理绑定自定义域名公开;或桶私有 + 预签名上传网站备份标准存储;backup/ 前缀 90 天转低频保持私有,服务器上用 rclone 定时 sync,下载走预签名用户直传桶私有;CORS 允许 PUT 与 Content-Type;开启 ETag 暴露后端签发预签名 PUT 链接,浏览器直传
      要点:一条经验
      建桶时就想清楚它的访问模式:确定要公开的(图片、静态资源)用自定义域名公开并配好 WAF;确定要保密的(备份、用户文件)保持私有,一律走预签名。混在一起是最难收拾的做法。

      本文档依据 Cloudflare 官方开发者文档撰写,价格与功能以官方最新说明为准。
      官方文档:developers.cloudflare.com/r2 · 价格计算器:r2-calculator.cloudflare.com
      作者 LCByte · www.lcbyte.com

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注