输入关键词开始搜索

设计思路文档

00 — 云融(YunRong)· 设计思路文档

版本:v0.1 状态:已发布 最后更新:2025-01


第 1 层:选型 — 为什么是云融(YunRong)+ Go 后端

技术栈选型

技术理由
客户端 GUIQt 6 Widgets (C++17)原生桌面控件,QPainter 自绘,QSS 主题
客户端网络Qt WebSocket + QNetworkAccessManagerQt 原生,零外部依赖,与信号槽无缝集成
客户端存储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.12025-01初稿