studynote/docs/superpowers/specs/2026-05-05-user-auth-cloud-sync-design.md

239 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 用户认证 + 云端同步 + 评论系统设计文档
**日期:** 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 token30 天足够,过期重新登录)
- 密码使用 bcrypt hashcost factor 10
- 所有 `/api/notes/*``/api/folders/*` 接口需携带 `Authorization: Bearer <token>`
---
## 同步策略
**本地优先,登录后同步:**
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 <token>
Body: { code: string }
Response: { success: true }
```
### 同步
```
GET /api/notes/sync
Header: Authorization: Bearer <token>
Response: { notes: Note[], folders: Folder[] }
POST /api/notes/sync
Header: Authorization: Bearer <token>
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 <token>
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` — 底部添加 `<UserMenu />`
- `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 评论区)