上手 Cloudflare D1:无服务器数据库入门与博客实战

概述

Cloudflare D1 是 Cloudflare 推出的无服务器 SQLite 数据库,基于 SQLite 构建,天然与 Cloudflare Workers 集成。它让你在边缘节点直接读写关系型数据,无需管理服务器,无需操心连接池,按需付费。

核心特性

  • SQLite 兼容:标准 SQL 语法,零学习成本
  • 边缘读取:数据在全球节点缓存,读请求毫秒级响应
  • 按需付费:无闲置费用,只有读写操作收费
  • 与 Workers 深度集成:通过 env.DB 直接调用,无需 HTTP 请求
  • 内置备份与回滚:自动备份,支持时间点恢复

免费额度

项目 额度
数据库数量 50,000 个
每个数据库大小 500 MB
读行数 50 亿行/月
写行数 1 亿行/月
存储 5 GB(超出 $0.001/GB/月)

对于个人博客来说,这个免费额度绰绰有余。


需求分析

为什么我的博客需要 D1?因为几个我想要的功能都需要数据库支撑:

  1. 登录/注册 — 用户管理与评论者身份识别
  2. 文章浏览数 — 记录每篇文章的 PV
  3. 聊天室 — 实时消息存储
  4. 评论功能 — 文章评论持久化

没有数据库之前,这些数据只能靠前端 LocalStorage 或远端 JSON 文件模拟,既不安全也无法跨设备同步。D1 提供了轻量级关系型数据库的能力,正好填补这个空缺。

下面以登录功能为例,演示 D1 从创建到使用到接入的完整流程。


一、创建 D1 数据库

进入 Cloudflare Dashboard → Storage & DatabasesD1 SQLite Database,点击 Create

创建数据库

填写数据库名称(例如 blog-database),创建成功后你会得到一个 database_id

c7e2f4b1-9937-4c7f-acd6-46aea0cb0504

数据库详情

记住这个 ID,后面两种操作方式都会用到它。


二、搭建用户表

在写代码之前,先设计好表结构。以用户表为例,完整的建表语句如下:

CREATE TABLE IF NOT EXISTS users (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    username      TEXT    NOT NULL UNIQUE,
    email         TEXT    UNIQUE NOT NULL,
    password_hash TEXT    NOT NULL,
    avatar_url    TEXT    DEFAULT '',
    bio           TEXT    DEFAULT '',
    role          TEXT    DEFAULT 'user' CHECK(role IN ('user', 'admin')),
    created_at    TEXT    DEFAULT (datetime('now')),
    updated_at    TEXT    DEFAULT (datetime('now'))
);

字段说明:

字段 说明
id 自增主键
username 用户名,唯一
email 邮箱,唯一,可用于登录
password_hash 密码哈希值(绝不存明文)
avatar_url 头像链接
bio 个人简介
role 角色:user 普通用户 / admin 管理员
created_at 注册时间
updated_at 更新时间

三、方式一:REST API 操作(Python)

如果你博客后端是 Python(Django、Flask 等),可以通过 D1 的 REST API 直接操作数据库。

1. 获取 Account ID

进入 Cloudflare Dashboard 首页,点击右侧右上角的三点菜单 → 复制 Account ID

Account ID

2. 创建 API Token

进入 My ProfileAPI TokensCreate Token,选择 Edit Cloudflare Workers 模板,权限至少包含 D1: Edit

3. 操作演示

import requests

ACCOUNT_ID = "你的Account ID"
DATABASE_ID = "你的database_id"
API_TOKEN = "你的API Token"

url = f"https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/d1/database/{DATABASE_ID}/query"
headers = {
    "Authorization": f"Bearer {API_TOKEN}",
    "Content-Type": "application/json"
}

# 创建表
sql = """
CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL
)
"""
res = requests.post(url, headers=headers, json={"sql": sql})
print(res.json()["success"])  # True 即创建成功

# 插入数据(使用 ? 占位符防 SQL 注入)
sql = "INSERT INTO users (name, email) VALUES (?, ?)"
params = ["Alice", "alice@example.com"]
res = requests.post(url, headers=headers, json={"sql": sql, "params": params})
print(res.json()["success"])

# 查询数据
sql = "SELECT * FROM users WHERE email = ?"
params = ["alice@example.com"]
res = requests.post(url, headers=headers, json={"sql": sql, "params": params})
data = res.json()
if data["success"]:
    print(data["result"][0]["results"])

4. 验证

在 Dashboard 的 D1 页面,可以直观看到数据:

Overview 页面

也可以点 Console 直接运行 SQL 查看结果:

Console 页面


四、方式二:Worker 绑定操作(推荐)

