无需服务器!使用 Upstash Redis 快速实现博客浏览量统计

在这篇博客中,我将分享我是如何为我的网站和博客文章实现浏览量统计功能的,并详细介绍了 Upstash 的相关信息,以帮助读者更全面地了解其优势和使用方法。

到我的博客阅读~

技术选型

我选择了以下技术栈来实现这个功能:

  • Upstash Redis:一个无服务器的 Redis 服务,提供 RESTful API 和全球多区域部署,特别适合用于计数器这类简单的存储需求。

  • Next.js API Routes:Next.js 内建的 API 功能,可以方便地创建后端接口。

为什么选择 Upstash Redis?

Upstash Redis 是专为无服务器和边缘计算架构设计的托管型 Redis 服务,具有以下特点:

  • RESTful API 支持:通过 HTTP 协议访问 Redis,无需持久化 TCP 连接,适用于 Vercel Edge Functions、Cloudflare Workers 等环境。

  • 全球多区域部署:数据可复制到多个 AWS 区域,提供低延迟的全球访问体验。

  • 按需计费:采用按请求计费模式,适合流量波动较大的应用。

  • 免费套餐:提供 256MB 存储和每月 500,000 次请求,适合小型项目和个人使用。

实现步骤

1. 后端 API (/pages/api/incr.ts)

我创建了一个 API 路由 /pages/api/incr.ts 来处理浏览量的增加请求。这个 API 主要做了以下几件事:

  • 接收 POST 请求:只接受 POST 方法的请求。

  • 解析请求体 :从 JSON 请求体中获取 slug(页面标识符)和 type(类型,如 "projects" 或 "blogs"),type 默认为 "projects"。

  • IP 地址去重

    • 获取请求者的 IP 地址。

    • 对 IP 地址进行 SHA-256 哈希处理,避免直接存储原始 IP。

    • 使用 Redis 的 SETNX 命令,为每个 slug 和哈希后的 IP 创建一个有时效(24小时)的记录。如果记录已存在(表示该 IP 在 24 小时内已访问过此页面),则不增加浏览量,直接返回。

  • 增加浏览量 :如果是新的访问,则使用 Redis 的 INCR 命令增加对应 typeslug 的浏览量计数。Redis 键的格式类似 pageviews:blogs:my-first-postpageviews:projects:my-cool-project

typescript 复制代码
import { Redis } from "@upstash/redis";
import { NextRequest, NextResponse } from "next/server";

const redis = Redis.fromEnv();
export const config = {
  runtime: "edge", // 使用 Edge Runtime 以获得更好的性能
};

export default async function incr(req: NextRequest): Promise<NextResponse> {
  if (req.method !== "POST") {
    return new NextResponse("use POST", { status: 405 });
  }
  if (req.headers.get("Content-Type") !== "application/json") {
    return new NextResponse("must be json", { status: 400 });
  }

  const body = await req.json();
  let slug: string | undefined = undefined;
  let type: string | undefined = "projects"; // 默认为 projects

  if ("slug" in body) {
    slug = body.slug;
  }
  // 检查请求体中是否有 type,没有则使用默认值
  if ("type" in body && typeof body.type === "string") {
    type = body.type;
  }

  if (!slug) {
    return new NextResponse("Slug not found", { status: 400 });
  }
  const ip = req.ip;

  if (ip) {
    // 哈希 IP 地址
    const buf = await crypto.subtle.digest(
      "SHA-256",
      new TextEncoder().encode(ip),
    );
    const hash = Array.from(new Uint8Array(buf))
      .map((b) => b.toString(16).padStart(2, "0"))
      .join("");

    // IP 去重,键格式为 "deduplicate:hash:slug"
    const isNew = await redis.set(["deduplicate", hash, slug].join(":"), true, {
      nx: true, // 只在键不存在时设置
      ex: 24 * 60 * 60, // 过期时间 24 小时
    });
    // 如果不是新访问,则直接返回
    if (!isNew) {
      return new NextResponse(null, { status: 202 });
    }
  }

  // 增加页面浏览量,键格式为 "pageviews:type:slug"
  await redis.incr(["pageviews", type, slug].join(":"));
  return new NextResponse(null, { status: 202 });
}

