Appearance
二、系统架构设计 (Architecture)
2.1 整体架构图
下图展示了 TodoApp 的整体技术架构,从客户端到服务端的完整层次关系。
mermaid
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#EEF2FF",
"primaryTextColor": "#1E1B4B",
"primaryBorderColor": "#6366F1",
"lineColor": "#6366F1",
"secondaryColor": "#F0FDF4",
"tertiaryColor": "#FFF7ED",
"fontFamily": "ui-sans-serif, system-ui, sans-serif",
"fontSize": "14px"
}
}}%%
graph TB
%% ── 客户端层 ──────────────────────────────────────────
subgraph CLIENT[" 客户端层 "]
direction LR
WIN["🖥 Windows 11\nFlutter Desktop"]
AND["📱 Android\nFlutter Mobile"]
end
%% ── 本地存储层 ────────────────────────────────────────
subgraph LOCAL[" 本地持久化 "]
direction LR
WDB[("💾 SQLite\nWindows 本地库")]
ADB[("💾 SQLite\nAndroid 本地库")]
end
%% ── 网络层 ────────────────────────────────────────────
subgraph NET[" 网络层 "]
NGX["🔒 Nginx\nHTTPS 终止 · 反向代理"]
end
%% ── 后端层 ────────────────────────────────────────────
subgraph BACK[" 后端层(Docker) "]
direction LR
API["⚙️ Dart Shelf\nREST API · SSE 广播"]
PG[("🗄 PostgreSQL\n云端数据库")]
end
%% ── 连接关系 ──────────────────────────────────────────
WIN -- "① REST 增 / 改 / 删 / 查" --> NGX
AND -- "① REST 增 / 改 / 删 / 查" --> NGX
NGX -. "② SSE 实时推送变更通知" .-> WIN
NGX -. "② SSE 实时推送变更通知" .-> AND
NGX -- "③ 内网转发 127.0.0.1:8080" --> API
API -- "④ 读 / 写" --> PG
WIN <-- "⑤ 本地读写(离线优先)" --> WDB
AND <-- "⑤ 本地读写(离线优先)" --> ADB
%% ── 节点样式 ──────────────────────────────────────────
classDef client fill:#EEF2FF,stroke:#6366F1,stroke-width:1.5px,color:#1E1B4B
classDef storage fill:#F0FDF4,stroke:#22C55E,stroke-width:1.5px,color:#14532D
classDef network fill:#FFF7ED,stroke:#F97316,stroke-width:1.5px,color:#7C2D12
classDef backend fill:#FDF4FF,stroke:#A855F7,stroke-width:1.5px,color:#3B0764
classDef db fill:#F0FDF4,stroke:#22C55E,stroke-width:1.5px,color:#14532D
class WIN,AND client
class WDB,ADB storage
class NGX network
class API backend
class PG db连接说明
- 实线 →:主动请求(客户端发起)
- 虚线 -.->:被动推送(服务端发起)
- 编号 ①~⑤:标注典型请求的经过路径
2.2 通信协议分工
系统采用两种通信协议,各自承担不同职责,互相配合:
| 协议 | 方向 | 用途 |
|---|---|---|
| REST (HTTPS) | 客户端 → 服务端(主动请求) | 登录 / 注册 / 密码重置 · 待办增 / 改 / 删 · 全量 / 增量数据拉取 |
| SSE (HTTPS) | 服务端 → 客户端(被动推送) | 其他设备修改待办 → 通知本端刷新 · 其他设备删除待办 → 通知本端删除 |
为什么不用 WebSocket?
WebSocket 是双向通信协议,适合聊天、游戏等需要客户端主动向服务端推送消息的场景。本项目的实时通知是严格单向的(服务端广播 → 客户端接收),SSE 完全够用,且实现更简单,对 Nginx 代理的配置要求也更低。
2.3 前端分层架构
| 层级 | 包含模块 | 职责 |
|---|---|---|
| UI 层 | HomeScreen · HomeScreenMobile · TodoItemCard · TodoDetailDialog · TodoFormDialog · LoginScreen … | 渲染界面与处理用户交互,不直接访问网络或数据库 |
| Service 层 | ApiService · SseService · SyncService · AuthService · ReminderService | 封装所有副作用:网络请求、SSE 连接、同步调度、提醒推送 |
| Repository 层 | TodoRepository | 统一数据访问入口,屏蔽本地与远端差异;联网时写操作同时入 sync_queue |
| Database 层 | AppDatabase (Drift/SQLite):todos 表 · sync_queue 表 · kv_store 表 | 本地 SQLite 读写,不感知网络状态 |
2.4 后端模块划分
bin/
└── server.dart ← 入口:启动 HttpServer,注册路由,SSE 原生处理
lib/
├── config.dart ← 环境变量读取(.env)
├── handlers/
│ ├── auth_handler.dart ← 注册 / 登录 / Token 刷新 / 登出
│ ├── todo_handler.dart ← 待办 CRUD,写完触发 SSE 广播
│ ├── reset_handler.dart ← 忘记密码 / 验证码 / 重置密码
│ ├── event_handler.dart ← SSE 长连接建立(已迁移至 server.dart)
│ └── log_handler.dart ← 日志查看接口(Basic Auth 保护)
├── middleware/
│ └── auth_middleware.dart ← JWT 验证,注入 userId 到请求上下文
├── services/
│ └── event_broadcaster.dart ← SSE 连接池,管理多端广播
├── database/
│ └── database.dart ← PostgreSQL 操作封装
└── utils/
├── jwt_util.dart ← Token 签发与验证
├── password_util.dart ← bcrypt 哈希
├── response_util.dart ← 统一响应格式
├── email_util.dart ← 邮件发送(Resend)
└── server_logger.dart ← 结构化日志工具2.5 数据流向全景
在线写操作(以"新建待办"为例)
mermaid
flowchart TD
A([用户点击「新建」]) --> B["写入本地 SQLite\nUI 立即刷新"]
B --> C["POST /todos/\n携带 Device-Id · Correlation-Id"]
C --> D["PostgreSQL\nINSERT todo"]
D --> E["SSE 广播 todos_updated\n→ 所有在线设备"]
E --> F{收到事件的设备\n是发起方?}
F -->|是| G(["忽略\n本地已是最新"])
F -->|否| H["GET /todos/?since=上次同步\n拉取增量数据"]
H --> I(["合并到本地 SQLite\nUI 自动刷新"])离线写操作(断网时新建待办)
mermaid
flowchart TD
A([用户点击「新建」]) --> B["写入本地 SQLite\nUI 立即刷新"]
B --> C["操作入队 sync_queue\n{ op: create, payload: todo }"]
C --> D(["等待联网\n队列中可积压多条操作"])
D --> E([检测到网络恢复])
E --> F["逐条重放队列\nPOST · PATCH · DELETE"]
F --> G{服务端响应}
G -->|成功| H["出队\n从 sync_queue 删除"]
G -->|失败| I(["保留队列\n下次联网继续重试"])
H --> J["拉取服务端最新数据\n合并到本地 SQLite"]
J --> K(["本地与服务端数据一致"])SSE 事件处理链路
mermaid
flowchart LR
E[收到 SSE 事件] --> T{事件类型}
T -->|todos_updated| D{deviceId\n是否是自己}
D -->|是| I[忽略\n本地已是最新]
D -->|否| SY[触发增量同步\nGET /todos/?since=]
T -->|todo_deleted| DL[直接删除\n本地对应记录]
T -->|connected| LOG[记录日志\n服务端确认连接]2.6 跨平台响应式外壳
TodoApp 用一套代码同时支持 Win11 桌面端和 Android 移动端,两端在导航结构和交互方式上差异显著,通过 PlatformUtil 在运行时动态切换。
mermaid
graph TD
MAIN["main.dart\n入口"] --> PU{PlatformUtil\n.isDesktop?}
PU -->|true| HS["HomeScreen\n桌面端布局"]
PU -->|false| HSM["HomeScreenMobile\n移动端布局"]
HS --> NR["NavigationRail\n左侧侧边栏\n+ 鼠标 Hover 交互"]
HSM --> BNB["BottomNavigationBar\n底部标签栏\n+ 滑动手势交互"]
NR --> SHARED["共享业务组件"]
BNB --> SHARED
SHARED --> TLP["TodoListPanel\n待办列表"]
SHARED --> TIC["TodoItemCard\n待办卡片"]
SHARED --> TFD["TodoFormDialog\n新建/编辑弹窗"]
SHARED --> TDD["TodoDetailDialog\n详情弹窗"]两端的核心差异:
| 交互维度 | Win11 桌面端 | Android 移动端 |
|---|---|---|
| 导航结构 | 侧边栏 | 底栏 |
| 编辑 / 删除 | 点击卡片进详情,弹窗内操作 | 右滑展开编辑 / 删除按钮 |
| 窗口管理 | 关闭拦截 + 最小化至系统托盘 | 标准 Android 生命周期 |
| 通知方式 | 系统托盘通知 | 推送通知 |