Qt6实战项目 Day3:CloudNote个人云笔记系统 - 网络通信客户端开发

🌐 Qt6实战项目 Day3:CloudNote个人云笔记系统 - 网络通信客户端开发

项目系列: CloudNote个人云笔记系统
技术栈: Qt 6.9.2 + QNetworkAccessManager + 异步网络编程
难度等级: ⭐⭐⭐⭐☆
预计用时: 1天

📋 项目回顾

前两天我们已经完成了:

  • Day 1: 数据库设计与本地存储功能
  • Day 2: HTTP服务器和RESTful API实现

今天我们将开发网络通信客户端,实现与服务器的完整交互,包括用户认证、笔记同步等功能。

🎯 今日学习目标

  • 掌握QNetworkAccessManager异步网络编程
  • 实现HTTP客户端请求封装
  • 构建请求队列和重试机制
  • 实现网络状态检测和错误处理
  • 设计数据同步策略
  • 集成JWT令牌管理

🏗️ 网络客户端架构设计

我们的网络客户端将采用分层架构,确保网络操作的可靠性和可维护性。

核心组件
网络客户端架构
网络管理器
请求队列
认证管理器
响应缓存
重试处理器
业务服务层
网络管理层
请求队列
认证管理
响应缓存
HTTP客户端
用户界面层
服务器API

🔧 核心网络管理类实现

🌐 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 &params = 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 &params, 
                           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 &note);
    QString getNote(int noteId);
    QString updateNote(const Note &note);
    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 &note);
    
    // 状态查询
    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> &notes, int totalCount, int page);
    void noteReceived(const Note &note);
    void noteCreated(const Note &note);
    void noteUpdated(const Note &note);
    void noteDeleted(int noteId);
    void notesSearchCompleted(const QList<Note> &notes, 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 &note);
    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 &note)
{
    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 &note)
{
    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> &notes, int totalCount, int page)
    {
        qDebug() << "✅ 收到笔记列表:";
        qDebug() << "   页码:" << page;
        qDebug() << "   总数:" << totalCount;
        qDebug() << "   当前页笔记数:" << notes.size();
        
        for (const Note &note : notes) {
            qDebug() << "   📝" << note.title << "(" << note.tags.join(", ") << ")";
        }
    }
    
    void onNoteCreated(const Note &note)
    {
        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"

🎉 今日总结

✅ 完成成果

  1. ✅ 完整的网络客户端架构

    • NetworkManager核心网络管理类
    • 请求队列和优先级管理
    • 响应缓存和LRU淘汰策略
    • JWT认证和令牌管理
  2. ✅ 异步网络编程实现

    • QNetworkAccessManager异步请求处理
    • 网络状态检测和自动重连
    • 请求重试和错误恢复机制
    • 超时处理和取消机制
  3. ✅ 完整的API服务封装

    • CloudNoteApiService高级API封装
    • 用户认证、笔记管理、分类操作
    • 数据同步和离线支持
    • 信号驱动的响应处理
  4. ✅ 性能和可靠性优化

    • 请求队列和并发控制
    • 响应缓存减少重复请求
    • 网络状态感知和智能重试
    • 内存管理和资源清理

📚 学到的核心技术

  • QNetworkAccessManager:Qt网络编程核心类使用
  • 异步编程模式:信号槽机制处理异步响应
  • 网络状态管理:连接检测和自动重连策略
  • 缓存策略:LRU缓存和TTL过期机制
  • 认证管理:JWT令牌解析和自动刷新
  • 错误处理:网络异常和API错误处理

🔮 明日预告

Day 4: 用户界面开发

  • Qt Widgets界面设计
  • 登录注册界面实现
  • 主窗口和笔记编辑器
  • 响应式布局和用户体验

💡 实用小贴士

🛠️ 网络编程技巧

  • 始终使用异步请求避免UI阻塞
  • 实现请求超时和取消机制
  • 合理设置重试策略和退避算法
  • 使用缓存减少不必要的网络请求

🚨 常见陷阱

  • 忘记处理网络断开情况
  • 请求并发过多导致服务器压力
  • 令牌过期后没有自动刷新
  • 内存泄漏(QNetworkReply没有正确删除)

📖 推荐阅读


🎯 下篇预告: 明天我们将开发用户界面,创建现代化的Qt应用程序界面。

📝 源码获取: 完整项目源码持续更新中,网络客户端功能完整可用!

💬 交流讨论: 对网络编程有疑问?欢迎在评论区讨论异步编程和性能优化技巧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吴纹185

扫1r呗

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值