最近使用Hexo重建了我的博客,使用Stellar主题过程中,在友链部分需要填入网站封面,太过繁琐,所以考虑部署一个网页截图API来实现,搜索过后发现已经有博主使用Cloudflare Workers通过Cloudflare 的“浏览器呈现”功能来实现了自动获取网页截图(使用Cloudflare制作自动更新的网站预览图),但博主仅自用很多代码均为写死的,遂拿过来改动一番。

核心代码

代码中的缓存设置为1天(86400秒),频繁使用可能会快速耗光额度,可以酌情调整。

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
export default {
async fetch(request, env, ctx) {
const cache = caches.default;
const kv = env.SCREENSHOT;

const requestUrl = new URL(request.url);
const targetUrl = requestUrl.searchParams.get("url");

if (!targetUrl) {
return new Response("Missing 'url' parameter", { status: 400 });
}

const referer = request.headers.get("referer") || "";
const origin = request.headers.get("origin") || "";
const allowedDomains = env.ALLOWED_DOMAINS ? env.ALLOWED_DOMAINS.split(",") : [];

const isAllowed = allowedDomains.some(domain => {
const trimmedDomain = domain.trim();
return referer.includes(trimmedDomain) || origin.includes(trimmedDomain);
});

if (allowedDomains.length > 0 && !isAllowed) {
return new Response("Access denied: request not from allowed domain", { status: 403 });
}

const date = new Date().toISOString().split("T")[0];
const cacheKey = targetUrl;
const datedKey = `${targetUrl}?${date}`;

// 工具函数:构建 Response 对象
const buildResponse = (buffer) =>
new Response(buffer, {
headers: {
"content-type": "image/png",
"cache-control": "public, max-age=86400, immutable",
},
});

// 工具函数:尝试从 KV 和 Cache 中加载已有截图
const tryGetCachedResponse = async (key) => {
let res = await cache.match(key);
if (res) return res;

const kvData = await kv.get(key, { type: "arrayBuffer" });
if (kvData) {
res = buildResponse(kvData);
ctx.waitUntil(cache.put(key, res.clone()));
return res;
}
return null;
};

// 1. 优先使用当日缓存
let res = await tryGetCachedResponse(datedKey);
if (res) return res;

// 2. 若缓存不存在,则请求 Cloudflare Screenshot API
try {
const payload = {
url: targetUrl,
viewport: { width: 1200, height: 800 },
gotoOptions: { waitUntil: "networkidle0" },
};

const apiRes = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${env.CF_ACCOUNT_ID}/browser-rendering/screenshot?cacheTTL=86400`,
{
method: "POST",
headers: {
Authorization: `Bearer ${env.CF_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
}
);

if (!apiRes.ok) throw new Error(`API returned ${apiRes.status}`);

const buffer = await apiRes.arrayBuffer();
res = buildResponse(buffer);

// 后台缓存更新
ctx.waitUntil(Promise.all([
kv.put(cacheKey, buffer),
kv.put(datedKey, buffer, { expirationTtl: 86400 }),
cache.put(cacheKey, res.clone()),
cache.put(datedKey, res.clone()),
]));

return res;
} catch (err) {
console.error("Screenshot generation failed:", err);

// 3. 回退到通用旧缓存
res = await tryGetCachedResponse(cacheKey);
if (res) return res;

return new Response("Screenshot generation failed", { status: 502 });
}
},
};

部署教程

创建相关服务

你需要在Cloudflare新建一个Workers服务(Compute菜单中)和Workers KV(存储和数据库菜单中),名称随意。

为Worker服务绑定KV

点进创建好的Workers服务,依次点击绑定、添加绑定、KV命名空间,将创建好的KV绑定到Worker服务中,变量名称需保证为SCREENSHOT

后续内容暂时没空写,等周末再来完善…

给这篇文章评分吧~
(0.0)