前言

自从上次将博客项目的图片从 七牛云 迁到了 Cloudflare R2 之后就发现,Cloudflare 这个赛博菩萨的产品是真的不错,非常的适合白嫖,DevNow 项目作为一个开源博客,整体来说是希望越少依赖一些服务越好,使整个构建、部署流程更加的 轻便 和 快捷 ,让对于前端不是很熟的同学也能快速的搭建一个自己的博客。

这篇文章其实完全是个人爱好,只要是实现如何集成 Cloudflare D1 来给文章详情页增加一个浏览量。目前还没想好是否要往 DevNow 项目上同步这个功能,就现在自己的 blog 上试试水,如果大家有需求的话,后边在看是不是可以写一个脚本集成到 DevNow 项目中。

方案

方案整体来说是很多的,这里简单说下我考量的一些因素:

  • 自己有服务器,可以自己搭建一个数据库实现。
  • 通过一些比较成熟的数据存储服务,可能需要订阅功能。
  • 借助类似 CloudflareVercel 这样的服务商上的一些服务来实现,数据量少的话就是白嫖。severless 的实现方案对前端来说也比较友好。

这里还有一个考量是后续可能会考虑通过 Cloudflare Page 部署前端的方案,完全从 Vercel 切到 Cloudflare,减少多个服务商带来的管理和集成的复杂度。

先简单介绍一些 Cloudflare D1 ;

官方的介绍:

使用 D1 短短数秒内即可创建一个无服务器的关系数据库。通过熟悉的查询语言、时间点恢复功能和经济实惠的定价,赋能您构建下一个重大项目。

其实这里主要看 Free 版的服务,这里其实有两个服务可以选择, KVD1 都可以用来存储数据,这里选择 D1 主要考量因素就是 KV 的每日写入操作只有 1000 次,整理来对于一些好的博客网站可能会超过,所以直接上 D1

集成到 DevNow 项目中

:::tip[注意]

整体来说所有的操作都有两种实现方式,一个是在 Cloudflare D1 的官网里边操作,一个是通过命令行来实现。

我这里主要记录一下命令行的实现,通过一些命令和代码来创建和部署 D1 的服务,可以更好的理解整个的流程。

前置条件 :已经完成 Cloudflare 账号的注册和信用卡的绑定,否则无法后续的流程

:::

参考文档:Cloudflare D1

1. 创建一个Worker

pnpm create cloudflare@latest d1-tutorial
// d1-tutorial 是 <WORDER_NAME>

对于设置,请选择以下选项:

  • For What would you like to start with? choose Hello World example.
  • For Which template would you like to use? choose Hello World Worker.
  • For Which language do you want to use? choose TypeScript.
  • For Do you want to use git for version control? choose Yes.
  • For Do you want to deploy your application? choose No (we will be making some changes before deploying).

然后项目中将会得到以下的一个目录。

2. 创建数据库

D1 数据库在概念上与许多其他数据库类似:数据库可能包含一个或多个表、查询这些表的能力以及可选索引。 D1 使用熟悉的SQL 查询语言 (与 SQLite 使用的一样)。

切换到您刚刚为 Workers 项目创建的目录:

cd d1-tutorial

运行以下命令并为您的数据库命名。在本教程中,数据库名为 devnow

npx wrangler d1 create devnow

# 然后会输出以下内容:
# [[d1 databases]]
# binding ="DB" # available in your Worker on env.DB
# database name ="prod-d1-tutorial"
# database id ="<unigue-ID-for-your-database>"

这将创建一个新的 D1 数据库并输出下一步所需的绑定配置。

wrangler命令行界面是 Cloudflare 的工具,用于在终端中管理和部署 Workers 应用程序和 D1 数据库。它是在您使用npm create cloudflare@latest初始化新项目时安装的。

3. 将 Worker 绑定到您的 D1 数据库

您必须为 Worker 创建绑定才能连接到 D1 数据库。绑定允许您的 Workers 访问 Cloudflare 开发者平台上的资源,例如 D1。您可以通过更新wrangler.toml文件来创建绑定。

复制第二步输出的结果到 wrangler.toml 文件中。

[[d1_databases]]
binding = "DB" # available in your Worker on env.DB
database_name = "prod-d1-tutorial"
database_id = "<unique-ID-for-your-database>"

到这里一个本地的 D1 数据库就建好了

4. 对 D1 数据库进行查询

4.1 更新 schema.sql 文件:

// d1-turorial/schema.sql

