当前位置: 首页 > news >正文

QT聊天项目DAY19

0.注册账户用于跨服务器测试

0.1 注册账户

注册完用户头像丢失,查看一下客户端代码

由于找头文件每次都很不方便,代码太多,所以新建筛选器来管理项目

最终加载头像的属于基础窗口的chatwidget,直接去该筛选器下找到对应的头文件和cpp文件

初始化头像是从用户下加载的用户数据

查看该用户数据是何时设置的

在处理登录响应时已经设置了

查看注册时客户端的代码

GateServer服务器的代码,Mysql正确写入了

查看聊天服务器的代码

首先再次登录就没有任何问题

查看日志发现,服务器发来的数据是空的,

代码写多了,才知道原来指针也是有值传递的,我下意识的以为指针都是引用,哈哈

改成这样,然后采用默认的赋值运算符即可

已解决

1.跨服务器通讯

继承编译生成好的GRPC服务,重载一下函数,然后自定义一个客户端,通过创建和chatserver的连接,来调用接口通过连接和chatserver进行通信

1.1 启动GRPC服务

chatserver服务器启动监听客户端连接和grpc服务

int main()
{auto pool = AsioIOServicePool::GetInstance();boost::asio::io_context io_ct;boost::asio::signal_set signals(io_ct, SIGINT, SIGTERM);// 监听客户端连接const int port = get<int>(ServerStatic::ParseConfig("SelfServer", "Port"));unsigned short port_ushort = static_cast<unsigned short>(port);shared_ptr<CServer> pServer = make_shared<CServer>(io_ct, port_ushort);LogicSystem::GetInstance()->SetServer(pServer);pServer->StartAccept();// 启动grpc服务const int rpcPort = get<int>(ServerStatic::ParseConfig("SelfServer", "RPCPort"));string IP = "localhost:" + to_string(rpcPort);ChatServiceImpl service;grpc::ServerBuilder builder;// 监听端口并添加要监听的服务builder.AddListeningPort(IP, grpc::InsecureServerCredentials());builder.RegisterService(&service);// 启动服务unique_ptr<grpc::Server> server(builder.BuildAndStart());// 创建单独的线程处理grpc服务thread grpcThread([&server](){cout << "grpcThread start" << endl;server->Wait();});signals.async_wait([&io_ct, pool, &server](auto, auto){server->Shutdown();io_ct.stop();pool->Stop();});io_ct.run();grpcThread.join();return 0;
}

1.2 跨服务器申请添加好友

1.chatserver1服务器处理客户端发来的添加好友申请

向chatserver2发送添加好友请求

2.chatserver2处理添加好友请求

获取申请者和授权者的信息然后往客户端发送请求即可

1.3 跨服务器授权好友申请

chatserver1服务器处理请求,然后查询申请者所在的服务器是否是chatserver1,如果是chatserver2则通过grpc请求向chatserver2进行通信,chatserver2收到通信后,获取申请者的会话,然后将授权结果返回给申请者

message AuthFriendRequest {int32 fromUid = 1;int32 toUid = 2;
}

gapc客户端发送请求

grpc服务器处理授权好友请求

