Skip to content

二、系统架构设计 (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 生命周期
通知方式系统托盘通知推送通知