2. 前端组件调用

在需要统计浏览量的页面(例如博客文章页或项目详情页),我使用了一个简单的 React 组件,在组件加载时(useEffect hook)向后端 API 发送请求。

对于博客页面 (/app/blogs/[slug]/view.tsx):

tsx 复制代码
"use client";

import { useEffect } from "react";

export const ReportView: React.FC<{ slug: string }> = ({ slug }) => {
  useEffect(() => {
    fetch("/api/incr", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      // 发送 slug 和 type="blogs"
      body: JSON.stringify({ slug, type: "blogs" }),
    });
  }, [slug]); // 依赖 slug,当 slug 变化时重新发送

  return null; // 这个组件不渲染任何 UI
};

对于项目页面 (/app/projects/[slug]/view.tsx):

tsx 复制代码
"use client";

import { useEffect } from "react";

export const ReportView: React.FC<{ slug: string }> = ({ slug }) => {
  useEffect(() => {
    fetch("/api/incr", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      // 只发送 slug,后端 API 会默认为 type="projects"
      body: JSON.stringify({ slug }),
    });
  }, [slug]);

  return null;
};

后来,我将这个逻辑提取到了一个更通用的组件 ViewTracker (/app/components/view-tracker.tsx),这样可以更方便地在不同类型的页面中使用:

tsx 复制代码
"use client";

import { useEffect } from "react";

export const ViewTracker = ({ slug, type }: { slug: string; type: string }) => {
  useEffect(() => {
    fetch("/api/incr", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ slug, type }), // 同时发送 slug 和 type
    });
  }, [slug, type]); // 依赖 slug 和 type

  return null;
};

Upstash 免费套餐和计费模式

Upstash 提供了一个免费的 Redis 实例,适合小型项目和个人使用。

Upstash 免费套餐 包括:

  • 256MB 数据存储

  • 每月 500,000 次命令([Upstash: Serverless Data Platform][3])

  • 每个账户一个免费数据库([Upstash: Serverless Data Platform][4])

如果你的网站流量较大,可以考虑升级到付费套餐,Upstash 的定价相对友好,按使用量计费,支持更高的存储和请求量。

总结

通过结合 Upstash Redis 和 Next.js API Routes,我实现了一个简单、高效且带有 IP 去重功能的浏览量统计系统。这种方法不仅易于实现,而且性能良好,能够轻松应对一定的访问量。

Upstash 的无服务器架构和全球多区域部署,使得它非常适合现代 Web 应用的需求。

如果你正在寻找一个轻量级、易于集成的浏览量统计解决方案,Upstash 是一个值得考虑的选择。

到我的博客阅读~

相关推荐
Electrolux1 小时前
【使用教程】一个前端写的自动化rpa工具
前端·javascript·程序员
赵大仁2 小时前
深入理解 Pinia:Vue 状态管理的革新与实践
前端·javascript·vue.js
小小小小宇2 小时前
业务项目中使用自定义Webpack 插件
前端
小小小小宇3 小时前
前端AST 节点类型
前端
小小小小宇3 小时前
业务项目中使用自定义eslint插件
前端
babicu1233 小时前
CSS Day07
java·前端·css
小鸡脚来咯3 小时前
spring IOC控制反转
java·后端·spring
小小小小宇3 小时前
业务项目使用自定义babel插件
前端
前端码虫4 小时前
JS分支和循环
开发语言·前端·javascript
GISer_Jing4 小时前
MonitorSDK_性能监控(从Web Vital性能指标、PerformanceObserver API和具体代码实现)
开发语言·前端·javascript