feat(DataManager/DataLoader/HttpRequsestManager):添加数据加载/管理模块

This commit is contained in:
duanshengchao 2025-07-02 09:56:58 +08:00
parent 41ec213b5a
commit 5ba65992c4
7 changed files with 800 additions and 0 deletions

View File

@ -12,6 +12,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS PrintSupport)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Network)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Concurrent)
set(ADS_VERSION 4.3.1)
add_subdirectory(QtADS)
@ -37,11 +39,14 @@ set(H_HEADER_FILES
include/dashboardFrame.h
include/dashboardNamingDialog.h
include/dataPanel.h
include/dataLoader.h
include/dataManager.h
include/panelSelectionDialog.h
include/panelConfigurationWidget.h
include/dateTimeWidget.h
include/customCalendarWidget.h
include/dateTimeSelectionPanel.h
include/httpRequestManager.h
)
set(CPP_SOURCE_FILES
@ -63,11 +68,14 @@ set(CPP_SOURCE_FILES
source/dashboardFrame.cpp
source/dashboardNamingDialog.cpp
source/dataPanel.cpp
source/dataLoader.cpp
source/dataManager.cpp
source/panelSelectionDialog.cpp
source/panelConfigurationWidget.cpp
source/dateTimeWidget.cpp
source/customCalendarWidget.cpp
source/dateTimeSelectionPanel.cpp
source/httpRequestManager.cpp
)
set(UI_FILES
@ -153,6 +161,8 @@ endif()
target_include_directories(PowerMaster PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(PowerMaster PRIVATE Qt${QT_VERSION_MAJOR}::PrintSupport)
target_link_libraries(PowerMaster PRIVATE Qt${QT_VERSION_MAJOR}::Network)
target_link_libraries(PowerMaster PRIVATE Qt${QT_VERSION_MAJOR}::Concurrent)
target_link_libraries(PowerMaster PRIVATE qt${QT_VERSION_MAJOR}advanceddocking)
target_link_libraries(PowerMaster PUBLIC Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui

58
include/dataLoader.h Normal file
View File

@ -0,0 +1,58 @@
#ifndef DATALOADER_H
#define DATALOADER_H
#include <QObject>
#include <QRunnable>
#include <QThreadPool>
#include <QHash>
#include <QMutex>
#include <QVariant>
class DataLoader : public QObject
{
Q_OBJECT
public:
explicit DataLoader(QObject* parent = nullptr);
~DataLoader();
void requestData(const QString& dataKey);
void cancelRequest(const QString& dataKey);
void setMaxThreads(int count);
signals:
void dataLoaded(const QString& dataKey, const QVariant& data);
void errorOccurred(const QString& dataKey, const QString& error);
public slots:
void handleShutdown();
private:
class LoadTask : public QRunnable
{
public:
LoadTask(DataLoader* loader, const QString& key)
:m_loader(loader), m_key(key)
{
setAutoDelete(false);//禁用自动析构由DataLoader控制管理
}
void run() override
{
QVariant data = fetchRealData(m_key);
emit m_loader->dataLoaded(m_key, data);
}
private:
QVariant fetchRealData(const QString& dataKey);
DataLoader* m_loader;
QString m_key;
};
QThreadPool m_threadPool;
QHash<QString, LoadTask*> m_runningTasks;
QMutex m_taskMutex;
};
#endif

75
include/dataManager.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef DATAMANAGER_H
#define DATAMANAGER_H
#include <QObject>
#include <QHash>
#include <QQueue>
#include <QVariant>
#include <QTimer>
#include <QDateTime>
#include <QMutexLocker>
#include <QReadWriteLock>
class HttpRequestManager;
class DataManager : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(DataManager) //禁止拷贝等价于DataManager(const DataManager&) = delete; DataManager& operator=(const DataManager&) = delete;
public:
struct CacheEntry
{
QVariant data;
QDateTime timestamp;
int accessCount = 0;
bool isUpdating = false;
//QSet<QObject*> pendingRequests;
};
static DataManager* instance();
void registerDataSource(const QString& dataKey, std::function<QVariant()> fetcher);
void registerHttpDataSource(const QString& dataKey, const QUrl& url, const QByteArray& method = "GET",
const QByteArray& body = QByteArray(), const QMap<QByteArray, QByteArray>& headers = {});
QVariant getCacheData(const QString& dataKey);
bool hasData(const QString& dataKey);
void requestData(const QString& dataKey, QObject* requester);
void setCacheTimeout(int seconds);
HttpRequestManager* httpManager();
signals:
void dataUpdated(const QString& dataKey, const QVariant& data);
void cacheInvalidated(const QString& dataKey);
public slots:
void invalidateCache(const QString& dataKey = QString());
void processRequestQueue();
void handleHttpDataReceived(const QString& dataKey, const QVariant& data);
void handleHttpRequsetFailed(const QString& dataKey, const QVariant& data);
private:
struct RequestInfo
{
QString dataKey;
QObject* requester;
};
explicit DataManager();
~DataManager();
void triggerDataUpdate(const QString& dataKey);
QHash<QString, CacheEntry> m_cache;
QHash<QString, std::function<QVariant()>> m_dataSources;
QSet<QString> m_pendingUpdates;
QQueue<RequestInfo> m_requestQueue;
QTimer m_requestTimer;
QReadWriteLock m_cacheLock;
QMutex m_requestMutex;
HttpRequestManager* m_httpManager;
int m_cacheTimeout = 300; //默认5分钟
int m_minRefreshInterval = 100; //最小刷新间隔(ms)
};
#endif

View File

@ -0,0 +1,71 @@
#ifndef HTTPREQUESTMANAGER_H
#define HTTPREQUESTMANAGER_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QTimer>
#include <QMap>
#include <QUrl>
#include <QJsonDocument>
class HttpRequestManager : public QObject
{
Q_OBJECT
public:
explicit HttpRequestManager(QObject* parent = nullptr);
~HttpRequestManager();
void registerEndpoint(const QString& dataKey, const QUrl& url, const QByteArray& method = "GET",
const QByteArray& body = QByteArray(), const QMap<QByteArray, QByteArray>& headers = {});
void requestData(const QString& dataKey);
void cancleRequest(const QString& dataKey);
void setDefaultTimeout(int ms);
void setRetryPolicy(int maxRetries, int retryInterval);
bool hasVaildEndpoint(const QString& dataKey);
signals:
void dataReceived(const QString& dataKey, const QVariant& data);
void requestFailed(const QString& dataKey, const QString& error);
//void requestProgress(const QString& dataKey, int progress);
private slots:
void onReplyFinished();
void onReplyError(QNetworkReply::NetworkError code);
private:
struct EndpointConfig
{
QString dataKey;
QUrl url;
QByteArray method; //"GET","POST",使用QByteArray而非QString是因为网络编程中的参数都是QByteArray,直接使用可以避免QString引起的转码开销
QByteArray body; //JSON格式的请求体内容,如"{\"key\":\"value\"}"
QMap<QByteArray, QByteArray> headers; //http请求头如{{"Content-Type", "application/json"}, {"Authorization", "Bearer token123"}}
QNetworkReply* activeReply = nullptr;
QTimer* timeoutTimer = nullptr;
int retryCount = 0; //当前重试次数
int maxRetries = 0; //最大重试次数
int retryInterval = 1000;
};
QNetworkAccessManager m_networkManager;
QMap<QString, EndpointConfig> m_endpoints;
int m_defaultTimeout = 5000;
int m_maxRetries = 2;
int m_retryIntrval = 1000;
void sendRequest(EndpointConfig& config);
void handleResponse(const QString& dataKey, QNetworkReply* reply);
void handleRequestTimeout(const QString& dataKey);
void scheduleRetry(const QString& dataKey);
void parseJsonResponse(const QString& dataKey, const QByteArray& data);
void parseTextResponse(const QString& dataKey, const QByteArray& data);
void parseBinaryResponse(const QString& dataKey, const QByteArray& data);
};
#endif

32
source/dataLoader.cpp Normal file
View File

@ -0,0 +1,32 @@
#include "dataLoader.h"
QVariant DataLoader::LoadTask::fetchRealData(const QString& key)
{
//后续替换成实际接口
return QVariant();
}
DataLoader::DataLoader(QObject* parent)
: QObject(parent)
{
m_threadPool.setMaxThreadCount(5);
}
DataLoader::~DataLoader()
{
handleShutdown();
}
void DataLoader::requestData(const QString& dataKey)
{
QMutexLocker lockder(&m_taskMutex);
if(m_runningTasks.contains(dataKey))
return;
LoadTask* task = new LoadTask(this, dataKey);
m_runningTasks.insert(dataKey, task);
m_threadPool.start(task);
}
void DataLoader::handleShutdown()
{}

236
source/dataManager.cpp Normal file
View File

@ -0,0 +1,236 @@
#include "dataManager.h"
#include "httpRequestManager.h"
#include <QtConcurrent>
DataManager* DataManager::instance()
{
static DataManager instance;
return &instance;
}
DataManager::DataManager()
:m_httpManager(new HttpRequestManager(this))
{
connect(m_httpManager, &HttpRequestManager::dataReceived, this, &DataManager::handleHttpDataReceived);
connect(m_httpManager, &HttpRequestManager::requestFailed, this, &DataManager::handleHttpRequsetFailed);
m_requestTimer.setInterval(100);
connect(&m_requestTimer, &QTimer::timeout, this, &DataManager::processRequestQueue);
m_requestTimer.start();
}
DataManager::~DataManager()
{}
void DataManager::triggerDataUpdate(const QString& dataKey)
{
bool isHttpSource = false;
{
QReadLocker locker(&m_cacheLock);
isHttpSource = m_dataSources.contains(dataKey) && m_httpManager->hasVaildEndpoint(dataKey);
}
if(isHttpSource)
m_httpManager->requestData(dataKey);
else
{
QThreadPool::globalInstance()->start([weakThis = QPointer<DataManager>(this), dataKey]() {
if(weakThis.isNull())
return;
QVariant newData;
bool success = false;
//获取数据源
std::function<QVariant()> fetcher;
{
QReadLocker locker(&weakThis->m_cacheLock);
if(!weakThis->m_dataSources.contains(dataKey))
{
qWarning() << "Data source not found for: " << dataKey;
weakThis->m_pendingUpdates.remove(dataKey);
return;
}
fetcher = weakThis->m_dataSources[dataKey];
}
try {
newData = fetcher();
success = true;
} catch (const std::exception& e) {
qWarning() << "Data fetch error for" << dataKey << ": " << e.what();
}
QWriteLocker locker(&weakThis->m_cacheLock);
if(weakThis->m_cache.contains(dataKey))
{
CacheEntry& entry = weakThis->m_cache[dataKey];
if(success)
{
entry.data = newData;
entry.timestamp = QDateTime::currentDateTime();
emit weakThis->dataUpdated(dataKey, newData);
}
entry.isUpdating = false;
}
weakThis->m_pendingUpdates.remove(dataKey);
});
}
}
HttpRequestManager* DataManager::httpManager()
{
return m_httpManager;
}
void DataManager::registerDataSource(const QString& dataKey, std::function<QVariant()> fetcher)
{
if(dataKey.isEmpty() || !fetcher)
{
qWarning() << "Invalid data source registration";
return;
}
QWriteLocker locker(&m_cacheLock);
m_dataSources[dataKey] = fetcher;
//初始化缓存条目
if(!m_cache.contains(dataKey))
m_cache[dataKey] = CacheEntry();
}
void DataManager::registerHttpDataSource(const QString& dataKey, const QUrl& url, const QByteArray& method,
const QByteArray& body, const QMap<QByteArray, QByteArray>& headers)
{
m_httpManager->registerEndpoint(dataKey, url, method, body, headers);
//调用registerDataSource初始化缓存
registerDataSource(dataKey, [](){return QVariant();}); //http的数据通过httpManager的信号进行接收所以生成函数给空值即可
}
QVariant DataManager::getCacheData(const QString& dataKey)
{
if(dataKey.isEmpty())
return QVariant();
QReadLocker locker(&m_cacheLock);
if(!m_cache.contains(dataKey))
{
qWarning() << "Unknown data key:" << dataKey;
return QVariant();
}
return m_cache[dataKey].data;
}
bool DataManager::hasData(const QString& dataKey)
{
QReadLocker locker(&m_cacheLock);
return m_cache.contains(dataKey) && !m_cache[dataKey].data.isNull();
}
void DataManager::requestData(const QString& dataKey, QObject* requester)
{
QMutexLocker lock(&m_requestMutex);
m_requestQueue.enqueue({dataKey, requester});
}
void DataManager::invalidateCache(const QString& dataKey)
{
QWriteLocker locker(&m_cacheLock);
if(dataKey.isEmpty())
{
m_cache.clear();
emit cacheInvalidated("");
}
else
{
m_cache.remove(dataKey);
emit cacheInvalidated(dataKey);
}
}
void DataManager::setCacheTimeout(int seconds)
{
m_cacheTimeout = qMax(60, seconds); //最小1分钟
}
void DataManager::processRequestQueue()
{
//收集到本地,减少互斥锁的持有时间,后续都是对本地数据进行操作,无需加锁
QList<RequestInfo> requests;
{
QMutexLocker lock(&m_requestMutex);
while(!m_requestQueue.isEmpty())
requests.append(m_requestQueue.dequeue());
}
//按数据类型进行分组,减少更新频率
QMap<QString, QSet<QObject*>> groupedRequests;
for(const auto& req : requests)
groupedRequests[req.dataKey].insert(req.requester);
QSet<QString> needsUpdate;
QDateTime curTime = QDateTime::currentDateTime();
//减少写锁范围
{
QWriteLocker locker(&m_cacheLock);
for(auto it = groupedRequests.begin(); it != groupedRequests.end(); ++it)
{
const QString& dataKey = it.key();
const QSet<QObject*>& requesters = it.value();
if(!m_cache.contains(dataKey))
{
qWarning() << "Skipping request for unknown data key:" << dataKey;
continue;
}
CacheEntry& entry = m_cache[dataKey];
//entry.pendingRequests.unite(it.value());
bool needsRefresh = false;
if(entry.timestamp.isNull() || entry.timestamp.msecsTo(curTime) > m_minRefreshInterval)
needsRefresh = true;
if(needsRefresh)
{
entry.isUpdating = true;
m_pendingUpdates.insert(dataKey);
needsUpdate.insert(dataKey);
}
}
} //写锁在这里自动释放
//触发更新
for(const QString& dataKey : needsUpdate)
triggerDataUpdate(dataKey);
}
void DataManager::handleHttpDataReceived(const QString& dataKey, const QVariant& data)
{
QWriteLocker locker(&m_cacheLock);
if(m_cache.contains(dataKey))
{
CacheEntry& entry = m_cache[dataKey];
entry.data = data;
entry.timestamp = QDateTime::currentDateTime();
entry.isUpdating = false;
emit dataUpdated(dataKey, data);
}
m_pendingUpdates.remove(dataKey);
}
void DataManager::handleHttpRequsetFailed(const QString& dataKey, const QVariant& data)
{
QWriteLocker locker(&m_cacheLock);
if(m_cache.contains(dataKey))
{
CacheEntry& entry = m_cache[dataKey];
entry.isUpdating = false;
}
m_pendingUpdates.remove(dataKey);
}

View File

@ -0,0 +1,318 @@
#include "httpRequestManager.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
HttpRequestManager::HttpRequestManager(QObject* parent)
: QObject(parent)
{
}
HttpRequestManager::~HttpRequestManager()
{
for(auto& config : m_endpoints)
{
if(config.activeReply)
{
config.activeReply->abort();
config.activeReply->deleteLater();
config.activeReply = nullptr;
}
if(config.timeoutTimer)
{
config.timeoutTimer->stop();
config.timeoutTimer->deleteLater();
config.timeoutTimer = nullptr;
}
}
}
void HttpRequestManager::sendRequest(EndpointConfig& config)
{
QNetworkRequest request(config.url);
for(auto it = config.headers.constBegin(); it != config.headers.constEnd(); ++it)
request.setRawHeader(it.key(), it.value());
QNetworkReply* reply = nullptr;
if(config.method == "GET")
reply = m_networkManager.get(request);
else if(config.method == "POST")
reply = m_networkManager.post(request, config.body);
else if(config.method == "PUT")
reply = m_networkManager.put(request, config.body);
else if(config.method == "DELETE")
reply = m_networkManager.deleteResource(request);
else
{
qWarning() << "Unsupported HTTP method:" << config.method;
emit requestFailed(config.dataKey, "Unsupported HTTP method");
return;
}
connect(reply, &QNetworkReply::finished, this, &HttpRequestManager::onReplyFinished);
connect(reply, &QNetworkReply::errorOccurred, this, &HttpRequestManager::onReplyError);
config.timeoutTimer = new QTimer(this);
config.timeoutTimer->setSingleShot(true);
config.timeoutTimer->setInterval(m_defaultTimeout);
connect(config.timeoutTimer, &QTimer::timeout, this, [this, &config](){
handleRequestTimeout(config.dataKey);
});
}
void HttpRequestManager::handleResponse(const QString& dataKey, QNetworkReply* reply)
{
if(!m_endpoints.contains(dataKey))
{
qWarning() << "Recived response for unregistered dataKey:" << dataKey;
reply->deleteLater();
return;
}
EndpointConfig& config = m_endpoints[dataKey];
//关闭超时重连定时器
if(config.timeoutTimer)
{
config.timeoutTimer->stop();
config.timeoutTimer->deleteLater();
config.timeoutTimer = nullptr;
}
if(reply->error() != QNetworkReply::NoError)
return; //具体错误信息在onReplyError中进行处理
QByteArray responseData = reply->readAll();
QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString();
if(contentType.contains("application/json"))
parseJsonResponse(dataKey, responseData);
else if(contentType.contains("text/"))
parseTextResponse(dataKey, responseData);
else
parseBinaryResponse(dataKey, responseData);
//数据重置
reply->deleteLater();
config.retryCount = 0;
config.activeReply = nullptr;
}
void HttpRequestManager::handleRequestTimeout(const QString& dataKey)
{
if(!m_endpoints.contains(dataKey))
return;
EndpointConfig& config = m_endpoints[dataKey];
qWarning() << "Request timeout for dataKey: " << dataKey;
if(config.activeReply)
config.activeReply->abort();
//重试
if(config.retryCount < config.maxRetries)
scheduleRetry(config.dataKey);
else
{
emit requestFailed(config.dataKey, "Request timed out");
config.retryCount = 0;
}
}
void HttpRequestManager::scheduleRetry(const QString& dataKey)
{
if(!m_endpoints.contains(dataKey))
return;
EndpointConfig& config = m_endpoints[dataKey];
config.retryCount++;
QTimer::singleShot(config.retryInterval, this, [this, dataKey](){
requestData(dataKey);
});
}
void HttpRequestManager::parseJsonResponse(const QString& dataKey, const QByteArray& data)
{
QJsonParseError parseError;
QJsonDocument document = QJsonDocument::fromJson(data, &parseError);
if(document.isNull())
{
emit requestFailed(dataKey, "get JSonDocument failed");
return;
}
if(parseError.error != QJsonParseError::NoError)
{
emit requestFailed(dataKey, "JSON parse error: " + parseError.errorString());
return;
}
if(document.isObject())
emit dataReceived(dataKey, document.object().toVariantMap());
else if(document.isArray())
emit dataReceived(dataKey, document.array().toVariantList());
else
emit dataReceived(dataKey, QVariant());
}
void HttpRequestManager::parseTextResponse(const QString& dataKey, const QByteArray& data)
{
emit dataReceived(dataKey, QString::fromUtf8(data));
}
void HttpRequestManager::parseBinaryResponse(const QString& dataKey, const QByteArray& data)
{
emit dataReceived(dataKey, data);
}
void HttpRequestManager::registerEndpoint(const QString& dataKey, const QUrl& url, const QByteArray& method,
const QByteArray& body, const QMap<QByteArray, QByteArray>& headers)
{
EndpointConfig config;
config.dataKey = dataKey;
config.url = url;
config.method = method.toUpper();
config.body = body;
config.headers = headers;
config.maxRetries = m_maxRetries;
config.retryInterval = m_retryIntrval;
m_endpoints[dataKey] = config;
}
void HttpRequestManager::requestData(const QString& dataKey)
{
if(!m_endpoints.contains(dataKey))
{
qWarning() << "This dataKey is not present in endpoints: " << dataKey;
emit requestFailed(dataKey, "dataKey is not present in endpoints");
return;
}
EndpointConfig& config = m_endpoints[dataKey];
if(config.activeReply)
{
config.activeReply->abort();
config.activeReply->deleteLater();
config.activeReply = nullptr;
}
if(config.timeoutTimer)
{
config.timeoutTimer->stop();
config.timeoutTimer->deleteLater();
config.timeoutTimer = nullptr;
}
sendRequest(config);
}
void HttpRequestManager::cancleRequest(const QString& dataKey)
{
if(!m_endpoints.contains(dataKey))
return;
EndpointConfig& config = m_endpoints[dataKey];
if(config.activeReply)
{
config.activeReply->abort();
config.activeReply->deleteLater();
config.activeReply = nullptr;
}
if(config.timeoutTimer)
{
config.timeoutTimer->stop();
config.timeoutTimer->deleteLater();
config.timeoutTimer = nullptr;
}
config.retryCount = 0;
}
void HttpRequestManager::setDefaultTimeout(int ms)
{
m_defaultTimeout = qMax(1000, ms);
}
void HttpRequestManager::setRetryPolicy(int maxRetries, int retryInterval)
{
m_maxRetries = qMax(0, maxRetries);
m_retryIntrval = qMax(500, retryInterval);
//更新所以已注册端点的充实策略
for(auto& config : m_endpoints)
{
config.maxRetries = m_maxRetries;
config.retryInterval = m_retryIntrval;
}
}
bool HttpRequestManager::hasVaildEndpoint(const QString& dataKey)
{
return m_endpoints.contains(dataKey) && m_endpoints[dataKey].url.isEmpty();
}
void HttpRequestManager::onReplyFinished()
{
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if(!reply)
return;
QString dataKey;
for(auto& config : m_endpoints)
{
if(config.activeReply == reply)
{
dataKey = config.dataKey;
break;
}
}
if(dataKey.isEmpty())
{
reply->deleteLater();
return;
}
handleResponse(dataKey, reply);
}
void HttpRequestManager::onReplyError(QNetworkReply::NetworkError code)
{
Q_UNUSED(code);
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if(!reply)
return;
QString dataKey;
for(auto& config : m_endpoints)
{
if(config.activeReply == reply)
{
dataKey = config.dataKey;
break;
}
}
if(dataKey.isEmpty())
return;
EndpointConfig& config = m_endpoints[dataKey];
//关闭超时重连定时器
if(config.timeoutTimer)
{
config.timeoutTimer->stop();
config.timeoutTimer->deleteLater();
config.timeoutTimer = nullptr;
}
//打印错误
QString error = reply->errorString();
qWarning() << "HTTP request failed for " << dataKey << ":" << error;
//重试
if(config.retryCount < config.maxRetries)
scheduleRetry(dataKey);
else
{
emit requestFailed(dataKey, error);
config.retryCount = 0;
}
reply->deleteLater();
config.activeReply = nullptr;
}