Skip to content

系统架构

本文档描述 Lampio 桌面端的当前系统架构。 修改时间以 git log -1 -- docs/architecture.md 为准——手写的"最后更新"必然漂移 (本行原写着 2026-03-29,而文件实际一直在改)。


目录

  1. 技术选型
  2. 系统架构图
  3. Multi-Webview 架构
  4. Rust 后端模块
  5. 前端状态管理
  6. Content-Script 架构
  7. IPC 通信模式
  8. Auth & Sync 数据流
  9. 关键技术决策

1. 技术选型

选择备选选择理由
桌面框架Tauri 2.10.3Electron包体 3-15MB vs 120MB,内存 50-150MB vs 500MB,未来支持 iOS/Android
前端React 19 + TypeScriptVue, Svelte生态成熟,TypeScript 严格模式
构建Vite 8Webpack开发体验,HMR 速度
样式Tailwind CSS 4CSS Modules@theme 自定义主题,实用优先
状态ZustandRedux, Jotai轻量,无 boilerplate,hook-based
本地数据库SQLite (双通道)IndexedDB结构化查询,Rust 直接访问
云端同步SupabaseFirebase, 自建PostgreSQL + Auth + RLS,免运维
SRS 算法SM-2FSRS与 RVH 移动端统一,2-button 简化

2. 系统架构图

┌─────────────────────────────────────────────────────┐
│                    Tauri Window                      │
│                                                     │
│  ┌──────────────────┐  ┌──────────────────────────┐ │
│  │   Main Webview   │  │   Content Webview (×N)   │ │
│  │   (React App)    │  │   content-0, content-1.. │ │
│  │                  │  │                          │ │
│  │  ┌────────────┐  │  │  ┌────────────────────┐  │ │
│  │  │  Zustand    │  │  │  │  content-script.js │  │ │
│  │  │  Stores (7) │  │  │  │  (include_str!)    │  │ │
│  │  └────────────┘  │  │  └────────────────────┘  │ │
│  │        │         │  │        │                  │ │
│  │   invoke()       │  │  __TAURI_INTERNALS__     │ │
│  │        │         │  │  .invoke()               │ │
│  └────────┼─────────┘  └────────┼─────────────────┘ │
│           └──────────┬──────────┘                    │
│                      ▼                               │
│  ┌──────────────────────────────────────────────┐   │
│  │              Rust Backend                     │   │
│  │  ┌─────────┐ ┌─────────┐ ┌───────────────┐  │   │
│  │  │Commands │ │  DbConn │ │ HTTP Client   │  │   │
│  │  │ (19个)  │ │ (Mutex) │ │ (reqwest)     │  │   │
│  │  └────┬────┘ └────┬────┘ └───────┬───────┘  │   │
│  │       │           │              │           │   │
│  └───────┼───────────┼──────────────┼───────────┘   │
└──────────┼───────────┼──────────────┼───────────────┘
           │           │              │
      ┌────▼────┐ ┌────▼────┐  ┌─────▼──────┐
      │ rusqlite│ │ tauri-  │  │  Supabase  │
      │  0.31   │ │plugin-sql│  │  (REST)    │
      └────┬────┘ └────┬────┘  └─────┬──────┘
           └─────┬─────┘             │
            ┌────▼────┐        ┌─────▼──────┐
            │ SQLite  │        │ PostgreSQL │
            │ (本地)   │  sync  │  (云端)    │
            └─────────┘ ◄────► └────────────┘

3. Multi-Webview 架构

窗口创建

  • tauri.conf.jsonwindows: [](空数组)
  • 窗口在 setup() 中手动创建
  • Main webview:auto_resize = true,label "main"
  • Content webviews:动态创建,label "content-{id}"

多标签实现

Tab 0 (active)  → content-0 webview  → 可见位置
Tab 1           → content-1 webview  → offscreen (-10000px)
Tab 2           → content-2 webview  → offscreen (-10000px)
  • 切换标签 = 移动 webview 位置(不销毁)
  • 创建标签 = Window::add_child() 新建 webview
  • 关闭标签 = 销毁 webview

Content-Script 注入

rust
// webview.rs
let script = include_str!("../content-script.js");
webview_builder = webview_builder.initialization_script(script);

每个 content webview 创建时自动注入 content-script.js


4. Rust 后端模块

主要 command 模块,按职责分组(全量清单见 CLAUDE.md §2「Rust Commands 模块」, 当前 23 个 —— 本表只收有独立职责说明价值的,不逐一追平):

