用户信息界面删除好友功能
前言
在上一集我们就完成了三大按钮的禁用逻辑的代码编写,以及复用了我们之前点击好友列表项跳转对应会话项功能,我们就能够点击用户详细消息窗口的发送消息按钮进行跳转对应的会话进行消息发送的功能!那么这一集我们就来实现一下删除好友的功能!
需求分析
我们选择一个好友进行点击删除好友的功能,我们点击之后,对应的聊天会话应该在会话列表中删除,我们这里的逻辑是有好友才会有对应的会话!
如果是我们删除的好友和我们正在访问的这个会话是同一个,那么我们就应该清空我们的右窗口的所有内容!如下:
当然我们删除的好友也应该在好友列表里面清除!
由于我们每次点击我们的用户头像进入到用户详细消息的窗口中都是需要重新初始化一遍,所以我们在里面就会重新判断该用户是否为我们的好友,所以对应的禁用关系不会乱套,而且我们删除好友之后会立即关闭我们的窗口,保证不会出岔子!
当然这只是讨论了我们自己对别的用户的删除好友操作,我们也有被删除的可能,所以我们也要去完成被删除的相关操作,这个操作是必须我们的服务器主动推送的功能,所以我们要借助websocket来完成!
当然我们这里的删除好友功能,我们要做成双向删除,并不会单向进行删除。
这里我们还是像之前推送消息一样,在测试服务端那边弄一个按钮进行操作即可。
我们老规矩先来看一下我们要用到的URL和http请求和响应以及我们websocket要用到的定义!
这个就是我们http网络通信的URL
这个就是http的请求和响应的定义。我们等下作为删除方就需要使用到这个请求和响应!
这就是作为被删除方那边要接受的内容,我们要用websocket服务端主动推送这些内容!
删除方角色
客户端
删除方其实还是比较常规的操作,我们的第一步还是在用户消息窗口那边给我们的删除好友按钮连接一个信号槽,每当点击,就会触发一个函数!
cpp
//删除好友
connect(deleteFriendBtn, &QPushButton::clicked, this, &UserInfoWidget::clickDeleteFriendBtn);
我们触发了这个函数,这个函数其实就是要先弹出一个对话框,这个对话框是为了确认我们是否真的要删除好友?这个操作很有必要,不然误触了都没地说理去!
我们只有点击了确认删除之后才可以进行删除,也会开始通过网络通信!
当然我们点击了删除之后一定要关闭这个窗口,以出现一些不必要的bug!
cpp
void UserInfoWidget::clickDeleteFriendBtn()
{
//弹出对话框
auto result = QMessageBox::warning(this, "确认删除", "确认删除当前好友?", QMessageBox::Ok | QMessageBox::Cancel);
if(result != QMessageBox::Ok){
LOG() << "删除好友取消";
return;
}
//点击了Ok
//发送网络请求删除好友
DataCenter* dataCenter = DataCenter::getInstance();
dataCenter->deleteFriendAsync(userInfo.userId);
//关闭窗口
this->close();
}
我们删除指定的好友,应该把这个窗口的好友的信息的用户id传递过去,当然还需要通过DataCenter进行传递loginSessionId!
cpp
void DataCenter::deleteFriendAsync(const QString &userId)
{
netClient.deleteFriend(loginSessionId, userId);
}
这里我们就可以把需要的参数传递给我们的NetClient当中,我们就可以通过NetClient构造http的请求,发送请求以及处理响应!
cpp
void NetClient::deleteFriend(const QString &loginSessionId, const QString &userId)
{
//构造http请求body
bite_im::FriendRemoveReq pbReq;
pbReq.setRequestId(makeRequestId());
pbReq.setSessionId(loginSessionId);
pbReq.setPeerId(userId);
QByteArray body = pbReq.serialize(&serializer);
LOG() << "[删除好友] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << pbReq.sessionId()
<<", peerId=" << pbReq.peerId();
//发送http请求
QNetworkReply* resp = this->sendHttpRequest("/service/friend/remove_friend", body);
//处理响应
connect(resp, &QNetworkReply::finished, this, [=](){
bool ok = false;
QString reason;
auto pbResp = this->handleHttpResponse<bite_im::FriendRemoveRsp>(resp, &ok, &reason);
//判定是否出错
if(!ok){
LOG() << "[删除好友]失败!reason=" << reason;
return;
}
//设置数据到DataCenter,从好友列表中删除该用户
dataCenter->removeFriend(userId);
//发送信号,通知调用
emit dataCenter->deleteFriendDone();
LOG() << "[删除好友] 处理响应完毕! requestId=" << pbResp->requestId();
});
}
我们这里的构造请求和发送http的请求还有处理响应的一些内容都是常规的操作!
当我们响应没有任何出错,我们就可以从我们的DataCenter里的friendList里面删除对应的用户了!
我们这个删除操作也应该慎重考虑!
我们不仅仅要去friendList进行删除指定的用户!我们应该把对应的会话也要删除才行,因为我们的好友和会话是绑定在一起的!没有对应的好友就不能有对应的会话!所以我们就要去遍历我们的会话列表!
当然!我们上面需求分析的时候也提到了一个特殊的情景,就是我们选中的会话刚好是我们的这个好友所绑定的会话,我们应该清空我们的右侧区域的相关内容!
所以我们不仅仅要去判断这个会话是否跟我们删除的用户是否有关系,我们只需要对比会话信息里面的userId和传输过来的userId即可!
这里还有一个特殊情况,就是我们群聊是一定要保留的!当然我们也很好判断,因为群聊我们之前设置的是userId为nullptr!
所以我们的代码如下!
cpp
void DataCenter::removeFriend(const QString &userId)
{
//遍历friendList
if(friendList == nullptr || chatSessionList == nullptr){
return;
}
friendList->removeIf([=](const UserInfo& userInfo){
return userInfo.userId == userId; //true就删除
});
//会话列表也要删除
chatSessionList->removeIf([=](const ChatSessionInfo& chatSessionInfo){
if(chatSessionInfo.userId == nullptr){
//群聊不受影响
return false;
}
if(chatSessionInfo.userId == userId){
//如果删除的会话刚好是用户选中的会话!
//清空!!!
if(chatSessionInfo.chatSessionId == this->currentChatSessionId){
emit this->clearCurrentSession();
}
return true;
}
return false;
});
}
当我们要清空这个界面的时候就发送一个清空当前会话的信号!
由于我们要清空我们的右侧区域,所以我们应该到主界面进行清空!
所以我们就要在主界面的代码当中连接我们的信号槽!
我们要清空的第一个就是我们的会话标题!之后就是消息展示区清空,最后也是比较重要的一点,我们要重置我们的DataCenter里面的当前会话id!
cpp
//删除当前会话(删除好友)
connect(dataCenter, &DataCenter::clearCurrentSession, this, [=](){
sessionTitleLabel->setText("");
messageShowArea->clear();
dataCenter->setCurrentChatSessionId("");
LOG() << "清空当前会话";
});
那么这个特殊情况我们就完成了!
那么我们这个时候就会回到我们处理响应中的发送信号的部分,我们发送信号告知我们的主界面,我们可以更新我们的会话列表以及好友列表了!
当然这些更新列表内容的功能,我们在之前就已经封装完成了,我们会清空所有列表的元素,重新遍历我们的DataCenter里面的数据进行界面的渲染,当然我们更新的内容的标签和当前标签不符,我们就不会更新界面,但是我们的数据是存放到了DataCenter里面的,我们再次去到对应的标签页的时候,我们更新的内容是会呈现出来的!
cpp
//处理删除好友
connect(dataCenter, &DataCenter::deleteFriendDone, this, [=](){
//更新会话和好友列表
this->updateFriendList();
this->updateChatSessionList();
LOG() << "删除好友完成";
});
这就是删除方角色的客户端的内容!
测试服务端
我们还是两步走!
第一步,配置路由!
cpp
httpServer.route("/service/friend/remove_friend", [=](const QHttpServerRequest& req){
return this->removeFriend(req);
});
第二步,构造响应并发送响应到客户端!
cpp
QHttpServerResponse HttpServer::removeFriend(const QHttpServerRequest &req)
{
//解析请求
bite_im::FriendRemoveReq pbReq;
pbReq.deserialize(&serializer, req.body());
LOG() << "[REQ 删除好友] requestId=" << pbReq.requestId() << ", loginSessionId=" << pbReq.sessionId()
<<", peerId=" << pbReq.peerId();
//构造响应
bite_im::FriendRemoveRsp pbResp;
pbResp.setRequestId(pbReq.requestId());
pbResp.setSuccess(true);
pbResp.setErrmsg("");
QByteArray body = pbResp.serialize(&serializer);
//构造http响应
QHttpServerResponse resp(body, QHttpServerResponse::StatusCode::Ok);
resp.setHeader("Content-Type", "application/x-protobuf");
return resp;
}
这样,我们的删除方的内容就完成了!还是十分的简单的。
被删除方角色
测试服务端
那么就到了我们被删除方的角色,我们就需要编写websocket的内容,那么我们还得借助一下测试服务端那边的界面新增一个按钮来完成我们的内容!
我们还是直接在UI文件当中直接拖一个按钮组件过去即可,之后连接一下信号槽!信号是clicked即可。
当然我们要获取到socket才能主动推送消息给我们的客户端,所以我们要先获取websocket的实例,之后将我们的信号槽放置在连接websocket的信号槽底下,因为在这个里面我们才能捕获到我们的socket对象!和我们之前发送文本消息的做法是一样的!
cpp
void Widget::on_pushButton_2_clicked()
{
WebsocketServer* websocketServer = WebsocketServer::getInstance();
emit websocketServer->sendFriendRemove();
}
那么我们发送的信号就应该去到对应的信号槽!这里面我们就要去构造websocket的内容!
cpp
//发送好友删除
connect(this, &WebsocketServer::sendFriendRemove, this, [=](){
if(socket == nullptr || !socket->isValid()){
LOG() << "socket对象无效!";
return;
}
bite_im::NotifyMessage notifyMessage;
notifyMessage.setNotifyEventId("");
notifyMessage.setNotifyType(bite_im::NotifyTypeGadget::NotifyType::FRIEND_REMOVE_NOTIFY);
bite_im::NotifyFriendRemove notifyFriendRemove;
notifyFriendRemove.setUserId("9528");
notifyMessage.setFriendRemove(notifyFriendRemove);
QByteArray body = notifyMessage.serialize(&serializer);
socket->sendBinaryMessage(body);
LOG() << "通知对方好友被删除 userId=9528";
});
});
记住,我们构造的这个内容一定是通过二进制发送!
当然和发送文本消息一致,当我们的websocket的连接断开之后就应该主动断开,而不应该还继续提供服务!所以我们要在断开连接的信号槽底下添加一个主动断开连接该信号槽的内容。
cpp
connect(socket, &QWebSocket::disconnected, this, [=](){
qDebug() << "[websocket] 连接断开!";
disconnect(this, &WebsocketServer::sendTextResp, this, nullptr);
disconnect(this, &WebsocketServer::sendFriendRemove, this, nullptr);
});
此时测试服务端这边我们是完成了的!
客户端
我们这边就要去到NetClient那边去接收我们获取的WS内容!
cpp
connect(&webSocketClient, &QWebSocket::binaryMessageReceived, this, [=](const QByteArray& byteArray){
LOG() <<"websocket 收到二进制消息! length:" << byteArray.length();
bite_im::NotifyMessage notifyMessage;
notifyMessage.deserialize(&serializer, byteArray);
handleWsResponse(notifyMessage);
});
我们这里就会直接获取到WS的内容,当然我们反序列化后就可以得到我们要得到的内容了!之后就交给我们封装的处理Ws响应的函数进行进一步处理!
到这个函数当中,我们做的主要任务其实就是通过不同的通知类型进行不同的处理以及将最关键的数据传给更进一步的处理,我们更进一步的处理其实也是封装一个函数来处理。
cpp
else if(notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::FRIEND_REMOVE_NOTIFY){
//删除好友通知
const QString& userId = notifyMessage.friendRemove().userId();
handleWsRemoveFriend(userId);
}
我们这里最关键的数据其实就是userId!那么我们只需要传入userId即可!
我们就可以去到DataCenter中删除我们好友列表的指定userId的好友信息即可!
之后要通知我们更新好友/会话列表!这个功能其实我们在上面就已经构造完成了。
cpp
void NetClient::handleWsRemoveFriend(const QString &userId)
{
//删除DataCenter的好友列表数据
dataCenter->removeFriend(userId);
//通知界面变化 更新好友/会话列表
emit dataCenter->deleteFriendDone();
}
那么我们点击了这个按钮就能够实现删除一个好友了!当然由于是测试服务器,我们还没有连接正式的服务器,我们把内容是写死了的,我们这里就删除了指定的9528的好友。
可以看到我们的好友列表里面就没有对应的好友了!那么这一集我们就完成了用户信息界面删除好友功能!那么这一集就先到这里!