概述
将 Bookmark 阅读器从纯前端应用升级为全栈应用,实现数据持久化、多设备同步、用户系统等能力。
技术栈:React + TypeScript(前端)| NestJS(后端)| PostgreSQL(数据库)| Prisma(ORM)
一、功能清单
1.1 核心功能(MVP — 阶段一完成)
| 模块 |
功能点 |
说明 |
优先级 |
| 阅读器 |
EPUB 阅读 |
解析并渲染 EPUB 格式电子书 |
P0 |
| 阅读器 |
PDF 阅读 |
内嵌 PDF 阅读器 |
P0 |
| 阅读器 |
Web 阅读 |
网页内容阅读(剪藏后) |
P1 |
| 阅读器 |
页面缩放 |
阅读页面支持放大/缩小 |
P1 |
| 笔记系统 |
行内笔记 |
选中文本后添加笔记 |
P0 |
| 高亮系统 |
文本高亮 |
选中文本高亮标注,支持多色 |
P0 |
| 阅读进度 |
进度同步 |
记录并同步每本书的阅读位置 |
P0 |
| AI 助手 |
AI 对话 |
基于书籍内容的 AI 对话 |
P1 |
| 网页剪藏 |
内容剪藏 |
通过浏览器插件/URL 保存网页内容 |
P1 |
1.2 基础设施(阶段一必须)
| 模块 |
功能点 |
说明 |
优先级 |
| 用户系统 |
注册/登录 |
邮箱注册 + JWT Token 认证 |
P0 |
| 用户系统 |
个人设置 |
阅读偏好、主题等设置 |
P2 |
| 文件管理 |
书籍上传 |
上传 EPUB/PDF 文件到服务端存储 |
P0 |
| 文件管理 |
文件安全 |
文件类型校验、大小限制、防恶意文件 |
P0 |
| 权限控制 |
接口认证 |
JWT 中间件保护所有需登录接口 |
P0 |
1.3 增强功能(阶段二~三)
| 模块 |
功能点 |
说明 |
优先级 |
| 标签系统 |
书籍标签 |
给书籍打标签,支持筛选 |
P2 |
| 数据仪表盘 |
阅读统计 |
阅读时长、每日/每周趋势图 |
P2 |
| 数据仪表盘 |
笔记统计 |
笔记/高亮数量、热力图 |
P2 |
| 分享功能 |
笔记分享 |
生成公开分享链接(无需登录) |
P3 |
| AI 历史 |
对话持久化 |
AI 对话历史保存与回溯 |
P3 |
二、数据分析
2.1 数据分类与同步优先级
核心原则:丢了会心疼的数据最优先同步。用户的思考(笔记、高亮)不可重建,是最高优先级。
| 数据类型 |
丢了心疼吗? |
同步优先级 |
同步策略 |
存储位置 |
| 笔记和高亮 |
最心疼(自己的思考,无法重建) |
最高 |
操作即保存(增量) |
PostgreSQL |
| 书籍文件(EPUB/PDF) |
很心疼(可能付费/难找) |
高 |
上传时保存 |
文件系统/S3 |
| 阅读进度 |
有点烦(但可以重新翻到) |
中 |
定时同步(如30s一次) |
PostgreSQL |
| 网页剪藏内容 |
中等(可以重新剪藏) |
中 |
剪藏时保存 |
PostgreSQL + 文件存储 |
| AI 对话历史 |
看情况 |
低 |
后期再做 |
暂不存储 |
| 缩放/显示偏好 |
无所谓 |
低 |
仅存本地 |
localStorage |
2.2 数据实体关系
User(用户)
├── Book(书籍) 1:N 一个用户有多本书
│ ├── Note(笔记) 1:N 一本书有多条笔记
│ ├── Highlight(高亮) 1:N 一本书有多条高亮
│ ├── Progress(进度) 1:1 一本书一条进度记录
│ └── Tag(标签) M:N 书和标签多对多
└── Setting(设置) 1:1 一个用户一份设置
2.3 核心表结构预览
User 表
| 字段 |
类型 |
说明 |
| id |
UUID |
主键 |
| email |
VARCHAR(255) |
唯一,登录用 |
| password_hash |
VARCHAR(255) |
bcrypt 哈希,绝不存明文 |
| nickname |
VARCHAR(50) |
显示名 |
| created_at |
TIMESTAMP |
注册时间 |
Book 表
| 字段 |
类型 |
说明 |
| id |
UUID |
主键 |
| user_id |
UUID |
外键 → User |
| title |
VARCHAR(500) |
书名 |
| author |
VARCHAR(255) |
作者 |
| format |
ENUM |
epub / pdf / web |
| file_path |
VARCHAR(1000) |
文件存储路径 |
| cover_url |
VARCHAR(1000) |
封面图 URL |
| file_size |
BIGINT |
文件大小(字节) |
| created_at |
TIMESTAMP |
添加时间 |
Note 表
| 字段 |
类型 |
说明 |
| id |
UUID |
主键 |
| book_id |
UUID |
外键 → Book |
| content |
TEXT |
笔记内容 |
| cfi_range |
VARCHAR(500) |
EPUB 定位(CFI) |
| page_number |
INT |
PDF 页码 |
| created_at |
TIMESTAMP |
创建时间 |
| updated_at |
TIMESTAMP |
修改时间 |
Highlight 表
| 字段 |
类型 |
说明 |
| id |
UUID |
主键 |
| book_id |
UUID |
外键 → Book |
| text |
TEXT |
高亮原文 |
| color |
VARCHAR(20) |
高亮颜色 |
| position |
JSONB |
定位信息(利用 PostgreSQL 的 JSONB 类型) |
| created_at |
TIMESTAMP |
创建时间 |
Progress 表
| 字段 |
类型 |
说明 |
| id |
UUID |
主键 |
| book_id |
UUID |
外键 → Book(唯一约束) |
| location |
JSONB |
阅读位置(CFI/页码) |
| percentage |
DECIMAL(5,2) |
阅读百分比 |
| updated_at |
TIMESTAMP |
最后更新时间 |
三、系统架构
3.1 整体架构图
┌─────────────────────────────────────────────────────────────┐
│ 客户端层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ React SPA │ │ 浏览器插件 │ │ 未来:移动端 │ │
│ │ (阅读器) │ │ (网页剪藏) │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────────┘ │
└─────────┼─────────────────┼─────────────────────────────────┘
│ HTTPS │ HTTPS
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ API 网关层 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ NestJS 后端服务 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────────┐ │ │
│ │ │ Auth │ │ Book │ │ Note │ │ Highlight │ │ │
│ │ │ Module │ │ Module │ │ Module │ │ Module │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └───────────┘ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Upload │ │Progress │ │ AI Chat │ │ │
│ │ │ Module │ │ Module │ │ Module │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 公共层:JWT Guard | 日志 | 异常过滤器 | 校验管道 │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────┬───────────────────┬───────────────────┘
│ Prisma ORM │ 文件 I/O
▼ ▼
┌────────────────────────────┐ ┌──────────────────────┐
│ PostgreSQL 16 │ │ 文件存储 │
│ ┌──────┐ ┌──────┐ │ │ 开发:本地磁盘 │
│ │ User │ │ Book │ ... │ │ 生产:S3/OSS │
│ └──────┘ └──────┘ │ └──────────────────────┘
└────────────────────────────┘
3.2 请求流程
用户操作
│
▼
React 前端(统一 HTTP 封装,请求头带 JWT Token)
│
▼
NestJS 路由 → JWT Guard(验证 token)→ 校验管道(参数校验)
│
▼
Controller → Service(业务逻辑)→ Prisma Client(数据库操作)
│
▼
PostgreSQL 返回数据 → Service 处理 → Controller 返回 JSON
│
▼
React 前端渲染
3.3 认证流程
注册:
用户填写邮箱密码 → POST /auth/register → bcrypt 哈希密码 → 存 User 表 → 返回成功
登录:
用户填写邮箱密码 → POST /auth/login → 校验密码 → 签发 JWT(含 userId, email)→ 返回 token
访问受保护接口:
前端请求头 Authorization: Bearer <token> → JWT Guard 解析验证 → 注入 userId 到请求上下文
不需要登录的接口:
POST /auth/register, POST /auth/login, GET /share/:id(分享页)
3.4 数据同步策略
| 操作类型 |
同步方式 |
说明 |
| 新增笔记 |
即时增量 |
POST /books/:id/notes,只传新增的一条 |
| 修改高亮 |
即时增量 |
PATCH /highlights/:id,只传变更字段 |
| 删除笔记 |
即时 |
DELETE /notes/:id |
| 阅读进度 |
定时批量 |
前端每 30s 调用 PUT /books/:id/progress |
| 上传书籍 |
即时 |
POST /books/upload,multipart/form-data |
关键设计决策:采用增量操作而非全量覆盖。每次只传变化的数据,避免"200条高亮每次全传"的浪费,也避免多设备全量覆盖导致数据丢失。
四、技术选型
4.1 后端框架:NestJS
| 对比维度 |
Express |
Koa |
NestJS |
Fastify |
| 代码组织 |
无约束,项目大了容易乱 |
无约束 |
模块化架构,Controller/Service/Module 分层清晰 |
插件系统,不如 NestJS 规范 |
| TypeScript |
需手动配置 |
需手动配置 |
原生 TS,开箱即用 |
良好支持 |
| 依赖注入 |
无 |
无 |
内建 DI 容器 |
无 |
| 内置功能 |
极少,需逐一找中间件 |
极少 |
认证、校验、Swagger、WebSocket 等开箱即用 |
较少 |
| 学习曲线 |
低 |
低 |
中(有 Angular 经验更快) |
低 |
| 生态 |
最大 |
中等 |
大(兼容 Express 生态) |
增长中 |
选择理由(在 Bookmark 场景下):
- Bookmark 需要用户认证、文件上传、参数校验等,NestJS 全部内置,不用逐一寻找和集成中间件
- 模块化架构天然适合按功能拆分(Auth Module、Book Module、Note Module)
- 原生 TypeScript 与前端 React+TS 共享类型定义
- 依赖注入让单元测试更容易 mock
4.2 数据库:PostgreSQL
| 对比维度 |
PostgreSQL |
MySQL |
MongoDB |
SQLite |
| 关系支持 |
强,原生 JOIN |
强 |
弱,无原生 JOIN |
强 |
| JSON 支持 |
JSONB(可索引查询) |
JSON(不可索引) |
原生文档 |
JSON 函数有限 |
| 全文搜索 |
内置 tsvector |
需 FULLTEXT 索引 |
内置 |
需扩展 |
| 事务 |
完整 ACID |
完整 ACID |
多文档事务性能差 |
完整但单写者 |
| 并发 |
MVCC,高并发 |
锁粒度较粗 |
高 |
单写者,不适合多用户 |
选择理由(在 Bookmark 场景下):
- 书 ↔ 笔记 ↔ 高亮 ↔ 标签是典型的关系型数据,需要 JOIN 查询
JSONB 类型完美适配高亮定位信息(position 字段结构灵活但需要查询)
- 内置全文搜索未来可直接用于"搜索所有笔记内容"
- MongoDB 的多对多关系(书 ↔ 标签)需要手动维护引用,事务性能差,不适合这个场景
4.3 ORM:Prisma
| 对比维度 |
Prisma |
TypeORM |
Drizzle |
MikroORM |
| Schema 定义 |
独立 .prisma 文件,声明式 |
装饰器(与代码耦合) |
TypeScript 代码 |
装饰器 |
| 类型安全 |
从 Schema 自动生成完整 TS 类型 |
手动标注,运行时才报错 |
良好 |
中等 |
| 迁移 |
prisma migrate 自动生成 SQL |
复杂,容易出问题 |
手动为主 |
支持但较弱 |
| 查询 API |
直觉式链式调用 |
QueryBuilder 复杂 |
接近 SQL |
接近 SQL |
| 可视化 |
Prisma Studio(图形界面看数据) |
无 |
无 |
无 |
| 学习曲线 |
低 |
高(装饰器+多种模式) |
中 |
中 |
选择理由(在 Bookmark 场景下):
- 从 Schema 自动生成完整 TypeScript 类型,查询结果直接有类型提示,前后端类型可共享
prisma migrate dev 一键生成迁移文件,数据库变更安全可追溯
- Prisma Studio 方便开发阶段直接查看和编辑数据
- 对于 Bookmark 项目的 CRUD 为主的场景,Prisma 的声明式查询比 TypeORM 的 QueryBuilder 更直观
4.4 文件存储策略
| 阶段 |
方案 |
说明 |
| 开发阶段 |
本地磁盘 |
存在项目 uploads/ 目录,简单直接 |
| 生产阶段 |
对象存储(S3/阿里云 OSS) |
持久可靠、CDN 加速、按量付费 |
文件上传安全检查清单:
- 文件类型白名单:仅允许
.epub、.pdf
- 文件大小限制:单文件 ≤ 100MB
- 文件名重命名:使用 UUID 防止路径穿越攻击
- MIME 类型校验:不仅看后缀,还要检查文件头魔数
五、API 设计概览
5.1 认证模块
| 方法 |
路径 |
说明 |
需认证 |
| POST |
/auth/register |
注册 |
否 |
| POST |
/auth/login |
登录,返回 JWT |
否 |
| GET |
/auth/profile |
获取当前用户信息 |
是 |
5.2 书籍模块
| 方法 |
路径 |
说明 |
需认证 |
| GET |
/books |
获取书籍列表 |
是 |
| POST |
/books/upload |
上传书籍文件 |
是 |
| GET |
/books/:id |
获取书籍详情 |
是 |
| DELETE |
/books/:id |
删除书籍 |
是 |
| PUT |
/books/:id/progress |
更新阅读进度 |
是 |
5.3 笔记模块(增量操作)
| 方法 |
路径 |
说明 |
需认证 |
| GET |
/books/:id/notes |
获取一本书的所有笔记 |
是 |
| POST |
/books/:id/notes |
新增一条笔记 |
是 |
| PATCH |
/notes/:id |
修改笔记内容 |
是 |
| DELETE |
/notes/:id |
删除笔记 |
是 |
5.4 高亮模块(增量操作)
| 方法 |
路径 |
说明 |
需认证 |
| GET |
/books/:id/highlights |
获取一本书的所有高亮 |
是 |
| POST |
/books/:id/highlights |
新增一条高亮 |
是 |
| PATCH |
/highlights/:id |
修改高亮 |
是 |
| DELETE |
/highlights/:id |
删除高亮 |
是 |
六、项目目录结构
bookmark-backend/
├── prisma/
│ ├── schema.prisma # 数据模型定义
│ └── migrations/ # 数据库迁移文件
├── src/
│ ├── main.ts # 入口文件
│ ├── app.module.ts # 根模块
│ ├── auth/ # 认证模块
│ │ ├── auth.module.ts
│ │ ├── auth.controller.ts
│ │ ├── auth.service.ts
│ │ ├── jwt.strategy.ts
│ │ └── dto/
│ ├── book/ # 书籍模块
│ │ ├── book.module.ts
│ │ ├── book.controller.ts
│ │ ├── book.service.ts
│ │ └── dto/
│ ├── note/ # 笔记模块
│ ├── highlight/ # 高亮模块
│ ├── progress/ # 进度模块
│ ├── upload/ # 文件上传模块
│ └── common/ # 公共层
│ ├── guards/ # JWT Guard
│ ├── filters/ # 异常过滤器
│ ├── pipes/ # 校验管道
│ └── interceptors/ # 日志拦截器
├── uploads/ # 文件存储(开发环境)
├── test/ # 测试文件
├── .env # 环境变量(不提交到 git)
├── nest-cli.json
├── package.json
└── tsconfig.json
七、开发路线图
| 阶段 |
天数 |
里程碑 |
| 基础搭建 |
Day 1-3 |
项目初始化 + 数据库设计 + Prisma Schema |
| 用户系统 |
Day 4-5 |
注册/登录 + JWT 认证 |
| 书籍管理 |
Day 6-8 |
书籍 CRUD + 文件上传 + 笔记高亮同步 |
| 测试验证 |
Day 9 |
接口测试 + 错误处理 |
| 前后端联调 |
Day 10-14 |
前端对接后端 API + IndexedDB 数据迁移 |
| 部署上线 |
Day 21-25 |
Docker 部署 + CI/CD + 域名配置 |
| 数据可视化 |
Day 46-65 |
阅读统计仪表盘 |
八、风险与注意事项
| 风险 |
应对策略 |
| IndexedDB 数据迁移可能丢数据 |
写迁移脚本,迁移前自动备份到本地文件 |
| 大文件上传超时 |
分片上传 + 断点续传(生产阶段再做) |
| JWT 泄露风险 |
Token 有效期 7 天 + Refresh Token 机制 |
| 多设备冲突 |
增量操作 + 按时间戳合并,冲突时保留最新 |
| 数据库性能 |
单用户场景暂不用担心,关键字段加索引即可 |