DROP TABLE IF EXISTS PageView;
CREATE TABLE IF NOT EXISTS PageView (
PageId INTEGER PRIMARY KEY AUTOINCREMENT,
Path TEXT UNIQUE NOT NULL,
Count INTEGER,
LastVisited INTEGER
);
INSERT INTO PageView (PageId, Path, Count, LastVisited)
VALUES
(1, '/posts/1', 0, 1725673703),
(2, '/posts/2', 0, 1725673703),
(3, '/posts/3', 0, 1725673703);

4.2 初始化数本地据库

npx wrangler d1 execute devnow --local --file=./schema.sql

4.3 验证数据是否在数据库中

npx wrangler d1 execute devnow --local --command="SELECT * FROM PageView"

看到如下图结果,即表示本地数据库创建成功:

5 在 Worker 中编写查询方法

这里实现了根据 /d1/pageview/ 来请求,然后 获取更新 页面数据的接口。

// page: d1-tutorial/src/index.ts

export interface Env {
DB: D1Database;
} export default {
async fetch(request, env): Promise<Response> {
const url = new URL(request.url);
const pathname = url.pathname; // 获取页面路径 // 检查是否以 https://d1.laughingzhu.cn/d1/pageview 开头
if (!pathname.startsWith('/api/pageview')) {
return new Response('Not Found', { status: 404 });
} // 继续处理 https://d1.laughingzhu.cn/d1/pageview 路径下的请求
const trimmedPath = pathname.replace('/api/pageview/', '/'); // 去除开头部分,获取具体页面路径
if (request.method === 'GET') {
try {
// 查询指定 path 的浏览量
const result = await env.DB.prepare(`SELECT Count FROM PageView WHERE Path = ?`)
.bind(trimmedPath)
.first(); // 如果没有记录,返回 0
const count = result ? result.Count : 0;
return new Response(JSON.stringify({ path: trimmedPath, count }), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error: any) {
console.error('Database operation failed:', error);
return new Response(`Failed to fetch page views for ${trimmedPath}: ${error.message}`, {
status: 500
});
}
}
if (request.method === 'POST') {
// 使用 UPSERT 来尝试更新 count,如果没有该页面则插入新的记录
try {
// UTC 转成北京时间
const utcTimestamp = Math.floor(Date.now() / 1000);
const { results } = await env.DB.prepare(
`
INSERT INTO PageView (Path, Count, LastVisited)
VALUES (?, 1, ?)
ON CONFLICT(Path)
DO UPDATE SET Count = Count + 1, LastVisited = ?
`
)
.bind(trimmedPath, utcTimestamp, utcTimestamp)
.run();
// return Response.json(results);
if (results) {
return Response.json({
message: `Page views for ${trimmedPath} updated.`,
data: results
});
} else {
return new Response(`Page views for ${trimmedPath} updated, but no results returned.`, {
status: 200
});
}
} catch (error: any) {
console.log(error);
return new Response(`Failed to update page views for ${trimmedPath}: ${error.message}`, {
status: 500
});
}
} // 如果是其他方法,不允许操作
return new Response('Method Not Allowed', { status: 405 });
}
} satisfies ExportedHandler<Env>;

配置 Worker 后,您可以在全局部署之前在本地测试您的项目。

6. 本地开发运行 Worker

npx wrangler dev

当您运行 wrangler dev 时,Wrangler 会提供一个 URL(很可能是 localhost:8787)来查看您的 Worker。

示例:

我们通过浏览器访问 http://localhost:8787/d1/pageview/posts/hello-word

查询返回以下数据,这是因为数据库中没有,默认是0。

到这里本地调试就完事了。

7.部署线上 D1 数据库

创建远程线上数据库:

npx wrangler d1 execute devnow --remote --file=./schema.sql

查询远程线上数据库:

npx wrangler d1 execute prod-d1-tutorial --remote --command="SELECT * FROM Customers"

这里和步骤 4.2 、 4.3 的区别就是 --local 换成了 --remote .

7.1 部署 worker 方法

npx wrangler deploy

这里主要是部署我们在 worker 中实现的方法,不如上述步骤 4.4 在 d1-turorial/src/index 中实现的查询、更新页面数据的方法。

到这里就可以整个 wokrer 和 数据库就部署完成了,我们可以到线上去看一下。

我们可以看到 Cloudflare 给我们提供了默认的 Worker 的访问路由,为了方便,我们也可以添加一个自定义路由,通过自定义路由访问,如下。

到这里整个 Cloudflare D1 数据库这里就接入完成了。

接下来我们在前端项目中来增加相关的请求方法。

DevNow 前端项目中增加查询、更新浏览量方法

1. 实现方案:

整体来说这里由于期望 浏览量 这个数据比较实时,所以大概方案我能想到的是两种方案:

  • CSR 去做,可以通过集成 React 来实现在运行时去请求 api 来完成(最简单);
  • 渲染方案从 SSG 切换到 SSR ,这样页面会在每次访问的时候在服务端动态的生成,数据可以保持实时。(这里遇到一些坑,下边会提)

最终我选择了 SSR 方案,主要考量的因素如下:

使用 CSR 运行时请求会导致接口暴露,由于没有用户身份体系,很容易让一些无聊的人刷接口,主要我们白嫖每天是限额,超出是要收钱的。

所以这里采用了 SSR 的方案,这样就可以不暴露接口来实现,不过这里可能会牺牲掉 SSG 带来的更好缓存效果。

这里主要是删除、替换适用于 SSG 方案中的一些 API :

// page: src/posts/[...slug].astro

// 删除  export const prerender = true;

// 删除 getStaticPaths() 相关方法
//可以通过 getEntry 来获取对应的文章内容 import { getEntry } from 'astro:content';
const { slug } = Astro.params;
const post = await getEntry('doc', slug);

2. Astro 中如何接入

开始使用了 Astro 提供的 API 端点 ,后来仔细了解完这个是 SSG 的方案,之后在构建的时候请求,后边就不会走了,所以基于 API 端点 的实现方式 PASS

其实 Astro 中 数据请求 的文档中有提到,可以使用 Fetch 来实现。

官网关于 fetch 的

fetch 调用将会在构建时执行,并且数据都可用于组件模板中来生成动态 HTML。如果启用 SSR 模式,任何 fetch 调用都将在运行时执行。

// page: src/posts/[...slug].astro
//增加如下来实现动态获取访问量 const { slug } = Astro.params;
const data = await fetch(`https://xxx.d1.hosts/d1/pageview/posts/${slug}`);
const result = await data.json();

这里可以提出到一个指定的文件来维护,示例我就不改了。

优化空间:只有在线上环境才请求,线上不请求,来增加数据的准确度。

更新的实现同上。

3. 页面缓存的问题

部署到线上环境时发现个问题,只有部署完第一次访问会增加请求对应的方法,后边就不请求了,猜测是 Vercel 这边有缓存,然后在 Response 体里看到了 x-vercel-cache: HIT 。第一次是 MISS 以后所有的都是 HIT ,这里其实可以理解成部署完第一次访问的时候 Vercel 帮我们生成了一份缓存,后续所有的人访问都走的是缓存,这样在速度上就会有更好的体验,本来是个好东西,还给我卡主了。

然后就各种开始找方法来避免缓存生效。给请求加 禁止缓存字段,给 vercel.json header 增加对应路由不缓存的配置发现都没有用。

这里可以通过开启 D1 的实时日志来调试:

然后突然想到会不会是 ISR 导致的问题,之前默认打开了,搜索了一会发现,ISR 模式会自动缓存我们的文件。解决方案就是 将 astro.config.js 中的 ISR 配置删除即可。

完事,到这里项目里就可以展示 浏览量了。效果图如下:

原文链接:Cloudflare D1 - 免费数据存储

Cloudflare D1 - 免费数据存储的更多相关文章

  1. android 数据存储Ⅰ

    本章讲述在Android开发中,简单的数据存储.涉及知识主要是SharedPreferences,及多页面切换ViewPager. 1.功能需求 做一个小应用.启动的时候有左右引导图.只有第一次启动时 ...

  2. android中数据存储

    android中数据存储     Android 中存储数据的方式有五种:SQLite数据库.文件存储.内容提供者.网络.SharedPreferences(Key----value)五种存储方式. ...

  3. Android Learning:数据存储方案归纳与总结

    前言 最近在学习<第一行android代码>和<疯狂android讲义>,我的感触是Android应用的本质其实就是数据的处理,包括数据的接收,存储,处理以及显示,我想针对这几 ...

  4. 智能合约语言 Solidity 教程系列4 - 数据存储位置分析

    写在前面 Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊.智能合约有所了解, 如果你还不了解,建议你先看以太坊是什么 这部分的内容官方英文文档讲的不是很透,因此我在参考Soli ...

  5. Influxdb数据存储

    环境: CentOS6.5_x64 InfluxDB版本:1.1.0 InfluxDB存储引擎看起来很像一个LSM Tree,它包含预写日志和类似存储在LSM Tree中的SSTables只读数据. ...

  6. (转)MySQL 常用数据存储引擎区别

    MySQL 常用数据存储引擎区别 原文:https://laravel-china.org/articles/4198/mysql-common-data-storage-engine mysql有多 ...

  7. 第四天,同步和异常数据存储到mysql,item loader方法

    github对应代码:伯乐在线文章爬取     一. 普通插入方法 1. 连接到我的阿里云,用户名是test1,然后在navicat中新建数据库

  8. 服务追踪数据使用 RabbitMQ 进行采集 + 数据存储使用 Elasticsearch + 数据展示使用 Kibana

    服务追踪数据使用 RabbitMQ 进行采集 + 数据存储使用 Elasticsearch + 数据展示使用 Kibana https://www.cnblogs.com/xishuai/p/elk- ...

  9. MySQL 常用数据存储引擎区别

    mysql有多种存储引擎,目前常用的是 MyISAM 和 InnoDB 这两个引擎,除了这两个引擎以为还有许多其他引擎,有官方的,也有一些公司自己研发的.这篇文章主要简单概述一下常用常见的 MySQL ...

  10. SpringBoot 2.0 + InfluxDB+ Sentinel 实时监控数据存储

    前言 阿里巴巴提供的控制台只是用于演示 Sentinel 的基本能力和工作流程,并没有依赖生产环境中所必需的组件,比如持久化的后端数据库.可靠的配置中心等.目前 Sentinel 采用内存态的方式存储 ...

随机推荐

  1. c 语言学习第六天

    数组 语法: 类型 数组名[元素个数]; int a[6]; // 4*6 字节 char b[24]; // 1*24 字节 double c[2]; // 2*8 字节 访问数组中的元素 语法: ...

  2. Quartus Ⅱ调用FIFO IP核方法实现求和(Mega Wizard)

    摘要:本次实验学习记录主题为"FIFO_IP核实现算术求和",主要内容是上位机通过串口向FPGA发送一定规格的数字矩阵,FPGA对矩阵处理,按规定逻辑实现求和运算,将结果返回串口转 ...

  3. mysql大数据表添加字段

    方案一.老表数据迁移四部曲方案1.新建老表t_order_goods的备份表t_order_goods_bak,同时加一个字段:isVirtual 并给默认值2.迁移老表t_order_goods数据 ...

  4. git分支学习笔记2-解决合并的冲突

    来源:https://www.liuhaolin.com/git/115.html git中合并冲突是在不同的分支中同一个文件的内容不同导致的,如果进行合并就会冲突.文件可能是新增的文件,比如在两个分 ...

  5. Nginx使用upstream实现动静分离

    一.为什么要进行动静分离 分离资源,减少不必要到的请求消耗,减少请求延时. 注:我这里,是nginx处理静态资源,apache处理动态资源. 场景分析: 1.未分离之前的场景步骤 (1)客户端请求ur ...

  6. 学习 React 需要具备的 JavaScript 知识

    学习 React 需要具备的 JavaScript 知识 为什么要学习 React? React 可以与任何其他库或框架无缝集成,因为 React 是一个仅视图库(它是 Model View C on ...

  7. Fiddler 使用fiddler无法抓取苹果手机https请求问题解决方案

    使用fiddler无法抓取苹果手机https请求问题解决方案 by:授客 QQ:1033553122   测试环境 Win10 Fiddle4 IPhone6s 问题描述 使用fiddler抓取IPh ...

  8. mybatis关于大于小于:元素内容必须由格式正确的字符数据或标记组成。

    首先是原因: mybatis中< >这两个符号会被识别为标签的开始和结束,用了就会报解析的错误 会报错类似下面这些 1.元素内容必须由格式正确的字符数据或标记组成. 2.Error cre ...

  9. 如何为spring配置全局抛出异常注解

    0.首先了解一个注解@ControllerAdvice,他是spring里的一个注解,用于定义全局逻辑异常.数据绑定.请求处理等逻辑,与@ExceptionHandler等注解使用. Controll ...

  10. 内存溢出+CPU占用过高:问题排查+解决方案+复盘(超详细分析教程)

    内存溢出+CPU占用过高:问题排查+解决方案+复盘(超详细分析教程) 原文地址 https://zhanghan.blog.csdn.net/article/details/109255980 前言 ...