【Qt6聊天室项目】 主界面功能实现

news2024/11/26 11:07:19

1. 获取当前用户的个人信息

1.1 前后端逻辑分析(主界面功能)

主界面上所有的前后端交互逻辑相同,分析到加载会话列表后其余功能仅实现。

核心逻辑总结

异步请求-响应模型

  • 客户端发起请求,向服务器发送包含会话ID的请求
  • 服务端处理请求并响应,根据的会话ID查找用户信息,并返回结果
  • 客户端处理响应,解析服务器的响应并更新UI显式界面

前后端交互逻辑概述

前端流程

  • MainWidget::initData:前端入口函数,主要就是负责初始化数据,与此同时客户端向DataCenter发送异步请求以获取个人信息
  • DataCenter::getMyselfAsync:向NetClient发送请求,NetClient负责与服务器进行通信
  • NetClient::getMyself:这个函数则是通过HTTP请求向服务器发送用户登录会话ID(loginSessionId)来获取个人信息

后端流程

  • 服务器的HttpServer收到来自NetClient的请求后,然后解析请求数据(也就是会话ID)
  • HttpServer::getUserInfo:服务器根据会话ID,查找用户信息并构建响应,然后将用户信息返回给客户端

客户端处理响应

  • 服务器返回的响应信息由NetClient接收,然后通过DataCenter,最后在MainWidget中展示用户的头像信息

代码逻辑实现分析(前后端交互逻辑)

前端发起请求

MainWidget中通过初始化信号槽函数中,使用connect函数建立了一个连接,当DataCenter::getMyselfDone信号发出后,会执行一个回调函数,这个回调函数的作用就是获取用户信息(myself)然以后设置头像

dataCenter->getMyselfAsync():异步请求,也就是调用该函数进一步通过网络底层获取用户信息。

connect(dataCenter, &DataCenter::getMyselfDone, this, [=]() {
    const auto* myself = dataCenter->getMyself();
    this->userAvatar->setIcon(myself->avatar);
});
dataCenter->getMyselfAsync();

 异步请求逻辑

在数据核心类中,调用getMyselfAsync(()函数,然后又会调用NetClient中的getMyselef函数,然后该函数会将登陆的sessionId传递给NetClient,最后由NetClient来处理网络通信

void DataCenter::getMyselfAsync() const {
    netClient.getMyself(loginSessionId);
}

 前后端通信的实现(NetClient与服务端)

  • 构造请求:使用proto构建请求体,该处使用的是GetUserInfoReq协议消息
  • 发送请求:sendHttpRequest方法,通过传递请求路劲和请求体,通过http客户端post请求,然后返回一个HTTP响应
  • 处理响应:在httpResp请求完成后,netClient会接收服务端返回的响应信息,通过httpleHttpResponse方法解析响应,解析成功后,将用户信息传递给DataCenter ,同时发出getMyselfDone信号,最后通知MainWidget更新UI
void NetClient::getMyself(const QString &loginSessionId) {
    // 1. 构造请求体
    bite_im::GetUserInfoReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    
    // 2. 发送 HTTP 请求
    QByteArray body = req.serialize(&serializer);
    LOG() << "[获取个人信息] requestId=" << req.requestId() << ", sessionId=" << loginSessionId;
    QNetworkReply* httpResp = this->sendHttpRequest("/service/user/get_user_info", body);

    // 3. 处理 HTTP 响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        auto userInfoRsp = this->handleHttpResponse<bite_im::GetUserInfoRsp>(httpResp);
        if (!userInfoRsp) {
            return;
        }
        // b) 设置 DataCenter 中的用户信息
        dataCenter->resetMyself(userInfoRsp);
        // c) 发出信号通知获取完成
        emit dataCenter->getMyselfDone();
    });
}

