本地持久化与备份
本地持久化与备份 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
数据是个人知识站的命根子。本课给数据安家:SQLite 文件固定在 data/ 目录,配上导出、恢复和备份格式版本迁移三件套,最后用「删库重来」的狠招验证备份真的能救回记录。
问题场景
重装系统、或手滑删了 data/ 目录——三个月的笔记、进度、划线全部蒸发。又或者备份是三个月前导出的,字段结构已经对不上新代码。没有备份策略的数据等于不存在;没有版本概念的备份等于定时炸弹。
核心解释
先安家,再备份。 数据目录不能写死在代码里,用环境变量留出口:
// lib/db/client.ts(节选)
import fs from "node:fs";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
export const DATA_DIR =
process.env.REACTLAB_DATA_DIR ?? path.join(process.cwd(), "data");
export const DB_PATH = path.join(DATA_DIR, "database", "reactlab.db");
export function getDb(): DatabaseSync {
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
const db = new DatabaseSync(DB_PATH);
db.exec("PRAGMA journal_mode = WAL;");
return db;
}
导出:把数据库拍成一份带版本号的 JSON。
// app/api/backup/export/route.ts(核心逻辑)
import { getDb } from "@/lib/db/client";
export async function GET() {
const db = getDb();
const backup = {
backupVersion: 1, // 格式版本,恢复时的兼容依据
exported_at: new Date().toISOString(),
notes: db.prepare("SELECT * FROM note").all(),
progress: db.prepare("SELECT * FROM lesson_progress").all(),
};
return new Response(JSON.stringify(backup, null, 2), {
headers: {
"content-type": "application/json",
"content-disposition": 'attachment; filename="reactlab-backup.json"',
},
});
}
恢复:合并式导入。 逐条按主键比对——库里没有就插入,有就跳过或按 updated_at 新者胜,已存在的本地记录不被旧备份覆盖。整个导入包在事务里,中途失败整体回滚,不会留下半份残局。
版本迁移。 备份格式升级时(比如 v2 给笔记加了 tags 字段),导入入口先看 backupVersion:v1 就补上默认字段再入库,而不是让 SQL 报错。数据库本身的结构升级走迁移脚本(N15 展开),两者各管一段:迁移管库结构,backupVersion 管备份文件格式。
示例拆解
真刀真枪验证「记录不丢」:
- 写几条笔记,执行
curl http://localhost:3000/api/backup/export -o backup.json。 - 打开导出文件,确认 backupVersion 与数据条数符合预期。
- 把 data/database/ 整个改名藏起来(模拟删库),重启应用——空库自动重建。
- 调用导入接口恢复 backup.json,刷新页面——笔记全部回来,created_at 保持原值。
关键点:验证备份的唯一可信方式就是「真的恢复一次」。没走过恢复流程的备份只是心理安慰。
动手任务
在本机工程里完成:
- 确认数据都落在 REACTLAB_DATA_DIR(默认 data/)下,把该目录加入 .gitignore。
- 验证或实现 export 路由,导出文件必须含 backupVersion 字段。
- 完整走一次「藏库 → 重建 → 导入恢复」,核对笔记数量与字段完整性。
常见错误
- 备份只导了单张表:恢复后笔记在、进度丢;导出范围要覆盖所有业务表。
- 导入不做事务:第 37 条失败留下前 36 条,重试又重复导入;事务保证原子性。
- backupVersion 写在文档里而不是数据里:恢复代码拿不到版本号,迁移无从谈起。
- 把 data/ 提交进 git:二进制库文件每次运行都产生无意义 diff,还可能泄漏私密笔记。
检查与复盘
自查清单:
- 改 REACTLAB_DATA_DIR 换目录启动,数据目录跟着走
- 完整走过一次删除-恢复循环,记录数与字段核对无误
- 备份文件自描述:版本号、时间戳、数据齐全
参考资料
- Node.js 文档:node:sqlite —— 内置 SQLite 模块
- Next.js 文档:环境变量 —— DATA_DIR 这类配置的注入方式
- SQLite 文档:WAL 模式 —— 本项目启用的日志模式
读完这一课了?
标记完成后,工作台会安排接下来的学习。