分组模块职责
核心浏览webviewContent webview CRUD + 导航
favorite_sites站点收藏
history资源访问历史
词汇学习dictionary查词(vocabulary 表 + Supabase 缓冲池回填,按词性返回结构化释义)
vocabulary词汇保存/查询/删除(soft-delete 感知)
srsSM-2 复习(2-button Hard/Easy)
阅读增强reading阅读难度分析 + Reader 模式
translateAI 翻译(DeepL/DeepSeek/OpenAI)
analysisAI 句子分析
notes阅读笔记管理
内容源rssRSS 订阅 + 文章缓存
text_source粘贴文本来源(第三类阅读来源):落库/回填/笔记本查询 + 剪贴板预检。编辑态归 Reactcomponents/TextDraftEditor.tsx),本模块只提供命令
云端authSupabase Auth(signup/signin/signout/refresh)
sync同步引擎(push/pull + 墓碑传播)。协议全景见 sync-protocol.md;表数不在这里记(会漂),见 database-tables-overview.md §8
snapshot快照文件「在哪、缺了怎么办」:library 路径解析(按用户隔离)+ 云端按需取回 + 存量目录迁移
supabaseSupabase REST 工具
基础设施settingsKey-value 设置存储
dataCSV 导入/导出
init应用初始化
ankiAnki 导出

5. 前端状态管理

8 个 Zustand stores:

Store职责持久化
useTabsStore多标签状态(tabs[], activeTabId, CRUD)
useVocabStore词汇列表、过滤、分页
useAuthStoreAuth session + 自动同步生命周期(60s polling)token 持久化到 settings 表
useSettingsStore翻译 API 配置、主题等settings 表
useNavigationStore当前 URL、导航状态
useTranslationStore翻译面板状态
useThemeStore明暗主题切换localStorage
useReviewStore复习卡片 + Focus Review 状态机(isInFocusSession / sessionFilter / preFocusSnapshot / startSession / endSession)

顶层导航(Workspace 模块 / ActivityBar)

useWorkspaceStore.activeModule 驱动左 rail(ActivityBar)在 7 个认知模式间硬切换: Discover / Read / Review / Library / Stats(上区)+ Settings / Account(下区)。启动默认落 discover(不持久化)。

  • Discover = 全宽渲染 HomePage(推荐/站点/feeds/续读),产品进水口。Home 不再是 tab——App-shell 重构(2026-08-01,app-shell-redesign-plan.md)后从 Read 的 tab 系统移出、成独立模块(useTabsStore 初始 tabs:[REVIEW_TAB]activeTabId:'')。点文章/站点 → 开 tab 并切到 Read。
  • Read = 浏览 tab 条(TitleBar)+ AddressBar chrome;无浏览 tab 时显 ReadEmptyState(引导去 Discover)。
  • 顶栏语汇(方向 A,2026-08-01):标题栏是一条独立的栏bg-surface-chrome,不与下方 toolbar/内容黏连), 跨模块统一。Read 顶栏放浏览器 tab,但激活态 = 浮起的白色圆角 pillbg-surface-content+shadow, TitleBar),未激活 = 纯文字——不再是 Chrome 融合式。其余模块顶栏 = ModuleTitleBar(红绿灯 + 左对齐 安静模块标题,无 tab 药丸、无图标、不印 Lampio)——rail 已是顶层导航,标题栏不复述它。子区切换下沉到内容 顶部做段控SegmentedGroup+TabButton):Library [词汇│笔记]LibraryPanel 顶)、Review [复习│重温]ReviewPanel 卡侧栏顶);单一表面模块(Discover/Stats/Settings/Account)只有标题、无段控。已弃用的 FocusReviewHeader/LibrarySectionHeader/StatsHeader/SettingsHeader/AccountHeader 退役。 内容区顶角为直角(去 rounded-t-2xl,内容与 chrome 框平齐)。

面板互斥(3-3):左辅助面板(Sites/History/RSS/TOC)与右辅助面板(Discovery/Annotations/Pronunciation/ Tools)互斥——usePanelStore 开一侧自动收另一侧,任一时刻最多一个辅助面板占阅读宽度。

Focus Review 模式(Review 模块内的派生 chrome)

