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

Chap18-AddFriend

Chap18-AddFriend

这一节我们完成好友申请和通知的功能(发送好友申请 ,收到好友申请通知)。

为了清晰的分辨我们直接分为前端和后端的修改,而非穿插进行。

前端

添加好友请求

正如上节展示的那样,我们将每个查询到的用户数据都使用一个自定的FriendItem展示出来,在每个FriendItem插入到ListWidget的时候,我们都connect了这个添加按钮的信号和槽函数。当点击按钮,触发on_send_data信号,发送的内容如下

connect(_applyFriend,&QPushButton::clicked,this,[this](bool){QJsonObject obj;obj["fromUid"] = static_cast<int>(UserManager::GetInstance()->GetUid());obj["fromName"] = UserManager::GetInstance()->GetName();obj["fromEmail"] = UserManager::GetInstance()->GetEmail();obj["fromDesc"] = UserManager::GetInstance()->GetDesc();obj["fromSex"] = UserManager::GetInstance()->GetSex();obj["fromIcon"] = UserManager::GetInstance()->GetIcon();obj["toUid"] = this->_uid; // 对方的uidqDebug() << "fromUid" << obj["fromUid"] << "\t" << "toUid" << this->_uid;QJsonDocument doc;doc.setObject(obj);QByteArray data = doc.toJson(QJsonDocument::Compact);emit TcpManager::GetInstance()->on_send_data(RequestType::ID_ADD_FRIEND_REQ,data);this->_applyFriend->setEnabled(false);showToolTip(_applyFriend,"已发送好友请求");});

这时候我们就可以等待对端回包,当服务器将包回复过来之后,我们在TcpManager处理:

/*** @brief 用户添加请求回包处理*/_handlers[RequestType::ID_ADD_FRIEND_RSP] = [this](RequestType requestType,int len,QByteArray data){QJsonDocument jsonDoc = QJsonDocument::fromJson(data);if (jsonDoc.isNull()){qDebug() << "Error occured about Json";return;}QJsonObject jsonObj = jsonDoc.object();if (!jsonObj.contains("error")){int err = static_cast<int>(ErrorCodes::ERROR_JSON);qDebug() << "AddFriend Failed,Error Is Json Parse Error " <<err;return;}int err = jsonObj["error"].toInt();if (err != static_cast<int>(ErrorCodes::SUCCESS)){qDebug() << "AddFriend Failed,Error Is " << err;return;}UserInfo info;info.id = jsonObj["fromUid"].toInt();info.email = jsonObj["fromEmail"].toString();info.name = jsonObj["fromName"].toString();info.status = jsonObj["fromStatus"].toString();// TODO:qDebug() << "申请添加好友成功";emit on_add_friend(info);};

在这里我们触发了on_add_friend信号,我们接下来的思路是实现一个侧边滑动的专门用于查看好友通知系统通知的通知栏,我们将在通知栏中处理这个信号加入到通知栏的ListWidget中展示。

用户添加好友请求

当服务器发送给用户好友请求的时候,我们仍然需要在TcpManager里面进行处理:

/*** @brief 用户请求添加好友通知处理*/_handlers[RequestType::ID_NOTIFY_ADD_FRIEND_REQ] = [this](RequestType requestType,int len,QByteArray data){QJsonDocument jsonDoc = QJsonDocument::fromJson(data);if (jsonDoc.isNull()){return;}QJsonObject jsonObj = jsonDoc.object();if (!jsonObj.contains("error")){int err = static_cast<int>(ErrorCodes::ERROR_JSON);return;}int err = jsonObj["error"].toInt();if (err != static_cast<int>(ErrorCodes::SUCCESS)){return;}int from_uid = jsonObj["from_uid"].toInt();int from_sex = jsonObj["sex"].toInt();QString from_name = jsonObj["from_name"].toString();QString from_icon = jsonObj["from_icon"].toString();QString from_desc = jsonObj["from_desc"].toString();auto user_info = std::make_shared<UserInfo>();user_info->id = from_uid;user_info->sex = from_sex;user_info->name = from_name;user_info->avatar = from_icon;user_info->desc = from_desc;qDebug() << "收到好友请求";emit on_auth_friend(user_info);};

当服务器传回其他用户的好友通知时候,我们发送on_auth_friend信号,同样要在通知栏处理。

后端(ChatServer)

我们主要在LogicSystem对客户端发来的请求进行处理,对于好友请求申请我们如下处理:

 /** * @brief 好友申请请求*/_function_callbacks[MsgId::ID_ADD_FRIEND_REQ] = [this](std::shared_ptr<Session> session, uint16_t msg_id, const std::string& msg) {json j = json::parse(msg);j["error"] = ErrorCodes::SUCCESS;Defer defer([this, &j, session]() {// 回复请求方的信息session->Send(j.dump(), static_cast<int>(MsgId::ID_ADD_FRIEND_RSP));});auto toUid = j["toUid"].get<int>();auto fromUid = j["fromUid"].get<int>();auto fromName = j["fromName"].get<std::string>();auto fromSex = j["fromSex"].get<int>();auto fromDesc = j["fromDesc"].get<std::string>();// auto fromIcon = j["fromIcon"].get<std::string>();auto fromIcon = j.value("fromIcon", "");std::string uid_str = std::to_string(toUid);bool b_apply = MysqlManager::GetInstance()->AddFriendApply(std::to_string(fromUid), uid_str);if (!b_apply) {return;}auto to_key = USERIP_PREFIX + uid_str;std::string to_ip_value;bool b_ip = RedisManager::GetInstance()->Get(to_key, to_ip_value);if (!b_ip) {return;}// 是对方发送请求信息auto& cfg = ConfigManager::GetInstance();auto self_name = cfg["SelfServer"]["name"];if (to_ip_value == self_name) {auto session2 = UserManager::GetInstance()->GetSession(toUid);if (session2) {SPDLOG_INFO("FROM UID:{},to:{}", fromUid, toUid);SPDLOG_INFO("FROM SESSION:{},to:{}", session->GetSessionId(), session2->GetSessionId());json jj;jj["error"] = ErrorCodes::SUCCESS;jj["fromUid"] = fromUid;jj["fromName"] = fromName;session2->Send(jj.dump(), static_cast<int>(MsgId::ID_NOTIFY_ADD_FRIEND_REQ));}return;}// std::string base_key = USER_BASE_INFO_PREFIX + toUid;// auto apply_info = std::make_shared<UserInfo>();// bool b_info = GetBaseInfo(base_key, std::stoi(toUid),apply_info);AddFriendRequest req;req.set_fromuid(fromUid);req.set_touid(toUid);req.set_name(fromName);req.set_desc(fromDesc);req.set_sex(fromSex);req.set_icon(fromIcon);ChatGrpcClient::GetInstance()->NotifyAddFriend(to_ip_value, req);};

首先将申请好友信息加入mysql防止信息丢失(以后会拓展为离线不发送,上线再发送,不会丢失)。然后查询被申请用户的服务器名称,如果没有查询到,说明没有在线,我们直接return.如果查询到之后,我们需要检查被申请用户是否连接当前服务器,如果连接了当前服务器,我们直接根据uid查找session然后发送信息。如果连接的是其他服务器,我们就通过grpc服务器将信息发送给其他服务器转发给被申请人。

我们先看mysql(Dao)的AddFriendApply函数:

bool MysqlDao::AddFriendApply(const std::string& fromUid, const std::string& toUid)
{auto conn = _pool->GetConnection();if (!conn) {SPDLOG_ERROR("Failed to get connection from pool");return false;}Defer defer([this, &conn]() {_pool->ReturnConnection(std::move(conn));});try {mysqlpp::Query query = conn->query();query << "Insert into friend_apply (from_uid,to_uid) values(%0,%1) "<< "on duplicate key update from_uid = from_uid,to_uid=to_uid";query.parse();mysqlpp::SimpleResult res = query.execute(std::stoi(fromUid), std::stoi(toUid));int rowCount = res.rows();return rowCount >= 0;} catch (const mysqlpp::Exception& e) {SPDLOG_ERROR("MySQL++ exception: {}", e.what());return false;}return true;
}

接下来是grpc请求NotifyAddFriend(客户端)

AddFriendResponse ChatGrpcClient::NotifyAddFriend(std::string server_ip, const AddFriendRequest& req)
{SPDLOG_INFO("发送好友请求to:{}", req.touid());SPDLOG_INFO("目标服务名称:{}", server_ip);AddFriendResponse rsp;Defer defer([&rsp, &req]() {rsp.set_error(static_cast<int>(ErrorCodes::SUCCESS));rsp.set_fromuid(req.fromuid());rsp.set_touid(req.touid());});auto it = _pool.find(server_ip);if (it == _pool.end()) {return rsp;}auto& pool = it->second;SPDLOG_INFO("服务端ip,{}:{}", _pool[server_ip]->_host, _pool[server_ip]->_port);grpc::ClientContext context;auto stub = pool->GetConnection();Defer defer2([&pool, &stub]() {pool->ReturnConnection(std::move(stub));});Status status = stub->NotifyAddFriend(&context, req, &rsp);if (!status.ok()) {rsp.set_error(static_cast<int>(ErrorCodes::RPCFAILED));return rsp;}return rsp;
}

然后是grpc请求NotifyAddFriend(服务端):

Status ChatGrpcServer::NotifyAddFriend(grpc::ServerContext* context, const AddFriendRequest* request, AddFriendResponse* response)
{SPDLOG_INFO("Add Friend Request From {}", request->fromuid());// 首先在本服务器查询auto to_uid = request->touid();auto session = UserManager::GetInstance()->GetSession(to_uid);Defer defer([request, response]() {response->set_error(static_cast<int>(ErrorCodes::SUCCESS));response->set_fromuid(request->fromuid());response->set_touid(request->touid());});// 不在内存中if (session == nullptr) {return Status::OK;}// 在内存中z直接发送通知json j;j["error"] = ErrorCodes::SUCCESS;j["from_uid"] = request->fromuid();j["name"] = request->name();j["icon"] = request->icon();j["sex"] = request->sex();j["desc"] = request->desc();session->Send(j.dump(), static_cast<int>(MsgId::ID_NOTIFY_ADD_FRIEND_REQ));return Status::OK;
}

联调测试

image-20251112200057087

我们登陆两个客户端,uid分别为0和1,分别进入服务器1和服务器2。

我们从uid为1的客户端搜索uid为0的用户,然后点击添加好友发送请求,我们可以看到服务器的信息。

image-20251112200128521

确实从服务器发出了请求,再看另一服务器的grpc服务端的输出:

image-20251112200451561

也收到了grpc请求。

在看客户端:

image-20251112200526918发出了请求和image-20251112200540596收到了请求。

http://www.jsqmd.com/news/135684/

相关文章:

  • 个人理财收支记账系统 家庭理财系统APP_vj9n8--小程序论文
  • Chap23-Heartbeat
  • Java毕设项目:基于JavaWeb的点餐系统的设计与实现(源码+文档,讲解、调试运行,定制等)
  • 路由策略和策略路由区别是什么
  • 深入剖析WordPress插件漏洞:未授权攻击的成功之道
  • Java毕设项目:基于SpringBoot+Vue的高校志愿活动管理系统的设计与实现(源码+文档,讲解、调试运行,定制等)
  • Chap24-Reconnect-LockPrecisionOptimization-AvatarEditBox
  • 如果计算引擎是MapReduce,那么Hive能跑Spark SQL作业吗?
  • GIF压缩策略优化:从激进到智能的演进之路
  • 告别复杂笔记软件!Memos+cpolar,让你的笔记随时随地可用
  • Asio18-Coroutine
  • 一个 .NET 开源免费、功能强大的 UI 自动化库
  • Chap20-Communication
  • 03. 图像的基本处理
  • Asio16-MultiThreadServicesPool
  • Chap17-SearchUsers
  • 2025继续教育必备8个降AI率工具测评榜单
  • Asio17-MultiThreadPool
  • 【大模型技术研究】SGLang入门指南:高效大模型推理与编程的利器(附实战代码)
  • 一个使用 WPF 开发的 Diagram 画板工具(包含流程图FlowChart,思维导图MindEditor)
  • 领导根本不关心你干了多少活,只在意这3点
  • 70
  • Asio12-HandlePacketStickingProblemSimply
  • 第三章 SQL Server函数
  • Chap22-DistributedLock_MultiServer
  • Asio09-SendQueueAndEndian
  • 第四章 SQL Server备份和还原
  • 5分钟使用modelengine打造儿童数字人,小白也能快速上手以低代码的方式快速搭建智能应用,从而大幅降低开发难度
  • 基于springboot在线课程管理系统的设计与实现毕业论文+PPT(附源代码+演示视频)
  • LLM - 用 SpecKit 和 AICode 改造遗留系统 完整实践指南