1. 重构了WebSocket类的实现,并且设置其为项目的核心通信协议
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// Created by misaki on 2025/12/29.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* 本类为网络数据流会使用到的数据访问对象封装
|
||||
* 目的是便于统一数据访问方式
|
||||
* 同时屏蔽了端到端数据交换格式,使得上层调用不再需要关心数据格式,而只需要填入数据即可
|
||||
*/
|
||||
|
||||
/**
|
||||
* 简单描述一下Yosuga客户端所需要使用到的数据
|
||||
* 主要为音频数据,控制信息,文本信息。
|
||||
* 其中文本信息与音频数据为捆绑收发,并且其中还包括了一些特别的信息,例如音频时长等
|
||||
* 控制信息与各种业务逻辑相关,例如模拟点击,模拟输入等
|
||||
*/
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QScopedPointer>
|
||||
#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;
|
||||
};
|
||||
|
||||
/**
|
||||
* NetworkDO
|
||||
*/
|
||||
class NetworkDO final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(NetworkDO) // 禁用拷贝
|
||||
|
||||
public:
|
||||
// 单例访问点
|
||||
static NetworkDO* getInstance();
|
||||
// 显式销毁
|
||||
static void destroy();
|
||||
|
||||
// 定义发送回调函数类型
|
||||
using SenderFunc = std::function<void(const QString& type, const QJsonObject& data)>;
|
||||
|
||||
public:
|
||||
// 注入发送接口
|
||||
void registerSender(SenderFunc sender);
|
||||
|
||||
// 业务发送函数
|
||||
void sendAudioPacket(const AudioDataPacket& packet);
|
||||
void sendControlPacket(const ControlDataPacket& packet);
|
||||
|
||||
signals:
|
||||
// 业务接收信号
|
||||
void audioPacketReceived(const AudioDataPacket& packet);
|
||||
void controlPacketReceived(const ControlDataPacket& packet);
|
||||
void errorOccurred(const QString& errorMsg);
|
||||
|
||||
public slots:
|
||||
// 接收底层 JSON 数据
|
||||
void onDataReceived(const QString& type, const QJsonObject& data);
|
||||
public:
|
||||
~NetworkDO() override;
|
||||
private:
|
||||
// 构造/析构函数私有化
|
||||
explicit NetworkDO(QObject *parent = nullptr);
|
||||
|
||||
// 内部处理逻辑
|
||||
void handleAudioMessage(const QJsonObject& data);
|
||||
|
||||
static QScopedPointer<NetworkDO> m_instance;
|
||||
static QMutex m_mutex;
|
||||
|
||||
SenderFunc m_sender; // 注入的发送器
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// Created by misaki on 2025/12/29.
|
||||
//
|
||||
#include "NetWorkDO.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMutexLocker>
|
||||
|
||||
// 初始化静态成员
|
||||
QScopedPointer<NetworkDO> NetworkDO::m_instance;
|
||||
QMutex NetworkDO::m_mutex;
|
||||
|
||||
// 单例实现 (QScopedPointer + Mutex)
|
||||
NetworkDO* NetworkDO::getInstance()
|
||||
{
|
||||
if (m_instance.isNull()) {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_instance.isNull()) {
|
||||
// 使用 reset 创建实例,因为构造函数是私有的
|
||||
m_instance.reset(new NetworkDO());
|
||||
}
|
||||
}
|
||||
return m_instance.data();
|
||||
}
|
||||
|
||||
void NetworkDO::destroy()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_instance.isNull()) {
|
||||
m_instance.reset(); // 这会触发析构函数
|
||||
}
|
||||
}
|
||||
|
||||
NetworkDO::NetworkDO(QObject *parent) : QObject(parent)
|
||||
{
|
||||
qDebug() << "NetworkDO initialized";
|
||||
}
|
||||
|
||||
NetworkDO::~NetworkDO()
|
||||
{
|
||||
qDebug() << "NetworkDO destroyed";
|
||||
}
|
||||
|
||||
// 业务逻辑实现
|
||||
void NetworkDO::registerSender(SenderFunc sender)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex); // 简单保护一下赋值
|
||||
m_sender = std::move(sender);
|
||||
}
|
||||
|
||||
void NetworkDO::sendAudioPacket(const AudioDataPacket& 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);
|
||||
}
|
||||
|
||||
void NetworkDO::onDataReceived(const QString& type, const QJsonObject& data)
|
||||
{
|
||||
// 根据类型分发
|
||||
if (type == "textAudio") {
|
||||
handleAudioMessage(data);
|
||||
}
|
||||
else if (type == "control") {
|
||||
// handleControlMessage(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);
|
||||
}
|
||||
Reference in New Issue
Block a user