refactor(net): 抽出 buildResponse,sync/async 共用响应解析(DRY,行为不变)

This commit is contained in:
gaozheng 2026-06-11 19:43:37 +08:00
parent 66541b5ef8
commit c90ea83a04
4 changed files with 69 additions and 44 deletions

View File

@ -1,10 +1,9 @@
#include "ApiClient.hpp" #include "ApiClient.hpp"
#include <QByteArray> #include "ApiResponseParse.hpp"
#include <QEventLoop> #include <QEventLoop>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonParseError>
#include <QJsonValue>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkReply> #include <QNetworkReply>
#include <QNetworkRequest> #include <QNetworkRequest>
@ -14,36 +13,9 @@ namespace geopro::net {
namespace { namespace {
constexpr int kHttpStatusUnset = 0;
const char* const kContentTypeJson = "application/json"; const char* const kContentTypeJson = "application/json";
const char* const kTokenHeader = "geomativeauthorization"; const char* const kTokenHeader = "geomativeauthorization";
// 把响应体 JSON 解析进信封。解析失败或非对象时把原文/错误写入 rawError。
void parseBody(const QByteArray& body, ApiResponse& resp) {
if (body.isEmpty()) {
resp.rawError = QStringLiteral("empty response body");
return;
}
QJsonParseError perr{};
const QJsonDocument doc = QJsonDocument::fromJson(body, &perr);
if (perr.error != QJsonParseError::NoError || !doc.isObject()) {
resp.rawError = QStringLiteral("JSON parse error: %1; body: %2")
.arg(perr.errorString(), QString::fromUtf8(body));
return;
}
const QJsonObject obj = doc.object();
resp.code = obj.value(QStringLiteral("code")).toInt();
resp.msg = obj.value(QStringLiteral("msg")).toString();
const QJsonValue dataVal = obj.value(QStringLiteral("data"));
if (dataVal.isObject()) {
resp.data = dataVal.toObject();
} else {
// data 可能是标量(如 verifyCodeCheck 返回 true。包成 {"value": <data>}
// 便于上层统一通过 data["value"] 取用,同时不丢信息。
resp.data = QJsonObject{{QStringLiteral("value"), dataVal}};
}
}
} // namespace } // namespace
struct ApiClient::Impl { struct ApiClient::Impl {
@ -67,20 +39,7 @@ struct ApiClient::Impl {
QEventLoop loop; QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec(); loop.exec();
return buildResponse(reply);
ApiResponse resp;
const QVariant statusVar = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
resp.httpStatus = statusVar.isValid() ? statusVar.toInt() : kHttpStatusUnset;
const QByteArray body = reply->readAll();
if (reply->error() != QNetworkReply::NoError) {
resp.rawError = reply->errorString();
}
// 即便有传输层错误,服务端仍可能回带 JSON 体(如 4xx尽量解析。
if (!body.isEmpty()) {
parseBody(body, resp);
}
return resp;
} }
}; };

View File

@ -0,0 +1,53 @@
#include "ApiResponseParse.hpp"
#include <QByteArray>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QJsonValue>
#include <QNetworkReply>
#include <QNetworkRequest>
namespace geopro::net {
namespace {
constexpr int kHttpStatusUnset = 0;
void parseBody(const QByteArray& body, ApiResponse& resp) {
if (body.isEmpty()) {
resp.rawError = QStringLiteral("empty response body");
return;
}
QJsonParseError perr{};
const QJsonDocument doc = QJsonDocument::fromJson(body, &perr);
if (perr.error != QJsonParseError::NoError || !doc.isObject()) {
resp.rawError = QStringLiteral("JSON parse error: %1; body: %2")
.arg(perr.errorString(), QString::fromUtf8(body));
return;
}
const QJsonObject obj = doc.object();
resp.code = obj.value(QStringLiteral("code")).toInt();
resp.msg = obj.value(QStringLiteral("msg")).toString();
const QJsonValue dataVal = obj.value(QStringLiteral("data"));
if (dataVal.isObject()) {
resp.data = dataVal.toObject();
} else {
resp.data = QJsonObject{{QStringLiteral("value"), dataVal}};
}
}
} // namespace
ApiResponse buildResponse(QNetworkReply* reply) {
ApiResponse resp;
const QVariant statusVar = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
resp.httpStatus = statusVar.isValid() ? statusVar.toInt() : kHttpStatusUnset;
const QByteArray body = reply->readAll();
if (reply->error() != QNetworkReply::NoError) {
resp.rawError = reply->errorString();
}
if (!body.isEmpty()) {
parseBody(body, resp);
}
return resp;
}
} // namespace geopro::net

View File

@ -0,0 +1,12 @@
#pragma once
#include "ApiClient.hpp" // for geopro::net::ApiResponse
class QNetworkReply;
namespace geopro::net {
// 从一个【已完成】的 reply 读取状态码/响应体/错误并解析为 ApiResponse。
// 不等待、不删除 reply调用方负责生命周期。供同步 await 与异步 ApiCall 共用。
ApiResponse buildResponse(QNetworkReply* reply);
} // namespace geopro::net

View File

@ -3,6 +3,7 @@ find_package(Qt6 COMPONENTS Core Network REQUIRED)
add_library(geopro_net STATIC add_library(geopro_net STATIC
crypto/RsaEncryptor.cpp crypto/RsaEncryptor.cpp
ApiClient.cpp ApiClient.cpp
ApiResponseParse.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)