
结果图
原理
图源由 Cloudflare R2 托管,通过两个 Workers 连接 R2 以展示随机横屏/竖屏图片,静态页面引用 Workers 的 URL 以实现以上界面
创建 Cloudflare R2 存储桶
R2 实际上是一个对象存储。Cloudflare 提供 10G 的免费存储和每月 1000 万次的免费访问
-
进入Cloudflare 仪表盘,进入 R2 页面,如图
-
选择创建存储桶
-
为你的存储桶起一个名字,然后单击创建
-
进入如下页面就已经创建完毕了
-
返回 R2 首页。因为在下文我们需要使用 API 来进行文件传输,所以需要创建你的 R2 API 令牌,单击管理 R2 API 令牌
-
单击创建 API 令牌,如图
-
因为我们需要该 API 来管理单个 R2 存储桶,所以选择对象读和写,详细配置如图
-
创建 API 令牌后,新页面会展示令牌的详细信息,仅会展示一次!!! 保持这个页面,直到你将该页面的所有信息都已经妥善保存,不要关闭界面,否则,你需要轮转 API 令牌以禁用之前的旧密钥,如图
-
确保你已经妥善保存你的 R2 API 令牌,然后进行下一步
为你的存储桶添加文件
因为 Web 界面传输文件较慢且不支持传输大于 300MB 的文件。这里使用本地部署 AList 然后连接你的 R2 存储桶实现高速上传
-
笔者使用 Windows。前往AList - Github Release下载适用于 Windows 的最新可执行文件,如图
-
将下载的压缩包解压,并将其中的
alist.exe
放入一个空文件夹 -
单击搜索框,输入 cmd 并回车,如图
-
在 cmd 中输入
alist.exe server
并且不要关闭窗口,运行成功后如图 -
打开浏览器,输入
localhost:5244
即可进入 AList 控制台,如图 -
用户名:
admin
密码:在cmd窗口中,如图
。你可以使用鼠标左键在终端中框选内容然后单击鼠标右键进行复制操作 -
注意,在 cmd 中,鼠标左键点击或拖动 cmd 的终端界面会导致进入选择状态,程序将会被系统阻塞,需要在终端界面点按鼠标右键解除。若进程被阻塞,cmd 的进程名会多一个选择,请注意。如图是程序被阻塞的例子,在终端界面点按鼠标右键即可解除
-
现在,你已经成功以管理员身份登入了 AList单击最下面的管理
-
你会进入到如图界面。尽管 AList 运行在本地,也建议更改你的用户名和密码
-
更改账密,重新以新账密登录
-
进入控制台,然后单击存储,如图
-
选择添加,如图
-
详细配置如图。挂载路径即 AList 展示路径,推荐使用
/R2/你的存储桶名字
,地区为auto
回到主页,如图
-
尝试上传文件,如图
-
可以看到,速度非常快
-
为你的图床创建目录以分类横屏和竖屏图等,以便下文使用 Workers 连接 R2 来调用。后文我将使用R2的
/ri/h
路径作为横屏随机图目录、/ri/v
路径作为竖屏随机图目录
创建 Workers,连接 R2
-
进入Cloudflare 仪表盘,进入 Workers 和 Pages 页面,如图
-
单击创建,选择创建 Workers,名称自取,单击部署
-
选择编辑代码
-
粘贴代码(创建随机横屏图):
新代码:
export default { async fetch(request, env, ctx) { const bucket = env.MY_BUCKET; const url = new URL(request.url); const hostname = url.hostname;
// 初始化prefix let prefix = '';
// 根据域名判断prefix if (hostname === 'hpic.072103.xyz' || hostname === 'api-hpic.072103.xyz') { prefix = 'ri/h/'; } else if (hostname === 'vpic.072103.xyz' || hostname === 'api-vpic.072103.xyz') { prefix = 'ri/v/'; } else { return new Response('Invalid domain', { status: 400 }); }
try { // 如果是API域名,只返回数量 if (hostname.startsWith('api-')) { const objects = await bucket.list({ prefix: prefix }); const count = objects.objects.length; const headers = new Headers({ 'Access-Control-Allow-Origin': '*', 'Content-Type': 'text/plain' }); return new Response(count.toString(), { headers }); }
// 原有的随机图片逻辑 const objects = await bucket.list({ prefix: prefix }); const items = objects.objects;
if (items.length === 0) { return new Response('No images found', { status: 404 }); }
const randomItem = items[Math.floor(Math.random() * items.length)]; const object = await bucket.get(randomItem.key);
if (!object) { return new Response('Image not found', { status: 404 }); }
const headers = new Headers(); headers.set('Content-Type', object.httpMetadata.contentType || 'image/jpeg');
return new Response(object.body, { headers }); } catch (error) { console.error('Error:', error); return new Response('Internal Server Error', { status: 500 }); } },};
旧代码:
export default { async fetch(request, env, ctx) { // R2 bucket 配置 const bucket = env.MY_BUCKET;
try { // 列出 /ri/h 目录下的所有对象 const objects = await bucket.list({ prefix: 'ri/h/' });
// 从列表中随机选择一个对象 const items = objects.objects; if (items.length === 0) { return new Response('No images found', { status: 404 }); } const randomItem = items[Math.floor(Math.random() * items.length)];
// 获取选中对象 const object = await bucket.get(randomItem.key);
if (!object) { return new Response('Image not found', { status: 404 }); }
// 设置适当的 Content-Type const headers = new Headers(); headers.set('Content-Type', object.httpMetadata.contentType || 'image/jpeg');
// 返回图片内容 return new Response(object.body, { headers }); } catch (error) { console.error('Error:', error); return new Response('Internal Server Error', { status: 500 }); } },};
-
点击左侧的文件图标
-
在
wrangler.toml
中填入:
[[r2_buckets]]binding = "MY_BUCKET"bucket_name = "114514"
-
保存修改,点击右上角的部署
-
在设置 - 变量找到 R2 存储桶绑定,添加你的存储桶,变量名即上文的
MY_BUCKET
-
在设置 - 触发器添加你的自定义域名以便访问
-
访问效果,每次刷新都不一样
通过使用 HTML 的 <img>
标签引用即可达到开头的效果
如:<img src="你的域名" alt="">