Files
MyChat_Client/network/netclient.cpp
2025-06-25 13:00:28 +08:00

436 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "netclient.h"
#include "../model/datacenter.h"
using namespace model;
namespace network {
NetClient::NetClient(model::DataCenter* dataCenter)
:dataCenter(dataCenter)
{
initWebSocket();
}
void NetClient::ping()
{
QNetworkRequest httpReq;
httpReq.setUrl(QUrl(HTTP_URL + "/ping"));
QNetworkReply* httpResp = httpClient.get(httpReq);
connect(httpResp, &QNetworkReply::finished, this, [=]() {
//到这里面,说明响应已经回了
if (httpResp->error() != QNetworkReply::NoError) {
//请求失败
LOG() << "HTTP请求失败" << httpResp->errorString();
httpResp->deleteLater();
return;
}
//获取到响应的body
QByteArray body = httpResp->readAll();
LOG() << "响应的内容: " << body;
httpResp->deleteLater();
});
}
void NetClient::initWebSocket()
{
//准备好所需要的信号槽
connect(&websocketClient, &QWebSocket::connected, this, [=]() {
LOG() << "webSocket 连接成功";
//连接成功之后,进行发送身份认证
sendAuth();
});
connect(&websocketClient, &QWebSocket::disconnected, this, [=]() {
LOG() << "webSocket 连接断开";
});
connect(&websocketClient, &QWebSocket::textMessageReceived, this, [=](const QString& message) {
LOG() << "webSocket 收到文本消息 " << message;
});
connect(&websocketClient, &QWebSocket::binaryMessageReceived, this, [=](const QByteArray& byteArray) {
LOG() << "webSocket 收到二进制的消息" << byteArray.length();
bite_im::NotifyMessage notifyMessage;
notifyMessage.deserialize(&serializer, byteArray);
handleWsResponse(notifyMessage);
});
//和服务器真正建立连接
websocketClient.open(WEBSOCKET_URL);
}
void NetClient::handleWsResponse(const bite_im::NotifyMessage& notifyMessage)
{
if (notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::CHAT_MESSAGE_NOTIFY) {
//收到消息
//把pb中的MessageInfo转成客户端自己的message
Message message;
message.load(notifyMessage.newMessageInfo().messageInfo());
//针对自己的message做进一步的处理
handleWsMessage(message);
}
else if (notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::CHAT_SESSION_CREATE_NOTIFY) {
}
else if (notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::FRIEND_ADD_APPLY_NOTIFY) {
}
else if (notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::FRIEND_ADD_PROCESS_NOTIFY) {
}
else if (notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::FRIEND_REMOVE_NOTIFY) {
}
}
void NetClient::handleWsMessage(const model::Message& message)
{
// 这里要考虑两种情况
QList<Message>* messageList = dataCenter->getRecentMessageList(message.chatSessionId);
if (messageList == nullptr) {
//如果当前这个消息所属的会话,里面的消息列表没有在本地加载,此时需要通过网络先加载整个消息列表
connect(dataCenter, &DataCenter::getRecentMessageListDoneNoUI, this, &NetClient::receiveMessage, Qt::UniqueConnection);
dataCenter->getRecnetMessageListAsync(message.chatSessionId, false);
}
else {
//若已经在本地加载了,直接把这个消息尾插到消息列表中即可
messageList->push_back(message);
this->receiveMessage(message.chatSessionId);
}
}
void NetClient::sendAuth()
{
bite_im::ClientAuthenticationReq req;
req.setRequestId(makeRequestId());
req.setSessionId(dataCenter->getLoginSessionId());
QByteArray body = req.serialize(&serializer);
websocketClient.sendBinaryMessage(body);
LOG() << "[WS身份认证] requestId= " << req.requestId() << ",loginSessionId= " << req.sessionId();
}
QString NetClient::makeRequestId()
{
//基本的要求确保每个请求的Id都是不重复的唯一的
//通过UUID实现上述的效果
return "R" + QUuid::createUuid().toString().sliced(25, 12);
}
//通过这个函数把发送HTTP请求的操作封装一下
QNetworkReply* NetClient::sendHttpRequest(const QString& apiPath, const QByteArray& body)
{
//构造出HTTP请求
QNetworkRequest httpReq;
httpReq.setUrl(QUrl(HTTP_URL + apiPath));
httpReq.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-protobuf");
//发起HTTP请求
QNetworkReply* httpResp = httpClient.post(httpReq, body);
return httpResp;
}
//在这个函数内部,完成具体的网络通信即可
void NetClient::getMyself(const QString& loginSessionId)
{
//构造出HTTP请求body部分
bite_im::GetUserInfoReq req;
req.setRequestId(makeRequestId());
req.setSessionId(loginSessionId);
QByteArray body = req.serialize(&serializer);
LOG() << "获取个人信息->发送请求: requestId=" << req.requestId() << ", loginSessionId=" << loginSessionId;
//构造出HTTP请求
QNetworkReply* httpResp = sendHttpRequest("/service/user/get_user_info", body);
//通过信号槽,获取到当前的响应
connect(httpResp, &QNetworkReply::finished, this, [=]() {
//if (httpResp->error() != QNetworkReply::NoError) {
// //说明HTTP请求出错了
// LOG() << "HTTP error: " << httpResp->errorString();
// httpResp->deleteLater();
// return;
//}
////说明拿到了body
//QByteArray respBody = httpResp->readAll();
////针对body进行反序列化,解析成对象
//bite_im::GetUserInfoRsp respObj;
//respObj.deserialize(&serializer, respBody);
////判定一下业务上是否出错
//if (!respObj.success()) {
// LOG() << "requestId= " << respObj.requestId() << ", errmsg=" << respObj.errmsg();
// httpResp->deleteLater();
// return;
//}
//继续处理后续的业务逻辑
//获取http回复
bool ok = false;
QString reason;
auto resp = handleHttpResponse<bite_im::GetUserInfoRsp>(httpResp, &ok, &reason);
//判定响应是否正确
if (!ok) {
LOG() << "获取个人信息->出错 requestId= " << req.requestId() << ", reason= " << reason;
return;
}
//把响应的数据保存到DataCenter
dataCenter->resetMyself(resp);
//通知调用逻辑,响应已经处理完成了,仍然通过信号槽通知
emit dataCenter->getMyselfDone();
//打印日志
LOG() << "获取个人信息->响应处理完毕 requestId" << req.requestId();
});
}
void NetClient::getFriendList(const QString& loginSessionId)
{
//通过protobuf构造body
bite_im::GetFriendListReq req;
req.setRequestId(makeRequestId());
req.setSessionId(loginSessionId);
QByteArray body = req.serialize(&serializer);
LOG() << "获取好友列表->发送请求 requestId=" << req.requestId() << ", loginSessionId= " << loginSessionId;
//发送HTTP请求
QNetworkReply* httpResp = this->sendHttpRequest("/service/friend/get_friend_list", body);
//处理响应
connect(httpResp, &QNetworkReply::finished, this, [=]() {
//解析响应
bool ok = false;
QString reason;
auto friendListResp = this->handleHttpResponse<bite_im::GetFriendListRsp>(httpResp, &ok, &reason);
if (!ok) {
LOG() << "获取好友列表->失败 requestId= " << req.requestId() << ", reason= " << reason;
}
LOG() << "获取好友列表->成功";
//把结果保存在DataCenter中
dataCenter->resetFriendList(friendListResp);
// 发送信号
emit dataCenter->getFriendListDone();
});
}
void NetClient::getChatSessionList(const QString& loginSessionId)
{
//通过protobuf构造body
bite_im::GetChatSessionListReq req;
req.setRequestId(makeRequestId());
req.setSessionId(loginSessionId);
QByteArray body = req.serialize(&serializer);
LOG() << "获取会话列表->发送请求 requestId= " << req.requestId() << ", loginSessionId= " << loginSessionId;
//发送HTTP请求
QNetworkReply* resp = this->sendHttpRequest("/service/friend/get_chat_session_list", body);
//针对响应进行处理
connect(resp, &QNetworkReply::finished, this, [=]() {
//解析响应
bool ok = false;
QString reason;
auto pbResp = this->handleHttpResponse<bite_im::GetChatSessionListRsp>(resp, &ok, &reason);
//判断响应是否正确
if (!ok) {
LOG() << "获取会话列表->失败 reason= " << reason;
return;
}
//到这里说明没有问题把得到的数据写入到DataCenter中
dataCenter->resetChatSessionList(pbResp);
//通知调用者,响应已经处理完毕了
emit dataCenter->getChatSessionListDone();
//打印日志
LOG() << "获取会话列表->处理响应完毕 requestId= " << pbResp->requestId();
});
}
void NetClient::getApplyList(const QString& loginSessionId)
{
//通过protobuf构造body
bite_im::GetPendingFriendEventListReq req;
req.setRequestId(makeRequestId());
req.setSessionId(loginSessionId);
QByteArray body = req.serialize(&serializer);
LOG() << "获取好友申请列表->发送请求 reason= " << req.requestId() << ", loginSessonId= " << loginSessionId;
//发送HTTP请求
QNetworkReply* resp = this->sendHttpRequest("/service/friend/get_pending_friend_events", body);
//处理响应
connect(resp, &QNetworkReply::finished, this, [=]() {
//解析响应
bool ok = false;
QString reason;
auto pbResp = this->handleHttpResponse<bite_im::GetPendingFriendEventListRsp>(resp, &ok, &reason);
//判定结果是否正确
if (!ok) {
LOG() << "获取好友申请列表->失败 reason= " << reason;
return;
}
//拿到的数据写入到DataCenter中
dataCenter->resetApplyList(pbResp);
//发送信号通知界面,已经处理完毕
emit dataCenter->getApplyListDone();
LOG() << "获取好友申请列表->处理响应完成 requestId= " << req.requestId();
});
}
void NetClient::getRecentMessageList(const QString& loginSessionId, const QString& chatSessionId, bool updateUI)
{
//通过protobuf构造请求body
bite_im::GetRecentMsgReq req;
req.setRequestId(makeRequestId());
req.setChatSessionId(chatSessionId);
req.setMsgCount(50);
req.setSessionId(loginSessionId);
QByteArray body = req.serialize(&serializer);
LOG() << "获取最近消息->发送请求 requestId= " << req.requestId() << ", loginSessionId= " << loginSessionId;
//发送http请求
QNetworkReply* resp = this->sendHttpRequest("/service/message_storage/get_recent", body);
//处理响应
connect(resp, &QNetworkReply::finished, this, [=]() {
//解析响应,反序列化
bool ok = false;
QString reason;
auto pbResp = this->handleHttpResponse<bite_im::GetRecentMsgRsp>(resp, &ok, &reason);
//判定响应是否出错
if (!ok) {
LOG() << "获取最近消息->失败 reason= " << reason;
return;
}
//把拿到的数据设置到DataCenter中
dataCenter->resetRecentMessageList(chatSessionId, pbResp);
//发送信号,告知界面进行更新
if (updateUI) {
emit dataCenter->getRecentMessageListDone(chatSessionId);
}
else {
emit dataCenter->getRecentMessageListDoneNoUI(chatSessionId);
}
});
}
//此处的extraInfo可以用来传递扩展信息尤其是对文件消息来说通过这个字段表示“文件名”
//其他类型的消息暂时不涉及,就直接设置为空,如果后续有消息类型需要,都可以给这个参数,赋予一定的特殊意义
void NetClient::sendMessage(const QString& loginSessionId, const QString& chatSessionId,
model::MessageType messageType, const QByteArray& content, const QString& extraInfo)
{
//通过protobuf 构造 body
bite_im::NewMessageReq req;
req.setRequestId(makeRequestId());
req.setSessionId(loginSessionId);
req.setChatSessionId(chatSessionId);
//构造MessageContent
bite_im::MessageContent messageContent;
if (messageType == MessageType::TEXT_TYPE) {
messageContent.setMessageType(bite_im::MessageTypeGadget::MessageType::STRING);
bite_im::StringMessageInfo stringMessageInfo;
stringMessageInfo.setContent(content);
messageContent.setStringMessage(stringMessageInfo);
}
else if (messageType == MessageType::IMAGE_TYPE) {
messageContent.setMessageType(bite_im::MessageTypeGadget::MessageType::IMAGE);
bite_im::ImageMessageInfo imageMessageInfo;
imageMessageInfo.setFileId("");
imageMessageInfo.setImageContent(content);
messageContent.setImageMessage(imageMessageInfo);
}
else if (messageType == MessageType::FILE_TYPE) {
messageContent.setMessageType(bite_im::MessageTypeGadget::MessageType::FILE);
bite_im::FileMessageInfo fileMessageInfo;
fileMessageInfo.setFileId("");
fileMessageInfo.setFileSize(content.size());
fileMessageInfo.setFileName(extraInfo);
fileMessageInfo.setFileContents(content);
messageContent.setFileMessage(fileMessageInfo);
}
else if (messageType == MessageType::SPEECH_TYPE) {
messageContent.setMessageType(bite_im::MessageTypeGadget::MessageType::SPEECH);
bite_im::SpeechMessageInfo speechMessageInfo;
speechMessageInfo.setFileId("");
speechMessageInfo.setFileContents(content);
messageContent.setSpeechMessage(speechMessageInfo);
}
else {
LOG() << "错误的消息类型 messageType= " << messageType;
}
req.setMessage(messageContent);
//序列化操作
QByteArray body = req.serialize(&serializer);
LOG() << "发送消息->发送请求 requestId= " << req.requestId() << ", loginSessionId=" << req.sessionId() <<
", chatSessionId= " << req.chatSessionId() << ", messageType=" << req.message().messageType();
//发送HTTP请求
QNetworkReply* resp = this->sendHttpRequest("/service/message_transmit/new_message", body);
//处理HTTP响应
connect(resp, &QNetworkReply::finished, this, [=]() {
//针对响应结果,进行解析
bool ok = false;
QString reason;
auto pbResp = this->handleHttpResponse<bite_im::NewMessageRsp>(resp, &ok, &reason);
//判定响应是否正确
if (!ok) {
LOG() << "发送消息->处理出错 reason= " << reason;
return;
}
//此处只是记录成功和失败不需要把内写入到DataCenter中
//通知调用者,响应处理完毕
emit dataCenter->sendMessageDone(messageType, content, extraInfo);
//打印日志
LOG() << "发送消息->响应处理完毕 requestId=" << pbResp->requestId();
});
}
void NetClient::receiveMessage(const QString& chatSessionId)
{
//要先判定一下,这个收到消息对应的会话是否是正在被用户选中的“当前会话”
//当前会话,就需要把消息显示到消息展示区,也需要更新会话列表消息预览
//若不是,只更新消息预览,并且更新“未读消息数目”
if (chatSessionId == dataCenter->getCurrentSessionId()) {
//说明是选中的会话
const Message& lastMessage = dataCenter->getRecentMessageList(chatSessionId)->back();
//通过信号让NetClient模块能够通知界面
emit dataCenter->receiveMessageDone(lastMessage);
}
else {
//说明不是
dataCenter->addUnread(chatSessionId);
}
//统一更新会话列表的消息预览
emit dataCenter->updateLastMessage(chatSessionId);
}
} //end namespace network