Cloudflare R2 对象存储:零成本搭建博客图床,告别图片拖慢网站

zheng头像
zheng 2026-07-18 10:41
楼主

创建 R2 存储桶

登录 Cloudflare 控制台,左侧菜单找到 R2 → 创建存储桶:

  • 存储桶名称blog-images(全局唯一,建议加前缀如 zjunwei-blog-images
  • 位置:选择「自动」(亚太用户数据会自动路由到就近节点)

创建完成后,你会看到存储桶的控制台页面。可以直接在这里上传文件,但我们的目标是自动化——用命令行批量管理。


用 wrangler 操作 R2

如果你还没装 wrangler(上篇 Workers 教程应该装过了):

npm install -g wrangler
wrangler login

创建一个专门管理 R2 的 Worker 项目(R2 需要绑定到 Worker 才能通过自定义域名访问):

mkdir r2-proxy && cd r2-proxy
wrangler init r2-proxy

wrangler.toml 中绑定刚刚建的 R2 桶:

name = "r2-proxy"
main = "src/index.js"
compatibility_date = "2025-01-01"

[[r2_buckets]]
binding = "IMAGES"          # 代码里用这个名字引用
bucket_name = "zjunwei-blog-images"

编辑 src/index.js,让 Worker 代理 R2 的图片访问:

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const key = url.pathname.slice(1);  // /photo.png → photo.png

    if (!key) {
      return new Response('R2 Image Proxy', { status: 200 });
    }

    // 从 R2 读取对象
    const object = await env.IMAGES.get(key);

    if (!object) {
      return new Response('Not Found', { status: 404 });
    }

    // 设置合理的缓存头
    const headers = new Headers();
    object.writeHttpMetadata(headers);
    headers.set('cache-control', 'public, max-age=31536000, immutable');
    headers.set('etag', object.httpEtag);

    return new Response(object.body, { headers });
  },
};

部署:

wrangler deploy

绑定自定义域名

部署完 Worker 后,默认域名是 r2-proxy.你的子域.workers.dev。我们需要绑定自己的域名,比如 img.zjunwei.com

前提:域名已接入 Cloudflare DNS。

在 Cloudflare 控制台 → Workers 和 Pages → r2-proxy → 触发器 → 自定义域,添加 img.zjunwei.com

等 DNS 生效后,访问 https://img.zjunwei.com/photo.png 就能直接从 R2 拉图片了。


上传图片:命令行批量操作

R2 兼容 S3 API,所以任何 S3 工具都能用。推荐用 awsclirclone

方式一:awscli(轻量)

# 安装
pip install awscli

# 配置(R2 的 Access Key 在 Cloudflare 控制台 → R2 → 管理 R2 API 令牌 获取)
aws configure --profile r2
# AWS Access Key ID: 你的R2_Access_Key
# AWS secret Access Key: 你的R2_secret_Key
# Default region name: auto

上传单张图片:

aws s3 cp ./screenshot.png s3://zjunwei-blog-images/ \
  --endpoint-url https://你的AccountID.r2.cloudflarestorage.com \
  --profile r2

批量上传整个目录:

aws s3 sync ./local-images/ s3://zjunwei-blog-images/ \
  --endpoint-url https://你的AccountID.r2.cloudflarestorage.com \
  --profile r2

方式二:rclone(功能更全)

# 安装
curl https://rclone.org/install.sh | sudo bash

# 配置(交互式,选 S3 类型,填 R2 的 endpoint 和密钥)
rclone config

配置好后,上传和同步就很简单了:

rclone copy ./local-images/ r2:zjunwei-blog-images/

博客图片自动压缩 + 上传脚本

每次截图后手动压缩再上传太麻烦。写一个脚本,一键完成:

#!/bin/bash
# upload-image.sh - 图片压缩并上传到 R2

BUCKET="zjunwei-blog-images"
ENDPOINT="https://你的AccountID.r2.cloudflarestorage.com"
PROFILE="r2"
MAX_WIDTH="1200"  # 博客内容区宽度,再大没意义

