From 0b21ff06444ca93c129b9cfc85a407d34d56d02d Mon Sep 17 00:00:00 2001 From: MikiVL Date: Tue, 5 May 2026 05:09:50 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E7=94=A8=E6=88=B7=E8=AE=A4=E8=AF=81+?= =?UTF-8?q?=E4=BA=91=E5=90=8C=E6=AD=A5+=E8=AF=84=E8=AE=BA=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-05-05-user-auth-cloud-sync-design.md | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-05-user-auth-cloud-sync-design.md diff --git a/docs/superpowers/specs/2026-05-05-user-auth-cloud-sync-design.md b/docs/superpowers/specs/2026-05-05-user-auth-cloud-sync-design.md new file mode 100644 index 0000000..834af79 --- /dev/null +++ b/docs/superpowers/specs/2026-05-05-user-auth-cloud-sync-design.md @@ -0,0 +1,238 @@ +# 用户认证 + 云端同步 + 评论系统设计文档 + +**日期:** 2026-05-05 +**作者:** MikiVL +**状态:** 已批准 + +--- + +## 背景与目标 + +当前笔记应用(www.mikivl.online/app)的所有数据存储在浏览器 IndexedDB 中,清除浏览器数据或换设备即丢失。本次扩展目标: + +1. 添加用户账号系统(用户名+密码注册/登录) +2. 登录用户的笔记双存——本地 IndexedDB 继续作为缓存,服务端 SQLite 持久化 +3. 云存储功能需邀请码激活(预生成 10 个) +4. 主页(www.mikivl.online)新增评论区,登录用户可发评论 + +--- + +## 架构总览 + +``` +前端(React) 服务端(Hono + Node.js) 数据库(SQLite + Drizzle) +──────────────── ────────────────────── ─────────────────────────── +IndexedDB(本地) ←→ /api/auth/* users + /api/notes/* ←──── notes + /api/folders/* ←──── folders + /api/comments/* ←──── comments + /api/ai/*(已有) invite_codes +``` + +--- + +## 数据库 Schema + +### users +```sql +id TEXT PRIMARY KEY -- nanoid +username TEXT UNIQUE NOT NULL +passwordHash TEXT NOT NULL -- bcrypt hash +cloudEnabled INTEGER DEFAULT 0 -- 0=未激活,1=已激活(填写邀请码后) +createdAt INTEGER NOT NULL +``` + +### notes +```sql +id TEXT PRIMARY KEY +userId TEXT NOT NULL REFERENCES users(id) +title TEXT NOT NULL DEFAULT '' +content TEXT NOT NULL DEFAULT '' +folderId TEXT -- null = 根目录 +tags TEXT NOT NULL DEFAULT '[]' -- JSON 数组 +starred INTEGER DEFAULT 0 +wordCount INTEGER DEFAULT 0 +deletedAt INTEGER -- null = 未删除,软删除时间戳 +createdAt INTEGER NOT NULL +updatedAt INTEGER NOT NULL +``` + +### folders +```sql +id TEXT PRIMARY KEY +userId TEXT NOT NULL REFERENCES users(id) +name TEXT NOT NULL +parentId TEXT -- null = 根目录 +order INTEGER DEFAULT 0 +createdAt INTEGER NOT NULL +``` + +### comments +```sql +id TEXT PRIMARY KEY +userId TEXT NOT NULL REFERENCES users(id) +content TEXT NOT NULL +createdAt INTEGER NOT NULL +``` + +### invite_codes +```sql +code TEXT PRIMARY KEY +usedByUserId TEXT -- null = 未使用 +usedAt INTEGER -- null = 未使用 +``` + +--- + +## 认证方案 + +- **JWT**,签发时有效期 30 天,存储在 `localStorage`(key: `mikivl_token`) +- 无 refresh token(30 天足够,过期重新登录) +- 密码使用 bcrypt hash(cost factor 10) +- 所有 `/api/notes/*`、`/api/folders/*` 接口需携带 `Authorization: Bearer ` + +--- + +## 同步策略 + +**本地优先,登录后同步:** + +1. **未登录**:完全使用 IndexedDB,功能不受影响 +2. **登录时**:执行全量同步——拉取云端数据与本地合并,以 `updatedAt` 较新的为准 +3. **本地写操作**(create/update/delete):操作完成后立即异步推送到云端(fire-and-forget,失败静默) +4. **冲突处理**:同一条笔记本地和云端都有修改,取 `updatedAt` 较大的版本 + +**同步接口:** + +- `GET /api/notes/sync` — 返回该用户全量笔记和文件夹 +- `POST /api/notes/sync` — 批量 upsert 笔记和文件夹(幂等) + +--- + +## API 接口定义 + +### 认证 + +``` +POST /api/auth/register +Body: { username: string, password: string } +Response: { token: string, user: { id, username, cloudEnabled } } + +POST /api/auth/login +Body: { username: string, password: string } +Response: { token: string, user: { id, username, cloudEnabled } } + +POST /api/auth/activate +Header: Authorization: Bearer +Body: { code: string } +Response: { success: true } +``` + +### 同步 + +``` +GET /api/notes/sync +Header: Authorization: Bearer +Response: { notes: Note[], folders: Folder[] } + +POST /api/notes/sync +Header: Authorization: Bearer +Body: { notes: Note[], folders: Folder[] } +Response: { synced: number } +``` + +### 笔记 CRUD(单条操作,供实时同步用) + +``` +PUT /api/notes/:id -- upsert 单条笔记 +DELETE /api/notes/:id -- 软删除(设置 deletedAt) +PUT /api/folders/:id -- upsert 单条文件夹 +DELETE /api/folders/:id -- 删除文件夹 +``` + +### 评论 + +``` +GET /api/comments -- 获取所有评论(公开) +POST /api/comments -- 发布评论(需登录) +Header: Authorization: Bearer +Body: { content: string } +Response: { id, username, content, createdAt } +``` + +--- + +## 前端改动 + +### 新增文件 +- `src/lib/auth.ts` — JWT 存取、login/register/activate API 封装 +- `src/lib/sync.ts` — 全量同步、单条推送、冲突合并逻辑 +- `src/components/auth/LoginModal.tsx` — 登录/注册弹窗(Tab 切换),含邀请码激活入口 +- `src/components/auth/UserMenu.tsx` — 侧边栏底部用户区域(头像、用户名、云同步状态、登出) + +### 修改文件 +- `src/stores/appStore.ts` — 添加 `currentUser`、`syncStatus` state;写操作(createNote/updateNote/deleteNote/restoreNote/createFolder/updateFolder/deleteFolder)完成后触发 `sync.pushOne()` +- `src/components/sidebar/Sidebar.tsx` — 底部添加 `` +- `homepage/index.html` — 底部新增评论区(读取 `/api/comments`,登录用户可发评论) + +### appStore 新增 state +```typescript +currentUser: { id: string; username: string; cloudEnabled: boolean } | null +syncStatus: 'idle' | 'syncing' | 'error' +``` + +--- + +## 服务端文件结构 + +``` +server/ +├── index.ts (已有,注册新路由) +├── db.ts (Drizzle 初始化,schema 定义,DB 文件路径:/opt/mikivl/data/app.db) +├── middleware/ +│ └── auth.ts (JWT 验证,注入 c.set('userId', ...)) +└── routes/ + ├── auth.ts (register/login/activate) + ├── notes.ts (sync GET/POST + 单条 CRUD) + ├── folders.ts (单条 CRUD) + └── comments.ts (list/create) +``` + +--- + +## 邀请码 + +预生成 10 个,数据库初始化时写入: + +``` +MIKI-A7X2-KP9Q +MIKI-B3N8-WR4L +MIKI-C6M1-ZT7Y +MIKI-D9F5-HJ2V +MIKI-E4K3-QN6U +MIKI-F8R7-XG1S +MIKI-G2W4-LB5E +MIKI-H5T9-MC8D +MIKI-J1P6-VF3O +MIKI-K7Q2-YH4N +``` + +--- + +## 部署变更 + +- 服务端新增依赖:`drizzle-orm`、`better-sqlite3`、`bcryptjs`、`jsonwebtoken` +- SQLite 数据库文件:`/opt/mikivl/data/app.db`(首次启动自动创建) +- 服务器需创建目录:`mkdir -p /opt/mikivl/data` +- PM2 重启 hono-server 后生效 + +--- + +## 分拆子项目顺序 + +由于子系统间有依赖关系,按以下顺序实现: + +1. **服务端基础层**(db.ts + schema + auth 路由) +2. **前端认证**(LoginModal + UserMenu + auth.ts + appStore 扩展) +3. **云同步**(sync.ts + notes/folders 路由 + appStore 写操作触发同步) +4. **首页评论**(comments 路由 + homepage/index.html 评论区)