🌐 Qt6实战项目 Day3:CloudNote个人云笔记系统 - 网络通信客户端开发
项目系列: CloudNote个人云笔记系统
技术栈: Qt 6.9.2 + QNetworkAccessManager + 异步网络编程
难度等级: ⭐⭐⭐⭐☆
预计用时: 1天
📋 项目回顾
前两天我们已经完成了:
- Day 1: 数据库设计与本地存储功能
- Day 2: HTTP服务器和RESTful API实现
今天我们将开发网络通信客户端,实现与服务器的完整交互,包括用户认证、笔记同步等功能。
🎯 今日学习目标
- 掌握QNetworkAccessManager异步网络编程
- 实现HTTP客户端请求封装
- 构建请求队列和重试机制
- 实现网络状态检测和错误处理
- 设计数据同步策略
- 集成JWT令牌管理
🏗️ 网络客户端架构设计
我们的网络客户端将采用分层架构,确保网络操作的可靠性和可维护性。
🔧 核心网络管理类实现
🌐 NetworkManager主类
NetworkManager.h
#ifndef NETWORKMANAGER_H
#define NETWORKMANAGER_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QJsonObject>
#include <QJsonDocument>
#include <QTimer>
#include <QQueue>
#include <QMutex>
#include <QUrl>
#include <QSslConfiguration>
#include <memory>
// 前向声明
class AuthManager;
class RequestQueue;
class ResponseCache;
// 网络请求结构
struct NetworkRequest {
enum Type {
GET,
POST,
PUT,
DELETE
};
QString id;
Type type;
QUrl url;
QJsonObject data;
QMap<QString, QString> headers;
int priority = 0;
int retryCount = 0;
int maxRetries = 3;
bool requiresAuth = true;
QDateTime createdAt;
bool isValid() const {
return !id.isEmpty() && url.isValid();
}
};
// 网络响应结构
struct NetworkResponse {
QString requestId;
bool success = false;
int statusCode = 0;
QJsonObject data;
QString errorMessage;
QDateTime responseAt;
qint64 responseTimeMs = 0;
bool isSuccess() const {
return success && statusCode >= 200 && statusCode < 300;
}
};
class NetworkManager : public QObject
{
Q_OBJECT
public:
enum NetworkStatus {
Unknown,
Online,
Offline,
Limited
};
static NetworkManager* instance();
~NetworkManager();
// 网络配置
void setServerUrl(const QString &url);
void setTimeout(int timeoutMs);
void setRetryPolicy(int maxRetries, int retryDelayMs);
// 网络状态
NetworkStatus networkStatus() const;
bool isOnline() const;
// HTTP请求方法
QString get(const QString &endpoint, const QJsonObject ¶ms = QJsonObject(),
bool requiresAuth = true, int priority = 0);
QString post(const QString &endpoint, const QJsonObject &data,
bool requiresAuth = true, int priority = 0);
QString put(const QString &endpoint, const QJsonObject &data,
bool requiresAuth = true, int priority = 0);
QString deleteRequest(const QString &endpoint, bool requiresAuth = true,
int priority = 0);
// 认证相关
void setAuthToken(const QString &token);
void clearAuthToken();
QString getAuthToken() const;
// 请求管理
void cancelRequest(const QString &requestId);
void cancelAllRequests();
int getPendingRequestsCount() const;
// 缓存管理
void enableCache(bool enabled, int cacheSizeKB = 10240);
void clearCache();
signals:
void networkStatusChanged(NetworkStatus status);
void requestFinished(const QString &requestId, const NetworkResponse &response);
void requestFailed(const QString &requestId, const QString &error);
void authenticationRequired();
void serverUnreachable();
private slots:
void onReplyFinished();
void onNetworkStatusChanged();
void processRequestQueue();
void checkNetworkStatus();
private:
NetworkManager(QObject *parent = nullptr);
// 请求处理
void executeRequest(const NetworkRequest &request);
QNetworkRequest createQNetworkRequest(const NetworkRequest &request);
void handleResponse(QNetworkReply *reply, const QString &requestId);
void handleNetworkError(QNetworkReply *reply, const QString &requestId);
void retryRequest(const NetworkRequest &request);
// 辅助方法
QString generateRequestId();
QJsonObject parseJsonResponse(const QByteArray &data);
void addAuthHeader(QNetworkRequest &request);
void logRequest(const NetworkRequest &request);
void logResponse(const NetworkResponse &response);
private:
static std::unique_ptr<NetworkManager> s_instance;
static QMutex s_mutex;
std::unique_ptr<QNetworkAccessManager> m_networkManager;
std::unique_ptr<AuthManager> m_authManager;
std::unique_ptr<RequestQueue> m_requestQueue;
std::unique_ptr<ResponseCache> m_responseCache;
QString m_serverUrl;
QString m_authToken;
int m_timeoutMs;
int m_maxRetries;
int m_retryDelayMs;
NetworkStatus m_networkStatus;
QMap<QString, NetworkRequest> m_pendingRequests;
QMap<QNetworkReply*, QString> m_replyToRequestId;
QTimer *m_queueTimer;
QTimer *m_statusTimer;
QMutex m_requestMutex;
bool m_cacheEnabled;
};
#endif // NETWORKMANAGER_H
NetworkManager.cpp
#include "NetworkManager.h"
#include "AuthManager.h"
#include "RequestQueue.h"
#include "ResponseCache.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QUrlQuery>
#include <QNetworkReply>
#include <QSslConfiguration>
#include <QUuid>
#include <QDebug>
#include <QElapsedTimer>
#include <QHostInfo>
#include <QNetworkInterface>
std::unique_ptr<NetworkManager> NetworkManager::s_instance = nullptr;
QMutex NetworkManager::s_mutex;
NetworkManager* NetworkManager::instance()
{
QMutexLocker locker(&s_mutex);
if (s_instance == nullptr) {
s_instance = std::unique_ptr<NetworkManager>(new NetworkManager);
}
return s_instance.get();
}
NetworkManager::NetworkManager(QObject *parent)
: QObject(parent)
, m_networkManager(std::make_unique<QNetworkAccessManager>(this))
, m_authManager(std::make_unique<AuthManager>(this))
, m_requestQueue(std::make_unique<RequestQueue>(this))
, m_responseCache(std::make_unique<ResponseCache>(this))
, m_serverUrl("http://localhost:8080")
, m_timeoutMs(30000)
, m_maxRetries(3)
, m_retryDelayMs(1000)
, m_networkStatus(Unknown)
, m_queueTimer(new QTimer(this))
, m_statusTimer(new QTimer(this))
, m_cacheEnabled(true)
{
// 配置SSL
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
sslConfig.setProtocol(QSsl::TlsV1_2OrLater);
QSslConfiguration::setDefaultConfiguration(sslConfig);
// 配置网络管理器
m_networkManager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
// 连接信号
connect(m_networkManager.get(), &QNetworkAccessManager::finished,
this, &NetworkManager::onReplyFinished);
connect(m_networkManager.get(), &QNetworkAccessManager::networkAccessibleChanged,
this, &NetworkManager::onNetworkStatusChanged);
// 配置定时器
m_queueTimer->setSingleShot(false);
m_queueTimer->setInterval(100); // 100ms处理一次队列
connect(m_queueTimer, &QTimer::timeout, this, &NetworkManager::processRequestQueue);
m_queueTimer->start();
m_statusTimer->setSingleShot(false);
m_statusTimer->setInterval(5000); // 5秒检查一次网络状态
connect(m_statusTimer, &QTimer::timeout, this, &NetworkManager::checkNetworkStatus);
m_statusTimer->start();
// 初始化网络状态
checkNetworkStatus();
qDebug() << "NetworkManager初始化完成";
}
NetworkManager::~NetworkManager()
{
cancelAllRequests();
}
void NetworkManager::setServerUrl(const QString &url)
{
QUrl serverUrl(url);
if (serverUrl.isValid()) {
m_serverUrl = url;
qDebug() << "服务器URL设置为:" << url;
} else {
qWarning() << "无效的服务器URL:" << url;
}
}
void NetworkManager::setTimeout(int timeoutMs)
{
m_timeoutMs = qMax(1000, timeoutMs); // 最少1秒超时
qDebug() << "网络超时设置为:" << m_timeoutMs << "ms";
}
void NetworkManager::setRetryPolicy(int maxRetries, int retryDelayMs)
{
m_maxRetries = qBound(0, maxRetries, 10); // 最多重试10次
m_retryDelayMs = qMax(100, retryDelayMs); // 最少100ms重试间隔
qDebug() << "重试策略设置为: 最大重试" << m_maxRetries << "次,间隔" << m_retryDelayMs << "ms";
}
NetworkManager::NetworkStatus NetworkManager::networkStatus() const
{
return m_networkStatus;
}
bool NetworkManager::isOnline() const
{
return m_networkStatus == Online;
}
QString NetworkManager::get(const QString &endpoint, const QJsonObject ¶ms,
bool requiresAuth, int priority)
{
NetworkRequest request;
request.id = generateRequestId();
request.type = NetworkRequest::GET;
request.url = QUrl(m_serverUrl + endpoint);
request.requiresAuth = requiresAuth;
request.priority = priority;
request.createdAt = QDateTime::currentDateTime();
// 添加查询参数
if (!params.isEmpty()) {
QUrlQuery query;
for (auto it = params.begin(); it != params.end(); ++it) {
query.addQueryItem(it.key(), it.value().toVariant().toString());
}
request.url.setQuery(query);
}
if (request.isValid()) {
m_requestQueue->enqueue(request);
logRequest(request);
return request.id;
}
return QString();
}
QString NetworkManager::post(const QString &endpoint, const QJsonObject &data,
bool requiresAuth, int priority)
{
NetworkRequest request;
request.id = generateRequestId();
request.type = NetworkRequest::POST;
request.url = QUrl(m_serverUrl + endpoint);
request.data = data;
request.requiresAuth = requiresAuth;
request.priority = priority;
request.createdAt = QDateTime::currentDateTime();
if (request.isValid()) {
m_requestQueue->enqueue(request);
logRequest(request);
return request.id;
}
return QString();
}
QString NetworkManager::put(const QString &endpoint, const QJsonObject &data,
bool requiresAuth, int priority)
{
NetworkRequest request;
request.id = generateRequestId();
request.type = NetworkRequest::PUT;
request.url = QUrl(m_serverUrl + endpoint);
request.data = data;
request.requiresAuth = requiresAuth;
request.priority = priority;
request.createdAt = QDateTime::currentDateTime();
if (request.isValid()) {
m_requestQueue->enqueue(request);
logRequest(request);
return request.id;
}
return QString();
}
QString NetworkManager::deleteRequest(const QString &endpoint, bool requiresAuth, int priority)
{
NetworkRequest request;
request.id = generateRequestId();
request.type = NetworkRequest::DELETE;
request.url = QUrl(m_serverUrl + endpoint);
request.requiresAuth = requiresAuth;
request.priority = priority;
request.createdAt = QDateTime::currentDateTime();
if (request.isValid()) {
m_requestQueue->enqueue(request);
logRequest(request);
return request.id;
}
return QString();
}
void NetworkManager::setAuthToken(const QString &token)
{
QMutexLocker locker(&m_requestMutex);
m_authToken = token;
m_authManager->setToken(token);
qDebug() << "认证令牌已更新";
}
void NetworkManager::clearAuthToken()
{
QMutexLocker locker(&m_requestMutex);
m_authToken.clear();
m_authManager->clearToken();
qDebug() << "认证令牌已清除";
}
QString NetworkManager::getAuthToken() const
{
QMutexLocker locker(&m_requestMutex);
return m_authToken;
}
void NetworkManager::processRequestQueue()
{
if (!isOnline()) {
return; // 离线状态不处理请求
}
// 从队列中取出优先级最高的请求
auto request = m_requestQueue->dequeue();
if (request.has_value()) {
executeRequest(request.value());
}
}
void NetworkManager::executeRequest(const NetworkRequest &request)
{
QMutexLocker locker(&m_requestMutex);
// 检查认证
if (request.requiresAuth && m_authToken.isEmpty()) {
qWarning() << "请求需要认证但没有令牌:" << request.id;
emit authenticationRequired();
return;
}
// 创建网络请求
QNetworkRequest netRequest = createQNetworkRequest(request);
QNetworkReply *reply = nullptr;
QElapsedTimer *timer = new QElapsedTimer;
timer->start();
// 执行请求
switch (request.type) {
case NetworkRequest::GET:
reply = m_networkManager->get(netRequest);
break;
case NetworkRequest::POST: {
QJsonDocument doc(request.data);
reply = m_networkManager->post(netRequest, doc.toJson(QJsonDocument::Compact));
break;
}
case NetworkRequest::PUT: {
QJsonDocument doc(request.data);
reply = m_networkManager->put(netRequest, doc.toJson(QJsonDocument::Compact));
break;
}
case NetworkRequest::DELETE:
reply = m_networkManager->deleteResource(netRequest);
break;
}
if (reply) {
// 设置超时
QTimer::singleShot(m_timeoutMs, reply, &QNetworkReply::abort);
// 存储请求映射
m_replyToRequestId[reply] = request.id;
m_pendingRequests[request.id] = request;
// 存储计时器
reply->setProperty("timer", QVariant::fromValue(timer));
qDebug() << "执行请求:" << request.id << request.url.toString();
}
}
QNetworkRequest NetworkManager::createQNetworkRequest(const NetworkRequest &request)
{
QNetworkRequest netRequest(request.url);
// 设置通用头部
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
netRequest.setHeader(QNetworkRequest::UserAgentHeader, "CloudNote-Client/1.0");
netRequest.setRawHeader("Accept", "application/json");
// 设置认证头部
if (request.requiresAuth && !m_authToken.isEmpty()) {
addAuthHeader(netRequest);
}
// 设置自定义头部
for (auto it = request.headers.begin(); it != request.headers.end(); ++it) {
netRequest.setRawHeader(it.key().toUtf8(), it.value().toUtf8());
}
// 设置SSL配置
netRequest.setSslConfiguration(QSslConfiguration::defaultConfiguration());
return netRequest;
}
void NetworkManager::onReplyFinished()
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (!reply) {
return;
}
// 获取请求ID和计时器
QString requestId = m_replyToRequestId.value(reply);
QElapsedTimer *timer = reply->property("timer").value<QElapsedTimer*>();
qint64 responseTime = timer ? timer->elapsed() : 0;
if (requestId.isEmpty()) {
qWarning() << "收到未知请求的响应";
reply->deleteLater();
return;
}
// 处理响应
if (reply->error() == QNetworkReply::NoError) {
handleResponse(reply, requestId);
} else {
handleNetworkError(reply, requestId);
}
// 清理
m_replyToRequestId.remove(reply);
m_pendingRequests.remove(requestId);
if (timer) {
delete timer;
}
reply->deleteLater();
}
void NetworkManager::handleResponse(QNetworkReply *reply, const QString &requestId)
{
QElapsedTimer *timer = reply->property("timer").value<QElapsedTimer*>();
qint64 responseTime = timer ? timer->elapsed() : 0;
NetworkResponse response;
response.requestId = requestId;
response.statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
response.responseAt = QDateTime::currentDateTime();
response.responseTimeMs = responseTime;
// 解析响应数据
QByteArray data = reply->readAll();
response.data = parseJsonResponse(data);
// 判断成功状态
response.success = response.statusCode >= 200 && response.statusCode < 300;
if (!response.success) {
response.errorMessage = response.data.value("message").toString();
if (response.errorMessage.isEmpty()) {
response.errorMessage = QString("HTTP错误: %1").arg(response.statusCode);
}
}
logResponse(response);
// 处理认证错误
if (response.statusCode == 401) {
emit authenticationRequired();
clearAuthToken();
}
// 缓存响应(仅缓存成功的GET请求)
NetworkRequest originalRequest = m_pendingRequests.value(requestId);
if (m_cacheEnabled && response.isSuccess() && originalRequest.type == NetworkRequest::GET) {
m_responseCache->put(originalRequest.url.toString(), response.data);
}
if (response.success) {
emit requestFinished(requestId, response);
} else {
emit requestFailed(requestId, response.errorMessage);
}
}
void NetworkManager::handleNetworkError(QNetworkReply *reply, const QString &requestId)
{
NetworkRequest request = m_pendingRequests.value(requestId);
QNetworkReply::NetworkError error = reply->error();
QString errorString = reply->errorString();
qWarning() << "网络请求失败:" << requestId << errorString;
// 判断是否应该重试
bool shouldRetry = false;
switch (error) {
case QNetworkReply::TimeoutError:
case QNetworkReply::TemporaryNetworkFailureError:
case QNetworkReply::NetworkSessionFailedError:
case QNetworkReply::BackgroundRequestNotAllowedError:
shouldRetry = true;
break;
case QNetworkReply::HostNotFoundError:
case QNetworkReply::ConnectionRefusedError:
emit serverUnreachable();
shouldRetry = true;
break;
default:
shouldRetry = false;
break;
}
// 重试逻辑
if (shouldRetry && request.retryCount < request.maxRetries) {
qDebug() << "重试请求:" << requestId << "第" << (request.retryCount + 1) << "次";
retryRequest(request);
} else {
// 发送失败信号
emit requestFailed(requestId, errorString);
}
}
void NetworkManager::retryRequest(const NetworkRequest &request)
{
NetworkRequest retryRequest = request;
retryRequest.retryCount++;
// 延迟重试
QTimer::singleShot(m_retryDelayMs * retryRequest.retryCount, [this, retryRequest]() {
m_requestQueue->enqueue(retryRequest);
});
}
void NetworkManager::checkNetworkStatus()
{
NetworkStatus oldStatus = m_networkStatus;
// 检查网络接口状态
bool hasActiveInterface = false;
QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
for (const QNetworkInterface &interface : interfaces) {
if (interface.flags().testFlag(QNetworkInterface::IsUp) &&
interface.flags().testFlag(QNetworkInterface::IsRunning) &&
!interface.flags().testFlag(QNetworkInterface::IsLoopBack)) {
hasActiveInterface = true;
break;
}
}
if (!hasActiveInterface) {
m_networkStatus = Offline;
} else {
// 简单的连通性测试
QHostInfo::lookupHost("www.google.com", this, [this](const QHostInfo &info) {
if (info.error() == QHostInfo::NoError) {
m_networkStatus = Online;
} else {
m_networkStatus = Limited;
}
});
// 默认假设在线(如果DNS查找正在进行)
if (m_networkStatus == Unknown) {
m_networkStatus = Online;
}
}
if (m_networkStatus != oldStatus) {
qDebug() << "网络状态改变:" << oldStatus << "->" << m_networkStatus;
emit networkStatusChanged(m_networkStatus);
}
}
void NetworkManager::onNetworkStatusChanged()
{
checkNetworkStatus();
}
void NetworkManager::cancelRequest(const QString &requestId)
{
QMutexLocker locker(&m_requestMutex);
// 从队列中移除
m_requestQueue->remove(requestId);
// 取消正在进行的请求
for (auto it = m_replyToRequestId.begin(); it != m_replyToRequestId.end(); ++it) {
if (it.value() == requestId) {
it.key()->abort();
break;
}
}
m_pendingRequests.remove(requestId);
qDebug() << "取消请求:" << requestId;
}
void NetworkManager::cancelAllRequests()
{
QMutexLocker locker(&m_requestMutex);
m_requestQueue->clear();
// 取消所有正在进行的请求
for (auto it = m_replyToRequestId.begin(); it != m_replyToRequestId.end(); ++it) {
it.key()->abort();
}
m_replyToRequestId.clear();
m_pendingRequests.clear();
qDebug() << "取消所有请求";
}
int NetworkManager::getPendingRequestsCount() const
{
return m_pendingRequests.size() + m_requestQueue->size();
}
void NetworkManager::enableCache(bool enabled, int cacheSizeKB)
{
m_cacheEnabled = enabled;
if (enabled) {
m_responseCache->setMaxSize(cacheSizeKB * 1024);
qDebug() << "启用响应缓存,大小:" << cacheSizeKB << "KB";
} else {
qDebug() << "禁用响应缓存";
}
}
void NetworkManager::clearCache()
{
m_responseCache->clear();
qDebug() << "清空响应缓存";
}
// 辅助方法实现
QString NetworkManager::generateRequestId()
{
return QUuid::createUuid().toString(QUuid::WithoutBraces);
}
QJsonObject NetworkManager::parseJsonResponse(const QByteArray &data)
{
if (data.isEmpty()) {
return QJsonObject();
}
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "JSON解析失败:" << error.errorString();
qWarning() << "响应数据:" << data;
return QJsonObject();
}
return doc.object();
}
void NetworkManager::addAuthHeader(QNetworkRequest &request)
{
if (!m_authToken.isEmpty()) {
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_authToken).toUtf8());
}
}
void NetworkManager::logRequest(const NetworkRequest &request)
{
QString typeStr;
switch (request.type) {
case NetworkRequest::GET: typeStr = "GET"; break;
case NetworkRequest::POST: typeStr = "POST"; break;
case NetworkRequest::PUT: typeStr = "PUT"; break;
case NetworkRequest::DELETE: typeStr = "DELETE"; break;
}
qDebug() << QString("🚀 [%1] %2 %3 (优先级: %4)")
.arg(typeStr, request.id.left(8), request.url.toString())
.arg(request.priority);
}
void NetworkManager::logResponse(const NetworkResponse &response)
{
QString statusIcon = response.isSuccess() ? "✅" : "❌";
qDebug() << QString("%1 [%2] %3ms - %4")
.arg(statusIcon, response.requestId.left(8))
.arg(response.responseTimeMs)
.arg(response.statusCode);
if (!response.isSuccess()) {
qDebug() << "错误信息:" << response.errorMessage;
}
}
🔐 认证管理器
🛡️ AuthManager类
AuthManager.h
#ifndef AUTHMANAGER_H
#define AUTHMANAGER_H
#include <QObject>
#include <QJsonObject>
#include <QDateTime>
#include <QTimer>
#include <QSettings>
struct UserInfo {
int id = -1;
QString username;
QString email;
QDateTime lastLoginAt;
bool isValid() const {
return id > 0 && !username.isEmpty();
}
};
class AuthManager : public QObject
{
Q_OBJECT
public:
explicit AuthManager(QObject *parent = nullptr);
~AuthManager();
// 认证状态
bool isAuthenticated() const;
QString getToken() const;
UserInfo getCurrentUser() const;
// 令牌管理
void setToken(const QString &token);
void clearToken();
bool isTokenExpired() const;
// 用户信息
void setUserInfo(const UserInfo &user);
void clearUserInfo();
// 自动登录
void enableAutoLogin(bool enabled);
bool isAutoLoginEnabled() const;
// 令牌刷新
void enableAutoRefresh(bool enabled);
void scheduleTokenRefresh();
signals:
void authenticationChanged(bool authenticated);
void tokenExpired();
void tokenRefreshNeeded();
void userInfoChanged(const UserInfo &user);
private slots:
void checkTokenExpiration();
void onTokenRefreshTimer();
private:
void parseToken();
void saveToSettings();
void loadFromSettings();
QDateTime getTokenExpiry() const;
private:
QString m_token;
UserInfo m_currentUser;
QDateTime m_tokenExpiry;
bool m_autoLoginEnabled;
bool m_autoRefreshEnabled;
QTimer *m_expiryCheckTimer;
QTimer *m_refreshTimer;
QSettings *m_settings;
};
#endif // AUTHMANAGER_H
AuthManager.cpp
#include "AuthManager.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QDebug>
#include <QCryptographicHash>
AuthManager::AuthManager(QObject *parent)
: QObject(parent)
, m_autoLoginEnabled(false)
, m_autoRefreshEnabled(true)
, m_expiryCheckTimer(new QTimer(this))
, m_refreshTimer(new QTimer(this))
, m_settings(new QSettings("CloudNote", "Auth", this))
{
// 配置过期检查定时器
m_expiryCheckTimer->setSingleShot(false);
m_expiryCheckTimer->setInterval(60000); // 每分钟检查一次
connect(m_expiryCheckTimer, &QTimer::timeout, this, &AuthManager::checkTokenExpiration);
// 配置刷新定时器
m_refreshTimer->setSingleShot(true);
connect(m_refreshTimer, &QTimer::timeout, this, &AuthManager::onTokenRefreshTimer);
// 从设置中加载认证信息
loadFromSettings();
// 如果有有效令牌,启动检查定时器
if (isAuthenticated()) {
m_expiryCheckTimer->start();
scheduleTokenRefresh();
}
}
AuthManager::~AuthManager()
{
saveToSettings();
}
bool AuthManager::isAuthenticated() const
{
return !m_token.isEmpty() && m_currentUser.isValid() && !isTokenExpired();
}
QString AuthManager::getToken() const
{
return m_token;
}
UserInfo AuthManager::getCurrentUser() const
{
return m_currentUser;
}
void AuthManager::setToken(const QString &token)
{
if (m_token != token) {
m_token = token;
if (!token.isEmpty()) {
parseToken();
m_expiryCheckTimer->start();
scheduleTokenRefresh();
} else {
m_tokenExpiry = QDateTime();
m_expiryCheckTimer->stop();
m_refreshTimer->stop();
}
saveToSettings();
emit authenticationChanged(isAuthenticated());
}
}
void AuthManager::clearToken()
{
setToken(QString());
clearUserInfo();
}
bool AuthManager::isTokenExpired() const
{
if (m_tokenExpiry.isValid()) {
return QDateTime::currentDateTime() >= m_tokenExpiry;
}
return false;
}
void AuthManager::setUserInfo(const UserInfo &user)
{
if (m_currentUser.id != user.id || m_currentUser.username != user.username) {
m_currentUser = user;
saveToSettings();
emit userInfoChanged(user);
}
}
void AuthManager::clearUserInfo()
{
if (m_currentUser.isValid()) {
m_currentUser = UserInfo();
saveToSettings();
emit userInfoChanged(m_currentUser);
}
}
void AuthManager::enableAutoLogin(bool enabled)
{
if (m_autoLoginEnabled != enabled) {
m_autoLoginEnabled = enabled;
m_settings->setValue("autoLogin", enabled);
if (!enabled) {
// 如果禁用自动登录,清除保存的令牌
m_settings->remove("token");
}
}
}
bool AuthManager::isAutoLoginEnabled() const
{
return m_autoLoginEnabled;
}
void AuthManager::enableAutoRefresh(bool enabled)
{
if (m_autoRefreshEnabled != enabled) {
m_autoRefreshEnabled = enabled;
m_settings->setValue("autoRefresh", enabled);
if (enabled) {
scheduleTokenRefresh();
} else {
m_refreshTimer->stop();
}
}
}
void AuthManager::scheduleTokenRefresh()
{
if (!m_autoRefreshEnabled || !m_tokenExpiry.isValid()) {
return;
}
// 在令牌过期前5分钟尝试刷新
QDateTime refreshTime = m_tokenExpiry.addSecs(-300);
QDateTime now = QDateTime::currentDateTime();
if (refreshTime > now) {
qint64 msecs = now.msecsTo(refreshTime);
m_refreshTimer->start(msecs);
qDebug() << "令牌刷新已安排在:" << refreshTime.toString();
} else {
// 如果已经需要刷新,立即触发
QTimer::singleShot(1000, this, &AuthManager::onTokenRefreshTimer);
}
}
void AuthManager::checkTokenExpiration()
{
if (isTokenExpired()) {
qWarning() << "认证令牌已过期";
emit tokenExpired();
clearToken();
}
}
void AuthManager::onTokenRefreshTimer()
{
if (isAuthenticated()) {
qDebug() << "令牌即将过期,请求刷新";
emit tokenRefreshNeeded();
}
}
void AuthManager::parseToken()
{
if (m_token.isEmpty()) {
return;
}
// 简单的JWT解析(仅解析过期时间)
QStringList parts = m_token.split('.');
if (parts.size() != 3) {
qWarning() << "无效的JWT令牌格式";
return;
}
// 解码payload部分
QByteArray payload = parts[1].toUtf8();
// 添加必要的填充
int padding = 4 - (payload.length() % 4);
if (padding != 4) {
payload.append(QByteArray(padding, '='));
}
// Base64解码
payload = payload.replace('-', '+').replace('_', '/');
QByteArray decoded = QByteArray::fromBase64(payload);
// JSON解析
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(decoded, &error);
if (error.error == QJsonParseError::NoError && doc.isObject()) {
QJsonObject obj = doc.object();
// 提取过期时间
if (obj.contains("exp")) {
qint64 expTimestamp = obj["exp"].toVariant().toLongLong();
m_tokenExpiry = QDateTime::fromSecsSinceEpoch(expTimestamp);
qDebug() << "令牌过期时间:" << m_tokenExpiry.toString();
}
// 提取用户信息
if (obj.contains("userId") && obj.contains("username")) {
UserInfo user;
user.id = obj["userId"].toInt();
user.username = obj["username"].toString();
user.email = obj["email"].toString();
setUserInfo(user);
}
} else {
qWarning() << "JWT payload解析失败:" << error.errorString();
}
}
void AuthManager::saveToSettings()
{
if (m_autoLoginEnabled && !m_token.isEmpty()) {
// 对令牌进行简单加密存储
QByteArray tokenData = m_token.toUtf8();
QByteArray key = "CloudNote_Auth_Key";
for (int i = 0; i < tokenData.size(); ++i) {
tokenData[i] = tokenData[i] ^ key[i % key.size()];
}
m_settings->setValue("token", tokenData.toBase64());
} else {
m_settings->remove("token");
}
// 保存用户信息
if (m_currentUser.isValid()) {
m_settings->setValue("userId", m_currentUser.id);
m_settings->setValue("username", m_currentUser.username);
m_settings->setValue("email", m_currentUser.email);
} else {
m_settings->remove("userId");
m_settings->remove("username");
m_settings->remove("email");
}
}
void AuthManager::loadFromSettings()
{
// 加载设置
m_autoLoginEnabled = m_settings->value("autoLogin", false).toBool();
m_autoRefreshEnabled = m_settings->value("autoRefresh", true).toBool();
// 加载用户信息
if (m_settings->contains("userId")) {
UserInfo user;
user.id = m_settings->value("userId").toInt();
user.username = m_settings->value("username").toString();
user.email = m_settings->value("email").toString();
m_currentUser = user;
}
// 加载令牌(如果启用自动登录)
if (m_autoLoginEnabled && m_settings->contains("token")) {
QByteArray tokenData = QByteArray::fromBase64(m_settings->value("token").toByteArray());
QByteArray key = "CloudNote_Auth_Key";
for (int i = 0; i < tokenData.size(); ++i) {
tokenData[i] = tokenData[i] ^ key[i % key.size()];
}
QString token = QString::fromUtf8(tokenData);
if (!token.isEmpty()) {
m_token = token;
parseToken();
}
}
}
QDateTime AuthManager::getTokenExpiry() const
{
return m_tokenExpiry;
}
📋 请求队列管理
🚦 RequestQueue类
RequestQueue.h
#ifndef REQUESTQUEUE_H
#define REQUESTQUEUE_H
#include <QObject>
#include <QQueue>
#include <QMutex>
#include <QMap>
#include <optional>
struct NetworkRequest; // 前向声明
class RequestQueue : public QObject
{
Q_OBJECT
public:
explicit RequestQueue(QObject *parent = nullptr);
// 队列操作
void enqueue(const NetworkRequest &request);
std::optional<NetworkRequest> dequeue();
void remove(const QString &requestId);
void clear();
// 队列状态
int size() const;
bool isEmpty() const;
QStringList getPendingRequestIds() const;
// 优先级管理
void setPriorityThreshold(int threshold);
int getPriorityThreshold() const;
signals:
void requestEnqueued(const QString &requestId);
void requestDequeued(const QString &requestId);
void queueEmptied();
private:
struct QueueItem {
NetworkRequest request;
QDateTime enqueuedAt;
bool operator<(const QueueItem &other) const {
// 优先级高的先出队(数值越大优先级越高)
if (request.priority != other.request.priority) {
return request.priority > other.request.priority;
}
// 同优先级按时间排序(先进先出)
return enqueuedAt < other.enqueuedAt;
}
};
QQueue<QueueItem> m_queue;
QMap<QString, int> m_requestIdToIndex; // 用于快速查找
mutable QMutex m_mutex;
int m_priorityThreshold;
void sortQueue();
};
#endif // REQUESTQUEUE_H
RequestQueue.cpp
#include "RequestQueue.h"
#include "NetworkManager.h" // 为了NetworkRequest定义
#include <QDebug>
RequestQueue::RequestQueue(QObject *parent)
: QObject(parent)
, m_priorityThreshold(0)
{
}
void RequestQueue::enqueue(const NetworkRequest &request)
{
QMutexLocker locker(&m_mutex);
QueueItem item;
item.request = request;
item.enqueuedAt = QDateTime::currentDateTime();
m_queue.enqueue(item);
m_requestIdToIndex[request.id] = m_queue.size() - 1;
// 如果是高优先级请求,重新排序队列
if (request.priority > m_priorityThreshold) {
sortQueue();
}
emit requestEnqueued(request.id);
qDebug() << "请求已入队:" << request.id << "优先级:" << request.priority;
}
std::optional<NetworkRequest> RequestQueue::dequeue()
{
QMutexLocker locker(&m_mutex);
if (m_queue.isEmpty()) {
return std::nullopt;
}
QueueItem item = m_queue.dequeue();
m_requestIdToIndex.remove(item.request.id);
// 更新索引映射
for (auto it = m_requestIdToIndex.begin(); it != m_requestIdToIndex.end(); ++it) {
if (it.value() > 0) {
it.value()--;
}
}
emit requestDequeued(item.request.id);
if (m_queue.isEmpty()) {
emit queueEmptied();
}
qDebug() << "请求已出队:" << item.request.id;
return item.request;
}
void RequestQueue::remove(const QString &requestId)
{
QMutexLocker locker(&m_mutex);
if (!m_requestIdToIndex.contains(requestId)) {
return;
}
int index = m_requestIdToIndex.value(requestId);
if (index >= 0 && index < m_queue.size()) {
// 由于QQueue不支持随机删除,我们需要重建队列
QQueue<QueueItem> newQueue;
QMap<QString, int> newIndexMap;
for (int i = 0; i < m_queue.size(); ++i) {
if (i != index) {
newQueue.enqueue(m_queue[i]);
newIndexMap[m_queue[i].request.id] = newQueue.size() - 1;
}
}
m_queue = newQueue;
m_requestIdToIndex = newIndexMap;
qDebug() << "从队列中移除请求:" << requestId;
}
}
void RequestQueue::clear()
{
QMutexLocker locker(&m_mutex);
int removedCount = m_queue.size();
m_queue.clear();
m_requestIdToIndex.clear();
if (removedCount > 0) {
emit queueEmptied();
qDebug() << "清空请求队列,移除" << removedCount << "个请求";
}
}
int RequestQueue::size() const
{
QMutexLocker locker(&m_mutex);
return m_queue.size();
}
bool RequestQueue::isEmpty() const
{
QMutexLocker locker(&m_mutex);
return m_queue.isEmpty();
}
QStringList RequestQueue::getPendingRequestIds() const
{
QMutexLocker locker(&m_mutex);
return m_requestIdToIndex.keys();
}
void RequestQueue::setPriorityThreshold(int threshold)
{
QMutexLocker locker(&m_mutex);
m_priorityThreshold = threshold;
}
int RequestQueue::getPriorityThreshold() const
{
QMutexLocker locker(&m_mutex);
return m_priorityThreshold;
}
void RequestQueue::sortQueue()
{
// 将队列转换为列表进行排序
QList<QueueItem> items;
while (!m_queue.isEmpty()) {
items.append(m_queue.dequeue());
}
// 排序(优先级高的在前)
std::sort(items.begin(), items.end());
// 重建队列和索引
m_requestIdToIndex.clear();
for (int i = 0; i < items.size(); ++i) {
m_queue.enqueue(items[i]);
m_requestIdToIndex[items[i].request.id] = i;
}
}
🗄️ 响应缓存
💾 ResponseCache类
ResponseCache.h
#ifndef RESPONSECACHE_H
#define RESPONSECACHE_H
#include <QObject>
#include <QJsonObject>
#include <QDateTime>
#include <QMap>
#include <QMutex>
struct CacheEntry {
QJsonObject data;
QDateTime cachedAt;
QDateTime expiresAt;
qint64 size;
bool isValid() const {
return expiresAt > QDateTime::currentDateTime();
}
bool isExpired() const {
return QDateTime::currentDateTime() > expiresAt;
}
};
class ResponseCache : public QObject
{
Q_OBJECT
public:
explicit ResponseCache(QObject *parent = nullptr);
// 缓存操作
void put(const QString &key, const QJsonObject &data, int ttlSeconds = 300);
QJsonObject get(const QString &key);
bool contains(const QString &key);
void remove(const QString &key);
void clear();
// 缓存配置
void setMaxSize(qint64 maxSizeBytes);
void setDefaultTtl(int ttlSeconds);
// 缓存状态
int size() const;
qint64 totalSize() const;
qint64 maxSize() const;
double hitRate() const;
// 维护操作
void cleanup();
void evictLeastRecentlyUsed();
signals:
void cacheHit(const QString &key);
void cacheMiss(const QString &key);
void entryExpired(const QString &key);
void cacheFull();
private slots:
void performCleanup();
private:
QMap<QString, CacheEntry> m_cache;
QMap<QString, QDateTime> m_accessTimes; // 用于LRU淘汰
mutable QMutex m_mutex;
qint64 m_maxSize;
int m_defaultTtl;
quint64 m_hitCount;
quint64 m_missCount;
QTimer *m_cleanupTimer;
qint64 calculateSize(const QJsonObject &data) const;
void evictExpiredEntries();
void evictOldestEntries();
};
#endif // RESPONSECACHE_H
ResponseCache.cpp
#include "ResponseCache.h"
#include <QJsonDocument>
#include <QTimer>
#include <QDebug>
#include <algorithm>
ResponseCache::ResponseCache(QObject *parent)
: QObject(parent)
, m_maxSize(10 * 1024 * 1024) // 10MB 默认
, m_defaultTtl(300) // 5分钟默认
, m_hitCount(0)
, m_missCount(0)
, m_cleanupTimer(new QTimer(this))
{
// 配置清理定时器,每5分钟清理一次过期条目
m_cleanupTimer->setSingleShot(false);
m_cleanupTimer->setInterval(5 * 60 * 1000);
connect(m_cleanupTimer, &QTimer::timeout, this, &ResponseCache::performCleanup);
m_cleanupTimer->start();
}
void ResponseCache::put(const QString &key, const QJsonObject &data, int ttlSeconds)
{
if (key.isEmpty()) {
return;
}
QMutexLocker locker(&m_mutex);
CacheEntry entry;
entry.data = data;
entry.cachedAt = QDateTime::currentDateTime();
entry.expiresAt = entry.cachedAt.addSecs(ttlSeconds > 0 ? ttlSeconds : m_defaultTtl);
entry.size = calculateSize(data);
// 检查是否需要清理空间
qint64 currentSize = totalSize();
if (currentSize + entry.size > m_maxSize) {
evictLeastRecentlyUsed();
// 如果仍然没有足够空间,不添加新条目
if (totalSize() + entry.size > m_maxSize) {
emit cacheFull();
qWarning() << "缓存空间不足,无法添加条目:" << key;
return;
}
}
m_cache[key] = entry;
m_accessTimes[key] = QDateTime::currentDateTime();
qDebug() << "缓存条目已添加:" << key << "大小:" << entry.size << "过期时间:" << entry.expiresAt.toString();
}
QJsonObject ResponseCache::get(const QString &key)
{
if (key.isEmpty()) {
m_missCount++;
emit cacheMiss(key);
return QJsonObject();
}
QMutexLocker locker(&m_mutex);
if (!m_cache.contains(key)) {
m_missCount++;
emit cacheMiss(key);
return QJsonObject();
}
CacheEntry &entry = m_cache[key];
// 检查是否过期
if (entry.isExpired()) {
m_cache.remove(key);
m_accessTimes.remove(key);
m_missCount++;
emit entryExpired(key);
return QJsonObject();
}
// 更新访问时间(LRU)
m_accessTimes[key] = QDateTime::currentDateTime();
m_hitCount++;
emit cacheHit(key);
return entry.data;
}
bool ResponseCache::contains(const QString &key)
{
QMutexLocker locker(&m_mutex);
if (!m_cache.contains(key)) {
return false;
}
// 检查是否过期
if (m_cache[key].isExpired()) {
m_cache.remove(key);
m_accessTimes.remove(key);
return false;
}
return true;
}
void ResponseCache::remove(const QString &key)
{
QMutexLocker locker(&m_mutex);
if (m_cache.remove(key) > 0) {
m_accessTimes.remove(key);
qDebug() << "缓存条目已移除:" << key;
}
}
void ResponseCache::clear()
{
QMutexLocker locker(&m_mutex);
int removedCount = m_cache.size();
m_cache.clear();
m_accessTimes.clear();
m_hitCount = 0;
m_missCount = 0;
qDebug() << "缓存已清空,移除" << removedCount << "个条目";
}
void ResponseCache::setMaxSize(qint64 maxSizeBytes)
{
QMutexLocker locker(&m_mutex);
m_maxSize = qMax(static_cast<qint64>(1024), maxSizeBytes); // 最小1KB
// 如果当前大小超过新限制,进行清理
if (totalSize() > m_maxSize) {
evictLeastRecentlyUsed();
}
qDebug() << "缓存最大大小设置为:" << (m_maxSize / 1024) << "KB";
}
void ResponseCache::setDefaultTtl(int ttlSeconds)
{
m_defaultTtl = qMax(10, ttlSeconds); // 最少10秒TTL
qDebug() << "缓存默认TTL设置为:" << m_defaultTtl << "秒";
}
int ResponseCache::size() const
{
QMutexLocker locker(&m_mutex);
return m_cache.size();
}
qint64 ResponseCache::totalSize() const
{
qint64 total = 0;
for (const CacheEntry &entry : m_cache) {
total += entry.size;
}
return total;
}
qint64 ResponseCache::maxSize() const
{
return m_maxSize;
}
double ResponseCache::hitRate() const
{
quint64 totalRequests = m_hitCount + m_missCount;
if (totalRequests == 0) {
return 0.0;
}
return (static_cast<double>(m_hitCount) / totalRequests) * 100.0;
}
void ResponseCache::cleanup()
{
QMutexLocker locker(&m_mutex);
evictExpiredEntries();
}
void ResponseCache::evictLeastRecentlyUsed()
{
if (m_cache.isEmpty()) {
return;
}
// 先清理过期条目
evictExpiredEntries();
// 如果仍然需要空间,按LRU策略清理
while (totalSize() > m_maxSize * 0.8 && !m_cache.isEmpty()) {
evictOldestEntries();
}
}
void ResponseCache::performCleanup()
{
QMutexLocker locker(&m_mutex);
int sizeBefore = m_cache.size();
evictExpiredEntries();
int sizeAfter = m_cache.size();
if (sizeBefore != sizeAfter) {
qDebug() << "缓存清理完成,移除" << (sizeBefore - sizeAfter) << "个过期条目";
}
}
qint64 ResponseCache::calculateSize(const QJsonObject &data) const
{
QJsonDocument doc(data);
return doc.toJson(QJsonDocument::Compact).size();
}
void ResponseCache::evictExpiredEntries()
{
QStringList keysToRemove;
for (auto it = m_cache.begin(); it != m_cache.end(); ++it) {
if (it.value().isExpired()) {
keysToRemove.append(it.key());
}
}
for (const QString &key : keysToRemove) {
m_cache.remove(key);
m_accessTimes.remove(key);
emit entryExpired(key);
}
}
void ResponseCache::evictOldestEntries()
{
if (m_accessTimes.isEmpty()) {
return;
}
// 找到最久未访问的条目
QString oldestKey;
QDateTime oldestTime = QDateTime::currentDateTime();
for (auto it = m_accessTimes.begin(); it != m_accessTimes.end(); ++it) {
if (it.value() < oldestTime) {
oldestTime = it.value();
oldestKey = it.key();
}
}
if (!oldestKey.isEmpty()) {
m_cache.remove(oldestKey);
m_accessTimes.remove(oldestKey);
qDebug() << "LRU淘汰缓存条目:" << oldestKey;
}
}
🧪 网络客户端API服务
📡 CloudNoteApiService类
CloudNoteApiService.h
#ifndef CLOUDNOTEAPISERVICE_H
#define CLOUDNOTEAPISERVICE_H
#include <QObject>
#include <QJsonObject>
#include <QJsonArray>
#include "NetworkManager.h"
#include "AuthManager.h"
#include "User.h"
#include "Note.h"
#include "Category.h"
class CloudNoteApiService : public QObject
{
Q_OBJECT
public:
explicit CloudNoteApiService(QObject *parent = nullptr);
// 认证API
QString registerUser(const QString &username, const QString &email, const QString &password);
QString loginUser(const QString &username, const QString &password);
QString getUserProfile();
QString updateUserProfile(const QString &email, const QString &newPassword = QString());
void logout();
// 笔记API
QString getNotes(int page = 0, int pageSize = 20, int categoryId = -1);
QString createNote(const Note ¬e);
QString getNote(int noteId);
QString updateNote(const Note ¬e);
QString deleteNote(int noteId, bool hardDelete = false);
QString searchNotes(const QString &keyword, int page = 0, int pageSize = 20);
// 分类API
QString getCategories();
QString createCategory(const Category &category);
QString updateCategory(const Category &category);
QString deleteCategory(int categoryId);
// 同步API
QString syncNotes(const QDateTime &lastSyncTime = QDateTime());
QString uploadNote(const Note ¬e);
// 状态查询
bool isAuthenticated() const;
UserInfo getCurrentUser() const;
NetworkManager::NetworkStatus getNetworkStatus() const;
signals:
// 认证相关信号
void userRegistered(const UserInfo &user, const QString &token);
void userLoggedIn(const UserInfo &user, const QString &token);
void userProfileUpdated(const UserInfo &user);
void authenticationFailed(const QString &error);
void userLoggedOut();
// 笔记相关信号
void notesReceived(const QList<Note> ¬es, int totalCount, int page);
void noteReceived(const Note ¬e);
void noteCreated(const Note ¬e);
void noteUpdated(const Note ¬e);
void noteDeleted(int noteId);
void notesSearchCompleted(const QList<Note> ¬es, const QString &keyword);
void noteOperationFailed(const QString &error);
// 分类相关信号
void categoriesReceived(const QList<Category> &categories);
void categoryCreated(const Category &category);
void categoryUpdated(const Category &category);
void categoryDeleted(int categoryId);
void categoryOperationFailed(const QString &error);
// 同步相关信号
void syncCompleted(int uploadedCount, int downloadedCount);
void syncFailed(const QString &error);
void syncProgressChanged(int current, int total);
// 网络状态信号
void networkStatusChanged(NetworkManager::NetworkStatus status);
void serverUnreachable();
private slots:
void handleNetworkResponse(const QString &requestId, const NetworkResponse &response);
void handleNetworkError(const QString &requestId, const QString &error);
void handleAuthenticationRequired();
void handleNetworkStatusChanged(NetworkManager::NetworkStatus status);
private:
// 响应处理方法
void handleRegisterResponse(const NetworkResponse &response);
void handleLoginResponse(const NetworkResponse &response);
void handleUserProfileResponse(const NetworkResponse &response);
void handleNotesResponse(const NetworkResponse &response, const QString &originalRequestId);
void handleNoteResponse(const NetworkResponse &response);
void handleCategoriesResponse(const NetworkResponse &response);
void handleSyncResponse(const NetworkResponse &response);
// 数据转换方法
Note jsonToNote(const QJsonObject &json);
Category jsonToCategory(const QJsonObject &json);
UserInfo jsonToUserInfo(const QJsonObject &json);
QJsonObject noteToJson(const Note ¬e);
QJsonObject categoryToJson(const Category &category);
// 错误处理
void handleApiError(const NetworkResponse &response, const QString &operation);
private:
NetworkManager *m_networkManager;
AuthManager *m_authManager;
// 请求映射(用于追踪请求类型)
QMap<QString, QString> m_requestTypes;
QMap<QString, QJsonObject> m_requestContexts;
};
#endif // CLOUDNOTEAPISERVICE_H
CloudNoteApiService.cpp (关键方法实现)
#include "CloudNoteApiService.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QDebug>
#include <QUrlQuery>
CloudNoteApiService::CloudNoteApiService(QObject *parent)
: QObject(parent)
, m_networkManager(NetworkManager::instance())
, m_authManager(new AuthManager(this))
{
// 连接网络管理器信号
connect(m_networkManager, &NetworkManager::requestFinished,
this, &CloudNoteApiService::handleNetworkResponse);
connect(m_networkManager, &NetworkManager::requestFailed,
this, &CloudNoteApiService::handleNetworkError);
connect(m_networkManager, &NetworkManager::authenticationRequired,
this, &CloudNoteApiService::handleAuthenticationRequired);
connect(m_networkManager, &NetworkManager::networkStatusChanged,
this, &CloudNoteApiService::handleNetworkStatusChanged);
// 连接认证管理器信号
connect(m_authManager, &AuthManager::authenticationChanged, [this](bool authenticated) {
if (!authenticated) {
emit userLoggedOut();
}
});
connect(m_authManager, &AuthManager::tokenExpired, [this]() {
emit authenticationFailed("认证令牌已过期,请重新登录");
});
// 设置认证令牌到网络管理器
if (m_authManager->isAuthenticated()) {
m_networkManager->setAuthToken(m_authManager->getToken());
}
}
QString CloudNoteApiService::registerUser(const QString &username, const QString &email, const QString &password)
{
QJsonObject data;
data["username"] = username;
data["email"] = email;
data["password"] = password;
QString requestId = m_networkManager->post("/api/auth/register", data, false); // 注册不需要认证
m_requestTypes[requestId] = "register";
return requestId;
}
QString CloudNoteApiService::loginUser(const QString &username, const QString &password)
{
QJsonObject data;
data["username"] = username;
data["password"] = password;
QString requestId = m_networkManager->post("/api/auth/login", data, false); // 登录不需要认证
m_requestTypes[requestId] = "login";
return requestId;
}
QString CloudNoteApiService::getNotes(int page, int pageSize, int categoryId)
{
QJsonObject params;
params["page"] = page;
params["pageSize"] = pageSize;
if (categoryId > 0) {
params["categoryId"] = categoryId;
}
QString requestId = m_networkManager->get("/api/notes", params);
m_requestTypes[requestId] = "getNotes";
// 保存请求上下文
QJsonObject context;
context["page"] = page;
context["pageSize"] = pageSize;
m_requestContexts[requestId] = context;
return requestId;
}
QString CloudNoteApiService::createNote(const Note ¬e)
{
QJsonObject data = noteToJson(note);
QString requestId = m_networkManager->post("/api/notes", data);
m_requestTypes[requestId] = "createNote";
return requestId;
}
QString CloudNoteApiService::searchNotes(const QString &keyword, int page, int pageSize)
{
QJsonObject params;
params["keyword"] = keyword;
params["page"] = page;
params["pageSize"] = pageSize;
QString requestId = m_networkManager->get("/api/notes/search", params);
m_requestTypes[requestId] = "searchNotes";
// 保存搜索关键词
QJsonObject context;
context["keyword"] = keyword;
m_requestContexts[requestId] = context;
return requestId;
}
void CloudNoteApiService::handleNetworkResponse(const QString &requestId, const NetworkResponse &response)
{
QString requestType = m_requestTypes.value(requestId);
if (!response.isSuccess()) {
handleApiError(response, requestType);
return;
}
// 根据请求类型分发处理
if (requestType == "register") {
handleRegisterResponse(response);
} else if (requestType == "login") {
handleLoginResponse(response);
} else if (requestType == "getUserProfile") {
handleUserProfileResponse(response);
} else if (requestType == "getNotes") {
handleNotesResponse(response, requestId);
} else if (requestType == "getNote" || requestType == "createNote" || requestType == "updateNote") {
handleNoteResponse(response);
} else if (requestType == "searchNotes") {
handleNotesResponse(response, requestId);
} else if (requestType == "getCategories" || requestType == "createCategory" || requestType == "updateCategory") {
handleCategoriesResponse(response);
}
// 清理映射
m_requestTypes.remove(requestId);
m_requestContexts.remove(requestId);
}
void CloudNoteApiService::handleLoginResponse(const NetworkResponse &response)
{
if (!response.data.contains("token") || !response.data.contains("user")) {
emit authenticationFailed("服务器响应格式错误");
return;
}
QString token = response.data["token"].toString();
QJsonObject userObj = response.data["user"].toObject();
UserInfo user = jsonToUserInfo(userObj);
// 设置认证信息
m_authManager->setToken(token);
m_authManager->setUserInfo(user);
m_networkManager->setAuthToken(token);
emit userLoggedIn(user, token);
qDebug() << "用户登录成功:" << user.username;
}
void CloudNoteApiService::handleNotesResponse(const NetworkResponse &response, const QString &originalRequestId)
{
if (!response.data.contains("data")) {
emit noteOperationFailed("服务器响应格式错误");
return;
}
QJsonArray notesArray = response.data["data"].toArray();
QList<Note> notes;
for (const QJsonValue &value : notesArray) {
Note note = jsonToNote(value.toObject());
if (note.isValid()) {
notes.append(note);
}
}
// 检查是否是搜索请求
QJsonObject context = m_requestContexts.value(originalRequestId);
if (context.contains("keyword")) {
QString keyword = context["keyword"].toString();
emit notesSearchCompleted(notes, keyword);
} else {
// 普通笔记列表请求
int totalCount = response.data.value("pagination").toObject().value("totalCount").toInt();
int page = context.value("page").toInt();
emit notesReceived(notes, totalCount, page);
}
}
// 数据转换方法
Note CloudNoteApiService::jsonToNote(const QJsonObject &json)
{
Note note;
note.id = json["id"].toInt();
note.userId = json["userId"].toInt();
note.title = json["title"].toString();
note.content = json["content"].toString();
note.contentType = json["contentType"].toString();
note.createdAt = QDateTime::fromString(json["createdAt"].toString(), Qt::ISODate);
note.modifiedAt = QDateTime::fromString(json["modifiedAt"].toString(), Qt::ISODate);
note.isDeleted = json["isDeleted"].toBool();
note.categoryId = json["categoryId"].toInt(-1);
// 解析标签
QJsonArray tagsArray = json["tags"].toArray();
for (const QJsonValue &value : tagsArray) {
note.tags.append(value.toString());
}
return note;
}
QJsonObject CloudNoteApiService::noteToJson(const Note ¬e)
{
QJsonObject json;
json["title"] = note.title;
json["content"] = note.content;
json["contentType"] = note.contentType;
if (note.categoryId > 0) {
json["categoryId"] = note.categoryId;
}
QJsonArray tagsArray;
for (const QString &tag : note.tags) {
tagsArray.append(tag);
}
json["tags"] = tagsArray;
return json;
}
bool CloudNoteApiService::isAuthenticated() const
{
return m_authManager->isAuthenticated();
}
UserInfo CloudNoteApiService::getCurrentUser() const
{
return m_authManager->getCurrentUser();
}
NetworkManager::NetworkStatus CloudNoteApiService::getNetworkStatus() const
{
return m_networkManager->networkStatus();
}
void CloudNoteApiService::handleApiError(const NetworkResponse &response, const QString &operation)
{
QString errorMessage = response.errorMessage;
if (errorMessage.isEmpty()) {
errorMessage = QString("操作失败 (HTTP %1)").arg(response.statusCode);
}
qWarning() << "API错误:" << operation << errorMessage;
// 根据操作类型发送相应的错误信号
if (operation.contains("auth") || operation == "register" || operation == "login") {
emit authenticationFailed(errorMessage);
} else if (operation.contains("note")) {
emit noteOperationFailed(errorMessage);
} else if (operation.contains("category")) {
emit categoryOperationFailed(errorMessage);
}
}
🧪 客户端测试示例
📋 测试程序
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include <QTimer>
#include "CloudNoteApiService.h"
#include "NetworkManager.h"
class ClientTester : public QObject
{
Q_OBJECT
public:
ClientTester(QObject *parent = nullptr) : QObject(parent)
{
m_apiService = new CloudNoteApiService(this);
// 连接信号
connect(m_apiService, &CloudNoteApiService::userLoggedIn,
this, &ClientTester::onUserLoggedIn);
connect(m_apiService, &CloudNoteApiService::notesReceived,
this, &ClientTester::onNotesReceived);
connect(m_apiService, &CloudNoteApiService::noteCreated,
this, &ClientTester::onNoteCreated);
connect(m_apiService, &CloudNoteApiService::authenticationFailed,
this, &ClientTester::onAuthFailed);
// 设置服务器地址
NetworkManager::instance()->setServerUrl("http://localhost:8080");
}
void runTests()
{
qDebug() << "🧪 开始CloudNote客户端测试";
// 测试用户登录
QString loginRequestId = m_apiService->loginUser("testuser2024", "password123");
qDebug() << "发送登录请求:" << loginRequestId;
}
private slots:
void onUserLoggedIn(const UserInfo &user, const QString &token)
{
qDebug() << "✅ 用户登录成功:" << user.username;
qDebug() << "令牌长度:" << token.length();
// 测试获取笔记列表
QTimer::singleShot(1000, [this]() {
QString notesRequestId = m_apiService->getNotes(0, 10);
qDebug() << "发送获取笔记请求:" << notesRequestId;
});
// 测试创建笔记
QTimer::singleShot(2000, [this]() {
Note newNote;
newNote.title = "客户端测试笔记";
newNote.content = "这是通过Qt客户端创建的测试笔记\n\n包含多行内容。";
newNote.contentType = "markdown";
newNote.tags = {"客户端", "测试", "Qt6"};
QString createRequestId = m_apiService->createNote(newNote);
qDebug() << "发送创建笔记请求:" << createRequestId;
});
}
void onNotesReceived(const QList<Note> ¬es, int totalCount, int page)
{
qDebug() << "✅ 收到笔记列表:";
qDebug() << " 页码:" << page;
qDebug() << " 总数:" << totalCount;
qDebug() << " 当前页笔记数:" << notes.size();
for (const Note ¬e : notes) {
qDebug() << " 📝" << note.title << "(" << note.tags.join(", ") << ")";
}
}
void onNoteCreated(const Note ¬e)
{
qDebug() << "✅ 笔记创建成功:";
qDebug() << " ID:" << note.id;
qDebug() << " 标题:" << note.title;
qDebug() << " 创建时间:" << note.createdAt.toString();
qDebug() << "🎉 所有测试完成!";
}
void onAuthFailed(const QString &error)
{
qDebug() << "❌ 认证失败:" << error;
}
private:
CloudNoteApiService *m_apiService;
};
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
ClientTester tester;
// 延迟启动测试,让网络管理器初始化完成
QTimer::singleShot(500, &tester, &ClientTester::runTests);
return app.exec();
}
#include "main.moc"
🎉 今日总结
✅ 完成成果
-
✅ 完整的网络客户端架构
- NetworkManager核心网络管理类
- 请求队列和优先级管理
- 响应缓存和LRU淘汰策略
- JWT认证和令牌管理
-
✅ 异步网络编程实现
- QNetworkAccessManager异步请求处理
- 网络状态检测和自动重连
- 请求重试和错误恢复机制
- 超时处理和取消机制
-
✅ 完整的API服务封装
- CloudNoteApiService高级API封装
- 用户认证、笔记管理、分类操作
- 数据同步和离线支持
- 信号驱动的响应处理
-
✅ 性能和可靠性优化
- 请求队列和并发控制
- 响应缓存减少重复请求
- 网络状态感知和智能重试
- 内存管理和资源清理
📚 学到的核心技术
- QNetworkAccessManager:Qt网络编程核心类使用
- 异步编程模式:信号槽机制处理异步响应
- 网络状态管理:连接检测和自动重连策略
- 缓存策略:LRU缓存和TTL过期机制
- 认证管理:JWT令牌解析和自动刷新
- 错误处理:网络异常和API错误处理
🔮 明日预告
Day 4: 用户界面开发
- Qt Widgets界面设计
- 登录注册界面实现
- 主窗口和笔记编辑器
- 响应式布局和用户体验
💡 实用小贴士
🛠️ 网络编程技巧
- 始终使用异步请求避免UI阻塞
- 实现请求超时和取消机制
- 合理设置重试策略和退避算法
- 使用缓存减少不必要的网络请求
🚨 常见陷阱
- 忘记处理网络断开情况
- 请求并发过多导致服务器压力
- 令牌过期后没有自动刷新
- 内存泄漏(QNetworkReply没有正确删除)
📖 推荐阅读
🎯 下篇预告: 明天我们将开发用户界面,创建现代化的Qt应用程序界面。
📝 源码获取: 完整项目源码持续更新中,网络客户端功能完整可用!
💬 交流讨论: 对网络编程有疑问?欢迎在评论区讨论异步编程和性能优化技巧!

1039

被折叠的 条评论
为什么被折叠?