for img in "$@"; do
    filename=$(basename "$img")
    ext="${filename##*.}"

    # 如果不是 WebP,转换成 WebP(体积平均减少 60%)
    if [ "$ext" != "webp" ]; then
        newname="${filename%.*}.webp"
        convert "$img" -resize "${MAX_WIDTH}x" -quality 85 "$newname" 2>/dev/null || {
            echo "ImageMagick 未安装,跳过压缩"
            cp "$img" "$newname"
        }
    else
        newname="$img"
    fi

    echo "上传: $newname"
    aws s3 cp "./$newname" "s3://$BUCKET/" \
        --endpoint-url "$ENDPOINT" \
        --profile "$PROFILE" \
        --content-type "image/webp"

    echo "链接: https://img.zjunwei.com/$newname"
done

用法:

# 装 ImageMagick(macOS)
brew install imagemagick

# 一键上传
chmod +x upload-image.sh
./upload-image.sh screenshot1.png cover.jpg diagram.png

在 Typecho 中使用 R2 图片

Typecho 编辑器里直接粘贴 R2 链接就行:

![架构图](https://img.zjunwei.com/architecture.webp)

如果你的文章里引用服务器本地的图片太多,写个批量替换脚本:

# 把文章里 /usr/uploads/ 的图片路径替换为 R2 链接
sed -i 's|/usr/uploads/|https://img.zjunwei.com/|g' your-article.md

更进一步:在 Typecho 主题里改附件上传逻辑,上传后自动同步到 R2,然后本地删除。不过这需要改 PHP 代码,取决于你对主题的定制深度。


安全与防盗链

R2 默认是公开访问的(通过 Worker 代理),如果想限制:

方案一:Token 鉴权(简单)

在 Worker 里加 token 校验:

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const key = url.pathname.slice(1);

    // 只允许带有效 token 的请求
    const token = url.searchParams.get('token');
    if (token !== '你的密钥') {
      return new Response('Forbidden', { status: 403 });
    }

    const object = await env.IMAGES.get(key);
    if (!object) return new Response('Not Found', { status: 404 });

    const headers = new Headers();
    object.writeHttpMetadata(headers);
    headers.set('cache-control', 'public, max-age=31536000');
    return new Response(object.body, { headers });
  },
};

图片链接变成:https://img.zjunwei.com/photo.webp?token=xxx

方案二:Referer 防盗链

用 Cloudflare WAF 规则,只允许来自 zjunwei.com 的 Referer 访问图片 Worker 的路由。在 Cloudflare 控制台 → 安全性 → WAF → 自定义规则 里设置。


一个月能省下什么

假设博客日均 3000 PV,每页 10 张图,每张 150KB:

项目 服务器直出 R2 方案
月度外网流量 ~135GB 0(R2 出站免费)
服务器带宽压力 VPS 跑满 0(Cloudflare 扛)
CDN 回源流量费 看 CDN 厂商 0(自带 CDN)
图片加载速度 看 VPS 位置 全球 300+ 节点,50ms

如果 VPS 带宽是 1Mbps 小水管,135GB 流量能把服务器拖垮。换成 R2 后,图片请求全部走 Cloudflare 边缘节点,你的 VPS 只处理 PHP 动态请求,负载直降 90%。


Q&A

Q:R2 和普通的图床(如 SM.MS、imgur)比有什么优势?

自建可控,不怕图床跑路。而且 R2 免费 + 不跑外网流量,普通图床要么限流量、要么限容量、要么塞广告。唯一的代价是前期配置需要 10 分钟。

Q:R2 上传慢怎么办?

aws s3 syncrclone 都是多线程上传,几百张图几分钟搞定。日常写文章一张张传,几乎感觉不到延迟。

Q:Typecho 能不能直接把附件存到 R2?

可以,装 Typecho 插件「COS for Typecho」或类似 S3 兼容插件,把 endpoint 指向 R2 就行。不过 R2 的 S3 兼容需要设置 AWS_REGION=auto 和正确的 endpoint URL。

Q:10GB 用完了怎么办?

超出后 $0.015/GB/月。10GB 以外,每多存 1GB 一个月多花一毛五。对于个人博客,10GB 可能这辈子都用不完。


服务器的硬盘和带宽是有限的,但 Cloudflare 的 300 个边缘节点不是。把图片交给 R2,你的 VPS 只专心跑好 PHP——各司其职,才是正经架构。

Cloudflare Workers 入门:零成本搭建你的第一个 Serverless 应用 自建闲鱼自动发货系统:Docker 部署全流程,24小时自动回复发卡券

最新回复

暂无回复,来说两句吧

发表评论

站点总访问量:6233