代码实现逻辑(服务端处理请求)

  • 解析请求:服务端首先解析客户端传过来的请求体,然后根据sessionID查找用户信息
  • 构建响应:通过构建UserInfo对象,填充用户的详细信息,将这些信息装入响应体GetUserInfoRsp,设置success为true就表明操作成功
  • 返回响应:将序列化后的响应体返回给客户端,客户端收到这个响应后会解析数据,然后更新页面
QHttpServerResponse HttpServer::getUserInfo(const QHttpServerRequest &req) {
    // 解析请求
    bite_im::GetUserInfoReq pbReq;
    pbReq.deserialize(&serializer, req.body());
    LOG() << "[REQ 获取用户信息] requestId=" << pbReq.requestId() << ", sessionId=" << pbReq.sessionId();

    // 根据 sessionId 获取用户信息
    bite_im::UserInfo userInfo;
    userInfo.setUserId("1234");
    userInfo.setNickname("张三");
    userInfo.setDescription("这是个性签名");
    userInfo.setPhone("18612345678");
    userInfo.setAvatar(loadImageToByteArray(":/image/defaultAvatar.png"));

    // 构造响应
    bite_im::GetUserInfoRsp pbRsp;
    pbRsp.setSuccess(true);
    pbRsp.setUserInfo(userInfo);

    // 序列化响应体并发送
    QByteArray body = pbRsp.serialize(&serializer);
    return QHttpServerResponse(body, QHttpServerResponse::StatusCode::Ok);
}

代码实现逻辑(客户端处理响应) 

客户端接收到服务端返回的响应后,NetClient然后进行相应的处理

  • 解析响应:首先通过handleHttpResponse解析其中的HTTP响应体,生成GetUserInfoRsp对象
  • 更新数据:更新核心数据类中的数据
  • 发出信号:最后发出getMyselfDone信号,通知前端MainWidget数据获取完成,从而实现更新用户界面
auto userInfoRsp = this->handleHttpResponse<bite_im::GetUserInfoRsp>(httpResp);
if (!userInfoRsp) {
    return;
}
// b) 设置 DataCenter 中的数据
dataCenter->resetMyself(userInfoRsp);
// c) 发出信号通知获取完成
emit dataCenter->getMyselfDone();

不同文件交互梳理

类似于外卖系统中的订单处理

  • 客户端下单:用户提交外卖订单,这也就相当于MainWidget发起了获取用户信息的请求
  • 订单传递给平台:订单会进入外卖平台的处理系统,相对于DataCenter调用NetClient来发起网络请求
  • 平台传递订单给餐厅:外卖平台将订单信息发送给餐厅,类似于NetClient发送HTTP请求给服务器
  • 餐厅处理并回传订单状态:此时餐厅已经准备食品,然后将订单完成状态返回给平台,这也就是服务器返回用户信息给客户端
  • 平台通知客户端:外卖平台将订单完成状态通知用户,客户端根据订单的状态更新用户的UI界面,这也就对应这UI更新用户信息

架构实现分析 

该模块功能的实现使用MVC架构,也就是将数据、试图、控制逻辑三者进行分离,从而使得代码结构清晰模块化。具体来说就是Model负责数据管理、View负责UI展示、Controller负责业务逻辑的处理。

客户端

当网络请求发生的时候,打开主窗口,然后可以获取用户头像以及个人信息,然后头像显示到主窗口上,个人信息显示到个人资料上。 

构建HTTP请求

获取和重置用户信息(在核心数据类中实现)

请求处理封装成模版类型

 网络通信内部实现逻辑

获取个人信息逻辑 

  • 主窗口启动,关联信号槽,发起请求
  • 构造HTTP请求,然后处理响应
  • 响应处理完成后保存到中心数据类中,最后告知响应已经处理完成

测试服务器

分析HTTP服务器处理流程

