跳转到主要内容
依人相的月光集市
← 返回首页2026-04-07· 约 7 分钟

Bookmark Reader 全栈技术设计方案

概述

将 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 机制
多设备冲突 增量操作 + 按时间戳合并,冲突时保留最新
数据库性能 单用户场景暂不用担心,关键字段加索引即可