基于脚手架微服务的视频点播框架-数据管理与网络通信部分的预备工作

一.数据管理

在前⾯的实现中,程序中的数据、以及界⾯操作等全部搅合在⼀起,不利于代码的维护,为了降低耦合度,引⼊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;
          //获取分类对应的id
          int getKindId(const QString& kind) const;
          //获取标签对应的id
          int 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而不直接是数据名称呢?
博主查询网络总结了以下四点:

  1. 效率与性能 (Efficiency & Performance)
    **数据量小:**一个整型或UUID类型的ID(例如 12345)所占的存储空间和网络传输带宽,远小于一个可能很长的字符串名称(例如 “iPhone 15 Pro Max 1TB 钛金属原色”)。在高并发、海量请求的场景下,这能极大地节省网络带宽、降低I/O压力、加快序列化/反序列化速度。
    **索引优化:**在数据库(如MySQL)中,对数字型的ID字段建立索引并进行查询,其速度远快于对字符串类型的名称字段进行查询。数字比较比字符串比较要快得多,并且数字索引通常更紧凑。直接查询 WHERE id = 12345 的效率远远高于 WHERE product_name = ‘iPhone 15 Pro Max…’。
  2. 一致性与唯一性 (Consistency & Uniqueness)
    **名称可变:**名称是可以修改的。比如一本书的书名、一个用户的昵称、一个产品的标题。如果客户端靠名称“《深入理解C++》”来请求数据,而当这本书改名成“《C++ Primer Plus》”后,所有用旧名称发起的请求都会失败。而ID(如 ISBN: 9787115580xxx)是唯一且不变的标识符,它指向一个唯一的实体,与这个实体的属性(如名称)如何变化无关。
    **名称不唯一:**名称可能存在重复。世界上可能有成千上万个叫“张三”的用户,或者多个同名的产品。但他们的ID(如用户ID、产品SKU)在系统内是唯一的。使用ID可以精确地定位到唯一实体,避免了歧义。
  3. 解耦与维护 (Decoupling & Maintainability)
    **隔离变化:**这个设计遵循了“解耦”的原则。客户端只需要知道一个稳定不变的标识符(ID),而不需要关心服务端内部的数据结构。服务端可以自由地修改实体的名称、描述等其他信息,甚至重构底层数据库,只要ID保持不变,客户端就完全不受影响。如果客户端直接传递名称,服务端任何对名称的修改都会导致客户端逻辑崩溃。
    **安全性:**有时,名称本身可能是敏感的,或者你不希望暴露内部的命名规则。直接暴露ID(尤其是非自增的、无规律的UUID)相比暴露一个具有语义的名称,通常更安全一些。
  4. 国际化 (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);
    //初始化kinds
    auto 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生成requestId
      QString 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.查看业务逻辑是否有误-errorId
              QJsonObject respBody = respDocument.object();
              if(respBody["errorCode"].toInt() != 0)
              {
              //业务逻辑出错
              reason = respBody["errorMsg"].toString();
              return QJsonObject();
              }
              ok = true;
              //说明reply没有问题,将ok置为true
              return respBody;
              }
              void NetClient::hello()
              {
              //请求报文的body
              QJsonObject reqbody;
              reqbody["requestId"] = makeRequestId();
              QNetworkReply* reply = sendReqToHttpServer("/hello",reqbody);
              //异步处理服务端的response
              connect(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()
              {
              //请求报文的body
              QJsonObject reqbody;
              reqbody["requestId"] = makeRequestId();
              QNetworkReply* reply = sendReqToHttpServer("/ping",reqbody);
              //异步处理服务端的response
              connect(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(LimePlayerMockServer
PRIVATE
Qt6::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_NAMESPACE
          class MockServer
          : public QWidget
          {
          Q_OBJECT
          public:
          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到这里我们的预备工作已经准备完毕,下一篇文章我们再来实现实际的业务逻辑。

posted @ 2025-09-17 16:28  wzzkaifa  阅读(21)  评论(0)    收藏  举报