该测试代码的任务就是负责处理“获取用户信息”请求,然后返回一个Protobuf序列化响应,使用QHttpServerRequest解析客户端请求,返回一个带有用户信息的QHttpServerResponse响应

  • 请求解析:从HTTP请求的body中提取数据,然后使用Protobuf反序列化成为GetUserInFoReq对象,提取请求中包含的用户信息
  • 构建响应:生成包含用户信息的GetUserInfoRsp响应性响应,并将其序列化后作为HTTP响应的body
  • 发送响应:使用protobuf序列化后的数据构建HTTP响应,设置响应头,并将其发送给客户端

服务器正常功能测试

 功能实现分析

根据前后端接口编写客户端(界面---dataCenter---netClient---服务器---反显示后---调用datacenter---给出一个信号---界面)---编写服务器---测试

2. 加载好友列表

实现目标与实现思路分析

  • 实现目标:从服务器上获取好友列表然后显示到客户端界面上
  • 实现方式
    • 从核心数据类中获取好友列表数据
    • 判定数据核心类中是否已经有数据
      • 如果有数据则直接加载本地数据
      • 如果没有数据则需要从服务器中获取数据
    • 更新数据内容到客户端上
      • 如果内存中有数据的话,则直接将数据从内存中拿取出显示即可
  • 总体逻辑
    • 内存读取:首先从本地缓存DataCenter中获取好友数据,如果缓存中有则直接加载到界面
    • 网络请求:缓存中没有数据则需要进行网络请求,向服务器获取好友列表
    • 数据存储:服务器返回数据后,先调用数据中心类的接口,清空原有数据列表中的数据,然后将新的好友列表加载到内存中缓存,方便后续的快速读取
    • 界面更新:网络请求完成后,通过信号的方式通知主界面,然后根据获取的新好友列表更新界面

       

本地文件加载逻辑实现

 

网络请求获取好友列表数据

向服务器发送好友列表请求

  • Protobuf构造GetFriendListenReq请求消息,设置RequestId 和 SessionId
  • 向服务器发起请求,并指定URL(预先约定好)
  •  connect()函数负责等待服务器的响应到来,然后接收数据
  • 响应到达后,解析响应,获取friendListResp,也就是好友列表响应 

 网络请求成功后将好友列表存储在核心数据类中

测试服务器逻辑

  • 注册路由
    • 监听指定的URL地址,当请求到达的时候,则调用getChatSessionList函数进行处理
  • 解析请求
    • 将客户端的请求体反序列化为缓冲区的Protobuf对象,同时通过日志记录
  • 构造响应
    • 初始化响应对象,然后设置请求ID、成功状态、错误信息
  • 发送响应
    • 构建所有会话信息后,服务器将响应序列化发送给客户端

前后端总体实现逻辑总结

 

 

3. 加载会话列表

逻辑分析

4. 加载好友申请列表

实现逻辑汇总 

 

 

 

 

5. 加载会话的最近消息

初始化逻辑 

该功能的实现并非在程序启动的时候,而是当用户点击某个会话的时候才会触发,下面从选择好友开始梳理其逻辑

 

 

逻辑梳理

 

服务器处理

 

客户端处理服务器响应

细节问题处理  

保证滚动条每次直接到达的末尾位置

6. 处理点击好友列表项 

功能分析

点击好友列表后

  • 切换到会话列表
  • 选中对应的会话,根据点击好友的userid和会话列表中的userid匹配
  • 消息展示区中,加载出对应会话的最近消息

逻辑整理

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2222965.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python画图|曲线动态输出

【1】引言 前序教程中的曲线动态输出&#xff0c;其实是把曲线按照左右移动的形式输出&#xff08;波的传递形式&#xff09;。 python画图|曲线动态输出基础教程_python 动态曲线-CSDN博客 但有些时候我们更期待的是曲线不移动&#xff0c;随着自变量的增加而输出因变量&am…

信号与系统学习:傅里叶级数

