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; // 对方的uid
qDebug() << "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;
}
联调测试

我们登陆两个客户端,uid分别为0和1,分别进入服务器1和服务器2。
我们从uid为1的客户端搜索uid为0的用户,然后点击添加好友发送请求,我们可以看到服务器的信息。

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

也收到了grpc请求。
在看客户端:
发出了请求和
收到了请求。

浙公网安备 33010602011771号