【Qt聊天室客户端】消息功能--发布程序

ops/2024/11/17 4:18:21/

1. 获取文件内容

主要目标是实现获取内容二进制数据的接口,主要是为后面的消息功能提供服务

具体实现

客户端发送请求

服务端处理请求,同时支持三种数据类型

客户端处理服务端的响应

2. 发送图片消息

客户端与服务端的通信约定

客户端从服务器中获取图片消息的时候的,仅返回消息中的文件ID,而不是直接包含文件内容。如果需要文件的实际内容则需要客户端进行二次请求来获取相应的数据

设计目的为了减少初始消息传输体积,从而提高传输效率。客户端和服务端通信,直接传输问价的时候,可能会影响性能,通过文件ID二次获取内容,确保消息的基本信息与文件数据分开传输,从而避免占用过多带宽

服务器和服务器之间,直接附带文件内容,而不会单独的通过文件ID请求。因为服务器的网络环境是相对稳定的,传输文件不会造成较大的性能问题

客户端界面发送图片消息实现

整体流程首先是初始化显示图片的控件,然后配置其样式。然后异步加载图片,如果图片数据没有加载那么就异步获取图片内容;如果获取了图片数据,那么就出发updateUI进行页面更新。最后根据父组件的大小在页面上进行绘制

MessageImageLabel::MessageImageLabel(const QString &fileId, const QByteArray &content, bool isLeft):fileId(fileId),content(content),isLeft(isLeft)
{this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);imageBtn = new QPushButton(this);imageBtn->setStyleSheet("QPushButton { border: none; }");if(content.isEmpty()){DataCenter* dataCenter = DataCenter::genInstance();connect(dataCenter, &DataCenter::getSingleFileDone, this, &MessageImageLabel::updateUI);dataCenter->getSingleFileAsync(fileId);}
}void MessageImageLabel::updateUI(const QString &fileId, const QByteArray &content)
{//不是当前FileIDif(this->fileId != fileId){return;}this->content = content;this->update();
}void MessageImageLabel::paintEvent(QPaintEvent *event)
{(void)event;//1.根据父控件计算图片最大宽度QObject* object = this->parent();if(!object->isWidgetType()){return;}QWidget* parent = dynamic_cast<QWidget*>(object);int width = parent->width()*0.6;//最大宽度设置为父控件的0.6倍//2.加载图片数据QImage image;if(content.isEmpty()){//图片数据为空的时候,加载默认图片QByteArray tmpContent = loadFileToByteArray(":/resource/image/image.png");image.loadFromData(tmpContent);}else{image.loadFromData(content);}//3.根据父控件宽度缩放照片int height = 0;if(image.width() > width){height = static_cast<int>(((double)image.height() / image.width()) * width);}else{width = image.width();height = image.height();}//4.将QImage转换为QPixmapQPixmap pixmap = QPixmap::fromImage(image);imageBtn->setIconSize(QSize(width,height));imageBtn->setIcon(QIcon(pixmap));//5.动态调整父组件高度parent->setFixedHeight(height + 50);//6.根据消息类型调整按钮位置if(isLeft){//左侧消息靠左显示imageBtn->setGeometry(10,0,width,height);}else{//右侧消息靠右显示int leftPos = this->width() - width -10;imageBtn->setGeometry(leftPos,0,width,height);}}

websocket推送图片消息实现

总体逻辑仍然通过按钮触发信号,然后通过服务器槽函数进行处理,向客户端推送图片消息,最后客户端对接收到的响应进行处理即可(处理响应已经在获取文件内容进行了统一处理)

3. 发送文件消息

具体实现

 点击图片按钮触发该处点击逻辑

通过客户端发送请求到服务端

在消息显示区中,将文件信息显示上去

4. 语音消息

4.1 录制音频

 具体实现

实现鼠标按下录制,松开完成录制的功能

发送语音逻辑

4.2 播放音频

具体实现

点击语音消息的时候触发该处逻辑

更新UI

4.3 语音转文字

 具体实现

补充:音频代码

#ifndef SOUNDRECORDER_H
#define SOUNDRECORDER_H#include <QObject>
#include <QStandardPaths>
#include <QFile>
#include <QAudioSource>
#include <QAudioSink>
#include <QMediaDevices>class SoundRecorder : public QObject
{Q_OBJECT
public:const QString RECORD_PATH = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/sound/tmpRecord.pcm";const QString PLAY_PATH = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/sound/tmpPlay.pcm";public:static SoundRecorder* getInstance();//// 录制语音语音/// 开始录制void startRecord();// 停止录制void stopRecord();private:static SoundRecorder* instance;explicit SoundRecorder(QObject *parent = nullptr);QFile soundFile;QAudioSource* audioSource;//// 播放语音/
public:// 开始播放void startPlay(const QByteArray& content);// 停止播放void stopPlay();private:QAudioSink *audioSink;QMediaDevices *outputDevices;QAudioDevice outputDevice;QFile inputFile;signals:// 录制完毕后发送这个信号void soundRecordDone(const QString& path);// 播放完毕发送这个信号void soundPlayDone();};#endif // SOUNDRECORDER_H
#include "soundrecorder.h"
#include <QDir>
#include <QMediaDevices>#include "model/data.h"
#include "toast.h"/
/// 单例模式
/
SoundRecorder* SoundRecorder::instance = nullptr;SoundRecorder *SoundRecorder::getInstance()
{if (instance == nullptr) {instance = new SoundRecorder();}return instance;
}// 播放参考 https://www.cnblogs.com/tony-yang-flutter/p/16477212.html
// 录制参考 https://doc.qt.io/qt-6/qaudiosource.html
SoundRecorder::SoundRecorder(QObject *parent): QObject{parent} {// 1. 创建目录QDir soundRootPath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));soundRootPath.mkdir("sound");// 2. 初始化录制模块soundFile.setFileName(RECORD_PATH);QAudioFormat inputFormat;inputFormat.setSampleRate(16000);inputFormat.setChannelCount(1);inputFormat.setSampleFormat(QAudioFormat::Int16);QAudioDevice info = QMediaDevices::defaultAudioInput();if (!info.isFormatSupported(inputFormat)) {LOG() << "录制设备, 格式不支持!";return;}audioSource = new QAudioSource(inputFormat, this);connect(audioSource, &QAudioSource::stateChanged, this, [=](QtAudio::State state) {if (state == QtAudio::StoppedState) {// 录制完毕if (audioSource->error() != QAudio::NoError) {LOG() << audioSource->error();}}});// 3. 初始化播放模块outputDevices = new QMediaDevices(this);outputDevice = outputDevices->defaultAudioOutput();QAudioFormat outputFormat;outputFormat.setSampleRate(16000);outputFormat.setChannelCount(1);outputFormat.setSampleFormat(QAudioFormat::Int16);if (!outputDevice.isFormatSupported(outputFormat)) {LOG() << "播放设备, 格式不支持";return;}audioSink = new QAudioSink(outputDevice, outputFormat);connect(audioSink, &QAudioSink::stateChanged, this, [=](QtAudio::State state) {if (state == QtAudio::IdleState) {LOG() << "IdleState";this->stopPlay();emit this->soundPlayDone();} else if (state == QAudio::ActiveState) {LOG() << "ActiveState";} else if (state == QAudio::StoppedState) {LOG() << "StoppedState";if (audioSink->error() != QtAudio::NoError) {LOG() << audioSink->error();}}});
}void SoundRecorder::startRecord() {soundFile.open( QIODevice::WriteOnly | QIODevice::Truncate );audioSource->start(&soundFile);
}void SoundRecorder::stopRecord() {audioSource->stop();soundFile.close();emit this->soundRecordDone(RECORD_PATH);
}void SoundRecorder::startPlay(const QByteArray& content) {if (content.isEmpty()) {Toast::showMessage("数据加载中, 请稍后播放");return;}// 1. 把数据写入到临时文件model::writeByteArrayToFile(PLAY_PATH, content);// 2. 播放语音inputFile.setFileName(PLAY_PATH);inputFile.open(QIODevice::ReadOnly);audioSink->start(&inputFile);
}void SoundRecorder::stopPlay() {audioSink->stop();inputFile.close();
}

5. 历史消息调整

补充之前历史消息的遗漏问题,历史消息可以显示文本消息和语音消息,其中点击文件消息可以出触发保存操作;点击语音消息可以触发播放操作 

 具体实现

调用地方在历史消息显示窗口,其中通过判断不同消息类型进行创建

图片历史消息

  • 初始化的时候,如果图片内容存在就直接显示;如果图片为空,那么就通过DataCenter请求图片数据
  • 图片数据从网络中加载完成后,通过更新界面的方法显示到界面上,同时根据窗口大小进行调整

文件历史消息

基本逻辑与图片消息相同,只是多了一个重写鼠标点击操作,点击触发另存为操作

语音历史消息

逻辑和文件消息相同,点击语音消息可以触发播放操作

 

6. 发布程序

借助Qt下的windeployqt.ext实现程序自动获得依赖文件

 release版本中添加外部依赖库

创建好的文件结构


http://www.ppmy.cn/ops/134330.html

相关文章

Shell基础2

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团…

【freertos】FreeRTOS时间管理

FreeRTOS时间管理 一、睡眠延时函数1、vTaskDelay2、vTaskDelayUntil3、相对延时与绝对延时对比 二、自定义延时函数1、微秒延时2、毫秒延时 一、睡眠延时函数 1、vTaskDelay \quad 在UCOSIII 中延时函数OSTimeDly()可以设置为三种模式:相对模式、周期模式和绝对模式。在FreeR…

【Three.js基础学习】24. shader patterns

前言 课程回顾: ShaderMaterial 这里用的是着色器材质 所以顶点和片段着色器就不需要像原始着色器那样添加需要的属性 然后写 片段着色器需要属性 &#xff1a; 顶点 属性 -》变化 -》 片段中 顶点中的属性不需要声明 只需要声明传送的变量 例如 varying vec vUv; vUv uv; 补充…

量子前沿英雄谱|光量子计算的前沿探险家:Jeremy O‘Brien

大航海时代&#xff0c;书写了一部人类探索与发现的壮丽史诗&#xff0c;而作为宏大叙事背后若干个体之一&#xff0c;那些大大小小航船上载着的&#xff0c;是一群不断航向未知海域的坚定探险家。 日光底下无新事。从著名物理学家费曼提出量子计算机的概念起 ... 大航海时代&a…

RK3568平台开发系列讲解(GPIO篇)GPIO的sysfs调试手段

🚀返回专栏总目录 文章目录 一、内核配置二、GPIO sysfs节点介绍三、命令行控制GPIO3.1、sd导出GPIO3.2、设置GPIO方向3.3、GPIO输入电平读取3.4、GPIO输出电平设置四、Linux 应用控制GPIO4.1、控制输出4.2、输入检测4.3、使用 GPIO 中断沉淀、分享、成长,让自己和他人都能有…

241115

A 自闭 考虑到每列每行的差值一定&#xff0c;就考虑排序后使用暴力判断去了 一看标签还带个图论 一开始想向图论方向思考&#xff0c;发现直接爆空间了&#xff0c;时间两说 结果是用并查集维护插值相同的连通块 寄了 C 字符串距离 看我 n 2 m n^2m n2m巨型复杂度直接拿…

前海华海金融创新中心的工地餐点探寻

​前海的工地餐大部分都是13元一份的哈。我在前海华海金融创新中心的工地餐点吃过一份猪杂饭&#xff0c;现做13元一份。我一般打包后回公司吃或直接桂湾公园找个环境优美的地方吃饭。 ​我点的这份猪杂汤粉主要是瘦肉、猪肝、肉饼片、豆芽和生菜&#xff0c;老板依旧贴心问需要…

tdengine学习笔记

官方文档&#xff1a;用 Docker 快速体验 TDengine | TDengine 文档 | 涛思数据 整体架构 TDENGINE是分布式&#xff0c;高可靠&#xff0c;支持水平扩展的架构设计 TDengine分布式架构的逻辑结构图如下 一个完整的 TDengine 系统是运行在一到多个物理节点上的&#xff0c;包含…