From bb602e20113293658ab29ce89fef95f068bc420b Mon Sep 17 00:00:00 2001 From: gaozheng Date: Thu, 11 Jun 2026 20:19:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(data):=20=E5=BC=82=E6=AD=A5=E4=BB=93?= =?UTF-8?q?=E5=82=A8=E6=8E=A5=E5=8F=A3=20+=20ChartLoad/GridLoad=20?= =?UTF-8?q?=E5=8F=A5=E6=9F=84(=E6=8A=BD=E8=B1=A1=E5=9F=BA+Api=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0)=20+=20=E7=A6=BB=E7=BA=BF=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/data/CMakeLists.txt | 5 +- src/data/api/DatasetLoadHandles.cpp | 66 +++++++++++++++++++++++ src/data/api/DatasetLoadHandles.hpp | 60 +++++++++++++++++++++ src/data/api/DatasetLoads.hpp | 21 ++++++++ src/data/repo/IAsyncDatasetRepository.hpp | 17 ++++++ tests/CMakeLists.txt | 1 + tests/data/test_dataset_load_handles.cpp | 51 ++++++++++++++++++ 7 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 src/data/api/DatasetLoadHandles.cpp create mode 100644 src/data/api/DatasetLoadHandles.hpp create mode 100644 src/data/api/DatasetLoads.hpp create mode 100644 src/data/repo/IAsyncDatasetRepository.hpp create mode 100644 tests/data/test_dataset_load_handles.cpp diff --git a/src/data/CMakeLists.txt b/src/data/CMakeLists.txt index 16da104..9319112 100644 --- a/src/data/CMakeLists.txt +++ b/src/data/CMakeLists.txt @@ -6,8 +6,9 @@ add_library(geopro_data STATIC dto/NavDto.cpp dto/DatasetChartDto.cpp api/ApiProjectRepository.cpp - api/ApiDatasetRepository.cpp) + api/ApiDatasetRepository.cpp + api/DatasetLoadHandles.cpp) target_include_directories(geopro_data PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(geopro_data PUBLIC geopro_core geopro_net Qt6::Core PRIVATE nlohmann_json::nlohmann_json) target_compile_features(geopro_data PUBLIC cxx_std_17) -set_target_properties(geopro_data PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF) +set_target_properties(geopro_data PROPERTIES AUTOMOC ON AUTOUIC OFF AUTORCC OFF) diff --git a/src/data/api/DatasetLoadHandles.cpp b/src/data/api/DatasetLoadHandles.cpp new file mode 100644 index 0000000..cd80aa8 --- /dev/null +++ b/src/data/api/DatasetLoadHandles.cpp @@ -0,0 +1,66 @@ +#include "api/DatasetLoadHandles.hpp" +#include + +namespace geopro::data { + +namespace { +QString reasonOf(const geopro::net::ApiResponse& r) { + return r.msg.isEmpty() ? r.rawError : r.msg; +} +} // namespace + +ApiChartLoad::ApiChartLoad(geopro::net::ApiBatch* batch, Parser parse, QObject* parent) + : ChartLoad(parent), batch_(batch), parse_(std::move(parse)) { + QObject::connect(batch, &geopro::net::ApiBatch::succeeded, this, + [this](const QList& resps) { + if (aborted_) return; // §5.0 + try { + emit done(parse_(resps)); + } catch (const std::exception& e) { + emit failed(QString::fromUtf8(e.what())); + } + deleteLater(); + }); + QObject::connect(batch, &geopro::net::ApiBatch::failed, this, + [this](int, const geopro::net::ApiResponse& r) { + if (aborted_) return; + emit failed(reasonOf(r)); + deleteLater(); + }); +} + +void ApiChartLoad::abort() { + if (aborted_) return; + aborted_ = true; + if (batch_) batch_->abort(); + deleteLater(); +} + +ApiGridLoad::ApiGridLoad(geopro::net::ApiBatch* batch, Parser parse, QObject* parent) + : GridLoad(parent), batch_(batch), parse_(std::move(parse)) { + QObject::connect(batch, &geopro::net::ApiBatch::succeeded, this, + [this](const QList& resps) { + if (aborted_) return; + try { + emit done(parse_(resps)); + } catch (const std::exception& e) { + emit failed(QString::fromUtf8(e.what())); + } + deleteLater(); + }); + QObject::connect(batch, &geopro::net::ApiBatch::failed, this, + [this](int, const geopro::net::ApiResponse& r) { + if (aborted_) return; + emit failed(reasonOf(r)); + deleteLater(); + }); +} + +void ApiGridLoad::abort() { + if (aborted_) return; + aborted_ = true; + if (batch_) batch_->abort(); + deleteLater(); +} + +} // namespace geopro::data diff --git a/src/data/api/DatasetLoadHandles.hpp b/src/data/api/DatasetLoadHandles.hpp new file mode 100644 index 0000000..cc65328 --- /dev/null +++ b/src/data/api/DatasetLoadHandles.hpp @@ -0,0 +1,60 @@ +#pragma once +#include +#include +#include +#include +#include +#include "ApiBatch.hpp" +#include "DatasetLoads.hpp" + +namespace geopro::data { + +// ── 抽象句柄(可测试缝,类比 IApiCall):仓储返回基类指针,控制器/测试只依赖它 ── +class ChartLoad : public QObject { + Q_OBJECT +public: + using QObject::QObject; + ~ChartLoad() override = default; + virtual void abort() = 0; +signals: + void done(const geopro::data::ChartParts& parts); + void failed(const QString& message); +}; + +class GridLoad : public QObject { + Q_OBJECT +public: + using QObject::QObject; + ~GridLoad() override = default; + virtual void abort() = 0; +signals: + void done(const geopro::data::GridParts& parts); + void failed(const QString& message); +}; + +// ── Api 实现:包一个 ApiBatch + 注入的解析函数。batch.succeeded→解析→done;failed→failed ── +class ApiChartLoad : public ChartLoad { + Q_OBJECT +public: + using Parser = std::function&)>; + ApiChartLoad(geopro::net::ApiBatch* batch, Parser parse, QObject* parent = nullptr); + void abort() override; +private: + QPointer batch_; + Parser parse_; + bool aborted_ = false; +}; + +class ApiGridLoad : public GridLoad { + Q_OBJECT +public: + using Parser = std::function&)>; + ApiGridLoad(geopro::net::ApiBatch* batch, Parser parse, QObject* parent = nullptr); + void abort() override; +private: + QPointer batch_; + Parser parse_; + bool aborted_ = false; +}; + +} // namespace geopro::data diff --git a/src/data/api/DatasetLoads.hpp b/src/data/api/DatasetLoads.hpp new file mode 100644 index 0000000..1e303ae --- /dev/null +++ b/src/data/api/DatasetLoads.hpp @@ -0,0 +1,21 @@ +#pragma once +#include +#include "model/Field.hpp" +#include "model/ColorScale.hpp" +#include "model/Anomaly.hpp" + +namespace geopro::data { + +// 原数据加载结果:scatter + 散点色阶(type1)。 +struct ChartParts { + geopro::core::ScatterField scatter; + geopro::core::ColorScale scatterScale; +}; +// 网格数据加载结果:grid(rows) + 网格色阶(type2) + 异常。 +struct GridParts { + geopro::core::Grid grid{1, 1}; // Grid 无默认构造;占位 + geopro::core::ColorScale gridScale; + std::vector anomalies; +}; + +} // namespace geopro::data diff --git a/src/data/repo/IAsyncDatasetRepository.hpp b/src/data/repo/IAsyncDatasetRepository.hpp new file mode 100644 index 0000000..5103b03 --- /dev/null +++ b/src/data/repo/IAsyncDatasetRepository.hpp @@ -0,0 +1,17 @@ +#pragma once +#include + +namespace geopro::data { + +class ChartLoad; +class GridLoad; + +// 数据集详情异步仓储抽象。返回自管理句柄(完成/失败后 deleteLater)。 +class IAsyncDatasetRepository { +public: + virtual ~IAsyncDatasetRepository() = default; + virtual ChartLoad* loadChartAsync(const std::string& dsId) = 0; // scatter + 散点色阶(type1) + virtual GridLoad* loadGridAsync(const std::string& dsId) = 0; // grid(rows) + 色阶(type2) + 异常 +}; + +} // namespace geopro::data diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c5a1e21..98ee36e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -38,6 +38,7 @@ target_sources(geopro_tests PRIVATE data/test_parsers.cpp) target_sources(geopro_tests PRIVATE data/test_local_repo.cpp) target_sources(geopro_tests PRIVATE data/test_nav_dto.cpp) target_sources(geopro_tests PRIVATE data/test_dataset_chart_dto.cpp) +target_sources(geopro_tests PRIVATE data/test_dataset_load_handles.cpp) target_link_libraries(geopro_tests PRIVATE geopro_data) # net 层:RSA 加密器。测试需直接用 OpenSSL 生成/解密密钥,故显式 find_package diff --git a/tests/data/test_dataset_load_handles.cpp b/tests/data/test_dataset_load_handles.cpp new file mode 100644 index 0000000..4dfd038 --- /dev/null +++ b/tests/data/test_dataset_load_handles.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "api/DatasetLoadHandles.hpp" +#include "net/FakeApiCall.hpp" + +using namespace geopro::data; +using geopro::net::ApiBatch; +using geopro::net::ApiResponse; +using geopro::net::test::FakeApiCall; + +namespace { +ApiResponse ok() { ApiResponse r; r.code = 200; r.httpStatus = 200; return r; } +ApiResponse bad() { ApiResponse r; r.code = 500; r.httpStatus = 200; r.msg = QStringLiteral("boom"); return r; } +auto isFailure = [](const ApiResponse& r) { return r.code != 200 || !r.rawError.isEmpty(); }; +} + +TEST(DatasetLoadHandles, ChartLoadEmitsDoneOnSuccess) { + auto* a = new FakeApiCall; + auto* b = new FakeApiCall; + auto* batch = new ApiBatch({a, b}, isFailure); + bool parsed = false; + auto* load = new ApiChartLoad(batch, [&](const QList& resps) { + parsed = (resps.size() == 2); + return ChartParts{}; + }); + QSignalSpy doneSpy(load, &ChartLoad::done); + a->fire(ok()); + b->fire(ok()); + EXPECT_EQ(doneSpy.count(), 1); + EXPECT_TRUE(parsed); +} + +TEST(DatasetLoadHandles, ChartLoadEmitsFailedOnBatchFailure) { + auto* a = new FakeApiCall; + auto* b = new FakeApiCall; + auto* batch = new ApiBatch({a, b}, isFailure); + auto* load = new ApiChartLoad(batch, [](const QList&) { return ChartParts{}; }); + QSignalSpy failSpy(load, &ChartLoad::failed); + a->fire(bad()); + EXPECT_EQ(failSpy.count(), 1); +} + +TEST(DatasetLoadHandles, GridLoadAbortSuppressesLateDone) { + auto* a = new FakeApiCall; + auto* batch = new ApiBatch({a}, isFailure); + auto* load = new ApiGridLoad(batch, [](const QList&) { return GridParts{}; }); + QSignalSpy doneSpy(load, &GridLoad::done); + load->abort(); + a->fire(ok()); // 迟到 + EXPECT_EQ(doneSpy.count(), 0); // batch.aborted_ + load.aborted_ 双闸门 +}