模块详细设计
05 — 云融(YunRong)· 模块详细设计文档
前置文档:02-系统架构设计文档、03-通信协议详细设计文档、04-数据库设计文档 设计思路:00-设计思路文档
版本:v0.1 状态:已发布 最后更新:2025-01
1. 文档概述
本文档对系统架构中定义的各模块进行详细设计,包含:
- 类接口定义(头文件级别的 API 契约)
- 核心算法伪代码
- 关键时序图
- 线程交互细节
所有代码示例使用 C++17,遵循项目编码规范。
2. 网络通信模块 (core/net/)
⭐ 核心强化模块。这是整个项目中设计密度最高的部分。
2.1 模块内部类图
┌─────────────────────────────────────────────────────────┐
│ ConnectionManager (Facade) │
│ + connect(serverUrl) │
│ + disconnect() │
│ + sendMessage(msg, callback) │
│ + sendRequest(cmd) │
│ + getConnectionState() -> ConnectionState │
│ signal: onStateChanged(ConnectionState) │
│ signal: onMessageReceived(Message) │
└───────────┬─────────────────────────────┬───────────────┘
│ │
┌───────▼────────┐ ┌─────────▼──────────┐
│ WsClient │ │ HttpClient │
│ + connect(url) │ │ + get(url, cb) │
│ + sendText(json)│ │ + post(url,body,cb) │
│ + sendBinary(b) │ │ + upload(file, cb) │
│ signal: onText │ │ + download(url,cb) │
│ signal: onBinary│ │ signal: onProgress │
└───────┬─────────┘ └─────────────────────┘
│
┌───────▼──────────┐
│ Protocol │ (无状态,纯函数)
│ + encode(msg)->str│
│ + decode(str)->msg│
│ + encodeBinary() │
│ + decodeBinary() │
│ + validate(json) │
└──────────────────┘
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
│ Heartbeat │ │ ReconnectStrategy│ │ RateLimiter │
│ + start() │ │ + nextDelay(n) │ │ + tryConsume │
│ + stop() │ │ + reset() │ │ + setLimit │
│ signal:overtime│ │ + shouldRetry() │ └──────────────┘
└──────────────┘ └──────────────────┘
2.2 核心接口定义
2.2.1 IConnection — 连接抽象
// core/net/i_connection.h
#pragma once
#include <functional>
#include <string>
#include <vector>
#include <cstdint>
namespace core::net {
enum class ConnectionState {
Idle,
Connecting,
Connected,
Reconnecting,
Error
};
// 所有连接类型的基类接口
class IConnection {
public:
using TextCallback = std::function<void(const std::string& json)>;
using BinaryCallback = std::function<void(const std::vector<uint8_t>& data)>;
using StateCallback = std::function<void(ConnectionState)>;
using ErrorCallback = std::function<void(int code, const std::string& msg)>;
virtual ~IConnection() = default;
virtual void connect(const std::string& url) = 0;
virtual void disconnect() = 0;
virtual void sendText(const std::string& json) = 0;
virtual void sendBinary(const std::vector<uint8_t>& data) = 0;
virtual ConnectionState state() const = 0;
virtual void setOnText(TextCallback cb) = 0;
virtual void setOnBinary(BinaryCallback cb) = 0;
virtual void setOnStateChanged(StateCallback cb) = 0;
virtual void setOnError(ErrorCallback cb) = 0;
};
} // namespace core::net
2.2.2 ConnectionManager — 连接管理器(外观)
// core/net/connection_mgr.h
#pragma once
#include "i_connection.h"
#include "protocol.h"
#include "heartbeat.h"
#include "reconnect_strategy.h"
#include "rate_limiter.h"
#include "../model/message.h"
#include "../infra/thread_pool.h"
#include <memory>
#include <queue>
#include <unordered_map>
namespace core::net {
// 网络请求命令(Command 模式)
struct NetCommand {
enum class Type { SendMessage, MarkRead, SetStatus, FileCheck };
Type type;
std::string payload_json;
uint32_t seq = 0;
int retry_count = 0;
std::function<void(bool, const std::string&)> callback;
};
class ConnectionManager : public QObject {
Q_OBJECT
public:
explicit ConnectionManager(infra::ThreadPool& pool);
~ConnectionManager();
// ---- 生命周期 ----
void connectToServer(const std::string& url, const std::string& token);
void disconnect();
// ---- 消息发送 (Command 模式) ----
void sendMessage(const model::Message& msg,
std::function<void(bool, const std::string&)> callback);
void markRead(uint64_t convId, uint32_t readToSeq);
void sendHeartbeat();
// ---- 文件操作 ----
void uploadFile(const std::string& path,
std::function<void(double progress)> progressCb,
std::function<void(bool, std::string url)> doneCb);
void downloadFile(const std::string& url, const std::string& savePath,
std::function<void(double progress)> progressCb,
std::function<void(bool)> doneCb);
// ---- 状态 ----
ConnectionState connectionState() const { return state_; }
double bytesPerSecondUp() const { return rateLimiter_.upSpeed(); }
double bytesPerSecondDown() const { return rateLimiter_.downSpeed(); }
signals:
void onStateChanged(ConnectionState newState);
void onMessageReceived(model::Message msg);
void onNotificationReceived(model::Task notification);
void onUploadProgress(const std::string& transferId, double progress);
void onDownloadProgress(const std::string& transferId, double progress);
private:
// 内部组件
std::unique_ptr<IConnection> wsClient_;
std::unique_ptr<IConnection> httpClient_;
Protocol protocol_;
Heartbeat heartbeat_;
ReconnectStrategy reconnect_;
RateLimiter rateLimiter_;
infra::ThreadPool& threadPool_;
// 状态
ConnectionState state_ = ConnectionState::Idle;
std::string serverUrl_;
std::string authToken_;
// 待确认帧表
std::unordered_map<uint32_t, NetCommand> pendingCommands_;
uint32_t nextSeq_ = 1;
// 连接状态机驱动
void onWsConnected();
void onWsDisconnected();
void onHeartbeatTimeout();
void startReconnect();
void onReconnected();
// 协议处理
void handleIncomingFrame(const std::string& json);
void handleAck(uint32_t ackedSeq);
void handleRetransmit();
void retryCommand(uint32_t seq);
};
} // namespace core::net
2.2.3 Protocol — 协议编解码器
// core/net/protocol.h
#pragma once
#include "../model/message.h"
#include "../model/task.h"
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
#include <optional>
#include <cstdint>
namespace core::net {
using json = nlohmann::json;
// 帧类型枚举
enum class FrameType : uint8_t {
// JSON 帧
Auth = 0,
Msg = 1,
Ack = 2,
Ping = 3,
Pong = 4,
Sync = 5,
SyncData = 6,
Notify = 7,
MsgRead = 8,
Error = 9,
// 二进制帧
Thumbnail = 0x01,
FileChunk = 0x02,
FileChunkAck= 0x03,
FileMeta = 0x04,
};
class Protocol {
public:
// ---- JSON 帧编码 ----
std::string encodeAuth(const std::string& token, uint32_t seq);
std::string encodeMessage(const model::Message& msg, uint32_t seq);
std::string encodeAck(uint32_t ackSeq);
std::string encodePing();
std::string encodeSync(uint32_t lastSeq, uint64_t lastTs, uint16_t limit);
// ---- JSON 帧解码 ----
enum class DecodedType { Message, Notification, Ack, AuthOk, AuthFail,
SyncData, StateChange, Error, Unknown };
struct DecodedFrame {
DecodedType type = DecodedType::Unknown;
uint32_t seq = 0;
uint64_t ts = 0;
json payload;
};
DecodedFrame decode(const std::string& json);
// ---- 二进制帧 ----
std::vector<uint8_t> encodeFileChunk(uint16_t index, uint32_t offset,
const uint8_t* data, size_t len);
std::vector<uint8_t> encodeFileMeta(const std::string& name, uint64_t size,
const std::string& hash, uint32_t chunkSize);
struct FileChunkAck {
uint16_t receivedCount;
std::vector<bool> bitmap;
};
FileChunkAck decodeFileChunkAck(const std::vector<uint8_t>& data);
// ---- 校验 ----
std::optional<std::string> validate(const json& frame);
static uint32_t extractSeq(const json& frame);
};
} // namespace core::net
2.2.4 ReconnectStrategy — 重连策略
// core/net/reconnect_strategy.h
#pragma once
#include <chrono>
#include <cstdint>
namespace core::net {
class ReconnectStrategy {
public:
static constexpr int MAX_RETRIES = 10;
ReconnectStrategy();
// 获取下一次重试的延迟
std::chrono::milliseconds nextDelay();
// 是否还应该继续重试
bool shouldRetry() const { return attempt_ < MAX_RETRIES; }
// 重置计数器(连接成功后调用)
void reset();
int attempt() const { return attempt_; }
private:
int attempt_ = 0;
};
// 实现
inline std::chrono::milliseconds ReconnectStrategy::nextDelay() {
attempt_++;
if (attempt_ <= 4) {
return std::chrono::milliseconds(1000 * (1 << (attempt_ - 1)));
// 1s, 2s, 4s, 8s
} else if (attempt_ <= 6) {
return std::chrono::milliseconds(attempt_ == 5 ? 30000 : 60000);
// 30s, 60s
} else {
return std::chrono::milliseconds(60000);
// 封顶 60s
}
}
} // namespace core::net
3. 数据访问模块 (core/data/)
3.1 接口与实现类图
┌──────────────────────────────────────┐
│ IDataStore (接口) │
│ + storeMessage(msg) -> bool │
│ + getMessages(convId, from, n, cb) │
│ + searchMessages(keyword, cb) │
│ + upsertContact(contact) │
│ + getConversations(cb) │
│ + updateUnreadCount(convId, delta) │
│ + saveDraft(convId, text) │
│ + getConfig(key) -> optional<string>│
│ + setConfig(key, value) │
│ + pushOfflineOp(op) │
│ + getOfflineOps() -> vector<Op> │
└──────┬───────────────┬───────────────┘
│ │
┌──────▼──────┐ ┌─────▼─────────┐
│ SqliteStore │ │ PgsqlStore │
│ (本地存储) │ │ (远程存储) │
└─────────────┘ └───────────────┘
┌──────────────────────────────────────┐
│ SyncManager │
│ + startSync() │
│ + replayOfflineQueue() │
│ + getSyncState() -> SyncState │
│ signal: onSyncComplete() │
│ signal: onSyncProgress(double) │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ CacheManager │
│ + getContact(id) -> optional<Contact>│
│ + putContact(id, contact) │
│ + warmCache(contacts) │
│ + getOnlineContacts() -> set<id> │
│ (内部使用 unordered_map + shared_mutex)│
└──────────────────────────────────────┘
3.2 IDataStore 完整接口
// core/data/i_data_store.h
#pragma once
#include "../model/message.h"
#include "../model/conversation.h"
#include "../model/contact.h"
#include "../model/task.h"
#include "../model/file_transfer.h"
#include <string>
#include <vector>
#include <optional>
#include <functional>
namespace core::data {
using MessageCallback = std::function<void(std::vector<model::Message>)>;
using ConvCallback = std::function<void(std::vector<model::Conversation>)>;
using ContactCallback = std::function<void(std::vector<model::Contact>)>;
using TaskCallback = std::function<void(std::vector<model::Task>)>;
struct OfflineOp {
int64_t id;
std::string operation;
std::string payloadJson;
int64_t createdAt;
};
// Strategy 模式的核心接口
class IDataStore {
public:
virtual ~IDataStore() = default;
// ---- 消息 ----
virtual bool storeMessage(const model::Message& msg) = 0;
virtual void getMessages(int64_t convId, uint64_t beforeTs,
int limit, MessageCallback cb) = 0;
virtual void searchMessages(const std::string& keyword,
MessageCallback cb) = 0;
virtual void markMessageSynced(const std::string& msgId) = 0;
virtual void markMessageFailed(const std::string& msgId) = 0;
// ---- 会话 ----
virtual void getConversations(ConvCallback cb) = 0;
virtual void updateLastMessage(int64_t convId, const std::string& preview,
uint64_t ts) = 0;
virtual void incrementUnread(int64_t convId, int delta) = 0;
virtual void clearUnread(int64_t convId) = 0;
// ---- 联系人 ----
virtual void upsertContact(const model::Contact& c) = 0;
virtual void getContacts(ContactCallback cb) = 0;
virtual void searchContacts(const std::string& query,
ContactCallback cb) = 0;
// ---- 任务/通知 ----
virtual void storeTask(const model::Task& t) = 0;
virtual void getTasks(TaskCallback cb) = 0;
virtual void updateTaskStatus(int64_t id, int status) = 0;
// ---- 文件传输 ----
virtual void saveTransferState(const model::FileTransfer& ft) = 0;
virtual void updateTransferProgress(const std::string& transferId,
int completedChunks) = 0;
virtual void getActiveTransfers(
std::function<void(std::vector<model::FileTransfer>)> cb) = 0;
// ---- 草稿 ----
virtual void saveDraft(int64_t convId, const std::string& text) = 0;
virtual std::optional<std::string> getDraft(int64_t convId) = 0;
virtual void deleteDraft(int64_t convId) = 0;
// ---- 配置 ----
virtual std::optional<std::string> getConfig(const std::string& key) = 0;
virtual void setConfig(const std::string& key, const std::string& val) = 0;
// ---- 离线队列 ----
virtual void pushOfflineOp(const OfflineOp& op) = 0;
virtual std::vector<OfflineOp> getPendingOfflineOps() = 0;
virtual void markOfflineOpDone(int64_t id) = 0;
virtual void markOfflineOpFailed(int64_t id) = 0;
// ---- 维护 ----
virtual void cleanup() = 0; // 清理过期数据
virtual int64_t dbSize() = 0; // 数据库文件大小
};
} // namespace core::data
3.3 SqliteStore 关键实现骨架
// core/data/sqlite_store.h
#pragma once
#include "i_data_store.h"
#include <sqlite3.h>
#include <memory>
#include <string>
namespace core::data {
class SqliteStore : public IDataStore {
public:
explicit SqliteStore(const std::string& dbPath, const std::string& key = "");
~SqliteStore() override;
// IDataStore 全部方法实现 ...
private:
sqlite3* db_ = nullptr;
std::string dbPath_;
// 预编译语句缓存
struct PreparedStatements {
sqlite3_stmt* insert_msg = nullptr;
sqlite3_stmt* query_msg_by_conv = nullptr;
sqlite3_stmt* search_msg_fts = nullptr;
sqlite3_stmt* upsert_contact = nullptr;
sqlite3_stmt* upsert_conv = nullptr;
sqlite3_stmt* inc_unread = nullptr;
// ... 所有常用语句
} stmt_;
void prepareAll(); // 启动时预编译
void finalizeAll(); // 关闭时释放
void applyPragmas(); // WAL / cache_size 等
void migrateIfNeeded(); // schema 迁移
// 线程安全:此对象只在 DB Thread 中被调用,无需加锁
};
} // namespace core::data
3.4 SyncManager 算法
// core/data/sync_mgr.h
#pragma once
#include "i_data_store.h"
#include <memory>
namespace core::data {
enum class SyncState { Idle, InProgress, Complete, Failed };
class SyncManager {
public:
SyncManager(std::shared_ptr<IDataStore> local,
std::shared_ptr<IDataStore> remote,
std::function<void(const std::string& json)> wsSender);
void startSync();
void onSyncDataReceived(const std::string& json);
void replayOfflineQueue();
SyncState state() const { return state_; }
private:
std::shared_ptr<IDataStore> local_;
std::shared_ptr<IDataStore> remote_;
std::function<void(const std::string&)> wsSender_;
SyncState state_ = SyncState::Idle;
bool hasMore_ = false;
uint32_t lastServerSeq_ = 0;
};
// ---- 核心算法 ----
inline void SyncManager::startSync() {
state_ = SyncState::InProgress;
// 1. 读取本地水位线
auto lastSeqStr = local_->getConfig("sync.last_seq");
lastServerSeq_ = lastSeqStr ? std::stoul(*lastSeqStr) : 0;
auto lastTsStr = local_->getConfig("sync.last_ts");
uint64_t lastTs = lastTsStr ? std::stoull(*lastTsStr) : 0;
// 2. 发送 sync 请求(通过 WebSocket)
auto syncFrame = Protocol::encodeSync(lastServerSeq_, lastTs, 200);
wsSender_(syncFrame);
}
inline void SyncManager::onSyncDataReceived(const std::string& json) {
auto frame = Protocol::decode(json);
// 3. 批量写入本地
for (auto& msg : frame.payload["messages"]) {
local_->storeMessage(parseMessage(msg));
}
for (auto& contact : frame.payload.value("contacts_delta", json::array())) {
local_->upsertContact(parseContact(contact));
}
// 4. 更新水位线
uint32_t endSeq = frame.payload["end_seq"];
local_->setConfig("sync.last_seq", std::to_string(endSeq));
// 5. 检查是否还有更多
hasMore_ = frame.payload.value("has_more", false);
if (hasMore_) {
lastServerSeq_ = endSeq;
startSync(); // 继续拉取下一批
} else {
// 6. 同步完成 → 重放离线队列
state_ = SyncState::Complete;
replayOfflineQueue();
}
}
} // namespace core::data
4. 业务服务模块 (core/service/)
4.1 IMService
// core/service/im_service.h
#pragma once
#include "../net/connection_mgr.h"
#include "../data/i_data_store.h"
#include "../model/message.h"
#include "../model/conversation.h"
#include <memory>
namespace core::service {
class IMService : public QObject {
Q_OBJECT
public:
IMService(std::shared_ptr<net::ConnectionManager> net,
std::shared_ptr<data::IDataStore> store);
// 发送消息
void sendTextMessage(int64_t convId, const std::string& text,
const std::string& quoteMsgId = "");
// 获取会话列表(优先本地)
void loadConversations(std::function<void(std::vector<model::Conversation>)> cb);
// 获取消息历史(本地分页)
void loadMessages(int64_t convId, uint64_t beforeTs, int limit,
std::function<void(std::vector<model::Message>)> cb);
// 搜索消息
void searchMessages(const std::string& keyword,
std::function<void(std::vector<model::Message>)> cb);
// 标记已读
void markConversationRead(int64_t convId);
// 处理收到的消息
void onIncomingMessage(const model::Message& msg);
signals:
void conversationUpdated(const model::Conversation& conv);
void newMessageReceived(const model::Message& msg);
void unreadCountChanged(int totalUnread);
private:
std::shared_ptr<net::ConnectionManager> net_;
std::shared_ptr<data::IDataStore> store_;
// 消息处理管线(Decorator 模式)
void processIncomingMessage(const model::Message& msg);
bool validateMessage(const model::Message& msg); // 校验
bool deduplicateMessage(const model::Message& msg); // 去重
void persistMessage(const model::Message& msg); // 存储
void notifyUI(const model::Message& msg); // 通知 GUI
};
} // namespace core::service
4.2 FileService — 文件传输编排
// core/service/file_service.h
#pragma once
#include "../net/connection_mgr.h"
#include "../data/i_data_store.h"
#include "../model/file_transfer.h"
#include <memory>
#include <functional>
namespace core::service {
class FileService : public QObject {
Q_OBJECT
public:
FileService(std::shared_ptr<net::ConnectionManager> net,
std::shared_ptr<data::IDataStore> store);
// 上传文件
void uploadFile(const std::string& localPath, int64_t convId = 0);
// 下载文件
void downloadFile(const std::string& remoteUrl, const std::string& savePath);
// 获取传输历史
void getTransferHistory(
std::function<void(std::vector<model::FileTransfer>)> cb);
// 取消传输
void cancelTransfer(const std::string& transferId);
void pauseTransfer(const std::string& transferId);
void resumeTransfer(const std::string& transferId);
signals:
void transferProgressChanged(const std::string& transferId,
double progress, double speedBps);
void transferCompleted(const std::string& transferId);
void transferFailed(const std::string& transferId, const std::string& error);
private:
std::shared_ptr<net::ConnectionManager> net_;
std::shared_ptr<data::IDataStore> store_;
// 活跃传输任务(内存中)
std::unordered_map<std::string, model::FileTransfer> activeTransfers_;
// 上传编排
void doUpload(const model::FileTransfer& task);
void uploadNextChunk(const model::FileTransfer& task);
void onChunkSent(const std::string& transferId, int chunkIndex, bool ok);
// 下载编排
void doDownload(const model::FileTransfer& task);
void downloadNextChunk(const model::FileTransfer& task);
void onChunkReceived(const std::string& transferId, int chunkIndex,
const std::vector<uint8_t>& data);
};
} // namespace core::service
5. 基础设施模块 (core/infra/)
5.1 ThreadPool
// core/infra/thread_pool.h
#pragma once
#include <thread>
#include <vector>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>
namespace core::infra {
class ThreadPool {
public:
explicit ThreadPool(size_t numThreads = std::thread::hardware_concurrency());
~ThreadPool();
// 提交任务,返回 future
template<typename F, typename... Args>
auto submit(F&& f, Args&&... args)
-> std::future<typename std::invoke_result<F, Args...>::type>;
size_t workerCount() const { return workers_.size(); }
size_t pendingTasks() const;
void resize(size_t newSize);
private:
std::vector<std::thread> workers_;
std::queue<std::function<void()>> tasks_;
std::mutex queueMutex_;
std::condition_variable cv_;
bool stop_ = false;
};
} // namespace core::infra
5.2 MessageBus — 线程安全通信
// core/infra/msg_bus.h
#pragma once
#include <queue>
#include <mutex>
#include <functional>
#include <memory>
namespace core::infra {
// 线程安全的事件总线,基于 std::queue + std::mutex
// 注:若性能成为瓶颈可替换为定制环形缓冲区
template<typename T>
class MessageBus {
public:
explicit MessageBus(size_t capacity = 4096)
: capacity_(capacity) {}
// 生产者端(任意线程调用)
bool publish(T event) {
std::lock_guard<std::mutex> lock(mutex_);
if (queue_.size() >= capacity_) return false;
queue_.push(std::move(event));
return true;
}
// 消费者端(通常在 GUI 线程批量消费)
template<typename Consumer>
void consume(Consumer&& consumer) {
std::lock_guard<std::mutex> lock(mutex_);
while (!queue_.empty()) {
consumer(std::move(queue_.front()));
queue_.pop();
}
}
// 非阻塞消费一条
bool tryConsume(T& event) {
std::lock_guard<std::mutex> lock(mutex_);
if (queue_.empty()) return false;
event = std::move(queue_.front());
queue_.pop();
return true;
}
size_t capacity() const { return capacity_; }
bool empty() const {
std::lock_guard<std::mutex> lock(mutex_);
return queue_.empty();
}
private:
std::queue<T> queue_;
mutable std::mutex mutex_;
size_t capacity_;
};
// 跨线程信号投递:将事件从工作线程安全投递到 Qt 主线程
class QtSignalBridge : public QObject {
Q_OBJECT
public:
template<typename Event>
void postToMainThread(Event&& event,
std::function<void(Event)> handler) {
// 使用 QMetaObject::invokeMethod 将 handler 的执行调度到主线程事件循环
QMetaObject::invokeMethod(this, [handler, e = std::forward<Event>(event)]() {
handler(e);
}, Qt::QueuedConnection);
}
};
} // namespace core::infra
5.3 ConfigManager
// core/infra/config_mgr.h
#pragma once
#include <string>
#include <optional>
#include <shared_mutex>
#include <unordered_map>
namespace core::infra {
// Meyer's Singleton — 线程安全的单例
class ConfigManager {
public:
static ConfigManager& instance() {
static ConfigManager mgr;
return mgr;
}
// 禁止拷贝和移动
ConfigManager(const ConfigManager&) = delete;
ConfigManager& operator=(const ConfigManager&) = delete;
// 类型安全的配置访问
template<typename T>
T get(const std::string& key, T defaultValue = T{}) const;
template<typename T>
void set(const std::string& key, const T& value);
std::optional<std::string> getRaw(const std::string& key) const;
void setRaw(const std::string& key, const std::string& value);
void loadFromFile(const std::string& path);
void saveToFile(const std::string& path);
private:
ConfigManager() = default;
mutable std::shared_mutex mutex_; // 多读单写
std::unordered_map<std::string, std::string> config_;
};
} // namespace core::infra
6. GUI 模块 (gui/)
6.1 MainWindow 结构
// gui/main_window.h
#pragma once
#include <QMainWindow>
#include <QStackedWidget>
#include <QSystemTrayIcon>
#include <memory>
namespace gui {
class ChatView;
class ContactTree;
class TaskList;
class FilePanel;
class Dashboard;
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = nullptr);
~MainWindow();
private:
void setupUI();
void setupTrayIcon();
void connectSignals();
// 导航
enum class Page { Chat, Tasks, Files, Contacts, Settings };
void switchPage(Page page);
// 三段式布局
QWidget* navBar_; // 左侧导航栏
QStackedWidget* contentArea_; // 中间内容区
QWidget* infoPanel_; // 右侧信息面板(可选)
// 子视图
ChatView* chatView_ = nullptr;
TaskList* taskList_ = nullptr;
FilePanel* filePanel_ = nullptr;
ContactTree* contactTree_ = nullptr;
Dashboard* dashboard_ = nullptr;
// 系统托盘
QSystemTrayIcon* trayIcon_ = nullptr;
int totalUnread_ = 0;
};
} // namespace gui
6.2 ChatView — 消息列表性能设计
// gui/views/chat_view.h
#pragma once
#include <QWidget>
#include <QListView>
#include <QTextEdit>
#include <QPushButton>
#include <memory>
namespace gui {
// 消息列表 Model(适配 core::model::Message → Qt Model)
class MessageListModel : public QAbstractListModel {
Q_OBJECT
public:
enum Roles {
ContentTypeRole = Qt::UserRole + 1,
SenderNameRole,
TimestampRole,
StatusRole,
IsMineRole,
ContentBodyRole,
};
int rowCount(const QModelIndex& parent = QModelIndex{}) const override;
QVariant data(const QModelIndex& index, int role) const override;
// 高效追加(避免 reset 整个 Model)
void appendMessages(const std::vector<model::Message>& msgs);
void prependMessages(const std::vector<model::Message>& msgs); // 向上翻页加载
void updateMessageStatus(const std::string& msgId, int status);
private:
std::vector<model::Message> messages_;
// 上限:最多保留 10000 条在 Model 中
static constexpr size_t MAX_CACHED = 10000;
};
class ChatView : public QWidget {
Q_OBJECT
public:
explicit ChatView(QWidget* parent = nullptr);
void loadConversation(int64_t convId);
void appendMessage(const model::Message& msg);
void updateMessageStatus(const std::string& msgId, int status);
private:
QListView* messageList_ = nullptr;
QTextEdit* inputBox_ = nullptr;
QPushButton* sendBtn_ = nullptr;
MessageListModel* model_ = nullptr;
int64_t currentConvId_ = 0;
// 滚动位置检测:如果用户在查看历史消息,新消息不自动滚到底部
bool isAtBottom_ = true;
void onSendClicked();
void onScrollChanged(int value);
void loadMoreHistory(); // 向上滚动到顶 → 加载更早的消息
};
} // namespace gui
6.3 自定义控件 — MessageBubble Delegate
// gui/delegates/message_delegate.h
#pragma once
#include <QStyledItemDelegate>
#include <QPainter>
namespace gui {
class MessageDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit MessageDelegate(QObject* parent = nullptr);
void paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const override;
QSize sizeHint(const QStyleOptionViewItem& option,
const QModelIndex& index) const override;
private:
// 气泡绘制
void paintBubble(QPainter* p, const QRect& rect,
const QString& text, bool isMine) const;
void paintImageContent(QPainter* p, const QRect& rect,
const QString& url, const QSize& imgSize) const;
void paintFileCard(QPainter* p, const QRect& rect,
const QString& fileName, int64_t fileSize) const;
// 布局计算
QRect bubbleRect(const QStyleOptionViewItem& option, bool isMine) const;
QSize textSize(const QString& text, int maxWidth) const;
// 样式常量
static constexpr int BUBBLE_RADIUS = 12;
static constexpr int BUBBLE_PADDING = 10;
static constexpr int MAX_BUBBLE_WIDTH = 400;
QColor myBubbleColor_ = QColor("#95EC69"); // 微信绿
QColor otherBubbleColor_ = QColor("#FFFFFF");
};
} // namespace gui
7. 关键时序图
7.1 消息发送全链路
User Main Thread Worker Pool Server
│ │ │ │
│ 点击发送 │ │ │
│────────────────►│ │ │
│ │ │ │
│ │ 1. IMService:: │ │
│ │ sendTextMessage│ │
│ │ │ │
│ │ 2. 追加到聊天界面 │ │
│◄─ 气泡:"发送中" │ │ │
│ │ │ │
│ │ 3. 投递编码任务 │ │
│ │──────────────────►│ │
│ │ │ 4. Protocol 编码 │
│ │ │ 分配 seq=42 │
│ │ │ 序列化 JSON │
│ │ │ 入 pending 表 │
│ │◄── EncodedFrame ──│ │
│ │ │ │
│ │ 5. WS send ──────────────────────────►│
│ │ │ │
│ │◄── Ack(seq=42) ──────────────────────│
│ │ │ │
│ │ 6. 从 pending 移除 │ │
│ │ 7. 更新 SQLite │ │
│ │ (synced=1, │ │
│ │ status=已送达) │ │
│ │ │ │
│◄─ 气泡:"已送达" │ │ │
7.2 文件上传全链路
User Main Thread FileService File Pool Server
│ │ │ │ │
│ 拖入文件 │ │ │ │
│───────────────►│ │ │ │
│ │ uploadFile() │ │ │
│ │───────────────►│ │ │
│ │ │ 1. SHA-256 │ │
│ │ │ 文件哈希 │ │
│ │ │ │ │
│ │ │ 2. 秒传检查 │ │
│ │ │──────────────────────────────►│
│ │ │◄─── {exists:false} ──────────│
│ │ │ │ │
│ │ │ 3. 建传输任务 │ │
│ │ │ 存 SQLite │ │
│ │ │ │ │
│◄── 进度: 0% │ │ │ │
│ │ │ │ │
│ │ │ 4. 分块循环 │ │
│ │ │───────────────►│ │
│ │ │ │─ Chunk 0 ────►│
│ │ │ │─ Chunk 1 ────►│
│ │◄── progress ───│◄───────────────│ │
│◄── 进度: 10% │ │ │ │
│ │ │ ... │ ... │
│ │ │ │─ Chunk N ────►│
│ │ │ │ │
│ │ │ │◄── complete ──│
│ │ │ │ │
│ │ │ 5. 更新 SQLite │ │
│ │ │ status=完成 │ │
│ │ │ │ │
│ │ │ 6. 发送文件消息 │ │
│ │ │──────────────────────────────►│
│ │ │ │ │
│◄── 上传完成 │ │ │ │
7.3 网络断开→重连→恢复
Main Thread Worker Pool DB Thread
│ │ │
│ [心跳连续3次超时] │ │
│ 状态→RECONNECTING │ │
│ 显示:"连接断开,重连中" │ │
│ │ │
│ 退避 1s/2s/4s 后重试 │ │
│── TCP+TLS+WS ──── ✓ │ │
│ │ │
│ 发送 auth 帧 │ │
│◄── auth_ok │ │
│ │ │
│ 检查 seq → 有缺口 │ │
│ 发送 sync(last_seq) │ │
│ │ │
│◄── sync_data (200条) │ │
│ │ │
│── RawBytes ─────────►│ │
│ │ 1. 批量解码/校验 │
│◄── DecodedMsgs ──────│ │
│ │ │
│── StoreBatch ───────────────────►[Signal]──│
│ │ 2. 批量写入 │
│ │ │
│◄── BatchDone ──────────────────[Signal]───│
│ 消息列表刷新 │ │
│ │ │
│◄── sync_data(has_more=f) │
│ │ │
│── 拉取离线队列 ─────────────────►[Signal]──│
│ │ 查询 offline_queue│
│◄── [send_msg, ...] ───────────[Signal]────│
│ │ │
│── 重放队列 ──────────►│ │
│ │ 3. 逐个编码/发送 │
│ │ 服务端去重+ACK │
│ │ │
│ 状态→CONNECTED │ │
│ 显示:"已连接" │ │
8. 错误处理策略
8.1 分层错误处理
网络层错误
├── 连接失败 → 自动重连 (ReconnectStrategy)
├── 超时 → 重试 or 失败回调
├── TLS 证书错误 → 通知用户(开发模式可忽略)
└── 帧解析失败 → 发送 error 帧 + 记录日志
数据层错误
├── SQLite BUSY → 重试 3 次 (WAL 模式下极少发生)
├── 磁盘满 → 通知用户清理
├── 迁移失败 → 备份旧 DB + 重建
└── 加密密钥错误 → 提示重新登录
业务层错误
├── 会话不存在 → 静默忽略 + 清理本地数据
├── 权限不足 → Toast 提示
└── Token 过期 → 自动刷新 or 跳转登录
GUI 层错误
├── 网络层未就绪 → 按钮灰化
└── 加载超时 → 骨架屏 + 重试按钮
8.2 错误传播模式
// 使用 std::optional 或 Result 类型
template<typename T>
struct Result {
bool ok;
T value;
std::string error;
int code = 0;
};
// 示例:带错误信息的回调
using SendResultCallback = std::function<void(Result<std::string> msgId)>;
// 错误日志
#define LOG_NET_ERROR(code, msg) \
Logger::error("NET", "{}:{} code={} msg={}", __FILE__, __LINE__, code, msg)
9. 数据模型定义 (core/model/)
9.1 核心数据结构
// core/model/message.h
#pragma once
#include <string>
#include <cstdint>
namespace core::model {
struct Message {
std::string msgId; // 全局唯一 ID
int64_t convId = 0; // 会话 ID
int64_t senderId = 0;
std::string senderName;
int contentType = 0; // 0=文本, 1=图片, 2=文件, 3=系统
std::string contentBody; // JSON
int status = 0; // 0=发送中, 1=已送达, 2=已读, 3=失败
uint32_t seq = 0;
uint64_t timestamp = 0;
bool isMine = false;
};
struct Conversation {
int64_t id = 0;
int type = 0; // 0=私聊, 1=群聊
std::string title;
std::string avatarUrl;
std::string lastMsgPreview;
uint64_t lastMsgTime = 0;
uint32_t lastMsgSeq = 0;
int unreadCount = 0;
bool isPinned = false;
bool isMuted = false;
};
struct Contact {
int64_t id = 0;
std::string name;
std::string avatarUrl;
std::string department;
std::string title;
int status = 0; // 0=离线, 1=在线
bool isFavorite = false;
};
struct Task {
int64_t id = 0;
std::string notifyId;
std::string taskType;
std::string title;
std::string body;
int priority = 1;
int status = 0; // 0=未读, 1=已读, 2=已处理
int64_t fromUserId = 0;
std::string fromUserName;
uint64_t createdAt = 0;
};
struct FileTransfer {
std::string transferId;
std::string fileName;
std::string filePath;
int64_t fileSize = 0;
std::string fileHash;
int direction = 0; // 0=上传, 1=下载
int64_t convId = 0;
int totalChunks = 0;
int completedChunks = 0;
int status = 0; // 0=等待, 1=传输中, 2=暂停, 3=完成, 4=失败
std::string remoteUrl;
uint64_t startedAt = 0;
uint64_t completedAt = 0;
};
} // namespace core::model
10. 构建配置
10.1 核心库 CMakeLists.txt
# src/core/CMakeLists.txt
add_library(enterprise_core STATIC
# 网络
net/connection_mgr.cpp
net/ws_client.cpp
net/http_client.cpp
net/protocol.cpp
net/heartbeat.cpp
net/reconnect_strategy.cpp
net/rate_limiter.cpp
# 数据
data/sqlite_store.cpp
data/pgsql_store.cpp
data/sync_mgr.cpp
data/cache_mgr.cpp
# 业务
service/im_service.cpp
service/task_service.cpp
service/file_service.cpp
service/report_service.cpp
# 基础设施
infra/thread_pool.cpp
infra/msg_bus.cpp
infra/config_mgr.cpp
infra/logger.cpp
infra/crypto.cpp
# 数据模型
model/message.cpp
model/conversation.cpp
model/contact.cpp
model/task.cpp
model/file_transfer.cpp
)
target_include_directories(enterprise_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(enterprise_core PUBLIC
Qt6::Network
Qt6::WebSockets
Qt6::Sql
nlohmann_json::nlohmann_json
spdlog::spdlog
SQLite::SQLite3
)
# SQLite 源码直接编译进项目 (或使用系统库)
# target_sources(enterprise_core PRIVATE thirdparty/sqlite3.c)
附录 A — 关键设计指标
| 模块 | 类数量 (估) | 接口数量 | 单元测试优先级 |
|---|---|---|---|
| net/ | 8 | 1 (IConnection) | 🔴 最高 |
| data/ | 5 | 1 (IDataStore) | 🔴 最高 |
| service/ | 4 | 0 | 🟡 中 |
| infra/ | 5 | 0 | 🟡 中 |
| gui/ | 15+ | 0 | 🟢 低(人工测试为主) |
| model/ | 5 (纯结构体) | 0 | 🟢 低 |
附录 B — 文档修订记录
| 版本 | 日期 | 作者 | 变更说明 |
|---|---|---|---|
| v0.1 | 2025-01 | — | 初稿,覆盖网络/数据/业务/基础设施/GUI 模块详细设计 |