Review 是一个 workspace 模块(非 Read 内的临时态)。进入即 enterReviewModule(快照 pre-review tab/panel + isInFocusSession=true),离开由 ActivityBar.switchModuleendSession 恢复。isInFocusSession 是单一真相 (I1:仅 enterReviewModule/endSession 翻转)。派生 chrome:

  • TitleBar → 替换为 ModuleTitleBar(「回顾」标题)
  • AddressBar → focus 段(AddressBarReviewSegment):Zone1(侧栏宽)= 复习/重温 段控(操作切换归 toolbar); Zone2 = 源页文章标题 + 多源翻页 + reader,隐藏跨内容导航
  • 卡侧栏ReviewPanel)顶部 = ReviewSessionHeader‹ 返回 + 组/笔记标题 + N/M 进度)——会话上下文 紧贴其描述的词卡(2026-08-01 与 toolbar 的模式段控交换位置)
  • 复习卡侧栏在最左(inFocusReview,可拖宽);右 panel 保留(查词语境),左 panel + Find 关闭
  • ReviewSessionComplete = 完成庆祝页
  • signOut → 必须先 endSession 再 resetAllTabs(I3)
  • 残留:复习期点 Settings/Account 走 switchModule → endSession 会丢会话;Modal 逃逸延 backlog(Item C)

6. Content-Script 架构

src-tauri/src/content-script.js 是注入到每个 content webview 的纯 JS 文件,包含 8 个功能模块:

模块功能触发方式
双击查词caretRangeAtPoint + Selection API 提取单词dblclick 事件
弹窗释义、音标、CEFR、例句、TTS 按钮查词后 DOM 注入
词汇保存自动保存到 learning_entries + 上下文句子查词时自动
CEFR 高亮A1-C2 颜色编码,按节点 TreeWalker 遍历页面加载后
Reader 模式提取正文,隐藏广告/导航用户触发
选区翻译选中文本经 Edge Function translate-batch 翻译选区工具栏
TTSAzure Neural 服务端合成(失败自动落系统语音)弹窗按钮
微复习Hard/Easy 两键复习已学单词弹窗内

关键约束

  • IPC:必须使用 window.__TAURI_INTERNALS__.invoke()(不能用 @tauri-apps/api
  • MutationObserver:DOM 操作前必须 disconnect(),操作后重新 observe()
  • SQL:禁止 LOWER(),用 COLLATE NOCASE

7. IPC 通信模式

Main Webview → Rust

typescript
// src/lib/commands.ts
import { invoke } from '@tauri-apps/api/core';
const result = await invoke('command_name', { arg1, arg2 });

Content Webview → Rust

javascript
// content-script.js(不能使用 @tauri-apps/api)
const result = await window.__TAURI_INTERNALS__.invoke('command_name', { arg1, arg2 });

Rust → Frontend(事件)

rust
app.emit_to("main", "event_name", payload)?;

8. Auth & Sync 数据流

认证流程

用户登录 → auth.rs sign_in()
         → Supabase REST /auth/v1/token
         → 返回 access_token + refresh_token
         → 持久化到 settings 表
         → useAuthStore 启动 60s auto-sync polling

同步协议

🧭 全景已收敛到 sync-protocol.md(2026-09-01),本节不再维护第二份。 那里讲:增量游标为什么用服务端时间戳 · 脏判定的三列 · 墓碑的三分支与两个守卫 · 冲突怎么裁 · 四个静默失效家族与五条不变量的关系 · 快照通道 · 协议版本与用户切换。

本节此前那份流程图长期停在已废弃的协议上(写着 WHERE updated_at > last_sync_at, 而 RB 红线 #5 早已改成 server_updated_at),同步表映射表也还列着一张不存在的 word_contexts —— 这正是「同一事实在两处并行维护」的现成例证。

想知道去处
同步怎么运作、为什么这么定sync-protocol.md
条文(做错会出事的那句话)../CLAUDE.md §4 · ../src-tauri/CLAUDE.md §2
逐表 yes/no 与列级映射database-tables-overview.md §8 · database-schema.md §9

9. 关键技术决策

决策选择原因
Tauri 而非 ElectronTauri 2.0包体小 10-40x,内存低 3-5x,原生性能
SM-2 而非 FSRSSM-2与 RVH 移动端统一算法,2-button 序列推导更简单
rusqlite 锁定 0.310.31与 tauri-plugin-sql 共享 libsqlite3-sys 0.28,升级会冲突
TEXT UUID 主键TEXT跨设备同步需要全局唯一 ID,INTEGER 自增在多端会冲突
Soft-deletedeleted_at 列跨端同步需要传播删除操作,hard delete 会丢失信息
Content-script 注入include_str!()编译时嵌入,无需运行时加载文件
Supabase 而非自建SupabasePostgreSQL + Auth + RLS 开箱即用,免运维
COLLATE NOCASE替代 LOWER()SQLite 的 LOWER() 不支持 Unicode