基于腳手架微服務的視頻點播系統-數據管理與網絡通信部分的預備工作
- 一.數據管理
- 二.網絡通信
- 2.1客戶端通信模塊及測試用例的實現
- 2.2MockServer搭建的相關接口介紹
- 2.3MockServer的搭建示例
一.數據管理
在前?的實現中,程序中的數據、以及界?操作等全部攪合在?起,不利于代碼的維護,為了降低耦合度,引?DataCenter類來專門管理程序中的各種數據,比如:分類和標簽、視頻信息、??信息等。
在此之前,我們要先處理下主窗口的close,需要在主窗口關閉時統一處理所有單例模式的實例指針釋放(將原來主界面上的quit綁定的槽函數更換為以下函數):
void LimePlayer::LimePlayerClose()
{close();if(limePlayer)delete limePlayer;limePlayer = nullptr;//釋放數據中心的單例對象-下面會給出model::DataCenter::getInstance()->deleteDataCenter();
}
為什么我們不采用智能指針或靜態局部變量來實現單例模式呢?那樣不是更簡潔方便嗎?實際上,博主也曾嘗試過這類方法,但在程序退出時卻頻繁引發段錯誤(Segmentation Fault)。其根本原因在于,這兩種方式在程序終止階段的對象釋放順序是無法保證的——很有可能在主程序或某些核心資源已經被銷毀之后,這些單例對象才被析構。而此時如果單例對象在析構過程中試圖訪問已釋放的資源(例如位于已卸載內存中的靜態數據或 Qt 系統資源),就會導致非法的內存訪問,從而觸發段錯誤。
**特別是在 Qt 環境中,許多模塊(如 GUI 組件、網絡層或數據庫連接)高度依賴于 Qt 自身的清理機制和對象生命周期管理。**若單例對象的析構順序與 Qt 內部資源的釋放順序不一致,就極易發生訪問已失效 QObject 或其他資源的情況,造成不可預知的崩潰。因此,在 Qt 這類框架中,通常建議使用顯式控制的單例生命周期管理機制(如手動銷毀或結合 QObject 父子關系),或采用具有明確析構順序的上下文環境,以避免退出時的資源沖突問題。
我們先新創建一個dataCenter類,然后將其寫為單例模式,并寫明該單例類的delete函數,以便讓主程序退出時釋放數據中心的單例指針(注意為了區分模塊,這里我們將這個新建的類放到工程目錄的model文件夾下,同時我們的類也放到名為model的命名空間中,避免引起沖突):
//////////////////////./model/datacenter.h
#ifndef DATACENTER_H
#define DATACENTER_H#include <QObject>
#include "data.h"namespace model{class DataCenter : public QObject
{Q_OBJECT
public:static DataCenter* getInstance();void deleteDataCenter();//主窗口關閉時調用以釋放單例對象~DataCenter();
private:explicit DataCenter(QObject *parent = nullptr);
private:static DataCenter* instance;
};
}#endif // DATACENTER_H////////////////////////////////./model/datacenter.cpp
#include "datacenter.h"namespace model{
//初始化instance
DataCenter* DataCenter::instance = nullptr;DataCenter *DataCenter::getInstance()
{if(instance == nullptr){instance = new DataCenter();}return instance;
}
void DataCenter::deleteDataCenter()
{//當主窗口關閉時釋放datacenter對象if(instance)delete instance;instance = nullptr;
}
}
接下來我們創建一個名為data的.h與.cpp文件,今后所有的數據都將存放到data中。因為現在還無法進行網絡通信,所以我們先對已有數據進行管理-分類與標簽:
//////////////////////////./model/data.h
#ifndef DATA_H
#define DATA_H#include <QString>
#include <QHash>
#include <QList>namespace model{class KindsAndTags{
public:KindsAndTags();//獲取所有分類const QList<QString> getAllKinds() const;//獲取對應分類下的所有標簽const QList<QString> getAllTagsFromKind(const QString& kind) const;//獲取分類對應的idint getKindId(const QString& kind) const;//獲取標簽對應的idint getTagId(const QString &kind,const QString& tag) const;
private:QHash<QString,int> kinds;QHash<QString,QHash<QString,int>> tags;static int id;//記錄數據對應的id號
};}#endif // DATA_H
///////////////////////////////////////./model/data.cpp
#include "data.h"namespace model{//初始id值為10000
int KindsAndTags:: id = 10000;KindsAndTags::KindsAndTags()
{//初始化分類QList<QString> allKinds = {"動畫","游戲","音樂","電影","歷史","美食","科技","運動"};for(auto& kind : allKinds){this->kinds.insert(kind,id++);}QHash<QString,QList<QString>> allTags = {{"動畫", {"熱血", "治愈", "冒險", "搞笑", "科幻", "致郁", "神作", "日常"}},{"游戲", {"開放世界", "角色扮演", "射擊", "策略", "獨立游戲", "多人聯機", "魂系", "解謎"}},{"音樂", {"流行", "搖滾", "電子", "民謠", "說唱", "古典", "爵士", "國風"}},{"電影", {"科幻", "懸疑", "喜劇", "文藝", "動作", "犯罪", "催淚", "史詩"}},{"歷史", {"上古", "中世紀", "文藝復興", "工業革命", "世界大戰", "文明古國", "帝王將相", "考古"}},{"美食", {"甜點", "燒烤", "家常菜", "火鍋", "烘焙", "小吃", "海鮮", "無辣不歡"}},{"科技", {"人工智能", "虛擬現實", "智能手機", "電動汽車", "航天", "編程", "區塊鏈", "深度學習"}},{"運動", {"籃球", "足球", "跑步", "健身", "羽毛球", "滑雪", "電競", "戶外"}}};//初始化標簽for(auto& kind : allKinds){QHash<QString,int> allTagsFromKind;for(auto& tag : allTags[kind]){allTagsFromKind.insert(tag,id++);}this->tags.insert(kind,allTagsFromKind);}
}const QList<QString> KindsAndTags::getAllKinds() const
{return kinds.keys();
}const QList<QString> KindsAndTags::getAllTagsFromKind(const QString &kind) const
{return tags[kind].keys();
}int KindsAndTags::getKindId(const QString &kind) const
{return kinds[kind];
}int KindsAndTags::getTagId(const QString &kind,const QString &tag) const
{return tags[kind][tag];
}}
然后在datacenter中添加上對應的成員指針:
////////////////////////////////./model/datacenter.h
#ifndef DATACENTER_H
#define DATACENTER_H#include <QObject>
#include "data.h"namespace model{class DataCenter : public QObject
{Q_OBJECT
public:static DataCenter* getInstance();const KindsAndTags* getKindsAndTags();void deleteDataCenter();~DataCenter();
private:explicit DataCenter(QObject *parent = nullptr);
private:static DataCenter* instance;//存儲所有分類及其對應的標簽數據KindsAndTags* kindsAndTags = nullptr;
};}#endif // DATACENTER_H
////////////////////////./model/datacenter.cpp
#include "datacenter.h"namespace model{
const KindsAndTags *DataCenter::getKindsAndTags()
{if(kindsAndTags == nullptr){kindsAndTags = new KindsAndTags();}return kindsAndTags;
}DataCenter::~DataCenter()
{delete kindsAndTags;
}
}
細心的讀者可能注意到了,我們上面在設置分類標簽數據時,還給每一個數據都加上了一個id,這是一種常見的作法。那么這里就有一個問題-為什么客戶端向服務端索取數據時,通常遞交與服務端的是數據對應的id而不直接是數據名稱呢?
博主查詢網絡總結了以下四點:
- 效率與性能 (Efficiency & Performance)
**數據量小:**一個整型或UUID類型的ID(例如 12345)所占的存儲空間和網絡傳輸帶寬,遠小于一個可能很長的字符串名稱(例如 “iPhone 15 Pro Max 1TB 鈦金屬原色”)。在高并發、海量請求的場景下,這能極大地節省網絡帶寬、降低I/O壓力、加快序列化/反序列化速度。
**索引優化:**在數據庫(如MySQL)中,對數字型的ID字段建立索引并進行查詢,其速度遠快于對字符串類型的名稱字段進行查詢。數字比較比字符串比較要快得多,并且數字索引通常更緊湊。直接查詢 WHERE id = 12345 的效率遠遠高于 WHERE product_name = ‘iPhone 15 Pro Max…’。 - 一致性與唯一性 (Consistency & Uniqueness)
**名稱可變:**名稱是可以修改的。比如一本書的書名、一個用戶的昵稱、一個產品的標題。如果客戶端靠名稱“《深入理解C++》”來請求數據,而當這本書改名成“《C++ Primer Plus》”后,所有用舊名稱發起的請求都會失敗。而ID(如 ISBN: 9787115580xxx)是唯一且不變的標識符,它指向一個唯一的實體,與這個實體的屬性(如名稱)如何變化無關。
**名稱不唯一:**名稱可能存在重復。世界上可能有成千上萬個叫“張三”的用戶,或者多個同名的產品。但他們的ID(如用戶ID、產品SKU)在系統內是唯一的。使用ID可以精確地定位到唯一實體,避免了歧義。 - 解耦與維護 (Decoupling & Maintainability)
**隔離變化:**這個設計遵循了“解耦”的原則。客戶端只需要知道一個穩定不變的標識符(ID),而不需要關心服務端內部的數據結構。服務端可以自由地修改實體的名稱、描述等其他信息,甚至重構底層數據庫,只要ID保持不變,客戶端就完全不受影響。如果客戶端直接傳遞名稱,服務端任何對名稱的修改都會導致客戶端邏輯崩潰。
**安全性:**有時,名稱本身可能是敏感的,或者你不希望暴露內部的命名規則。直接暴露ID(尤其是非自增的、無規律的UUID)相比暴露一個具有語義的名稱,通常更安全一些。 - 國際化 (Internationalization)
如果服務端需要支持多語言,一個產品可能對應多個名稱(中文名、英文名等)。客戶端很難知道應該傳遞哪個語言的名字來請求數據。而ID是語言無關的,客戶端傳遞ID,服務端可以根據客戶端的語言設置或請求頭信息,返回對應語言的名稱和其他數據。
那么接下來我們使用datacenter中的數據去替換我們之前的首頁分類標簽與填充上傳視頻界面的分類與標簽即可,首頁替換很簡單這里我們就不再給出了,我們主要來看下上傳視頻界面的數據導入:
///////////////////uploadvideopage.h
#ifndef UPLOADVIDEOPAGE_H
#define UPLOADVIDEOPAGE_H#include <QWidget>
#include "pageswitchbutton.h"namespace Ui {
class UploadVideoPage;
}class UploadVideoPage : public QWidget
{Q_OBJECT
private://當前選擇的分類被切換時的槽函數void onSelectKindChanged(int index);//切換tagLayout中的標簽void addTagsByKind(const QString& kind);
};#endif // UPLOADVIDEOPAGE_H////////////////////////////////uploadvideopage.cpp
#include "uploadvideopage.h"
#include "ui_uploadvideopage.h"
#include "util.h"
#include "toast.h"
#include "./model/datacenter.h"
#include <QMessageBox>UploadVideoPage::UploadVideoPage(QWidget *parent): QWidget(parent), ui(new Ui::UploadVideoPage)
{ui->setupUi(this);//初始化kindsauto dataCenter = model::DataCenter::getInstance();auto kindsAndTags = dataCenter->getKindsAndTags();ui->kinds->addItems(kindsAndTags->getAllKinds());ui->kinds->setCurrentIndex(-1);//默認不選擇任何分類//鏈接相應信號槽connect(ui->kinds,&QComboBox::currentIndexChanged,this,&UploadVideoPage::onSelectKindChanged);
}void UploadVideoPage::onSelectKindChanged(int index)
{const QString kind = ui->kinds->itemText(index);this->addTagsByKind(kind);
}void UploadVideoPage::addTagsByKind(const QString &kind)
{//如果分類為空不進行處理if(kind.isEmpty())return;//1.獲取分類標簽數據auto dataCenter = model::DataCenter::getInstance();auto kindsAndTags = dataCenter->getKindsAndTags();const QList<QString> tags = kindsAndTags->getAllTagsFromKind(kind);//2.清空原來所有的標簽QList<QPushButton*> outDataedTags = ui->tagWidget->findChildren<QPushButton*>();for(auto& button : outDataedTags){ui->tagLayout->removeWidget(button);delete button;}//3.添加標簽for(auto& tag : tags){QPushButton* tagBtn = new QPushButton(tag,ui->tagWidget);tagBtn->setFixedSize(98, 49);tagBtn->setCheckable(true); // 設置按鈕的狀態有兩種:選中和未選中狀態// QPushButton::checked 當前按鈕被選中// QPushButton::unchecked 當前按鈕未選中tagBtn->setStyleSheet("QPushButton{""border : 1px solid #3ECEFE;""border-radius : 4px;""color : #3ECEFE;""font-family : 微軟雅黑;""font-size : 16px;""background-color : #FFFFFF;}""QPushButton:checked{""background-color : #3ECEFE;""color : #FFFFFF;}""QPushButton:unchecked{""background-color : #FFFFFF;""color : #3ECEFE;}");ui->tagLayout->addWidget(tagBtn);}
}
然后我們就能在視頻上傳界面中看到如下效果:
二.網絡通信
2.1客戶端通信模塊及測試用例的實現
像數據中心的實現一樣,我們這里將新建的netclient類文件放到工程目錄的netclient文件夾下,而類也放到命名空間netclient中。
我們這里使用json進行序列化和反序列化,之前我們也使用過,所以我們再來回顧下相關接口:
1.QJsonObject:封裝了?個JSON對象,即鍵值對的集合。
2.QJsonArray:封裝了?個JSON數組,可以存儲?系列的QJsonValue對象,即值的有序集合。3.QJsonValue:封裝了?個JSON值,QJsonObject中key是字符串,value是QJsonValue對象。
4.QJsonDocument:?于解析和?產JSON?本,即對QJsonObject完成序列化和反序列化。
為了獲取數據方便,這里我們使用http協議進行服務端與客戶端的通信,客戶端要想使用相關通信模塊,需要在cmake中添加以下腳本:
find_package(Qt6 REQUIRED COMPONENTS Network)#網絡模塊
target_link_libraries(LimePlayer PRIVATE Qt6::Network)
對于http協議這里我們不再過多介紹,需要了解熟悉http協議的讀者可以移步我之前寫的關于http和https的博文:
HTTP協議解析:Session/Cookie機制與HTTPS加密體系的技術演進(一)
當然qt中已經對相關接口進行了高度封裝,我們直接拿來使用即可:
QNetworkAccessManager是Qt網絡模塊中的?個核心類,用于發送請求、處理響應、管理會話等,使得開發者可以方便地發送 HTTP 請求、處理響應等。
// 發送get請求
QNetworkReply *get(const QNetworkRequest &request);
// 發送post請求
QNetworkReply *post(const QNetworkRequest &request, const QByteArray &data);
// ...
// QT將HTTP協議常?的?法都封裝了,這?不在??列舉,需要?到時請翻閱Qt的官??檔。
// 請求響應成功后,觸發該信號
void finished(QNetworkReply *reply)
注意因為網絡傳輸是有延遲的,QNetworkAccessManager進行post或get之后是異步的直接返回,不會阻塞等待響應,響應到達時以finished信號通知,所以我們需要在QNetworkReply的finished信號進行接收服務端的response相關操作。
QNetworkRequest是Qt網絡模塊中的?個核心類,用于封裝網絡請求的所有關鍵信息,包括請求的 URL、HTTP 頭部信息、請求體等。
// 創建?個沒有指定URL的請求對象,之后可以通過setUrl()設置URL
QNetworkRequest();
// 使?指定的URL構造請求
QNetworkRequest(const QUrl &url);
// 設置請求的URL
void setUrl(const QUrl &url);
// 設置已知的HTTP頭部,?如 content-type
void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value);
利? QNetworkRequest 可以完成請求的設置,之后就可以將請求適合的方式發送給服務器。
了解了以上接口,我們就可以開始進行實現了。不過在確定具體實現方案之前,我們還需要深入思考一個問題:**應以何種方式組織模塊間的交互。**在實際運行過程中,用戶界面上的多數操作都需要與底層通信模塊進行數據交換。基于以往的經驗,通常有兩種實現思路:其一是在每一個需要網絡請求的界面類中直接內嵌一個通信對象——這種做法顯然不可取,不僅會導致代碼冗余,更會引入高度的重復依賴。另一種方案是將通信模塊設計為單例,雖然這種方式在一定程度上簡化了調用,但卻會使界面與通信實現緊密耦合,違背關注點分離的原則,從而嚴重制約后期的維護與擴展性。
在計算機領域有一個廣為人知的理念:當一個復雜問題難以直接解決時,就為它引入一個中間層。如果一層不夠,就再添加一層。而在我們的架構中,甚至無需額外引入——DataCenter 本身就是一個天然、完善的中間層。
此外,我們還需要解決另一個關鍵問題:如何有效區分不同的請求。解決方案其實非常直接——我們可以在每個 HTTP 請求的 Body 部分攜帶一個具有唯一性的標識字段,這里我們稱其為requestId。通過生成 UUID(通用唯一識別碼)來確保該值的全局唯一性,即可可靠地實現請求的追蹤與匹配。
所以基于上面的接口的了解與實現方案的分析,我們可以得到如下的實現方式:
///////////////////////////./model/datacenter.h
#ifndef DATACENTER_H
#define DATACENTER_H#include <QObject>
#include "data.h"
#include "./../netclient/netclient.h"namespace model{class DataCenter : public QObject
{Q_OBJECT
private://用于進行網絡通信的對象netclient::NetClient netClient;public:// 定義所有異步請求方法void helloAsync();void pingAsync();
signals:void helloDone();void pingDone();
};
}
#endif // DATACENTER_H
//////////////////////////////////./model/datacenter.cpp
#include "datacenter.h"namespace model{
DataCenter::DataCenter(QObject *parent): QObject{parent},netClient(this)
{}
void DataCenter::helloAsync()
{netClient.hello();
}
void DataCenter::pingAsync()
{netClient.ping();
}
}
//////////////////////////////////./netclient/netclient.h
#ifndef NETCLIENT_H
#define NETCLIENT_H#include <QObject>
#include <QNetworkAccessManager>namespace model{
class DataCenter;
}namespace netclient{
class NetClient : public QObject
{Q_OBJECT
public:explicit NetClient(model::DataCenter* dataCenterPtr,QObject *parent = nullptr);void hello();void ping();
private:QNetworkReply* sendReqToHttpServer(const QString& route,QJsonObject& reqbody);QJsonObject handleRespFromHttpServer(QNetworkReply* reply,bool& ok,QString& reson);//使用uuid生成requestIdQString makeRequestId();
private:const QString HTTP_URL = "http://127.0.0.1:8080";QNetworkAccessManager netClientManager;model::DataCenter* dataCenter = nullptr;
};
}//end netclient#endif // NETCLIENT_H
///////////////////////////////////////./netclient/netclient.cpp
#include "netclient.h"
#include "util.h"
#include "./../model/datacenter.h"
#include <QJsonObject>
#include <QJsonDocument>
#include <QNetworkReply>
#include <QUuid>/**{* requestId,* errorCode,* errorMsg,* data{* ? : ?* }*}*/
namespace netclient{
NetClient::NetClient(model::DataCenter* dataCenterPtr,QObject *parent): QObject{parent},dataCenter(dataCenterPtr)
{}QNetworkReply* NetClient::sendReqToHttpServer(const QString &route, QJsonObject &reqbody)
{QNetworkRequest request;//設置url及請求報頭request.setUrl(HTTP_URL + route);request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json;charset=utf8");//發送請求到服務端QJsonDocument document(reqbody);//body部分return netClientManager.post(request,document.toJson());
}QJsonObject NetClient::handleRespFromHttpServer(QNetworkReply *reply, bool &ok, QString &reason)
{ok = false;//1.檢查reply本身是否有錯if(reply->error() != QNetworkReply::NoError){reason = reply->errorString();return QJsonObject();}//2.說明請求本身無誤,檢查body部分是否能夠解析成功QJsonDocument respDocument(QJsonDocument::fromJson(reply->readAll()));if(respDocument.isNull()){//解析失敗reason = "解析 JSON ?件失敗! JSON ?件格式有錯誤!";return QJsonObject();}//3.查看業務邏輯是否有誤-errorIdQJsonObject respBody = respDocument.object();if(respBody["errorCode"].toInt() != 0){//業務邏輯出錯reason = respBody["errorMsg"].toString();return QJsonObject();}ok = true;//說明reply沒有問題,將ok置為truereturn respBody;
}void NetClient::hello()
{//請求報文的bodyQJsonObject reqbody;reqbody["requestId"] = makeRequestId();QNetworkReply* reply = sendReqToHttpServer("/hello",reqbody);//異步處理服務端的responseconnect(reply,&QNetworkReply::finished,this,[=](){bool ok = true;QString reason;QJsonObject respBody = handleRespFromHttpServer(reply,ok,reason);if(!ok){//說明reply有問題打印錯誤信息并返回LOG() << reason;reply->deleteLater();return;}reply->deleteLater();//解析成功,處理響應信息QJsonObject data = respBody["data"].toObject();LOG() << data["hello"].toString();//告訴界面處理從服務端獲取的數據(如果需要)emit dataCenter->helloDone();});
}void NetClient::ping()
{//請求報文的bodyQJsonObject reqbody;reqbody["requestId"] = makeRequestId();QNetworkReply* reply = sendReqToHttpServer("/ping",reqbody);//異步處理服務端的responseconnect(reply,&QNetworkReply::finished,this,[=](){bool ok = true;QString reason;QJsonObject respBody = handleRespFromHttpServer(reply,ok,reason);if(!ok){//說明reply有問題打印錯誤信息并返回LOG() << reason;reply->deleteLater();return;}reply->deleteLater();//解析成功,處理響應信息QJsonObject data = respBody["data"].toObject();LOG() << data["ping"].toString();emit dataCenter->pingDone();});
}QString NetClient::makeRequestId()
{return "R" + QUuid::createUuid().toString().sliced(25,12);
}
}//end netclient
2.2MockServer搭建的相關接口介紹
現在客戶端通信模塊搭建好了,但是我們怎么去測驗它是否有問題呢,這里我們就需要搭建一個假的服務器。MockServer是能夠提供Mock功能的服務器,通過模擬真實的服務器,提供對來?客?端請求的真實響應。它允許用戶創建模擬HTTP服務以模擬后端服務的響應,從而在開發和測試過程中,無需依賴實際的后端服務,也能夠進行接口測試和功能驗證。實際就是搭建?個HTTP服務器,構造模擬數據對客戶端接口進行測試。
這里我們新建一個項目,注意是項目,名為MockServer,基于QWidget和cmake。然后我們如果想要使用qt已經封裝好的httpServer模塊,需要在CMakeLists.txt中添加如下腳本:
find_package(Qt6 REQUIRED COMPONENTS HttpServer)
target_link_libraries(LimePlayerMockServerPRIVATEQt6::HttpServer
)
當然如果已經了解甚至熟悉了http協議,實現我們的假的服務器就很簡單了,只需要了解幾個qt提供的接口即可:
// 功能:創建QHttpServer實例
QHttpServer(QObject *parent = nullptr);
// 功能:綁定ip地址和端?號,將套接字設置為監聽套接字
// 參數:
// address: 指定服務器監聽的IP地址。QHostAddress::Any表?監聽所有可?的?絡地
址,
// 在 IPv4 中,它通常對應于 0.0.0.0,?在 IPv6 中,它對應于 ::
// port: 指定服務器監聽的端?號
// 返回值:如果服務器成功啟動并開始監聽指定的地址和端?時,返回端?號,否則返回0
quint16 listen(const QHostAddress &address = QHostAddress::Any, quint16 port =
0)
// 功能:定義HTTP請求的路由規則,將請求路徑和?法與處理函數綁定,當服務器收到匹配的請求
時,
// 會調?對應的處理函數
// Args: 可變參數包,最后?個參數是可調?對象(仿函數、lambda表達式、函數指針),其余參數
// ?于創建新路由規則
// 返回值:新的路由規則創建成功返回true,否則返回false
template <typename Rule = QHttpServerRouterRule, typename... Args>
bool QHttpServer::route(Args &&... args)
// server.route("/hello", [] (QHttpServerRequest &request) { return ""; });
// 功能:構造http響應對象
// 參數:
// data : 響應的正?內容,序列化之后的結果
// status: http的狀態碼,默認是響應成功。200是OK 404是 Not Found...
QHttpServerResponse(const QByteArray &data,
const QHttpServerResponse::StatusCode status =
StatusCode::Ok)
// 使?QHttpServer搭建Http服務器的步驟:
// 1. 創建QHttpServer對象
// 2. 調?route創建路由規則
// 3. 調?listen綁定ip地址和端?,并將套接字設置為監聽套接字,然后等待客?端鏈接
// 4. 實現響應?法
2.3MockServer的搭建示例
///////////////////////////////util.h
#ifndef UTIL_H
#define UTIL_H#include <QString>
#include <QFileInfo>
#include <QFile>static inline QString fileName(const QString& filePath)
{QFileInfo fileInfo(filePath);return fileInfo.fileName();
}//內斂函數以及static防止函數重定義// noquote()是QDebug中的一個成員函數,用于制定在輸出時不會自動為字符串添加引號
#define LOG() qDebug().noquote()<<QString("[%1:%2]:").arg(fileName(__FILE__),QString::number(__LINE__))#endif // UTIL_H////////////////////////////////mockserver.h
#ifndef MOCKSERVER_H
#define MOCKSERVER_H#include <QWidget>
#include <QHttpServer>QT_BEGIN_NAMESPACE
namespace Ui {
class MockServer;
}
QT_END_NAMESPACEclass MockServer : public QWidget
{Q_OBJECTpublic:bool init();static MockServer* getInstance();QHttpServerResponse hello(const QHttpServerRequest &request);QHttpServerResponse ping(const QHttpServerRequest &request);//void deleteMockServer();~MockServer();
private:MockServer(QWidget *parent = nullptr);private:Ui::MockServer *ui;QHttpServer httpServer;
};
#endif // MOCKSERVER_H
////////////////////////////mockserver.cpp
#include "mockserver.h"
#include "ui_mockserver.h"
#include "util.h"
#include <QJsonObject>
#include <QJsonDocument>MockServer::MockServer(QWidget *parent): QWidget(parent), ui(new Ui::MockServer)
{ui->setupUi(this);
}bool MockServer::init()
{//監聽指定端口quint16 port = 8080;quint16 ret = httpServer.listen(QHostAddress::Any,port);//設置路由httpServer.route("/hello",[=](const QHttpServerRequest &request){return hello(request);});httpServer.route("/ping",[=](const QHttpServerRequest &request){return ping(request);});return ret == 8080;
}MockServer *MockServer::getInstance()
{static MockServer instance;return &instance;
}QHttpServerResponse MockServer::hello(const QHttpServerRequest &request)
{//構建body信息QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();LOG() << "[hello] 收到 hello 請求, requestId = " <<reqData["requestId"].toString();QJsonObject respData;respData["requestId"] = reqData["requestId"].toString();respData["errorCode"] = 0;respData["errorMsg"] = "";QJsonObject data;data["hello"] = "world";respData["data"] = data;//構建http響應報文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader("Content-Type","application/json;charset=utf-8");return response;
}QHttpServerResponse MockServer::ping(const QHttpServerRequest &request)
{//構建body信息QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();LOG() << "[ping] 收到 ping 請求, requestId = " <<reqData["requestId"].toString();QJsonObject respData;respData["requestId"] = reqData["requestId"].toString();respData["errorCode"] = 0;respData["errorMsg"] = "";QJsonObject data;data["ping"] = "pang";respData["data"] = data;//構建http響應報文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader("Content-Type","application/json;charset=utf-8");return response;
}MockServer::~MockServer()
{delete ui;
}
//////////////////////////main.cpp
#include "mockserver.h"
#include "util.h"
#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);MockServer* w = MockServer::getInstance();if(!w->init()){LOG() << "服務器初始化失敗!";}LOG() << "服務器初始化成功!!";w->show();return a.exec();
}
接下來我們在客戶端的main.cpp函數中調用hello與ping模塊運行服務端與客戶端即可看到如下效果:
ok到這里我們的預備工作已經準備完畢,下一篇文章我們再來實現實際的業務邏輯。