Status ChatServiceImpl::NotifyAuthFriend(ServerContext* context, const AuthFriendRequest* request, AuthFriendResponse* response)
{// 查找用户是否在本服务器int apply_uid = request->touid();																					// 获取申请者UIDint auth_uid = request->fromuid();																					// 获取授权者UIDshared_ptr<CSession> pSession = UserMgr::GetInstance()->GetSession(apply_uid);										// 获取与申请者的会话// 返回响应ConnectionRAII raii([request, response](){response->set_error(ErrorCodes::SUCCESS);response->set_fromuid(request->fromuid());response->set_touid(request->touid());});// 用户不在线,该会话为空if (pSession == nullptr)return Status::OK;// 用户在线,发送通知给对方Json::Value jsonMsg;jsonMsg["error"] = ErrorCodes::SUCCESS;jsonMsg["auth_uid"] = auth_uid;																						// 授权者UID// 获取授权者的基本信息string baseKey = USER_BASE_INFO + to_string(auth_uid);// shared_ptr<UserInfo> pUserInfo(new UserInfo);																	// 先调用UserInfo的构造函数再调用shared_ptr的构造以传参接管裸指针shared_ptr<UserInfo> pUserInfo = make_shared<UserInfo>();															// 直接使用make_shared构造shared_ptrbool bInfo = GetBaseInfo(baseKey, auth_uid, pUserInfo);if (bInfo){jsonMsg["auth_name"] = pUserInfo->name;jsonMsg["auth_nick"] = pUserInfo->nick;jsonMsg["auth_icon"] = pUserInfo->icon;jsonMsg["auth_sex"] = pUserInfo->sex;jsonMsg["auth_desc"] = pUserInfo->desc;}else{jsonMsg["error"] = ErrorCodes::UID_INVALID;}string result = jsonMsg.toStyledString();// 向该在线用户发送好友申请信息pSession->Send(result, MSG_IDS::ID_NOTIFY_AUTH_FRIEND_REQUEST);return Status::OK;
}

1.4 跨服务器通讯

客户端发送消息

从编辑器中获取拆解的文本

假如全是文本,那么msgList的大小就是1

如果是文本 + 图片

大小为2

// 遍历消息列表,创建对应的消息气泡并添加到聊天视图中
for (int i = 0; i < msgList.size(); i++)
{// 限制文本长度if (msgList[i].MsgContent.length() > MAX_TEXT_LEN)continue;QString type = msgList[i].MsgType;ChatItemBase* item = new ChatItemBase(role);														// 聊天窗口item->SetUserName(userName);item->SetUserIcon(QPixmap(userIcon));QWidget* bubble = nullptr;																			// 根据消息类型来创建气泡类型// 生成消息IDQUuid uuid = QUuid::createUuid();QString uuidStr = uuid.toString();if (type == MSG_TYPE_TEXT){bubble = new TextBubble(role, msgList[i].MsgContent);											// 文本气泡// 如果文本长度超过限制,则发送给服务器if (textSize + msgList[i].MsgContent.length() > MAX_TEXT_LEN){SendMsgToTcp(textObj, textArray, textSize, userInfo->_uid);}textSize += msgList[i].MsgContent.length();QJsonObject obj;obj["content"] = msgList[i].MsgContent;obj["msgId"] = uuidStr;																			// 标识消息的IDobj["type"] = type;textArray.append(obj);																			// 添加到文本数组中QSharedPointer<TextChatData> chatMsg(new TextChatData(uuidStr, type, obj["content"].toString(),userInfo->_uid, _userInfo->_uid));emit SigAppendChatMsgToChatUserWnd(chatMsg);													// 将与该用户的聊天信息添加到对应的聊天小窗口中}else if (type == MSG_TYPE_IMAGE){QByteArray base64String = msgList[i].Msg_Image_Data.toUtf8();									// 先转为 UTF-8 QByteArrayQByteArray imgData = QByteArray::fromBase64(base64String);										// 这才是真正的图片二进制数据bubble = new PictureBubble(role, msgList[i].MsgPicture, imgData);QJsonObject obj;obj["content"] = msgList[i].Msg_Image_Data;obj["msgId"] = uuidStr;obj["type"] = type;textArray.append(obj);QSharedPointer<TextChatData> chatMsg(new TextChatData(uuidStr, type, obj["content"].toString(),userInfo->_uid, _userInfo->_uid));emit SigAppendChatMsgToChatUserWnd(chatMsg);													// 将与该用户的聊天信息添加到对应的聊天小窗口中}else if (type == MSG_TYPE_FILE){}// 添加到聊天窗口中if (bubble){item->SetWidget(bubble);ui.chatView->AppendChatItem(item);}
}

最后将循环中,创建的json数组QJsonArray,添加到最后要打包的格式里面

如下是最终打包发送的Json格式

