715 字
4 分钟
- 次浏览
Fuwari 功能扩展:View Count
实现思路
使用的方案是 Cloudflare Workers + KV Storage,整体架构如下:
前端页面 → Cloudflare Worker → KV Storage
- 前端通过 API 获取阅读量(GET)
- 页面加载时可选择上报一次访问(POST)
- Worker 负责计数、防刷与存储
其中,对于 API 防刷,基于 IP + slug 设计了 5 分钟的冷却期:统一用户短时间内重复访问不会重复计数。并通过白名单限制允许访问的域名,避免滥用接口。
KV 结构:
- views:{slug} → 阅读数
- cooldown:{ip}:{slug} → 冷却标记
代码实现
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/