1. 优化了与服务端数据传输时的架构设计

2. 解决了一些杂项内容
This commit is contained in:
Misaki
2026-01-23 23:09:30 +08:00
parent 8612cbfae3
commit 31e71edac0
12 changed files with 254 additions and 117 deletions
+1 -6
View File
@@ -7,10 +7,7 @@
* 虽然变得方便了,但也带来了危险,如果你肆意通过中介指针去调用GLCore的成员函数
* 可能会导致渲染问题等
*/
#ifndef YOSUGA_APPCONTEXT_H
#define YOSUGA_APPCONTEXT_H
#pragma once
#include "GLCore.h"
class AppContext {
@@ -24,5 +21,3 @@ public:
private:
static inline GLCore* s_glCore = nullptr; // C++17 内联静态成员
};
#endif //YOSUGA_APPCONTEXT_H
-1
View File
@@ -65,7 +65,6 @@ GLCore::GLCore(const int width, const int height, QWidget *parent)
this->setWindowFlag(Qt::Tool); // 隐藏应用程序图标
this->setAttribute(Qt::WA_TranslucentBackground); // 设置窗口背景透明
// 帧率控制初始化
frameTimer = new QTimer(this);
connect(frameTimer, &QTimer::timeout, [&]() {
+68
View File
@@ -0,0 +1,68 @@
//
// Created by misaki on 2026/1/13.
//
/**
* 数据传输对象 (DTO) 定义
* AudioDataTransferObject
* 与Yosuga_server对等
*/
#pragma once
#include <QObject>
#include <QByteArray>
#include <QJsonObject>
#include <QJsonValue>
#include "DataTransferObjectBase.h"
// 前向声明,减少依赖
class QJsonObject;
class AudioDataTransferObject final : public DataTransferObjectBase{
public:
// 构造函数(带默认值)
explicit AudioDataTransferObject(const QString& owner = "client",
bool isStream = false,
bool isStart = false,
bool isEnd = false,
int sequence = 0,
const QByteArray& data = {},
int sampleRate = 16000,
int channelCount = 1,
int bitDepth = 16,
double duration = 0.0,
const QString& text = "");
// 静态工厂方法
static AudioDataTransferObject fromJson(const QJsonObject& json);
[[nodiscard]] QString type() const override { return "audio_data"; }
// 序列化
[[nodiscard]] QJsonObject toJson() const override; // 通过多态即可统一调用方式
// 链式调用设置
AudioDataTransferObject& setData(const QString& key, const QJsonValue& value) override;
[[nodiscard]] QString owner() const { return m_owner; }
[[nodiscard]] bool isStream() const { return m_isStream; }
[[nodiscard]] bool isStart() const { return m_isStart; }
[[nodiscard]] bool isEnd() const { return m_isEnd; }
[[nodiscard]] int sequence() const { return m_sequence; }
[[nodiscard]] QByteArray audioData() const { return m_data; }
[[nodiscard]] int sampleRate() const { return m_sampleRate; }
[[nodiscard]] int channelCount() const { return m_channelCount; }
[[nodiscard]] int bitDepth() const { return m_bitDepth; }
[[nodiscard]] double duration() const { return m_duration; }
[[nodiscard]] QString text() const { return m_text; }
private:
QString m_owner; /// 音频数据的拥有者(server or client)
bool m_isStream; /// 音频数据是否为流式数据
bool m_isStart; /// 音频数据是否开始(流式时有效)
bool m_isEnd; /// 音频数据是否结束(流式时有效)
int m_sequence; /// 音频数据块序列号(流式时有效)
QByteArray m_data; /// 音频数据,流式时为分块数据,base64编码
int m_sampleRate; /// 音频采样率
int m_channelCount; /// 音频通道数
int m_bitDepth; /// 音频采样位数
double m_duration; /// 音频时长
QString m_text; /// 音频对应的文本
};
+21
View File
@@ -0,0 +1,21 @@
//
// Created by misaki on 2026/1/13.
//
#pragma once
#include <QObject>
class DataTransferObjectBase
{
public:
virtual ~DataTransferObjectBase() = default;
// 获取类型,用于区分不同的DTO子类对象
[[nodiscard]] virtual QString type() const = 0;
// 序列化
[[nodiscard]] virtual QJsonObject toJson() const = 0;
// 链式调用设置
virtual DataTransferObjectBase& setData(const QString& key, const QJsonValue& value) = 0;
};
+4 -36
View File
@@ -25,34 +25,8 @@
#include <QMutex>
#include <functional>
/**
* 数据传输对象 (DTO) 定义
*/
// 音频文本捆绑数据结构
struct AudioDataPacket {
QString text; // 文本内容
QByteArray audioData; // 音频原始数据 (二进制)
int sampleRate; // 采样率
int channels; // 通道数
qint64 duration; // 时长 (ms)
AudioDataPacket() : sampleRate(16000), channels(1), duration(0) {}
};
// 控制指令数据结构 (预留)
enum class ControlType {
Click,
Input,
Scroll
};
struct ControlDataPacket {
ControlType action;
int x;
int y;
QString extraData;
};
#include "DataTransferObjectBase.h"
#include "AudioDataTransferObject.h"
/**
* NetworkDO
*/
@@ -75,13 +49,11 @@ public:
void registerSender(SenderFunc sender);
// 业务发送函数
void sendAudioPacket(const AudioDataPacket& packet);
void sendControlPacket(const ControlDataPacket& packet);
void sendPacket(const DataTransferObjectBase& packet);
signals:
// 业务接收信号
void audioPacketReceived(const AudioDataPacket& packet); // 音频数据准备完成信号
void controlPacketReceived(const ControlDataPacket& packet); // 控制数据准备完成信号
void audioPacketReceived(const AudioDataTransferObject& packet); // 音频数据准备完成信号
void errorOccurred(const QString& errorMsg); // 错误信号
public slots:
@@ -92,10 +64,6 @@ public:
private:
// 构造/析构函数私有化
explicit NetworkDO(QObject *parent = nullptr);
// 内部处理逻辑
void handleAudioMessage(const QJsonObject& data);
static QScopedPointer<NetworkDO> m_instance;
static QMutex m_mutex;
+104
View File
@@ -0,0 +1,104 @@
//
// Created by misaki on 2026/1/13.
//
#include "AudioDataTransferObject.h"
#include <QJsonValue>
// 构造函数实现(初始化列表)
AudioDataTransferObject::AudioDataTransferObject(const QString& owner,
bool isStream,
bool isStart,
bool isEnd,
int sequence,
const QByteArray& data,
int sampleRate,
int channelCount,
int bitDepth,
double duration,
const QString& text)
: m_owner(owner)
, m_isStream(isStream)
, m_isStart(isStart)
, m_isEnd(isEnd)
, m_sequence(sequence)
, m_data(data)
, m_sampleRate(sampleRate)
, m_channelCount(channelCount)
, m_bitDepth(bitDepth)
, m_duration(duration)
, m_text(text) {
}
// 静态工厂方法:从 JSON 反序列化
AudioDataTransferObject AudioDataTransferObject::fromJson(const QJsonObject& json) {
// 逐个字段读取,不存在则用默认值
QString owner = json.value("Owner").toString("server");
bool isStream = json.value("isStream").toBool(false);
bool isStart = json.value("isStart").toBool(false);
bool isEnd = json.value("isEnd").toBool(false);
int sequence = json.value("sequence").toInt(0);
// 处理 base64 编码的 data 字段
QByteArray data;
if (json.contains("data")) {
const QString base64Str = json.value("data").toString();
data = QByteArray::fromBase64(base64Str.toUtf8());
}
int sampleRate = json.value("sampleRate").toInt(16000);
int channelCount = json.value("channelCount").toInt(1);
int bitDepth = json.value("bitDepth").toInt(16);
double duration = json.value("duration").toDouble(0.0);
QString text = json.value("text").toString();
// 调用构造函数创建对象
return AudioDataTransferObject(owner, isStream, isStart, isEnd,
sequence, data, sampleRate, channelCount,
bitDepth, duration, text);
}
// 序列化为 JSON
QJsonObject AudioDataTransferObject::toJson() const {
QJsonObject json;
json["Owner"] = m_owner;
json["isStream"] = m_isStream;
json["isStart"] = m_isStart;
json["isEnd"] = m_isEnd;
json["sequence"] = m_sequence;
// data 字段 base64 编码
json["data"] = QString(m_data.toBase64());
json["sampleRate"] = m_sampleRate;
json["channelCount"] = m_channelCount;
json["bitDepth"] = m_bitDepth;
json["duration"] = m_duration;
json["text"] = m_text;
return json;
}
// 链式设置
AudioDataTransferObject& AudioDataTransferObject::setData(const QString& key,
const QJsonValue& value) {
if (key == "Owner") {
m_owner = value.toString();
} else if (key == "isStream") {
m_isStream = value.toBool();
} else if (key == "isStart") {
m_isStart = value.toBool();
} else if (key == "isEnd") {
m_isEnd = value.toBool();
} else if (key == "sequence") {
m_sequence = value.toInt();
} else if (key == "data") {
// 这里要求已是 base64 字符串
m_data = QByteArray::fromBase64(value.toString().toUtf8());
} else if (key == "sampleRate") {
m_sampleRate = value.toInt();
} else if (key == "channelCount") {
m_channelCount = value.toInt();
} else if (key == "bitDepth") {
m_bitDepth = value.toInt();
} else if (key == "duration") {
m_duration = value.toDouble();
} else if (key == "text") {
m_text = value.toString();
}
// 如果 key 不存在,默认忽略
return *this; // 返回自身引用,支持链式调用
}
+4
View File
@@ -0,0 +1,4 @@
//
// Created by misaki on 2026/1/13.
//
#include "DataTransferObjectBase.h"
+12 -52
View File
@@ -48,71 +48,31 @@ void NetworkDO::registerSender(SenderFunc sender)
m_sender = std::move(sender);
}
void NetworkDO::sendAudioPacket(const AudioDataPacket& packet)
void NetworkDO::sendPacket(const DataTransferObjectBase &packet)
{
// 检查发送器是否已注入
if (!m_sender) {
emit errorOccurred("Sender not registered! Call registerSender() first.");
return;
}
// 封装数据 (DTO -> JSON)
QJsonObject dataObj;
dataObj["text"] = packet.text;
// 音频转 Base64 字符串传输
dataObj["audio"] = QString::fromLatin1(packet.audioData.toBase64());
dataObj["sampleRate"] = packet.sampleRate;
dataObj["channels"] = packet.channels;
dataObj["duration"] = packet.duration;
// 调用底层发送 (解耦)
// "textAudio" 是与后端约定的协议类型
m_sender("textAudio", dataObj);
}
void NetworkDO::sendControlPacket(const ControlDataPacket& packet)
{
if (!m_sender) return;
QJsonObject dataObj;
dataObj["action"] = static_cast<int>(packet.action);
dataObj["x"] = packet.x;
dataObj["y"] = packet.y;
m_sender("control", dataObj);
// 依赖注入 + 多态实现完美解耦
m_sender(packet.type(), packet.toJson());
}
// 接受并没有完全解耦
void NetworkDO::onDataReceived(const QString& type, const QJsonObject& data)
{
// 根据类型分发
if (type == "textAudio") {
handleAudioMessage(data);
// 根据类型分发数据包
// 为什么分发做在这里,而不是统一数据再去分发,如果不在这里做分发通知,分开发信号,而使用统一的信号
// 如果有多个观察者,让观察者自动识别数据包,这会导致信号广播,容易引起性能问题(因为这里依赖的是Qt的信号与槽机制)
// TODO: 在此处使用工厂模式,根据type内容快速创建对应的对象
if (type == "audio_data") {
emit audioPacketReceived(AudioDataTransferObject::fromJson(data)); // 构造并发送音频对象
}
else if (type == "control") {
// handleControlMessage(data);
else if (type == "control_data") {
}
else {
qWarning() << "[NetworkDO] Received unknown type:" << type;
}
}
void NetworkDO::handleAudioMessage(const QJsonObject& data)
{
AudioDataPacket packet;
// 解析基础字段 (JSON -> DTO)
packet.text = data.value("text").toString();
packet.sampleRate = data.value("sampleRate").toInt(16000);
packet.channels = data.value("channels").toInt(1);
// 注意类型转换,确保 long long 精度
packet.duration = static_cast<qint64>(data.value("duration").toDouble());
// 解析音频 (Base64 -> Binary)
QString base64Audio = data.value("audio").toString();
if (!base64Audio.isEmpty()) {
packet.audioData = QByteArray::fromBase64(base64Audio.toLatin1());
}
// 通知上层业务
emit audioPacketReceived(packet);
}
+22 -21
View File
@@ -117,13 +117,13 @@ void NetWorkPage::initWebSocketClient() {
client->connectToServer(); // 连接
// 使用定时器延迟检查连接状态
QTimer::singleShot(1000, this, [this, client]() {
if (client->isConnected()) {
ElaMessageBar::success(ElaMessageBarType::TopRight, "连通测试",
"连通测试成功", 800.0, this);
} else {
if (!client->isConnected()) {
ElaMessageBar::warning(ElaMessageBarType::TopLeft, "连通测试",
"无法连接到服务器,请检查地址和服务器状态", 1500.0, this);
return;
}
ElaMessageBar::success(ElaMessageBarType::TopRight, "连通测试",
"连通测试成功", 800.0, this);
});
});
connect(connectPushButton, &ElaPushButton::clicked, this, [this, client]() {
@@ -146,28 +146,29 @@ void NetWorkPage::initWebSocketClient() {
"正在连接服务器...", 800.0, this);
});
connect(disconnectPushButton, &ElaPushButton::clicked, this, [this, client]() {
if (client->isConnected()) {
client->disconnectFromServer();
ElaMessageBar::success(ElaMessageBarType::TopRight, "断开连接",
"已断开连接", 800.0, this);
} else {
if (!client->isConnected()) {
ElaMessageBar::information(ElaMessageBarType::BottomRight, "断开连接",
"当前未连接", 800.0, this);
return;
}
client->disconnectFromServer();
ElaMessageBar::success(ElaMessageBarType::TopRight, "断开连接",
"已断开连接", 800.0, this);
});
connect(sendTestPushButton, &ElaPushButton::clicked, this, [this, netDO]() {
connect(sendTestPushButton, &ElaPushButton::clicked, this, [this, netDO, client]() {
if (!client->isConnected()) {
ElaMessageBar::information(ElaMessageBarType::BottomRight, "断开连接",
"当前未连接", 800.0, this);
return;
}
// 创建数据包
AudioDataPacket packet;
packet.text = "Hello, World!";
// 填入元数据
packet.sampleRate = 16000;
packet.channels = 1;
packet.duration = 2500; // 2.5秒
// 填入音频数据 (模拟从文件或录音设备读取的二进制数据)
QByteArray rawAudioData;
rawAudioData.append("...这里是真实的PCM或WAV二进制数据...");
packet.audioData = rawAudioData;
netDO->sendAudioPacket(packet);
AudioDataTransferObject packet;
packet.setData("Owner", "client")
.setData("isStream", true)
.setData("sequence", 42)
.setData("text", "Hello World")
.setData("data", "SGVsbG8gV29ybGQ="); // 填入音频数据 (Hello World的base64)
netDO->sendPacket(packet);
ElaMessageBar::success(ElaMessageBarType::TopRight, "发送测试",
"已成功发送数据包", 1000.0, this);
});