feat(net): ApiClient.getAsync/postJsonAsync + IApiCall/ApiCall 异步句柄(abort+aborted_ 闸门,AUTOMOC ON)

This commit is contained in:
gaozheng 2026-06-11 19:51:48 +08:00
parent c90ea83a04
commit 8f94443323
7 changed files with 103 additions and 1 deletions

31
src/net/ApiCall.cpp Normal file
View File

@ -0,0 +1,31 @@
#include "ApiCall.hpp"
#include <QNetworkReply>
#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

22
src/net/ApiCall.hpp Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <QPointer>
#include "IApiCall.hpp"
class QNetworkReply;
namespace geopro::net {
// 包一个 QNetworkReply 的 IApiCall 实现。reply 完成 → buildResponse → emit finished → 自删。
// 安全不变量spec §5.0abort 后绝不 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<QNetworkReply> reply_;
bool aborted_ = false;
};
} // namespace geopro::net

View File

@ -1,5 +1,6 @@
#include "ApiClient.hpp" #include "ApiClient.hpp"
#include "ApiCall.hpp"
#include "ApiResponseParse.hpp" #include "ApiResponseParse.hpp"
#include <QEventLoop> #include <QEventLoop>
@ -66,4 +67,17 @@ ApiResponse ApiClient::postJson(const QString& path, const QJsonObject& body) {
return resp; 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 } // namespace geopro::net

View File

@ -8,6 +8,8 @@ class QNetworkAccessManager;
namespace geopro::net { namespace geopro::net {
class IApiCall;
// 服务端统一响应信封:{code, data, msg}。 // 服务端统一响应信封:{code, data, msg}。
// httpStatus 为 HTTP 状态码(如 200code/data/msg 解析自响应体 JSON。 // httpStatus 为 HTTP 状态码(如 200code/data/msg 解析自响应体 JSON。
// 网络层错误连接失败、超时、JSON 解析失败)写入 rawError此时 httpStatus 可能为 0。 // 网络层错误连接失败、超时、JSON 解析失败)写入 rawError此时 httpStatus 可能为 0。
@ -38,9 +40,16 @@ public:
ApiResponse get(const QString& path); ApiResponse get(const QString& path);
ApiResponse postJson(const QString& path, const QJsonObject& body); 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: private:
struct Impl; struct Impl;
std::unique_ptr<Impl> impl_; std::unique_ptr<Impl> impl_;
}; };
} // namespace geopro::net } // namespace geopro::net
#include <QMetaType>
Q_DECLARE_METATYPE(geopro::net::ApiResponse)

View File

@ -4,8 +4,10 @@ add_library(geopro_net STATIC
crypto/RsaEncryptor.cpp crypto/RsaEncryptor.cpp
ApiClient.cpp ApiClient.cpp
ApiResponseParse.cpp ApiResponseParse.cpp
IApiCall.cpp
ApiCall.cpp
AuthService.cpp) AuthService.cpp)
target_include_directories(geopro_net PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 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_link_libraries(geopro_net PUBLIC OpenSSL::SSL OpenSSL::Crypto Qt6::Core Qt6::Network)
target_compile_features(geopro_net PUBLIC cxx_std_17) 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)

6
src/net/IApiCall.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "IApiCall.hpp"
// IApiCall 是纯虚抽象句柄,无需额外实现。
// 此 .cpp 存在是为了让 AUTOMOC 扫描到 Q_OBJECT 宏并生成 moc_IApiCall.cpp。
namespace geopro::net {
} // namespace geopro::net

18
src/net/IApiCall.hpp Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <QObject>
#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_、中断、deleteLaterabort 后绝不 emit finished
signals:
void finished(const geopro::net::ApiResponse& resp); // 成功/错误均经此(错误写 rawError
};
} // namespace geopro::net