设计思路文档
00 — 云融(YunRong)· 设计思路文档
版本:v0.1 状态:已发布 最后更新:2025-01
第 1 层:选型 — 为什么是云融(YunRong)+ Go 后端
技术栈选型
| 层 | 技术 | 理由 |
|---|---|---|
| 客户端 GUI | Qt 6 Widgets (C++17) | 原生桌面控件,QPainter 自绘,QSS 主题 |
| 客户端网络 | Qt WebSocket + QNetworkAccessManager | Qt 原生,零外部依赖,与信号槽无缝集成 |
| 客户端存储 | SQLite 3 (本地) | 嵌入式数据库,零配置 |
| 后端服务 | Go (Gin/gorilla) | 高并发、轻量部署、REST+WS 双协议自然支持 |
| 后端存储 | PostgreSQL 14+ | 全量消息归档 + 多租户 |
| 构建 | CMake 3.20 + Go modules | 跨平台 C++ 构建 + Go 依赖管理 |
为什么选择 Qt 原生网络方案
Qt 6 内置了完整的网络模块——QWebSocket 和 QNetworkAccessManager。选择它们的原因不是”替代某个库”,而是它们本身就是最契合这个项目架构的方案:
-
异步 IO 在主线程 + 计算在 Worker Pool:QWebSocket / QNetworkAccessManager 的异步 IO 操作在 Qt 主事件循环中自然调度,网络数据的收发本身是轻量级操作,留在主线程避免不必要的跨线程回调。而 Protocol 编解码(JSON 解析/序列化)、加密/解密、消息校验/去重等 CPU 密集型业务逻辑,通过 QueuedConnection 分发给 Worker Thread Pool 执行——实现”IO 与业务线程隔离”,主线程永不阻塞。
-
零外部依赖:项目只需额外引入 nlohmann/json(JSON 编解码)和 spdlog(日志)两个轻量库,均通过 CMake FetchContent 一行配置引入。在 Windows 和 Linux 上只需安装 Qt 6 和 CMake 即可编译——不需要 100MB+ 的第三方网络库。
-
一致的开发体验:心跳用 QTimer,TLS 用 QSslSocket,HTTP 请求用 QNetworkAccessManager——全部是 Qt 原生 API。从写 GUI 控件到写网络层代码,心智模型完全一致,不需要在”Qt 信号槽”和”第三方异步回调”两种范式之间切换。
决策:客户端网络层全部使用 Qt 原生 API。
为什么选择 Go 后端
-
并发模型匹配业务:一个 WebSocket 连接 = 两个 goroutine(读 + 写),一个 HTTP 请求 = 一个 goroutine。Go 的 goroutine + channel 天然匹配”高并发连接 + 消息路由”的场景。gorilla/websocket 是业界使用最广泛的 Go WebSocket 库,API 设计成熟,文档丰富。
-
部署简单:Go 编译为单二进制文件(~10MB),无运行时依赖。拷贝到服务器即可运行,Docker 镜像可以
FROM scratch。对于需要私有化部署到企业内网的项目,部署复杂度直接影响交付成本。 -
开发效率:编译秒级完成,配合 air 等工具实现代码变更后自动重启。REST API 用 Gin 框架,路由 + 中间件模式简洁直观,标准库 net/http 性能足以支撑 10k 并发连接。
决策:客户端用 C++ Qt 做桌面 GUI,后端用 Go 做网络服务和业务逻辑。客户端通过 Qt WebSocket + REST 与 Go 后端通信。
“客户端 + 后端”的甜蜜点
C++ Qt 客户端(展示层) Go 后端(业务层)
QWebSocket ←──→ gorilla/websocket
消息渲染 消息路由 + 持久化
本地 SQLite 缓存 PostgreSQL 权威数据
QNetworkAccessManager ←─→ net/http REST API
客户端专注 GUI 体验,后端专注数据一致性和多端同步。两者通过 WebSocket (JSON 帧) + HTTP REST 通信,协议格式保持现有设计不变。
第 2 层:架构 — 为什么是四层
分层不变,网络层简化
Presentation (GUI/Qt Widgets)
↕ Signal/Slot
Application (Service — IMService/FileService/TaskService)
↕ Signal/Slot(新增,替代回调)
Service (Net + Data — QWebSocket + SqliteStore)
↕
Infrastructure (ThreadPool/Logger/ConfigManager)
QWebSocket 和 QNetworkAccessManager 的异步操作在 Qt 事件循环中自然调度,网络 IO 和 GUI 渲染共享同一线程。
线程模型
Main Thread (Qt Event Loop)
• GUI 渲染 + 网络 IO(QWebSocket / QNetworkAccessManager)
• Signal/Slot 调度 + 心跳定时器(QTimer)
• 轻量级消息路由(接收后直接分发,不做重计算)
│ QueuedConnection
┌────┴──────────┐ ┌──────────────────┐ ┌──────────────┐
│ Worker Pool │ │ DB Thread │ │ File Pool │
│ (N workers) │ │ (SQLite 串行) │ │ (N workers) │
│ • Protocol │ │ • 消息 CRUD │ │ • 分块上传 │
│ 编解码 │ │ • 离线缓存 │ │ • 分块下载 │
│ • 加密/解密 │ │ • 搜索查询 │ │ • Hash 校验 │
│ • 消息校验 │ └──────────────────┘ └──────────────┘
└───────────────┘
- 主线程:Qt 异步网络 IO 的收发回调天然在主事件循环中触发。单条消息的 JSON 编解码(2-5μs)在主线程直通——避免跨线程调度开销(10-50μs)大于计算本身。仅批量操作和加密任务通过 QueuedConnection 分发。
- Worker Pool:负责 ① 离线同步时的批量消息解析 ② AES 加解密 ③ 文件 SHA-256 计算。这些是真正 CPU 密集型的工作,独立线程池保证不抢占 GUI 帧预算。
- DB Thread:SQLite 写入串行化,避免 SQLITE_BUSY。
- File Pool:大文件分块传输可高度并行。
跨线程通信使用 Qt 原生的 QueuedConnection。关键约束:主线程绝不调用 wait()、join()、mutex.lock()——只发信号和读非阻塞队列。
第 3 层:通信协议
协议格式不变
JSON 帧:{ver, type, seq, ts, payload}
二进制帧:[TotalLen 4B|Type 1B|Payload]
C++ 客户端 ←──WebSocket──→ Go 后端
C++ 客户端 ←──HTTP REST──→ Go 后端
各组件实现方式
| 组件 | 实现 |
|---|---|
| WebSocket 客户端 | QWebSocket,信号槽驱动 |
| 心跳 | QTimer,与 Qt 事件循环统一 |
| 重连 | QTimer 延迟回调 + 指数退避 |
| ACK/重传 | ConnectionManager 内部 pendingCommands_ 表 |
| 服务端 | Go gorilla/websocket 服务 |
第 4 层:数据库 — 双存储不变
SQLite(客户端本地)+ PostgreSQL(Go 后端)。客户端通过 Go 后端 REST API 访问远程数据,不直连 PostgreSQL——API 接口稳定、安全可控。
第 5 层:设计模式落点不变
Observer (Signal/Slot)、Strategy (IDataStore)、Facade (ConnectionManager)、State (连接状态机)、Command (离线队列)——五种模式贯穿整个架构。具体落点见系统架构设计文档。
第 6 层:API 接口
REST API 端点由 Go 后端提供。WebSocket 帧协议保持不变。Mock Server(Qt 进程)用于开发调试和自动化测试。
文档修订记录
| 版本 | 日期 | 变更说明 |
|---|---|---|
| v0.1 | 2025-01 | 初稿 |