{"fromuid": "发送者ID","touid": "接收者ID","textArray": [{"content": "消息内容1","msgId": "唯一ID1","type": "消息类型1"},{"content": "消息内容2","msgId": "唯一ID2","type": "消息类型2"},...]
}

单服务器解析客户端发送的文本信息

就是把数据拿出来,然后装到C++的Json格式中而已,多了一个Error,当然这是针对于在同一个服务器上而言

// 1.解析客户端发来的聊天信息
reader.parse(msg_data, jsonResult);
int uid = jsonResult["fromuid"].asInt();
int toUid = jsonResult["touid"].asInt();cout << "Deal Chat Text Msg fromUid is " << uid << " toUid is " << toUid << "\n";// 聊天信息
const Json::Value arrays = jsonResult["textArray"];
jsonReturn["error"] = ErrorCodes::SUCCESS;
jsonReturn["senderUid"] = uid;
jsonReturn["receiverUid"] = toUid;
jsonReturn["textArray"] = arrays;

跨服务器解析客户端发送的文本信息,并根据通讯协议打包新的消息体发送给另一个服务器

需要通过自己设定的协议,来组装成对应的包,通过grpc发送给另一个聊天服务器(chatserver2)

这里的repeated表示的是这个包的数组,类似于vector<TextChatData>

message TextChatMsgRequest {int32 fromUid = 1;int32 toUid = 2;repeated TextChatData textMsgs = 3;
}message TextChatData{string msgId = 1;string msgContent = 2;string msgType = 3;
}

将json转换成TextChatMsgRequest,这个请求的数据格式如上,类似于上面说的Json格式

// 5.如果不在一个服务器上,向被聊天者所在的服务器发送聊天信息
TextChatMsgRequest textMsgRequest;
textMsgRequest.set_fromuid(uid);
textMsgRequest.set_touid(toUid);
for (const auto& text : arrays)
{auto content = text["content"].asString();string msgId = text["msgId"].asString();string msgType = text["type"].asString();cout << "Deal Chat Text Msg content is " << content << " msgId is " << msgId << "\n";TextChatData* textMsg = textMsgRequest.add_textmsgs();textMsg->set_msgid(msgId);textMsg->set_msgcontent(content);textMsg->set_msgtype(msgType);
}ChatGrpcClient::GetInstance()->NotifyTextChatMsg(toIpValue, textMsgRequest, jsonReturn);

grpc服务器处理文本请求

跟之前客户端打包Json一样,都是发送者ID + 接收者ID + 文本数组的形式

// 用户在线,发送通知给对方
Json::Value jsonMsg;
jsonMsg["error"] = ErrorCodes::SUCCESS;
jsonMsg["senderUid"] = request->fromuid();
jsonMsg["receiverUid"] = toUid;// 将聊天数据整合成Json嵌套的数据
Json::Value textArray;
for (auto& msg : request->textmsgs())
{Json::Value element;element["content"] = msg.msgcontent();element["msgId"] = msg.msgid();element["type"] = msg.msgtype();textArray.append(element);
}jsonMsg["textArray"] = textArray;string result = jsonMsg.toStyledString();// 向该在线用户发送聊天信息请求
pSession->Send(result, MSG_IDS::ID_NOTIFY_TEXT_CHAT_MSG_REQUSET);

接收者客户端解析服务器发来的Json

将Json解析成自定义的数据结构,并发送出去,交给ChatWidget类去处理

