私人快速笔记工具 —— 随时随地记录想法、灵感和学习笔记。
线上地址: https://notes.zihuichen.com
Zihui Notes 是一个私人快速笔记工具。核心理念是 方便 —— 打开网页就能写,写完即保存。
功能:
- 快速记录:一个文本框,写完按 ⌘+Enter 即可保存
- 引用来源:每条笔记可以附带一个引用(URL 或文字),URL 自动变成可点击的链接
- 点赞互动:登录用户可以对笔记点赞,显示点赞者的 GitHub 头像
- GitHub 登录:通过 GitHub OAuth 认证身份
- 管理功能:管理员可以创建和删除笔记
| 层 | 技术 | 作用 |
|---|---|---|
| 框架 | vinext (Next.js 16 on Vite) | 用 Next.js 的写法(App Router、Server Components、API Routes),底层用 Vite 打包,部署到 Cloudflare |
| 前端 | React 19 | 页面交互(笔记创建、点赞、删除) |
| 运行环境 | Cloudflare Workers | 代码运行在 Cloudflare 全球边缘节点 |
| 数据库 | Cloudflare D1 (SQLite) | 存储笔记、点赞记录、用户会话 |
| 认证 | GitHub OAuth | 用户登录和身份验证 |
| 构建工具 | Vite 7 + TypeScript | 代码打包和类型检查 |
| 域名 | notes.zihuichen.com | 通过 Cloudflare Workers Custom Domain 绑定 |
用户浏览器
|
| 访问 notes.zihuichen.com
v
┌─────────────────────────────────────────────────────┐
│ Cloudflare Workers (边缘节点) │
│ │
│ ┌────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ vinext │ │ API Routes │ │ GitHub │ │
│ │ (SSR) │ │ │ │ OAuth │ │
│ │ │ │ /api/notes │ │ │ │
│ │ 服务端渲染 │ │ /api/notes/ │ │ /api/auth/ │ │
│ │ 首页 HTML │ │ [id]/like │ │ login │ │
│ └─────┬──────┘ └─────┬──────┘ │ callback │ │
│ │ │ │ logout │ │
│ │ │ └──────┬──────┘ │
│ └───────────────┴─────────────────┘ │
│ │ │
│ ┌─────────┴─────────┐ │
│ │ D1 │ │
│ │ (SQLite) │ │
│ │ │ │
│ │ notes 表 │ │
│ │ likes 表 │ │
│ │ sessions 表 │ │
│ └───────────────────┘ │
└─────────────────────────────────────────────────────┘
│
│ OAuth 回调
v
GitHub API
(用户身份验证)
简单来说:
- 用户访问网站,vinext 在服务端从 D1 读取笔记和点赞数据,渲染成 HTML 返回
- 浏览器拿到页面后,React 接管交互(写笔记、点赞、删除)
- 交互操作通过 API Routes 与 D1 数据库通信
zihui_notes/
├── app/ # 前端页面和 API
│ ├── layout.tsx # 根布局 —— HTML 外壳、字体加载
│ ├── page.tsx # 首页 —— 服务端组件,从 D1 读数据
│ ├── home.tsx # 客户端交互组件 —— 笔记创建、点赞、背景效果
│ ├── globals.css # 全局样式(网格纸主题、动画、响应式)
│ └── api/
│ ├── notes/
│ │ ├── route.ts # GET /api/notes(获取所有笔记)
│ │ │ POST /api/notes(创建笔记,需管理员权限)
│ │ └── [id]/
│ │ ├── route.ts # DELETE /api/notes/:id(删除笔记,需管理员权限)
│ │ └── like/
│ │ └── route.ts # POST /api/notes/:id/like(点赞/取消点赞,需登录)
│ └── auth/
│ ├── login/route.ts # GET —— 重定向到 GitHub OAuth 授权页
│ ├── callback/route.ts # GET —— GitHub 回调,交换 token、创建会话
│ └── logout/route.ts # POST —— 清除会话 cookie
├── lib/ # 后端业务逻辑
│ ├── db.ts # 数据库操作(笔记 CRUD、点赞、会话管理)
│ ├── auth.ts # 认证逻辑(session cookie、用户身份、管理员判断)
│ └── env.ts # Cloudflare 环境变量获取
├── worker/ # Cloudflare Workers 入口
│ └── index.ts # HTTP 请求入口(vinext 自动生成)
├── migrations/ # 数据库迁移文件(按顺序执行)
│ ├── 0001_init.sql # 初始建表(旧版书评结构,已被 0003 替代)
│ ├── 0003_simplify.sql # 简化为快速笔记结构
│ └── 0004_likes.sql # 添加点赞表
├── vite.config.ts # Vite 构建配置
├── wrangler.jsonc # Cloudflare Workers 配置
├── tsconfig.json # TypeScript 配置
└── package.json # 依赖和脚本命令
所有页面共享的 HTML 外壳。定义了:
<html lang="zh-CN">—— 中文页面- Google Fonts 加载:Bricolage Grotesque(标题/正文)和 JetBrains Mono(等宽字体,用于时间戳、引用等)
- 引入全局样式
globals.css - 页面 metadata(标题和描述)
服务端组件,在 Workers 上运行。做两件事:
- 从请求的 Cookie 中识别当前用户身份
- 从 D1 查询所有笔记(含点赞数据)
然后把 notes 和 user 作为 props 传给客户端组件 <HomePage>。
用户请求 → 服务端执行 page.tsx → 查询 D1 → 渲染 HTML → 返回浏览器
文件顶部的 "use client" 标记,表示这个组件在浏览器运行。包含以下子组件:
| 组件 | 功能 |
|---|---|
QuickCapture |
笔记输入框 + 引用输入 + ⌘+Enter 快捷保存 |
NoteItem |
单条笔记的展示:内容、引用链接、时间、删除按钮 |
LikeButton |
点赞按钮 + 点赞者头像展示,支持乐观更新 |
SiteFooter |
页脚:项目链接、GitHub、版权信息 |
useGridBackground() |
Canvas 网格纸背景动画 + 鼠标交互 ripple 效果 |
背景效果:使用 Canvas 在淡黄色底色上绘制网格线和网格交叉点。鼠标移动时,附近的网格点会被推开并变色(灰→橙),点之间会出现橙色连接线,鼠标离开后弹簧回弹。适配了 devicePixelRatio 以在 Retina 屏幕上保持清晰。
设计系统采用 CSS 变量,主要特征:
- 淡黄色网格纸背景(
--bg: #fdf8ee) - 橙色作为强调色(
--accent: #f38020) - 半透明白色卡片(
--card: rgba(255,255,255,0.85)) - 入场动画(
fadeUp+ 交错延迟) - 点赞按钮弹跳动画(
likePop) - 移动端响应式适配(640px 断点)
| 路由 | 方法 | 功能 | 权限 |
|---|---|---|---|
/api/notes |
GET | 获取所有笔记(含点赞) | 公开 |
/api/notes |
POST | 创建新笔记 | 管理员 |
/api/notes/:id |
DELETE | 删除笔记 | 管理员 |
/api/notes/:id/like |
POST | 点赞/取消点赞(toggle) | 登录用户 |
/api/auth/login |
GET | 跳转 GitHub 授权页 | 公开 |
/api/auth/callback |
GET | GitHub OAuth 回调 | 公开 |
/api/auth/logout |
POST | 退出登录 | 公开 |
所有 D1 数据库交互封装在这个文件中:
| 函数 | 作用 |
|---|---|
getNotes() |
查询所有笔记,并批量查询每条笔记的点赞列表,合并返回 |
createNote() |
插入新笔记,返回自增 ID |
deleteNote() |
删除笔记及其关联的点赞记录 |
toggleLike() |
切换点赞状态:已赞则取消,未赞则添加 |
createSession() |
创建登录会话 |
getSession() |
根据 session ID 查询会话(自动过滤已过期的) |
deleteSession() |
删除会话(退出登录时调用) |
getNotes() 的实现细节:先查 notes 表,再用 WHERE note_id IN (...) 批量查 likes 表,在内存中合并。避免了 N+1 查询问题。
- 从请求的 Cookie 中提取 session ID
- 查询 D1 中的 sessions 表验证身份
- 判断是否为管理员(对比 GitHub 用户名列表)
- Session cookie 设置:
HttpOnly; Secure; SameSite=Lax; Max-Age=604800(7 天过期)
封装 Cloudflare Workers 的环境变量获取。在 Workers 中,环境变量不通过 process.env 获取,而是通过 cloudflare:workers 模块。
提供 DB(D1 数据库)、GITHUB_CLIENT_ID 和 GITHUB_CLIENT_SECRET 三个绑定。
由 vinext 框架自动生成的 Workers 入口文件。每当 HTTP 请求到达时,Workers 调用此文件的 fetch() 函数,由 vinext 路由到对应的页面或 API。
按编号顺序执行的 SQL 文件,用于版本控制数据库结构。
| 文件 | 内容 | 说明 |
|---|---|---|
0001_init.sql |
创建旧版表(notes、recommendations、sessions) | 初始版本,已被 0003 替代 |
0003_simplify.sql |
删除旧表,创建新的简化 notes 表 | 从书评模式转为快速笔记模式 |
0004_likes.sql |
创建 likes 表 | 添加点赞功能 |
注意:编号 0002 是历史遗留的 seed 数据文件,已删除。
GITHUB_CLIENT_SECRET 通过 wrangler secret 设置,不会出现在配置文件中。
plugins: [
vinext(), // vinext 框架插件(处理 App Router、SSR、RSC)
cloudflare() // Cloudflare 插件(打包为 Workers 格式)
]"types": ["@cloudflare/workers-types/experimental"]:提供 Workers API 类型"paths": { "@/*": ["./*"] }:路径别名,@/lib/db解析为./lib/db
项目使用 Cloudflare D1(托管 SQLite),包含 3 张表:
CREATE TABLE notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL, -- 笔记内容
reference TEXT DEFAULT '', -- 引用来源(URL 或文字)
created_at TEXT DEFAULT (datetime('now')) -- 创建时间(UTC)
);
CREATE INDEX idx_notes_created ON notes(created_at DESC);CREATE TABLE likes (
note_id INTEGER NOT NULL, -- 关联的笔记 ID
github_id INTEGER NOT NULL, -- 点赞者的 GitHub 用户 ID
github_login TEXT NOT NULL, -- 点赞者的 GitHub 用户名(用于显示头像)
created_at TEXT DEFAULT (datetime('now')),
PRIMARY KEY (note_id, github_id) -- 联合主键,防止重复点赞
);点赞者头像通过 https://avatars.githubusercontent.com/u/{github_id}?s=32 直接从 GitHub 获取,无需额外存储。
CREATE TABLE sessions (
id TEXT PRIMARY KEY, -- 随机生成的 session ID(64 字符十六进制)
github_id INTEGER NOT NULL, -- GitHub 用户 ID
github_login TEXT NOT NULL, -- GitHub 用户名
created_at TEXT DEFAULT (datetime('now')),
expires_at TEXT NOT NULL -- 过期时间(创建后 7 天)
);数据库经历了一次重大重构:
-
0001_init.sql(初始版本):创建了
notes(书评)、recommendations(推荐书目)、sessions三张表。notes 表包含 title、author、content、cover_url 等书评相关字段。 -
0003_simplify.sql(简化重构):删除了
notes和recommendations表,重建了全新的notes表,只保留content(笔记内容)和reference(引用来源)两个核心字段。这次迁移标志着项目从"读书笔记/书评网站"转型为"快速笔记工具"。 -
0004_likes.sql(添加点赞):创建
likes表,支持登录用户对笔记点赞。
执行迁移的方式:
# 本地
wrangler d1 execute zihui-notes-db --local --file=migrations/0004_likes.sql
# 线上
wrangler d1 execute zihui-notes-db --remote --file=migrations/0004_likes.sql1. 用户点击 "Sign in"
|
2. 重定向到 GitHub 授权页
GET https://github.com/login/oauth/authorize
?client_id=xxx
&redirect_uri=https://notes.zihuichen.com/api/auth/callback
&scope=read:user
|
3. 用户在 GitHub 授权后,GitHub 重定向回
GET /api/auth/callback?code=xxx
|
4. 服务端用 code 向 GitHub 交换 access_token
POST https://github.com/login/oauth/access_token
|
5. 用 access_token 调用 GitHub API 获取用户信息
GET https://api.github.com/user
|
6. 创建 session 存入 D1,设置 HttpOnly cookie
|
7. 重定向回首页,用户已登录
| 角色 | 能做什么 |
|---|---|
| 访客(未登录) | 浏览所有笔记 |
| 登录用户 | 浏览笔记 + 点赞/取消点赞 |
| 管理员 | 浏览笔记 + 点赞 + 创建笔记 + 删除笔记 |
管理员身份在 lib/auth.ts 中通过 GitHub 用户名判断。
1. [管理员] 在 QuickCapture 文本框中输入内容,按 ⌘+Enter
|
2. [home.tsx] 调用 fetch("/api/notes", { method: "POST", body: { content, reference } })
|
3. [api/notes/route.ts] 验证管理员身份 → 调用 createNote()
|
4. [lib/db.ts] INSERT INTO notes (content, reference) VALUES (?, ?)
|
5. [home.tsx] 保存成功后,调用 refresh() → fetch("/api/notes")
|
6. [api/notes/route.ts] 调用 getNotes()
|
7. [lib/db.ts] SELECT * FROM notes + SELECT * FROM likes WHERE note_id IN (...)
|
8. [home.tsx] setNotes(data.notes) → React 重新渲染列表
|
9. [浏览器] 用户看到新笔记出现在列表顶部
页面背景模拟淡黄色网格纸效果:
- 底色
#fdf8ee(暖黄色) - Canvas 绘制棕色细网格线(间距 28px,透明度 0.28)
- 网格交叉点有小圆点
基于 Canvas 的实时动画(参考自 zihuichen.com 首页的点阵效果):
- 鼠标靠近时,网格点被推开(弹簧物理模拟)
- 点的颜色从灰色渐变为橙色
- 附近的点之间出现橙色连接线
- 鼠标离开后,点弹回原位
- 适配
devicePixelRatio保证 Retina 屏清晰
- 点击心形按钮 toggle 点赞状态
- 乐观更新:点击后立即更新 UI,不等服务端返回
- 弹跳动画(
likePop,300ms) - 点赞者的 GitHub 头像叠加排列显示
- Node.js 18+
- npm
# 1. 安装依赖
npm install
# 2. 建立本地数据库
wrangler d1 execute zihui-notes-db --local --file=migrations/0001_init.sql
wrangler d1 execute zihui-notes-db --local --file=migrations/0003_simplify.sql
wrangler d1 execute zihui-notes-db --local --file=migrations/0004_likes.sql注意:本地开发时需要临时修改配置。
编辑 vite.config.ts,去掉 Cloudflare 插件:
import vinext from "vinext";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [vinext()],
});编辑 lib/env.ts,使用本地数据库:
import type { D1Database } from "@cloudflare/workers-types";
interface AppEnv { DB: D1Database; GITHUB_CLIENT_ID: string; GITHUB_CLIENT_SECRET: string; }
export async function getEnv(): Promise<AppEnv> {
const { getPlatformProxy } = await import("wrangler");
const proxy = await getPlatformProxy<AppEnv>();
return proxy.env;
}然后启动:
npx vinext dev --port 3002打开 http://localhost:3002 即可看到网站。
重要:部署前记得把这两个文件改回生产版本。
- Cloudflare 账号
- 登录 wrangler:
wrangler login
# 1. 创建线上 D1 数据库(只需一次)
wrangler d1 create zihui-notes-db
# 记下返回的 database_id,填入 wrangler.jsonc
# 2. 在线上数据库执行迁移
wrangler d1 execute zihui-notes-db --remote --file=migrations/0001_init.sql
wrangler d1 execute zihui-notes-db --remote --file=migrations/0003_simplify.sql
wrangler d1 execute zihui-notes-db --remote --file=migrations/0004_likes.sql
# 3. 设置 GitHub OAuth Secret
npx wrangler secret put GITHUB_CLIENT_SECRET
# 4. 构建并部署
npx vinext build && npx wrangler deploynpx vinext build && npx wrangler deploy- 前往 GitHub Developer Settings 创建 OAuth App
- Homepage URL:
https://notes.zihuichen.com - Authorization callback URL:
https://notes.zihuichen.com/api/auth/callback - 将 Client ID 填入
wrangler.jsonc的GITHUB_CLIENT_ID - 将 Client Secret 通过
npx wrangler secret put GITHUB_CLIENT_SECRET设置
在 wrangler.jsonc 中配置 routes:
"routes": [
{ "pattern": "notes.zihuichen.com", "custom_domain": true }
]部署后 Cloudflare 会自动创建 DNS 记录和 SSL 证书。同时设置 "workers_dev": true 以保留 *.workers.dev 域名作为备用。
| 命令 | 作用 |
|---|---|
npx vinext dev --port 3002 |
启动本地开发服务器 |
npx vinext build |
构建生产版本 |
npx wrangler deploy |
部署到 Cloudflare Workers |
wrangler d1 execute zihui-notes-db --local --file=<sql> |
在本地数据库执行 SQL 文件 |
wrangler d1 execute zihui-notes-db --remote --file=<sql> |
在线上数据库执行 SQL 文件 |
wrangler d1 execute zihui-notes-db --local --command "SQL" |
在本地执行单条 SQL |
wrangler d1 execute zihui-notes-db --remote --command "SQL" |
在线上执行单条 SQL |
npx wrangler secret put GITHUB_CLIENT_SECRET |
设置 OAuth Secret |
wrangler tail |
查看线上实时日志 |
wrangler login |
登录 Cloudflare |
Product By Chen Zihui
{ "name": "zihui-notes", // Workers 项目名 "main": "worker/index.ts", // 入口文件 "compatibility_date": "2026-03-01", // Workers 运行时版本 "compatibility_flags": ["nodejs_compat"], // Node.js 兼容模式 "workers_dev": true, // 同时启用 workers.dev 域名 "d1_databases": [{ "binding": "DB", // 代码中用 env.DB 访问 "database_name": "zihui-notes-db", "database_id": "698723a3-...", "migrations_dir": "migrations" }], "vars": { "GITHUB_CLIENT_ID": "Ov23li..." // GitHub OAuth App 的 Client ID }, "routes": [ { "pattern": "notes.zihuichen.com", "custom_domain": true } ], "r2_buckets": [{ // R2 存储桶(历史遗留,暂未使用) "binding": "IMAGES", "bucket_name": "zihui-notes-images" }] }