一、基本概念 1. 什么是傅里叶级数&#xff1f; 傅里叶级数是一种数学工具&#xff0c;可以将一个周期函数分解为一系列正弦和余弦函数&#xff08;即三角函数&#xff09;的和。这些正弦和余弦函数的频率是原函数的整数倍。 2. 为什么要使用傅里叶级数&#xff1f; 信号分…

【STM32+HAL】OV2640捕获图像显示

一、准备工作 有关CUBEMX的初始化配置&#xff0c;参见我的另一篇blog&#xff1a;【STM32HAL】CUBEMX初始化配置 二、所用工具 1、芯片&#xff1a; STM32F407ZGT6 2、IDE&#xff1a; MDK-Keil软件 3、库文件&#xff1a;STM32F4xxHAL库 三、实现功能 通过OV2640捕获图像…

Flutter UI组件库(JUI)

Flutter UI组件库 (JUI) 介绍 您是否正在寻找一种方法来简化Flutter开发过程&#xff0c;并创建美观、一致的用户界面&#xff1f;您的搜索到此为止&#xff01;我们的Flutter UI组件库&#xff08;JUI&#xff09;提供了广泛的预构建、可自定义组件&#xff0c;帮助您快速构建…

为什么会配置足够打LOL等网游很卡?12代大小核处理器最典型

卡顿原因及优化建议 大小核调度问题&#xff1a; 调度不当&#xff1a;某些游戏未针对大小核进行优化&#xff0c;可能导致系统将负载分配到效率核&#xff08;小核&#xff09;&#xff0c;而性能核&#xff08;大核&#xff09;未被充分利用。操作系统调度策略&#xff1a;尽…

15.6 JDBC数据库编程6——可滚动和可更新的ResultSet

目录 15.6 引言 15.6.1 可滚动的ResultSet 15.6.1 可更新的ResultSet 15.6 引言 可滚动的ResultSet是指在结果集对象上不但可以向前访问结果集中的记录&#xff0c;还可以向后访问结果集中记录。可更新的ResultSet是指不但可以访问结果集中的记录&#xff0c;还可以更新…

文件操作(1) —— 文件基础知识

目录 1. 为什么使用文件&#xff1f; 2. 文件种类【按功能分】 3. 文件名 4. 数据文件种类【按存储方式细分】 5. 文件的打开和关闭 5.1 流和标准流 5.2 文件指针 5.3 文件的打开和关闭函数 6. 文件缓冲区 1. 为什么使用文件&#xff1f; 如果没有⽂件&#xff0c;我…

Vue笔记-浏览器窗口改变时,重新计算表格高度并设置

当窗口大小改变时&#xff0c;你监听 window 对象的 resize 事件&#xff0c;然后在事件处理程序中重新计算表格的高度。在 Vue 中&#xff0c;可以在组件中通过 created 生命周期钩子来添加事件监听器&#xff0c;然后在组件销毁时移除事件监听器。 如下vue代码&#xff1a; …

Android GPU Inspector分析帧数据快速入门

使用 谷歌官方工具Android GPU Inspector (AGI) 可以对Android 应用进行深入和全面的系统性能分析和帧性能分析 。AGI 是一个非常强大的分析工具&#xff0c;尤其是在需要诊断 GPU 性能问题和优化应用时&#xff0c;可以帮助你精准找到性能瓶颈。本文介绍如何使用该工具对帧数据…

24V转3.3V2A同步降压WT6030

24V转3.3V2A同步降压WT6030 WT6030 是一种高效同步整流降压开关模式转换器&#xff0c;集成内部功率 MOSFET&#xff0c;能在宽输入电源范围内提供较高的输出电流&#xff0c;以下是使用 WT6030 将 24V 降压到 3.3V 输出 2A 电流的相关设计要点&#xff1a; 1. 电路设计 输入电…

零基础Java第九期:一维数组(二)和二维数组

