715 字
4 分钟
- 次浏览
Fuwari 功能扩展:View Count
2026-04-02
无标签

实现思路#

使用的方案是 Cloudflare Workers + KV Storage,整体架构如下:

前端页面 → Cloudflare Worker → KV Storage

  • 前端通过 API 获取阅读量(GET)
  • 页面加载时可选择上报一次访问(POST)
  • Worker 负责计数、防刷与存储

其中,对于 API 防刷,基于 IP + slug 设计了 5 分钟的冷却期:统一用户短时间内重复访问不会重复计数。并通过白名单限制允许访问的域名,避免滥用接口。

KV 结构:

  • views:{slug} → 阅读数
  • cooldown:{ip}:{slug} → 冷却标记

代码实现#

worker.js
export default {
async fetch(request, env) {
try {
const url = new URL(request.url);
const path = url.pathname;
// CORS 预检
if (request.method === "OPTIONS") {
return new Response(null, {
headers: corsHeaders(request),
});
}
if (path.startsWith("/blog/views")) {
const slug = url.searchParams.get("slug");
// 校验 slug 格式,只允许合法路径字符
if (!slug || !/^[a-zA-Z0-9\-_\/]+$/.test(slug)) {
return corsResponse("Invalid or missing slug", 400, request);
}
const key = `views:${slug}`;
if (request.method === "POST") {
// —— 防刷:基于 IP + slug 的冷却期(同一 IP 对同一篇文章 5 分钟内只算 1 次)——
const ip = request.headers.get("CF-Connecting-IP") || "unknown";
const cooldownKey = `cooldown:${ip}:${slug}`;
const cooldown = await env.BLOG_VIEWS.get(cooldownKey);
if (cooldown) {
// 冷却期内,直接返回当前值,不计数
const current = (await env.BLOG_VIEWS.get(key)) || "0";
return corsResponse(current, 200, request);
}
// 设置冷却期 5 分钟(300 秒后自动过期)
await env.BLOG_VIEWS.put(cooldownKey, "1", { expirationTtl: 300 });
// 递增计数
const current = parseInt((await env.BLOG_VIEWS.get(key)) || "0");
const updated = current + 1;
await env.BLOG_VIEWS.put(key, updated.toString());
return corsResponse(updated.toString(), 200, request);
} else if (request.method === "GET") {
const current = (await env.BLOG_VIEWS.get(key)) || "0";
return corsResponse(current, 200, request);
} else {
return corsResponse("Method not allowed", 405, request);
}
}
return corsResponse("Not found", 404, request);
} catch (err) {
return new Response("Worker error: " + err.message, { status: 500 });
}
},
};
// —— 工具函数 ——
const ALLOWED_ORIGINS = [
"https://shenxianovo.github.io",
"https://blog.shenxianovo.com",
// "http://localhost:4321"
];
function corsHeaders(request) {
const origin = request.headers.get("Origin") || "";
const allowedOrigin = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
return {
"Access-Control-Allow-Origin": allowedOrigin,
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Max-Age": "86400",
};
}
function corsResponse(body, status, request) {
return new Response(body, {
status,
headers: corsHeaders(request),
});
}
Fuwari 功能扩展:View Count
https://blog.shenxianovo.com/posts/blog-customization/view-count/
作者
shenxianovo
发布于
2026-04-02
许可协议
CC BY-NC-SA 4.0