我的图床方案

前言

为什么要折腾这个?

作为一个博客作者,图床的选择一直是问题:

  • GitHub + jsDelivr:曾经的神,现在国内访问经常挂掉,且污染 Commit 记录。
  • 公共免费图床(如 SM.MS、路过图床等):随时可能跑路,数据无价,不敢赌。
  • 国内云厂商 (OSS/COS):速度快但必须备案,且流量费是个无底洞,被人刷一下一夜回到解放前。对个人博客来说,备案流程繁琐。

Cloudflare R2 是目前的终极答案:

真是赛博活佛。。。

  1. 免费额度大:10GB 存储 + 1000 万次/月读取,个人博客用一辈子都够了。
  2. 免备案:国际大厂,无需 ICP 备案,至少不用担心突然跑路或删图。
  3. 零流量费:这是最骚的,R2 的出口流量完全免费(对比 AWS S3 的高昂流量费)。
  4. 兼容 S3 API:配合 PicGo/PicList 毫无压力。

本文将带你搭建一套 “防盗链 + 全球 CDN 加速 + 自动压缩 + 零成本” 的图床。

第一步:创建 R2 存储桶

  1. 注册/登录:进入 Cloudflare Dashboard

  2. 进入 R2:点击左侧菜单的 R2。如果是第一次使用,需要绑定信用卡/PayPal 开通(仅验证,不扣费)。

  3. 创建存储桶 (关键技巧)

    • 点击 Create bucket

    • 区域选择,R2 会根据你点击创建时的 IP 自动分配区域。

      • 挂美国梯子 -> 数据落地北美。
      • 挂日本/新加坡/香港梯子 -> 数据落地亚太 (推荐,国内访问更快)。
    • 给桶起个名字,比如 blog-img

  4. 获取 API 密钥

    • 回到 R2 主页,点击右侧下方的 Manage

    • 点击 Create API token

    • Permissions: 选择 Admin Read & Write (管理员读写)。

    • 点击创建,务必保存好以下三个信息

      (只显示一次):

      • Access Key ID
      • Secret Access Key
      • S3 API Endpoint (类似于 https://<account_id>.r2.cloudflarestorage.com)
  5. 回到储存桶管理页面,随便上传一张测试图片 test.png,以供后面测试使用

file-20260125110507147

第二步:部署 Worker 反代

R2 对象储存的个人免费额度是有限的,为了防止 DDoS / CC 等恶意消耗流量的攻击,有必要使用 Cloudflare Workers 反代 R2 对象储存,而不是直接将 R2 储存地址对外公开,实现自定义域名防盗链

  1. 创建 Worker
    • Cloudflare 后台 -> Workers & Pages -> Create application -> Create Worker
    • 命名为 r2-image-proxy,点击部署。
  2. 编辑代码
    • 点击 Edit code,清空原有代码,粘贴以下脚本(脚本参考自 Linux.DO 论坛 @leonkuku 大佬):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
export default {
async fetch(request, env) {
// 从环境变量读取防盗链配置
const ALLOWED_DOMAINS = (env.ALLOWED_DOMAINS_STR || "")
.split(',')
.map(domain => domain.trim())
.filter(domain => domain);

// 1. 验证请求来源 (防盗链)
if (ALLOWED_DOMAINS.length > 0) {
const origin = request.headers.get("Origin") || "";
const referer = request.headers.get("Referer") || "";
// 如果没有 Origin/Referer (比如直接在浏览器打开图片),通常允许访问,或者你可以根据需求严格禁止
// 这里逻辑是:如果有 Origin/Referer 且不在白名单内,则拒绝。
if (origin || referer) {
const isAllowed = ALLOWED_DOMAINS.some(domain =>
(origin && origin.startsWith(domain)) || (referer && referer.startsWith(domain))
);
if (!isAllowed) {
return new Response("Forbidden: 防盗链已启用", {
status: 403,
headers: { "Access-Control-Allow-Origin": "*" }
});
}
}
}

// 2. 只允许 GET / HEAD
if (!["GET", "HEAD"].includes(request.method)) {
return new Response("Method Not Allowed", { status: 405 });
}

// 3. 解析请求对象路径
const url = new URL(request.url);
const objectKey = decodeURIComponent(url.pathname.slice(1));
if (!objectKey || objectKey.length > 1024) {
return new Response("Bad Request", { status: 400 });
}

// 4. 从 R2 读取文件
const object = await env.R2_BUCKET.get(objectKey);
if (!object) {
return new Response("Not Found", { status: 404 });
}

// 5. 构造响应头
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
// 强制浏览器缓存 1 年 (配合后面的 Cache Rules)
headers.set("Cache-Control", "public, max-age=31536000, immutable");
headers.set("Access-Control-Allow-Origin", "*");

// 6. 处理条件请求(If-None-Match)
const ifNoneMatch = request.headers.get("If-None-Match");
if (ifNoneMatch && ifNoneMatch === object.httpEtag) {
return new Response(null, { status: 304, headers });
}

// 7. 返回文件流
return new Response(object.body, { headers });
}
};
  1. 配置绑定 进入 Worker 的 Settings 页面:

    • 绑定 R2 (Variables -> R2 Bucket Bindings)

      • 点击 Add binding
      • Variable name: R2_BUCKET (必须完全一致)。
      • R2 Bucket: 选择你刚才创建的 blog-img 桶。

    file-20260125110507144

    • 设置防盗链 (Variables -> Environment Variables)

      • 点击 Add variable
      • Variable name: ALLOWED_DOMAINS_STR
      • Value: 你的博客域名,如 https://blog.example.com,http://localhost:4000 (本地调试记得加 localhost)。
      • 如果不填这个变量,则不启用防盗链。
  2. 绑定自定义域名

    • Settings -> Domains & Routes -> ADD

    • 添加一个子域名,例如 img.example.com。Cloudflare 会自动处理 DNS。

    • 最后访问一下img.example.com/test.png能成功出现就好

第三步:配置 Cache Rules

浏览器缓存 = 存在用户的电脑/手机里。 (省用户的流量,速度最快,但你很难控制让它强制过期)

边缘缓存 = 存在 Cloudflare 的全球机房里。 (省你的服务器流量,速度很快,你可以随时在 Cloudflare 后台一键清除缓存)

Cloudflare R2 每月有 1000W 免费读取次数。如果你不放心,可以再增加一层缓存,既可以减少回源读取次数,又能加速访问速度。不过缓存可能导致图片修改以后更新不及时,有这方面需求的可以不启用此功能。

如果你没有经常更新图片/文章的需求,那么为了让图片加载飞快并节省 Worker/R2 的调用次数,我们需要配置 Cloudflare 的缓存。

  1. 进入域名管理 -> Caching -> Cache Rules -> Create rule

  2. 配置如下

    • Rule Name: R2 Images Cache
    • Field: Hostname equals img.example.com (你的图床域名)
    • Cache eligibility: Eligible for cache
    • Edge TTL (服务器缓存): Ignore cache-control... -> 1 Month (甚至 1 Year)
    • Browser TTL (浏览器缓存): Override origin... -> 1 Month
    • Cache Key: 保持默认的 Standard (不要选 Ignore Query String)。
  3. 部署

  4. 配置完成后点击右下角部署,稍等一会后缓存就将生效

    启用缓存后,每当图床的某张图片被访问,Cloudflare 会把它放入全球 CDN 缓存 24 小时,24 小时以内对这张图片的任意访问都将直接从缓存读取。这样大大降低了 Workers 和 R2 储存的读取请求开销,也提高了响应速度

  5. 每次更新文章后,记得来域名管理 -> 左侧栏找到Caching -> Configuration -> Purge Everything清空一下缓存

image-20260126193007162

第四步:配置 PicList 客户端

本来我是要用PicGo的,毕竟之前就在用,但是==好吧,我配置的时候发现没有自定义域名的选项,换PicList==

推荐使用 PicList 而不是 PicGo,它是 PicGo 的改版,维护更积极,功能更强。

  1. 下载安装PicList Github Release
  2. 配置 S3 图床
    • 打开 PicList -> 图床设置 -> Amazon S3
    • 配置名: 自己起一个名字(没错我是起名/选择困难症)
    • AccessKeyId: 第一步获取的 Key ID。
    • SecretAccessKey: 第一步获取的 Secret Key。
    • Bucket: blog-img (你的桶名)。
    • Region: auto (或者 apac 如果你在亚太)。
    • 自定义节点: 第一步获取的 S3 API Endpoint (注意:不要带桶名,格式如 https://xxx.r2.cloudflarestorage.com)。
    • 自定义域名: https://img.example.com (你的 Worker 域名)。
  3. 设为默认图床

进阶:图片自动压缩 (可选)

PicList 支持上传前自动压缩,强烈推荐配置。

  1. 安装插件picgo-plugin-compress-next

    • PicList 软件内 -> 插件设置 -> 搜索 picgo-plugin-compress-next 安装。

    file-20260125110507116

    • 如果安装慢:进入 PicList 安装目录,运行 npm i picgo-plugin-compress-next

    file-20260125110507048

  2. 配置插件

    • 点击插件的小齿轮图标。
    • 推荐组合:TinyPNG (需申请API Key)) + webp-converter (本地转换)。具体请看插件Github页
    • 开启 transformer - compress-next
    • 这样上传的图片会自动被压缩,体积减小 50% 以上,下面接着用PicList自带的压缩+图片转换能再进步
  3. 自带压缩

    • 回到图片上传页面,有需要的话可以设置一下右上角的图片处理设置
    • 这里除了插件压缩,前面我们用的是TinyPng+webp-converter,我们还可以用PicList再压缩+转换一次

    file-20260125111907938

    水印就不加了。。。

第五步:收尾

  1. Typora 设置

    • Typora -> 偏好设置 -> 图像 -> 上传服务 -> 选择 PicList (注意不是 PicGo-Core)。
    • 验证图片上传选项。

    file-20260125110507043 1

  2. 关闭 r2.dev 访问

    • 回到 Cloudflare R2 存储桶设置 -> Settings
    • 找到 Public access -> R2.dev subdomain
    • 确保它是 Disallow Access 状态。
    • 理由:我们已经有了自定义域名 Worker,不需要这个公开的测试域名,防止被绕过防盗链。

总结

到此,这个R2图床配置,可谓集百家之长(///。///)

现在,你拥有了一个:

  • 极速:Cloudflare 全球 CDN 加速(国内可能没那么加速??)。
  • 安全:Worker 隐藏源站 + 防盗链保护。
  • 省心:PicList 一键上传 + 自动压缩。
  • 免费:几乎不可能用完的免费额度。

Reference