为什么推荐?

  1. 零网络延迟env.DB 是 Worker 内部绑定,不走 HTTP
  2. 无需管理 API Token:绑定由 Cloudflare 证书自动处理,零泄露风险
  3. 原生 TypeScript 支持prepare().bind() 天然防 SQL 注入
  4. 与 Pages Functions 集成:博客和 API 可同域部署

1. wrangler.jsonc 配置

{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "blog-database",
      "database_id": "c7e2f4b1-9937-4c7f-acd6-46aea0cb0504"
    }
  ]
}

binding 名称(这里是 DB)后面通过 env.DB 访问。

2. Worker 操作演示

export default {
  async fetch(request: Request, env: { DB: D1Database }): Promise<Response> {
    try {
      // 创建表
      await env.DB.exec(
        "CREATE TABLE IF NOT EXISTS posts (" +
        "  id INTEGER PRIMARY KEY AUTOINCREMENT," +
        "  title TEXT," +
        "  views INTEGER DEFAULT 0," +
        "  created_at TEXT DEFAULT (datetime('now'))" +
        ")"
      );

      // 插入数据(参数化查询)
      await env.DB
        .prepare("INSERT INTO posts (title) VALUES (?)")
        .bind("我的第一篇博客")
        .run();

      // 查询数据
      const { results } = await env.DB
        .prepare("SELECT * FROM posts ORDER BY id DESC LIMIT 10")
        .all();

      return new Response(JSON.stringify({ code: 1, data: results }), {
        headers: { "content-type": "application/json" },
      });

    } catch (e: any) {
      return new Response(JSON.stringify({ code: 0, error: e.message }), {
        headers: { "content-type": "application/json" },
      });
    }
  },
};

3. D1 常用操作速查

// 单条查询 - 返回第一行
const user = await DB.prepare("SELECT * FROM users WHERE id = ?").bind(1).first();

// 多条查询 - 返回所有行
const { results } = await DB.prepare("SELECT * FROM users").all();

// 写入 - 返回执行结果
const { success, meta } = await DB
  .prepare("INSERT INTO users (name, email) VALUES (?, ?)")
  .bind("Bob", "bob@example.com")
  .run();

// 批量写入
const insert = DB.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
await DB.batch([
  insert.bind("Alice", "alice@example.com"),
  insert.bind("Bob", "bob@example.com"),
]);

五、实战:用 D1 写一个登录接口

回到最开始的需求,用 Worker + D1 实现登录其实只需要三个核心步骤:

1. 建表(sessions 表)

CREATE TABLE IF NOT EXISTS sessions (
  id          INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id     INTEGER NOT NULL,
  token       TEXT    NOT NULL UNIQUE,
  expires_at  TEXT    NOT NULL,
  created_at  TEXT    DEFAULT (datetime('now')),
  FOREIGN KEY (user_id) REFERENCES users(id)
);

2. 登录逻辑(仅展示核心 D1 操作)

// 注册:将用户数据写入 D1
await DB
  .prepare("INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)")
  .bind(username, email, hashedPassword)
  .run();

// 登录:从 D1 查询用户
const user = await DB
  .prepare("SELECT * FROM users WHERE username = ? OR email = ?")
  .bind(login, login)
  .first();

// 验证通过后,向 D1 写入 session token
await DB
  .prepare("INSERT INTO sessions (user_id, token, expires_at) VALUES (?, ?, ?)")
  .bind(user.id, token, expiresAt)
  .run();

// 鉴权:从 D1 校验 token
const session = await DB
  .prepare(`
    SELECT u.id, u.username, u.email, u.role
    FROM sessions s JOIN users u ON u.id = s.user_id
    WHERE s.token = ? AND s.expires_at > datetime('now')
  `)
  .bind(token)
  .first();

本质上就是在 D1 里读写三条 SQL 语句——注册一条 INSERT、登录一条 SELECT + 一条 INSERT、鉴权一条 JOIN 查询。这就是 D1 的典型用法。

3. 小技巧:用 Workflow 定时清理过期 session

// 在 Worker 中添加定时任务,每天清理一次过期 session
async function cleanExpiredSessions(DB: D1Database) {
  const { meta } = await DB
    .prepare("DELETE FROM sessions WHERE expires_at <= datetime('now')")
    .run();
  console.log(`清理了 ${meta.changes} 条过期 session`);
}

总结

D1 解决了博客对关系型数据库的需求,而 Workers 绑定让它用起来像本地变量一样简单:

  • 建库:Dashboard 点两下即可
  • 建表:标准 SQL,无需学习新语法
  • 读写:REST API 适合外部服务,Worker 绑定零延迟
  • 扩展:登录 → 文章浏览数 → 评论 → 聊天室,一个 D1 搞定所有

相关文档


📝 本文发布于 栏轩·阁

🌐 欢迎关注我的其他平台:

📧 联系我:mail@lxpavilion.top

posted @ 2026-07-15 23:49  PC2005-cloud  阅读(20)  评论(0)    收藏  举报