/* 客户端(接受文本)处理服务器发送的文本请求 */
_handlers.insert(ReqID::ID_NOTIFY_TEXT_CHAT_MSG_REQUEST, [this](ReqID id, int len, QByteArray data){QJsonObject jsonObj;bool ret = PraseJsonData(jsonObj, "NotifyChatMsgRequest", id, len, data);if (!ret){return;}qDebug() << QString::fromLocal8Bit("收到文本消息");// 判断消息类型QSharedPointer<TextChatMsg> chatMsg(new TextChatMsg(jsonObj["senderUid"].toInt(), jsonObj["receiverUid"].toInt(),jsonObj["textArray"].toArray()));emit SigTextChatMsg(chatMsg);});

ChatMsg结构体的格式跟打包发送和接受的消息格式一摸一样

发送者ID + 接收者ID + 文本数组

/* 聊天信息(多条信息) */
struct TextChatMsg
{TextChatMsg(int fromuid, int touid, QJsonArray arrays) :_from_uid(fromuid), _to_uid(touid) {for (auto msg_data : arrays) {auto msg_obj = msg_data.toObject();auto content = msg_obj["content"].toString();auto msgid = msg_obj["msgId"].toString();auto type = msg_obj["type"].toString();QSharedPointer<TextChatData> pMsg(new TextChatData(msgid, type, content, fromuid, touid));_chat_msgs.push_back(pMsg);}}int _to_uid;int _from_uid;QVector<QSharedPointer<TextChatData>> _chat_msgs;
};

在ChatWidget类下,先去通过解析的ChatMsg解析获取发送者ID,然后找到这个聊天窗口,并更新聊天信息

void ChatWidget::SlotTextChatMsg(QSharedPointer<TextChatMsg> msg)
{// 查找发送者的聊天项,通过该聊天项找到聊天窗口auto it = _chatItemMap.find(msg->_from_uid);if (it != _chatItemMap.end()){qDebug() << "Set Chat Item Msg, uid is " << msg->_from_uid;ui.chatUserList->insertItem(0, it.value());																// 选中该聊天项ui.chatUserList->setCurrentItem(it.value());															// 选中该聊天项QWidget* widget = ui.chatUserList->itemWidget(it.value());ChatUserWnd* chatUserWnd = qobject_cast<ChatUserWnd*>(widget);if(!chatUserWnd)return;chatUserWnd->UpdateLastMsgs(msg->_chat_msgs);															// 该聊天窗口更新最后一条信息UpdateChatMsg(msg->_chat_msgs);																			// 更新当前聊天界面信息UserMgr::Instance()->AppendFriendChatMsgs(msg->_from_uid, msg->_chat_msgs);								// 存储和当前用户的聊天信息return;}// 如果没有找到,创建新的聊天小窗口ChatUserWnd* chatUserWnd = new ChatUserWnd;																	// 聊天小窗口// 获取好友信息QSharedPointer<FriendInfo> friendInfo = UserMgr::Instance()->GetFriendByUid(msg->_from_uid);chatUserWnd->SetInfo(friendInfo);QListWidgetItem* item = new QListWidgetItem;																// 新建聊天项item->setSizeHint(chatUserWnd->sizeHint());chatUserWnd->UpdateLastMsgs(msg->_chat_msgs);																// 该聊天窗口更新最后一条信息UserMgr::Instance()->AppendFriendChatMsgs(msg->_from_uid, msg->_chat_msgs);									// 存储和当前用户的聊天信息ui.chatUserList->insertItem(0, item);																		// 插入到聊天列表的最前面ui.chatUserList->setItemWidget(item, chatUserWnd);															// 必须添加项的容器,告诉列表如何显示这个容器_chatItemMap[msg->_from_uid] = item;																		// 保存聊天项的映射关系
}

更新聊天信息

整体的逻辑如下,遍历文本数组,然后判断消息是自己发送的还是别人发送的,生成对应的消息气泡,如果是文本就生成文本气泡,如果是图片就生成图片气泡

void ChatWidget::UpdateChatMsg(QVector<QSharedPointer<TextChatData>> chatMsgs)
{for (auto chatMsg : chatMsgs){if (chatMsg->_from_uid != _curChatUid){break;}ui.chatPage->AppendChatMsg(chatMsg);}
}void ChatPage::AppendChatMsg(QSharedPointer<TextChatData> chatMsg)
{auto selfInfo = UserMgr::Instance()->GetUserInfo();														// 获取该客户端的信息ChatRole role;if (chatMsg->_from_uid == selfInfo->_uid){role = ChatRole::Self;ChatItemBase* pChatItem = new ChatItemBase(role);													// 创建自己的消息气泡pChatItem->SetUserName(selfInfo->_name);pChatItem->SetUserIcon(QPixmap(selfInfo->_icon));SpawnBubble(pChatItem, role, chatMsg->_msg_type, chatMsg->_msg_content);							// 创建气泡}else{role = ChatRole::Other;ChatItemBase* pChatItem = new ChatItemBase(role);													// 创建对方的消息气泡QSharedPointer<FriendInfo> otherInfo = UserMgr::Instance()->GetFriendByUid(chatMsg->_from_uid);if (otherInfo == nullptr){qDebug() << "Can't find friend info by uid:" << chatMsg->_from_uid;return;}pChatItem->SetUserName(otherInfo->_name);pChatItem->SetUserIcon(QPixmap(otherInfo->_icon));SpawnBubble(pChatItem, role, chatMsg->_msg_type, chatMsg->_msg_content);							// 创建气泡}
}void ChatPage::SpawnBubble(ChatItemBase* pChatItem, ChatRole role, QString msgType, QString msgContent)
{QWidget* pBubble = nullptr;qDebug() << "msg Type:" << msgType;if (msgType == MSG_TYPE_TEXT){pBubble = new TextBubble(role, msgContent);														// 创建文本气泡pChatItem->SetWidget(pBubble);																	// 插入消息内容ui.chatView->AppendChatItem(pChatItem);															// 添加到聊天视图中}else if (msgType == MSG_TYPE_IMAGE){QByteArray base64String = msgContent.toUtf8();													// 先转为 UTF-8 QByteArrayQByteArray imgData = QByteArray::fromBase64(base64String);										// 这才是真正的图片二进制数据QImage img;img.loadFromData(imgData);QPixmap pixmap = QPixmap::fromImage(img);pBubble = new PictureBubble(role, pixmap, imgData);												// 创建图片气泡pChatItem->SetWidget(pBubble);																	// 插入消息内容ui.chatView->AppendChatItem(pChatItem);															// 添加到聊天视图中}
}

2. Navicat 设置唯一键

右键设计表

点击索引

先选中字段,将字段下的申请者UID和授权者UID选中,设置索引类型为唯一,即可

http://www.lryc.cn/news/626126.html

相关文章:

  • 广东省省考备考(第八十一天8.19)——资料分析、数量(强化训练)
  • 第5.5节:awk算术运算
  • 基于深度学习的森林火灾图像识别实战
  • 【撸靶笔记】第七关:GET - Dump into outfile - String
  • 浙江电信IPTV天邑TY1613_高安版_晶晨S905L3SB_安卓9_原厂固件自改_线刷包
  • Linux中Docker k8s介绍以及应用
  • windows电脑对于dell(戴尔)台式的安装,与创建索引盘,系统迁移到新硬盘
  • 微信小程序连接到阿里云物联网平台
  • 高等数学 8.6 空间曲线及其方程
  • 添加右键菜单项以管理员权限打开 CMD
  • DNS有关知识(根域名服务器、顶级域名服务器、权威域名服务器)
  • 【C语言16天强化训练】从基础入门到进阶:Day 3
  • Vue 2 项目中快速集成 Jest 单元测试(超详细教程)
  • 【矢量数据】1:250w中国地质图地断层数据/岩性shp数据
  • EPM240T100I5N Altera FPGA MAX II CPLD
  • 无人机/航测/三维建模领域常见的“航线规划或建模方式
  • Everything 搜索工具下载安装使用教程(附安装包)Everything
  • 在 Python 中操作 Excel 文件的高效方案 —— Aspose.Cells for Python
  • mycat分库分表实验
  • [激光原理与应用-302]:光学设计 - 光学设计的流程、过程、方法、工具
  • mlir replace
  • C#传参调用外部exe
  • 线段树结合矩阵乘法优化动态规划
  • 福彩双色球第2025095期综合分析
  • C++排序算法学习笔记
  • AC 内容审计技术
  • 智慧水务流量在线监测系统解决方案
  • 项目过程管理的重点是什么
  • linux控制其他程序使用cpu低于50%——笔记
  • LangChain RAG 简述