#include "websocketclient.h" #include #include #include #include #include #include "networkaccessmanager.h" #include WebSocketClient::WebSocketClient(QObject* parent) : QObject(parent) , m_connected(false) , m_autoReconnect(true) , m_reconnectAttempts(0) , m_maxReconnectAttempts(300) , m_isReconnecting(false) { // 连接WebSocket信号 connect(&m_webSocket, &QWebSocket::connected, this, &WebSocketClient::onConnected); connect(&m_webSocket, &QWebSocket::disconnected, this, &WebSocketClient::onDisconnected); connect(&m_webSocket, &QWebSocket::textMessageReceived, this, &WebSocketClient::onTextMessageReceived); connect(&m_webSocket, QOverload::of(&QWebSocket::error), this, &WebSocketClient::onError); // 设置心跳包定时器 connect(&m_pingTimer, &QTimer::timeout, this, &WebSocketClient::sendPing); m_pingTimer.setInterval(30000); // 30秒发送一次心跳 // 设置重连定时器 m_reconnectTimer.setSingleShot(true); connect(&m_reconnectTimer, &QTimer::timeout, this, &WebSocketClient::tryReconnect); } WebSocketClient::~WebSocketClient() { disconnect(); } void WebSocketClient::connectToRoom(const QString& roomId) { if (m_connected) { disconnect(); } m_roomId = roomId; m_reconnectAttempts = 0; // 重置重连尝试次数 QUrl url = buildWebSocketUrl(roomId); qDebug() << "connectToRoom" << url; // 设置请求头 QNetworkRequest request(url); const QString token = AppEvent::instance()->jwtToken(); if (!token.isEmpty()) { request.setRawHeader("Authorization", "Bearer " + token.toUtf8()); } qDebug() << "---" << token; request.setRawHeader("Machine-Code", AppEvent::instance()->machineCode().toUtf8()); request.setRawHeader("Accept-Language", AppEvent::instance()->locale().toUtf8()); // 连接WebSocket m_webSocket.open(request); } void WebSocketClient::disconnect() { m_autoReconnect = false; // 禁用自动重连 m_reconnectTimer.stop(); // 停止重连定时器 if (m_connected) { m_pingTimer.stop(); m_webSocket.close(); m_connected = false; emit connectionStateChanged(false); } } void WebSocketClient::sendMessage(const QString& content, const QString& type) { if (!m_connected) { emit errorOccurred("未连接到聊天室,无法发送消息"); return; } QJsonObject message; message["content"] = content; message["type"] = type; message["roomId"] = m_roomId; message["time"] = QDateTime::currentDateTime().toUTC().toString(Qt::ISODate); QJsonDocument doc(message); m_webSocket.sendTextMessage(doc.toJson(QJsonDocument::Compact)); } bool WebSocketClient::isConnected() const { return m_connected; } void WebSocketClient::onConnected() { m_connected = true; m_isReconnecting = false; m_reconnectAttempts = 0; // 连接成功后重置重连计数 m_pingTimer.start(); emit connectionStateChanged(true); } void WebSocketClient::onDisconnected() { m_connected = false; m_pingTimer.stop(); emit connectionStateChanged(false); // 如果启用了自动重连,且当前不在重连过程中,则尝试重连 if (m_autoReconnect && !m_roomId.isEmpty() && !m_isReconnecting) { qDebug() << "WebSocket断开连接,准备重连..."; // 使用指数退避策略,每次重连间隔时间增加 int reconnectDelay = 1000 * (1 << qMin(m_reconnectAttempts, 5)); // 最大延迟32秒 m_reconnectTimer.start(reconnectDelay); } } void WebSocketClient::onTextMessageReceived(const QString& message) { QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8(), &error); if (error.error != QJsonParseError::NoError) { emit errorOccurred("解析消息失败: " + error.errorString()); return; } if (doc.isObject()) { QJsonObject obj = doc.object(); // 处理心跳响应 if (obj.contains("type") && obj["type"].toString() == "pong") { return; } // 处理聊天消息 ChatMessage chatMessage; const QString type = obj["type"].toString(); const QString content = obj["content"].toString(); // 设置消息类型 if (type == "room") { chatMessage.type = MessageType::Left; } else if (type == "system") { chatMessage.type = MessageType::System; } else if (type == "private") { chatMessage.type = MessageType::Right; } // 设置消息内容 chatMessage.text = content; // 设置时间(如果服务器提供) if (obj.contains("time") && !obj["time"].toString().isEmpty()) { QDateTime msgTime = QDateTime::fromString(obj["time"].toString(), Qt::ISODate); if (msgTime.isValid()) { chatMessage.timestamp = msgTime; } else { chatMessage.timestamp = QDateTime::currentDateTime(); } } else { chatMessage.timestamp = QDateTime::currentDateTime(); } // 设置发送者ID(如果服务器提供) // if (obj.contains("senderId")) { // chatMessage.senderId = obj["senderId"].toString(); // } emit messageReceived(chatMessage); } } void WebSocketClient::onError(QAbstractSocket::SocketError error) { Q_UNUSED(error); emit errorOccurred("WebSocket错误: " + m_webSocket.errorString()); // 错误发生时不需要额外处理,因为错误通常会触发disconnected信号, // 在onDisconnected中已经处理了重连逻辑 } void WebSocketClient::sendPing() { if (m_connected) { QJsonObject pingMessage; pingMessage["type"] = "ping"; pingMessage["time"] = QDateTime::currentDateTime().toString(Qt::ISODate); QJsonDocument doc(pingMessage); m_webSocket.sendTextMessage(doc.toJson(QJsonDocument::Compact)); } } QUrl WebSocketClient::buildWebSocketUrl(const QString& roomId) { // 从HTTP URL构建WebSocket URL QString baseUrl = TC::RequestClient::globalInstance()->baseUrl(); // 将http(s)://替换为ws(s):// QString wsUrl = baseUrl; if (wsUrl.startsWith("https://")) { wsUrl.replace(0, 8, "wss://"); } else if (wsUrl.startsWith("http://")) { wsUrl.replace(0, 7, "ws://"); } if (wsUrl.isEmpty()) { wsUrl = "ws://127.0.0.1:8200"; } // 构建完整的WebSocket URL return QUrl(wsUrl + "/api/ws/chat/" + roomId); } void WebSocketClient::tryReconnect() { // 防止重复进入重连流程 if (m_isReconnecting || !m_autoReconnect || m_roomId.isEmpty() || m_connected) { return; } m_isReconnecting = true; m_reconnectAttempts++; if (m_reconnectAttempts <= m_maxReconnectAttempts) { qDebug() << "尝试重新连接WebSocket,第" << m_reconnectAttempts << "次尝试..."; emit reconnecting(m_reconnectAttempts, m_maxReconnectAttempts); // 确保WebSocket处于关闭状态 if (m_webSocket.state() != QAbstractSocket::UnconnectedState) { m_webSocket.abort(); QTimer::singleShot(500, this, &WebSocketClient::doReconnect); } else { doReconnect(); } } else { qDebug() << "WebSocket重连失败,已达到最大尝试次数"; emit reconnectFailed(); m_autoReconnect = false; // 禁用自动重连 m_isReconnecting = false; } } void WebSocketClient::doReconnect() { QUrl url = buildWebSocketUrl(m_roomId); QNetworkRequest request(url); const QString token = AppEvent::instance()->jwtToken(); if (!token.isEmpty()) { request.setRawHeader("Authorization", "Bearer " + token.toUtf8()); } request.setRawHeader("Machine-Code", AppEvent::instance()->machineCode().toUtf8()); request.setRawHeader("Accept-Language", AppEvent::instance()->locale().toUtf8()); m_webSocket.open(request); // 添加超时保护,防止连接过程卡死 QTimer::singleShot(10000, this, [this]() { if (!m_connected && m_isReconnecting) { qDebug() << "重连超时,中断当前连接尝试"; m_webSocket.abort(); m_isReconnecting = false; // 继续下一次重连尝试 if (m_autoReconnect && m_reconnectAttempts < m_maxReconnectAttempts) { int reconnectDelay = 1000 * (1 << qMin(m_reconnectAttempts, 5)); m_reconnectTimer.start(reconnectDelay); } else if (m_reconnectAttempts >= m_maxReconnectAttempts) { emit reconnectFailed(); m_autoReconnect = false; } } }); } void WebSocketClient::setAutoReconnect(bool enable) { m_autoReconnect = enable; } void WebSocketClient::setMaxReconnectAttempts(int maxAttempts) { m_maxReconnectAttempts = maxAttempts; }