目录 一、数组的练习 1.1. 顺序表查找 1.2. 二分查找 1.3. 冒泡排序 二、二维数组 2.1. 二维数组的性质 2.2. 不规则二维数组 一、数组的练习 1.1. 顺序表查找 题目描述&#xff1a;给定一个数组, 再给定一个元素, 找出该元素在数组中的位置。 利用for循环去遍历数组&am…

听一听语音助手的声音

分享自制树莓派语音助手的博文也有一些日子了&#xff0c;今天咱们来听听语音助手自己的声音。 上图是本次对话的log记录&#xff0c;从图上可以看到&#xff0c;主要的对话耗时是用于录音&#xff08;默认5秒&#xff09;和语音识别&#xff08;平均5秒&#xff09;这两个组件…

【数据结构】包装类简单认识泛型-Java

包装类 在Java中&#xff0c;由于基本类型不是继承自Object&#xff0c;为了在泛型代码中可以支持基本类型&#xff0c;Java给每个基本类型都给了一个包装类型 基本数据类型和对应的包装类 基本数据类型包装类ByteByteshortShortint Integer longLongfloatFloatdoubleDoublec…

洞察前沿趋势!2024深圳国际金融科技大赛——西丽湖金融科技大学生挑战赛技术公开课指南

在当前信息技术与“互联网”深度融合的背景下&#xff0c;金融行业的转型升级是热门话题&#xff0c;创新与发展成为金融科技主旋律。随着区块链技术、人工智能技术、5G通信技术、大数据技术等前沿科技的飞速发展&#xff0c;它们与金融领域的深度融合&#xff0c;正引领着新型…

模型的部署:服务端与客户端建立连接(Flask)

目录 一、服务端部署&#xff08;使用Flask&#xff09; 1.安装Flask 2.加载模型&#xff08;这里以识别图片的类型模型为例&#xff09; 3.定义API端点 4.运行Flask应用 二、客户端请求 1.安装HTTP客户端库 2.发送请求 请求成功示例&#xff1a; 监控与日志 总结 在…

物联网消息队列Emqx日志配置及日志追踪以及Centos7上的rc.local开机不执行、git提交的小问题

一、物联网消息队列Emqx日志配置及日志追踪 EMQX支持将日志输出到控制台或者日志文件&#xff0c;或者同时使用两者。使用 Docker 部署 EMQX&#xff0c;默认只能通过 docker logs 命令查看 EMQX 日志。EMQX 的默认日志级别为 warning&#xff0c;默认在单日志文件超过10MB(log…

nginx 隐藏版本号与WEB服务器信息

nginx 隐藏版本号与WEB服务器信息 1.安装相关软件2.下载软件包解压并进入3.修改C文件4.编译配置./configure --prefix/usr/local/nginx5.编译安装make && make install5.1.错误处理15.2.错误处理25.2.编译安装make && make install 6.修改nginx配置文件,http节…

【Vue】Vue3.0(十四)接口,泛型和自定义类型的概念及使用

上篇文章&#xff1a; 【Vue】Vue3.0&#xff08;十三&#xff09;中标签属性ref&#xff08;加在普通标签上、加在组件标签上&#xff09;、局部样式 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Vue专栏&#xff1a;点击&#xff01; ⏰️创作时间&…

从0开始深度学习(20)——延后初始化和自定义层

一般情况下&#xff0c;模型参数在被创建时就被立即初始化了&#xff0c;但如果使用了延后初始化技术&#xff0c;就能在首次传入数据后&#xff0c;再初始化参数&#xff0c;旨在输入维度未知的情况下&#xff0c;预定义灵活的模型&#xff0c;动态推断各个层的参数大小。 有时…

robosense 激光雷达安装

官方github 1、ROBOSENSE 驱动安装并运行 1、改雷达型号 2、修改网口地址 3、改变点的类型 https://github.com/RoboSense-LiDAR/rslidar_sdk/blob/main/doc/howto/05_how_to_change_point_type.md 2、ROBOSENSE 点云转换成 velodyne git clone https://github.com/HVikto…