diff --git a/src/net/ApiCall.cpp b/src/net/ApiCall.cpp new file mode 100644 index 0000000..5ef287e --- /dev/null +++ b/src/net/ApiCall.cpp @@ -0,0 +1,31 @@ +#include "ApiCall.hpp" + +#include +#include "ApiResponseParse.hpp" + +namespace geopro::net { + +ApiCall::ApiCall(QNetworkReply* reply, QObject* parent) : IApiCall(parent), reply_(reply) { + QObject::connect(reply_, &QNetworkReply::finished, this, &ApiCall::onFinished); +} + +void ApiCall::onFinished() { + if (aborted_) return; // §5.0 入口守卫:迟到信号闸门 + ApiResponse resp = buildResponse(reply_); + if (reply_) reply_->deleteLater(); + emit finished(resp); + deleteLater(); +} + +void ApiCall::abort() { + if (aborted_) return; + aborted_ = true; // 先置标志(权威闸门) + if (reply_) { + QObject::disconnect(reply_, nullptr, this, nullptr); // 尽力而为:断开未派发连接 + reply_->abort(); // 会再触发一次 finished,已被 disconnect+aborted_ 双挡 + reply_->deleteLater(); + } + deleteLater(); // 禁止同步 delete +} + +} // namespace geopro::net diff --git a/src/net/ApiCall.hpp b/src/net/ApiCall.hpp new file mode 100644 index 0000000..94cb3f6 --- /dev/null +++ b/src/net/ApiCall.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include "IApiCall.hpp" + +class QNetworkReply; + +namespace geopro::net { + +// 包一个 QNetworkReply 的 IApiCall 实现。reply 完成 → buildResponse → emit finished → 自删。 +// 安全不变量(spec §5.0):abort 后绝不 emit;销毁一律 deleteLater,禁止同步 delete。 +class ApiCall : public IApiCall { + Q_OBJECT +public: + explicit ApiCall(QNetworkReply* reply, QObject* parent = nullptr); // 接管 reply + void abort() override; +private: + void onFinished(); + QPointer reply_; + bool aborted_ = false; +}; + +} // namespace geopro::net diff --git a/src/net/ApiClient.cpp b/src/net/ApiClient.cpp index c63e3ff..f9e9f93 100644 --- a/src/net/ApiClient.cpp +++ b/src/net/ApiClient.cpp @@ -1,5 +1,6 @@ #include "ApiClient.hpp" +#include "ApiCall.hpp" #include "ApiResponseParse.hpp" #include @@ -66,4 +67,17 @@ ApiResponse ApiClient::postJson(const QString& path, const QJsonObject& body) { return resp; } +IApiCall* ApiClient::getAsync(const QString& path) { + QNetworkRequest req = impl_->buildRequest(path); + QNetworkReply* reply = impl_->nam.get(req); + return new ApiCall(reply); +} + +IApiCall* ApiClient::postJsonAsync(const QString& path, const QJsonObject& body) { + QNetworkRequest req = impl_->buildRequest(path); + const QByteArray payload = QJsonDocument(body).toJson(QJsonDocument::Compact); + QNetworkReply* reply = impl_->nam.post(req, payload); + return new ApiCall(reply); +} + } // namespace geopro::net diff --git a/src/net/ApiClient.hpp b/src/net/ApiClient.hpp index 77c1965..b3b65ec 100644 --- a/src/net/ApiClient.hpp +++ b/src/net/ApiClient.hpp @@ -8,6 +8,8 @@ class QNetworkAccessManager; namespace geopro::net { +class IApiCall; + // 服务端统一响应信封:{code, data, msg}。 // httpStatus 为 HTTP 状态码(如 200);code/data/msg 解析自响应体 JSON。 // 网络层错误(连接失败、超时、JSON 解析失败)写入 rawError,此时 httpStatus 可能为 0。 @@ -38,9 +40,16 @@ public: ApiResponse get(const QString& path); ApiResponse postJson(const QString& path, const QJsonObject& body); + // 异步 GET / POST(JSON):立即返回自管理句柄,不阻塞。调用方连 IApiCall::finished。 + IApiCall* getAsync(const QString& path); + IApiCall* postJsonAsync(const QString& path, const QJsonObject& body); + private: struct Impl; std::unique_ptr impl_; }; } // namespace geopro::net + +#include +Q_DECLARE_METATYPE(geopro::net::ApiResponse) diff --git a/src/net/CMakeLists.txt b/src/net/CMakeLists.txt index 99d533b..d608c7b 100644 --- a/src/net/CMakeLists.txt +++ b/src/net/CMakeLists.txt @@ -4,8 +4,10 @@ add_library(geopro_net STATIC crypto/RsaEncryptor.cpp ApiClient.cpp ApiResponseParse.cpp + IApiCall.cpp + ApiCall.cpp AuthService.cpp) target_include_directories(geopro_net PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(geopro_net PUBLIC OpenSSL::SSL OpenSSL::Crypto Qt6::Core Qt6::Network) target_compile_features(geopro_net PUBLIC cxx_std_17) -set_target_properties(geopro_net PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF) +set_target_properties(geopro_net PROPERTIES AUTOMOC ON AUTOUIC OFF AUTORCC OFF) diff --git a/src/net/IApiCall.cpp b/src/net/IApiCall.cpp new file mode 100644 index 0000000..d93de1e --- /dev/null +++ b/src/net/IApiCall.cpp @@ -0,0 +1,6 @@ +#include "IApiCall.hpp" + +// IApiCall 是纯虚抽象句柄,无需额外实现。 +// 此 .cpp 存在是为了让 AUTOMOC 扫描到 Q_OBJECT 宏并生成 moc_IApiCall.cpp。 +namespace geopro::net { +} // namespace geopro::net diff --git a/src/net/IApiCall.hpp b/src/net/IApiCall.hpp new file mode 100644 index 0000000..db607c5 --- /dev/null +++ b/src/net/IApiCall.hpp @@ -0,0 +1,18 @@ +#pragma once +#include +#include "ApiClient.hpp" // geopro::net::ApiResponse + +namespace geopro::net { + +// 单个异步请求的抽象句柄(可测试缝)。ApiBatch 仅依赖此抽象,单测可注入假 call。 +class IApiCall : public QObject { + Q_OBJECT +public: + using QObject::QObject; + ~IApiCall() override = default; + virtual void abort() = 0; // 置 aborted_、中断、deleteLater;abort 后绝不 emit finished +signals: + void finished(const geopro::net::ApiResponse& resp); // 成功/错误均经此(错误写 rawError) +}; + +} // namespace geopro::net