本文未经授权,禁止转载
Day4、Day5回顾
已完成的内容
(1)查找用户,显示在线用户
(2)对处理消息函数的封装
Day6要完成的内容
(1)实现添加好友功能。在查找某个用户得到结果时,如果当前用户在线,就提示是否要添加好友。或者是在显示在线好友的列表中,双击用户名进行添加好友。
(2)刷新好友列表功能。在添加好友后,好友界面应该显示该用户所有好友的用户名。且在点击刷新好友之后,可以更新好友列表。
(3)删除好友功能。在好友界面,选择好友,点击删除好友按钮,即可删除该好友。
一、添加好友
添加好友功能展示
1.1 添加好友的流程
下面我们来分析一下,添加好友具体有那些流程?
到现在实现的功能,只有两处可以触发添加好友的功能
(1)在查找某个用户得到结果时,如果当前用户在线,就提示是否要添加好友。如果确认要添加好友,就需要发送添加好友的请求。
(2)在显示在线好友的列表中,双击用户名进行添加好友。
而添加用户,需要涉及到两个客户端,一个是当前客户端(Cur),需要发送添加好友的请求;一个是目标客户端(Tar),用来接收Cur发来的添加好友请求,并决定是否同意添加。
- Cur当前客户端触发添加好友,发送添加好友请求(将当前用户名和目标用户名发送给服务器)
- 当前客户端触发添加好友条件,获取目标用户的用户名
- 查找用户时,触发添加好友条件,获取查找的用户名
- 在线用户列表,触发添加好友条件,获取列表框中的目标用户名
- 得到成员变量,当前登录的用户名
- 构建自定义消息结构体对象pdu(使用固定长度的数组(caData[64]))因此在初始化PDU时,传入0即可。
- 在PDU协议中的添加对应功能的请求和响应消息类型
- 在消息类型的枚举值中,增加添加好友的请求和响应
- 在pdu填入消息类型和要发送的数据(当前用户名和目标用户名)
- 发送 pdu到服务器
- 当前客户端触发添加好友条件,获取目标用户的用户名
- 服务器接收Cur客户端的添加好友请求,判断是否满足条件,转发请求给Tar客户端
- 需要数据库操作,在数据库操作类中,增加一个处理添加好友的函数(判断是否满足添加好友条件)
- 判断目标用户是否在线,根据发来的目标用户名在数据库中查看其online字段。如目标用户已经下线,则返回已下线。
- 判断当前用户与目标用户是否已经是好友,如果已经是好友,则返回已经是好友
- 如果目标用户在线,同时当前用户与目标用户不是好友,则返回真值。
- 在服务器的请求处理器类中,增加添加好友的函数(判断是否转发),处理消息类型为添加好友请求的情况。
- 在MyTcpServer类中,添加一个转发的函数(接收目标用户名以及要转发的pdu)
- 目的是为了将消息转发给目标用户
- 取出 pdu 中的数据,将取出的数据传给处理数据库操作类中添加好友的函数(判断是否需要转发)的函数。并得到该函数的返回值。
- 根据返回结果,决定是否要转发消息
- 不转发,直接将返回值发回给Cur客户端。
- 需要转发,则调用转发函数,将pdu发送给目标用户Tar
- 需要数据库操作,在数据库操作类中,增加一个处理添加好友的函数(判断是否满足添加好友条件)
- Tar目标客户端接收添加好友请求,并作出决定
- Tar目标客户端,在响应处理器类中,定义处理添加好友请求的函数
- 读出Cur客户端的用户名
- 在函数中,弹出询问框,询问是否要添加 Cur客户端用户为好友(同意或者不同意)
- 在处理消息的函数中,根据消息类型为添加好友请求,调用处理添加好友请求的函数
- 构建响应pdu,消息类型为添加好友的响应
- 根据Tar客户端的选择,向服务器返回不同的内容。(同意或不同意)
- Tar目标客户端,在响应处理器类中,定义处理添加好友请求的函数
- 服务器接收Tar客户端,根据Tar响应进行处理,转发响应给Cur客户端
- 需要数据库操作,在数据库操作类中,增加一个处理添加好友响应的函数(在数据库的好友表中,插入一条数据)
- 在数据库用户信息表(user_info)表中,根据Cur客户端与Tar客户端的用户名,得到用户id
- 根据两个用户id,在friend表中,插入一条新的数据
- 返回是否插入成功
- 在服务器的请求处理器类中,增加添加好友响应的函数(判断Tar用户是否同意添加好友),处理消息类型为添加好友响应的情况。
- 根据Tar客户都的返回值(是否同意添加好友)来决定如何操作
- 同意添加好友:调用数据库操作类中,调用处理同意添加好友的函数,得到返回值(是否成功插入数据)
- 不同意添加好友:直接将Tar客户端的响应消息通过转发函数转发给Cur客户端,消息类型为添加好友的响应。
- 根据Tar客户都的返回值(是否同意添加好友)来决定如何操作
- 同意添加好友的情况
- 调用数据库操作类中,处理同意添加好友的函数,得到是否插入成功的返回值
- 将返回值分别转发给Cur客户端和Tar客户端,消息类型为添加好友的响应。
- 在Tar客户端中,应该将Cur和Tar两个客户端的用户名调换位置
- 需要数据库操作,在数据库操作类中,增加一个处理添加好友响应的函数(在数据库的好友表中,插入一条数据)
- Cur客户端 和 Tar客户端 接收添加好友响应,并对结果进行显示
- 在客户端,在响应处理器类中,定义处理添加好友响应的函数
- 读出Tar客户端的用户名,已经返回的结果
- 根据返回的结果,进行提示
- 返回值 -2:双方已经是好友
- 返回值 -1:添加过程出现错误
- 返回值 0:目标用户已经下线
- 返回值 1:添加好友成功
- 返回值 2:目标用户,拒绝添加好友请求
- 在客户端,在响应处理器类中,定义处理添加好友响应的函数
流程较长,画个图看一下,不专业(勿喷)
1.2 Cur客户端发送添加好友请求
- Cur当前客户端触发添加好友,发送添加好友请求(将当前用户名和目标用户名发送给服务器)
- 当前客户端触发添加好友条件,获取目标用户的用户名
- 查找用户时,触发添加好友条件,获取查找的用户名
- 在线用户列表,触发添加好友条件,获取列表框中的目标用户名
- 得到成员变量,当前登录的用户名
- 构建自定义消息结构体对象pdu(使用固定长度的数组(caData[64]))因此在初始化PDU时,传入0即可。
- 在PDU协议中的添加对应功能的请求和响应消息类型
- 在消息类型的枚举值中,增加添加好友的请求和响应
- 在pdu填入消息类型和要发送的数据(当前用户名和目标用户名)
- 发送 pdu到服务器
- 当前客户端触发添加好友条件,获取目标用户的用户名
1.2.1 在线用户列表的双击槽函数中,发送添加好友的请求
用户在点击在线用户后,列表框中会显示当前所有在线的用户,双击某个现在用户,触发添加好友。
在在线好友的界面(onlineUser.ui),选择列表框(QlistWidget)的双击槽函数itemDoubleClicked(),在该函数内,发送添加好友的请求。
1、双击在线的用户后,,获取双击的用户名,并弹出一个询问框,再次确认是否添加该用户为好友。
// 获取目标客户端的用户名
QString strTarName = item->text();
// 添加一个询问框,添加时进行确认
int ret = QMessageBox::question(this,"添加好友",QString("是否添加用户: %1 为好友?").arg(strTarName));
2、如果用户选择添加好友,则发送添加好友请求。用户选择取消,直接返回
if(ret == QMessageBox::Yes)
{
// 发送添加好友请求的代码
}
else
{
// 不添加好友,直接返回
return;
}
3、在确认添加好友的判断中,获取当前客户端的用户名,构建pdu,并将添加好友的请求发送给服务器
if(ret == QMessageBox::Yes)
{
// 要添加好友,得到当前客户端的用户名
QString strCurName = Client::getInstance().getLoginName();
// 初始化pdu
PDU* pdu = initPDU(0);
// 添加消息类型为 添加好友请求
pdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_REQUEST;
// 将当前用户名和目标用户名存放到pdu中
memcpy(pdu->caData,strCurName.toStdString().c_str(),32);
memcpy(pdu->caData+32,strTarName.toStdString().c_str(),32);
// 发送消息
Client::getInstance().sendPDU(pdu);
}
else
{
// 不添加好友,直接返回
return;
}
1.2.2 查找用户成功后,发送添加好友请求
我们先查看一下,查找好友的响应函数的结构
// 查找用户响应
void ResHandler::findUser()
{
// 将消息取出
char caName[32] = {'\0'};
int ret;
memcpy(&ret,m_pdu->caData,sizeof(int));
memcpy(caName,m_pdu->caData+32,32);
// 根据返回的响应进行处理
if(ret == -1)
{
QMessageBox::information(&Index::getInstance(),"查找用户",QString("用户 %1 不存在").arg(caName));
return;
}
else if(ret == 0)
{
QMessageBox::information(&Index::getInstance(),"查找用户",QString("用户 %1 不在线").arg(caName));
return;
}
else if(ret == 1)
{
int res = QMessageBox::information(&Index::getInstance(),"查找用户",QString("用户 %1 在线").arg(caName),"添加好友","取消");
}
else
{
QMessageBox::critical(&Index::getInstance(),"查找用户",QString("查找用户 %1 失败,内部错误").arg(caName));
return;
}
}
在查找用户的响应中,如果查询到当前用户在线,则触发添加好友的提示,询问是否要添加该用户为好友。
int res = QMessageBox::question(&Index::getInstance(),"查找用户",QString("用户 %1 在线,是否添加好友?").arg(caName),"添加好友","取消");
如果确认要添加好友,则发送添加好友请求。选择取消,则直接返回。
if(res == 0)
{
// 获取当前用户名 和 目标用户名
QString strCurName = Client::getInstance().getLoginName();
QString strTarName = caName;
// 初始化pdu
PDU* pdu = initPDU(0);
// 添加消息类型为 添加好友请求
pdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_REQUEST;
// 将当前用户名和目标用户名存放到pdu中
memcpy(pdu->caData,strCurName.toStdString().c_str(),32);
memcpy(pdu->caData+32,strTarName.toStdString().c_str(),32);
// 发送消息
Client::getInstance().sendPDU(pdu);
}
else
{
// 取消添加好友
return;
}
1.3 服务器处理Cur客户端的添加好友请求
- 服务器接收Cur客户端的添加好友请求,判断是否满足条件,转发请求给Tar客户端
- 需要数据库操作,在数据库操作类中,增加一个处理添加好友的函数(判断是否满足添加好友条件)
- 判断目标用户是否在线,根据发来的目标用户名在数据库中查看其online字段。如目标用户已经下线,则返回已下线。
- 判断当前用户与目标用户是否已经是好友,如果已经是好友,则返回已经是好友
- 如果目标用户在线,同时当前用户与目标用户不是好友,则返回真值。
- 在服务器的请求处理器类中,增加添加好友的函数(判断是否转发),处理消息类型为添加好友请求的情况。
- 在MyTcpServer类中,添加一个转发的函数(接收目标用户名以及要转发的pdu)
- 目的是为了将消息转发给目标用户
- 取出 pdu 中的数据,将取出的数据传给处理数据库操作类中添加好友的函数(判断是否需要转发)的函数。并得到该函数的返回值。
- 根据返回结果,决定是否要转发消息
- 不转发,直接将返回值发回给Cur客户端。
- 需要转发,则调用转发函数,将pdu发送给目标用户Tar
- 需要数据库操作,在数据库操作类中,增加一个处理添加好友的函数(判断是否满足添加好友条件)
1.3.1 数据库操作类,添加处理添加好友的函数(判断是否转发请求给目标用户)
在数据库操作类中,添加处理添加好友的函数,用于判断是否构成添加好友的条件(是否要将添加好友的请求发送给目标用户)
1、首先判断双方是否已经是好友。
(1)根据Cur客户端的用户名和Tar客户端的用户名,在用户信息表中,得到用户ID。
(2)接着根据ID在好友关系表中,查找是否有好友关系。
如果已经是好友了,返回 -2。
select * from friend
where
(
user_id =(select id from user_info where name='%1')
and
friend_id =(select id from user_info where name='%2')
)
or
(
friend_id =(select id from user_info where name='%1')
and
user_id =(select id from user_info where name='%2')
)
2、判断目标客户端是否已经下线
在Cur客户端展示在线用户,或者查找用户时,Tar客户端是在线状态。可以发送添加好友的请求。在显示在线用户到发送添加好友请求发送的过程中,Tar客户端就有可能下线了。因此需要再次判断Tar客户端是否在线。
select online from user_info where name = 'tarName'
直接返回在线状态,在线返回 1,离线返回 0
完整代码
operatedb.cpp
// 处理添加好友的函数
int OperateDB::handleAddFriend(const char *curName,const char *tarName)
{ // 返回值:1 在线、0 不在线、-1 错误、-2 已经是好友
if(curName==NULL||tarName==NULL)
{
return -1;
}
// 判断要添加的用户是否已经是好友了
QString sql = QString(R"(
select * from friend
where
(
user_id =(select id from user_info where name='%1')
and
friend_id =(select id from user_info where name='%2')
)
or
(
friend_id =(select id from user_info where name='%1')
and
user_id =(select id from user_info where name='%2')
)
)").arg(curName).arg(tarName);
QSqlQuery q;
// 执行失败,返回-1
if(!q.exec(sql)) return -1;
// 已经是好友了,返回-2
if(q.next()) return -2;
// 判断用户 在线状态,
sql = QString("select online from user_info where name = '%1'").arg(tarName);
// 执行失败,返回-1
if(!q.exec(sql)) return -1;
if(q.next())
{ // 返回用户在线状态
return q.value(0).toInt(); // 1在线,0不在线
}
else
{ // 没有查到,返回错误
return -1;
}
}
1.3.2 在MyTcpServer类中,添加转发函数
想要将一个客户端的信息转发给另一个客户端,就需要获取到当前所有在线客户端的socket,而我们在MyTcpServer类中,使用了一个成员变量QList<MyTcpSocket*> m_tcpSocketList(存储所有socket的列表)
因此需要在该类中,定义一个转发函数,传入目标用户名以及想要转发的pdu
只需要在m_tcpSocketList列表中,检索用户名为目标用户名的socket,然后向这个socket发送消息即可。
mytcpsocket.cpp
// 转发函数,将pdu转发给,目标用户
void MyTcpServer::resend(char *tarName, PDU *pdu)
{
qDebug()<<"resend tarName: "<<tarName;
// 在 m_tcpSocketList 列表中,查找要转发的目标用户
foreach(MyTcpSocket* tcpSocket, m_tcpSocketList)
{
if(tcpSocket->getLoginName() == tarName)
{
tcpSocket->write((char*)pdu,pdu->uiPDULen);
return;
}
}
}
1.3.3 请求处理器添加添加好友请求
在请求处理器中,定义添加好友请求的函数,服务器收到添加好友请求,根据消息类型,调用该函数。
1、拿到Cur客户端用户名 和 Tar客户端用户名。
// 取出Cur客户端 以及 Tar客户端的用户名
char curName[32] = {'\0'};
char tarName[32] = {'\0'};
memcpy(curName,m_pdu->caData,32);
memcpy(tarName,m_pdu->caData+32,32);
2、调用数据库操作类中,处理添加好友的函数。得到返回值(根据返回值判断是否需要转发)
(1)返回值 -2:双方已经是好友,不需要转发(直接发回Cur客户端)
(2)返回值 -1:添加过程出现错误,不需要转发(直接发回Cur客户端)
(3)返回值 0:目标用户已经下线,不需要转发(直接发回Cur客户端)
(4)返回值 1:双方不是好友,且目标用户在线,需要转发给目标函数(调用转发函数)
注意:此时的消息类型有所区别
(1)如果判断不需要转发(不构成添加好友的条件),则构建响应pdu,消息类型为添加好友的响应。caMsg中存放的是判断的返回值。
(2)如果判断需要转发,则直接将Cur发来的pdu,转发给Tar客户端,此时的消息类型为添加好友的请求。
// 在数据库中判断当前客户端与目标客户端是否已经是好友,以及目标客户端是否在线
int ret = OperateDB::getInstance().handleAddFriend(curName,tarName);
// 根据数据库响应,决定是否要转发
if(ret==1)
{
// 调用转发函数,将添加好友请求转发给目标用户
MyTcpServer::getInstance().resend(tarName,m_pdu);
return NULL;
}
else
{
// 如果添加过程出现错误,直接返回 添加好友的响应
// 初始化响应添加好友的PDU对象
PDU* resPdu = initPDU(sizeof (int));
// 消息类型为添加好友的响应
resPdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_RESPOND;
// 将消息存储到消息结构体
memcpy(resPdu->caData,curName,32);
memcpy(resPdu->caData+32,tarName,32);
memcpy(resPdu->caMsg,&ret,sizeof(int));
return resPdu;
}
1.4 Tar客户端接收添加好友请求
- Tar目标客户端接收添加好友请求,并作出决定
- Tar目标客户端,在响应处理器类中,定义处理添加好友请求的函数
- 读出Cur客户端的用户名
- 在函数中,弹出询问框,询问是否要添加 Cur客户端用户为好友(同意或者不同意)
- 在处理消息的函数中,根据消息类型为添加好友请求,调用处理添加好友请求的函数
- 构建响应pdu,消息类型为添加好友的响应
- 根据Tar客户端的选择,向服务器返回不同的内容。(同意或不同意)
- Tar目标客户端,在响应处理器类中,定义处理添加好友请求的函数
1、目标客户端(Tar)接收添加好友的请求,弹出询问框,是否要添加Cur用户为好友
// 取出Cur客户端的用户名
char curName[32] = {'\0'};
memcpy(curName,m_pdu->caData,32);
int ret = QMessageBox::question(Index::getInstance().getFriend(),"添加好友",QString("是否同意 %1 的添加好友请求?").arg(curName),"同意添加","拒绝添加");
2、根据用户选中,构建响应pdu,并发送给服务器
(1)同意添加,响应消息为 1,消息类型为 添加好友的响应
(2)不同意添加,响应消息为 2,消息类型为 添加好友的响应
if(ret == 0)
{
// 同意添加好友
qDebug()<<"ResHandler addFriendRequest QMessageBox::Yes";
// 构建响应pdu
PDU* pdu = initPDU(sizeof(int));
ret = 1;
pdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_RESPOND;
memcpy(pdu->caData,m_pdu->caData,64);
memcpy(pdu->caMsg,&ret,sizeof(int));
// 发送消息
Client::getInstance().sendPDU(pdu);
}
else
{
// 拒绝添加好友
qDebug()<<"ResHandler addFriendRequest QMessageBox::no";
// 构建响应pdu
PDU* pdu = initPDU(sizeof(int));
ret = 2;
pdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_RESPOND;
memcpy(pdu->caData,m_pdu->caData,64);
memcpy(pdu->caMsg,&ret,sizeof(int));
// 发送消息
Client::getInstance().sendPDU(pdu);
}
1.5 服务器接收Tar客户端 的添加好友响应
- 服务器接收Tar客户端,根据Tar响应进行处理,转发响应给Cur客户端
- 需要数据库操作,在数据库操作类中,增加一个处理同意添加好友的函数(在数据库的好友表中,插入一条数据)
- 在数据库用户信息表(user_info)表中,根据Cur客户端与Tar客户端的用户名,得到用户id
- 根据两个用户id,在friend表中,插入一条新的数据
- 返回是否插入成功
- 在服务器的请求处理器类中,增加同意添加好友的函数(判断Tar用户是否同意添加好友),处理消息类型为添加好友请求的情况。
- 根据Tar客户都的返回值(是否同意添加好友)来决定如何操作
- 同意添加好友:调用数据库操作类中,调用处理同意添加好友的函数,得到返回值(是否成功插入数据)
- 不同意添加好友:直接将Tar客户端的响应消息通过转发函数转发给Cur客户端,消息类型为添加好友的响应。
- 根据Tar客户都的返回值(是否同意添加好友)来决定如何操作
- 同意添加好友的情况
- 调用数据库操作类中,处理同意添加好友的函数,得到是否插入成功的返回值
- 将返回值分别转发给Cur客户端和Tar客户端,消息类型为添加好友的响应。
- 在Tar客户端中,应该将Cur和Tar两个客户端的用户名调换位置
- 需要数据库操作,在数据库操作类中,增加一个处理同意添加好友的函数(在数据库的好友表中,插入一条数据)
1.5.1 在数据库操作类中,增加 处理同意添加好友的函数
在数据库操作类中,增加一个 处理同意添加好友的函数,目的是为了在Tar客户端同意添加好友之后,在好友关系表中添加一条新的数据。
在数据库用户信息表(user_info)表中,根据Cur客户端与Tar客户端的用户名,得到用户id。根据两个用户id,在friend表中,插入一条新的数据。返回是否插入成功。
// 建立好友关系
int OperateDB::handleAddFriendAgree(const char *curName, const char *tarName)
{
// 合法检测,避免错误内容
if(curName==NULL||tarName==NULL)
{
return -1;
}
// 在好友列表中,插入一条数据
QString sql = QString(R"(
insert into friend(user_id,friend_id)
select u1.id,u2.id
from user_info u1,user_info u2
where u1.name = '%1' and u2.name = '%2')"
).arg(curName).arg(tarName);
QSqlQuery q;
// 插入失败,返回 -1
if(!q.exec(sql)) return -1;
// 插入成功,返回 1
return 1;
}
1.5.2 在请求处理器中,增加 添加好友响应的函数
reqhandler.cpp addFriendRes()
在服务器的请求处理器类中,增加一个添加好友响应的函数。服务器收到添加好友响应,调用此函数。
1、拿到Cur用户名、Tar用户名以及Tar客户端发来的响应(是否同意添加好友)
// 取出用户名,以及目标用户的态度
char curName[32] = {'\0'};
char tarName[32] = {'\0'};
int ret;
memcpy(curName,m_pdu->caData,32);
memcpy(tarName,m_pdu->caData+32,32);
memcpy(&ret,m_pdu->caMsg,sizeof(int));
2、根据是否同意添加好友,执行不同的内容
- 同意添加好友:调用数据库操作类中,调用处理同意添加好友的函数,得到返回值(是否成功插入数据)
- 不同意添加好友:直接将Tar客户端的响应消息通过转发函数转发给Cur客户端,消息类型为添加好友的响应。
(1)同意添加好友
调用数据库操作类中处理同意添加好友的函数,得到返回值(是否成功插入数据)。
向Cur客户端与Tar客户端,发回添加好友是否成功的信息。
注意:在向Tar客户端发回响应消息时,要将Cur和Tar的位置进行调换(对于Tar客户端来说,它是当前客户端,Cur才是目标客户端)
if(ret==1)
{
// 目标用户同意添加好友
// 调用数据库操作类中,处理同意添加好友的函数,得到是否成功插入数据
int res = OperateDB::getInstance().handleAddFriendAgree(curName,tarName);
// 将得到的返回值,存储到消息结构体
memcpy(m_pdu->caMsg,&res,sizeof(int));
// 向Cur客户端转发添加响应
MyTcpServer::getInstance().resend(curName,m_pdu);
// 调换Cur用户名与Tar客户端用户名的位置
memcpy(m_pdu->caData,tarName,32);
memcpy(m_pdu->caData+32,curName,32);
// 将结果转发回目标用户
MyTcpServer::getInstance().resend(tarName,m_pdu);
}
(2)不同意添加好友
直接将消息转发给Cur客户端
else
{
// 目标用户拒绝添加好友
MyTcpServer::getInstance().resend(curName,m_pdu);
}
1.6 客户端接收服务器发回的添加好友响应
这里的客户端是指Cur客户端和Tar客户端,两个客户端处理添加好友响应的函数是一样的。
- 在客户端,在响应处理器类中,定义处理添加好友响应的函数
- 读出Tar客户端的用户名,已经返回的结果
- 根据返回的结果,进行提示
- 返回值 -2:双方已经是好友
- 返回值 -1:添加过程出现错误
- 返回值 0:目标用户已经下线
- 返回值 1:添加好友成功
- 返回值 2:目标用户,拒绝添加好友请求
Cur客户端可能接收到上面的所有返回值、而Tar客户端只能在同意添加好友之后,接收到返回值 -1 或 1 。
客户端 -- reshandler.cpp -- addFriendRes()
// 处理添加好友的响应
void ResHandler::addFriendRes()
{
// 将消息取出
int ret;
char tarName[32] = {'\0'};
memcpy(tarName,m_pdu->caData+32,32);
memcpy(&ret,m_pdu->caMsg,sizeof(int));
// 根据返回的响应进行处理
if(ret == -2)
{
QMessageBox::information(&Index::getInstance(),"添加好友","该用户已经是你的好友!");
return;
}
else if(ret == 0)
{
QMessageBox::information(&Index::getInstance(),"添加好友","该用户已下线");
return;
}
else if(ret == 1)
{
QMessageBox::information(&Index::getInstance(),"添加好友",QString("已经成功添加用户 %1 为好友!").arg(tarName));
return;
}
else if(ret == 2)
{
QMessageBox::information(&Index::getInstance(),"添加好友",QString("用户 %1 不同意添加请求,添加好友失败!").arg(tarName));
return;
}
else
{
QMessageBox::critical(&Index::getInstance(),"添加好友","添加好友错误!");
return;
}
}
二、刷新好友
上面的内容,我们完成了添加好友的功能,在添加完好友之后,我们希望能在好友列表中,显示该用户的所有好友。
因此,需要接下来要完成的是刷新好友功能。并且,在登录成功,添加好友,删除好友之后,就能自动刷新好友,获得新的好友列表。
刷新好友功能展示
2.1 刷新好友的流程
下面我们来分析一下,刷新好友具体有那些流程?
什么操作会需要触发刷新好友的功能呢?
(1)登录成功之后,就要显示好友列表,需要刷新好友
(2)添加好友成功之后,需要刷新好友列表
(3)点击刷新好友的按钮,需要刷新好友列表
(4)删除好友之后,需要刷新好友列表(未实现)
- 客户端触发刷新好友请求,发送刷新好友请求(发送请求到服务器)
- 发送刷新好友请求 封装为一个函数,触发刷新好友时,调用该函数,发送刷新好友请求
- 登录成功之后、添加好友成功之后、删除好友之后,调用函数
- 点击刷新好友的按钮,在该按钮的槽函数中,调用函数
- 构建自定义消息结构体对象pdu,只发消息类型即可。因此在初始化PDU时,传入0
- 在PDU协议中的添加对应功能的请求和响应消息类型
- 在消息类型的枚举值中,添加刷新好友的请求和响应
- 在pdu填入消息类型(刷新好友的请求)
- 发送 pdu到服务器
- 发送刷新好友请求 封装为一个函数,触发刷新好友时,调用该函数,发送刷新好友请求
- 服务器接收客户端的刷新好友请求,获取用户的好友信息并发回客户端
- 需要数据库操作,在数据库操作类中,增加一个处理刷新好友的函数(在好友关系表中查找该用户的所有好友信息)
- 将该用户的所有好友信息,存在一个字符串列表中(QStringList)
- 返回这个字符串列表
- 在服务器的请求处理器类中,增加刷新好友的函数(获取用户好友列表),处理消息类型为刷新好友请求的情况。
- 调用数据库操作类中刷新好友的函数。并得到该函数的返回值(用户的好友列表)
- 构建响应pdu,将用户的好友列表放入响应pdu,并发回客户端
- 需要数据库操作,在数据库操作类中,增加一个处理刷新好友的函数(在好友关系表中查找该用户的所有好友信息)
- 客户端 接收刷新好友响应,并对结果进行显示
- 在好友类中,定义一个展示好友的函数,该函数接收字符串列表(用户的好友列表),并将该列表中的好友用户名更新到用户的好友界面
- 在客户端,在响应处理器类中,定义处理刷新好友响应的函数
- 读出pdu中的数据,并将其存放到字符串列表中(QStringList)
- 调用好友类中,展示好友的函数,传入接收的好友列表。
2.2 客户端发送刷新好友请求
1、在好友类中,将发送刷新好友请求的功能,封装为一个函数
(1)构建pdu
(2)添加消息类型为刷新好友请求(需要在procotol.h中的消息类型枚举中,添加新的消息类型)
(3)发送pdu到服务器
// 发送刷新好友的请求
void Friend::flushFriendReq()
{
// 测试
qDebug()<<"Friend flushFriendReq";
PDU* pdu = initPDU(0);
pdu->uiMsgType = ENUM_MSG_TYPE_FLUSH_FRIEND_REQUEST;
// 发送消息
Client::getInstance().sendPDU(pdu);
}
2、在需要触发刷新好友请求时,调用该函数
我们在Day4中,定义了获取好友界面的函数(getFriend()),通过该函数就可以调用刷新好友的函数。
(1)登录成功后
在响应处理器类的登录函数中(reshanlder.cpp -- login()),在判断登录成功之后,调用刷新好友的函数。
// 登录成功的情况
if(ret)
{
// 设置当前客户端登录的用户名
Client::getInstance().getLoginName() = caName;
QMessageBox::information(&Client::getInstance(),"登录","登录成功");
// 隐藏登录界面
Client::getInstance().hide();
// 登录成功后,跳转到首页
Index::getInstance().show();
// 登录成功后,刷新好友列表
Index::getInstance().getFriend()->flushFriendReq();
}
(2)添加好友成功后
在响应处理器类的添加好友响应的函数中(reshanlder.cpp -- addFriendRes()),在添加好友成功之后,调用刷新好友的函数。
// 添加好友成功的情况
else if(ret == 1)
{
QMessageBox::information(&Index::getInstance(),"添加好友",QString("已经成功添加用户 %1 为好友!").arg(tarName));
// 刷新好友
Index::getInstance().getFriend()->flushFriendReq();
return;
}
(3)点击刷新好友按钮
在好友的ui界面中,选择刷新好友的按钮,转到槽,选择clicked()点击的槽函数,在该函数中调用刷新好友请求的函数
// 刷新好友按钮的点击槽函数
void Friend::on_flushFriend_PB_clicked()
{
qDebug()<<"Friend on_flushFriend_PB_clicked";
flushFriendReq();
}
(4)删除好友后(未完成)
2.3 服务器处理刷新好友请求
2.3.1 在数据库操作类中,添加一个处理刷新好友的函数
该函数的目的是用来获取当前用户的所有好友信息。
在好友信息表(friend)中,根据当前登录的用户名(在MyTcpSocket类的成员变量中,有记录当前登录的用户名),搜索该用户是好友关系的用户id
注意:好友关系是双向的,在好友信息表中,当前登录的用户,可能在user_id字段,也有可能在friend_id字段。
select name from user_info
where id in
(
select user_id from friend where friend_id = (select id from user_info where name = '%1')
union
select friend_id from friend where user_id = (select id from user_info where name = '%1')
)
将得到的好友用户名存挨个存放到QStringList中,并返回。
// 刷新好友,返回好友列表
QStringList OperateDB::handleFlushFriend(const QString& name)
{
QStringList friendList;
if(name == NULL)
{
return friendList;
}
// 查找当前用户的好友
QString sql = QString(R"(
select name from user_info
where id in
(
select user_id from friend where friend_id = (select id from user_info where name = '%1')
union
select friend_id from friend where user_id = (select id from user_info where name = '%1')
)
)"
).arg(name);
QSqlQuery q;
q.exec(sql);
// 将好友的用户名添加到列表中
while(q.next())
{
friendList.append(q.value(0).toString());
}
return friendList;
}
2.3.2 请求处理器中,添加刷新好友的函数
reqhandler.cpp addFriend(QString& curName)
在请求处理器类中,添加刷新好友的函数,该函数接收一个参数(QString类型:当前登录的用户名),在服务器收到刷新好友的请求后,调用该函数,并传入当前登录的用户名。
1、调用数据库操作类中,处理刷新好友的函数,得到用户的好友列表
// 处理消息
QStringList friendList = OperateDB::getInstance().handleFlushFriend(curName);
2、计算用户好友列表大小,构建响应pdu,将好友信息挨个放入响应pdu中
// 刷新好友列表请求
PDU *ReqHandler::flushFriend(QString& curName)
{
// 处理消息
QStringList friendList = OperateDB::getInstance().handleFlushFriend(curName);
// 获取好友列表大小
int listSize = friendList.size();
uint uiMsgLen = listSize*32;
// 向客户端发送响应
// 初始化响应刷新好友的PDU对象
PDU* resPdu = initPDU(uiMsgLen);
// 消息类型为刷新好友的响应
resPdu->uiMsgType = ENUM_MSG_TYPE_FLUSH_FRIEND_RESPOND;
// 将用户名 挨个放到 caMsg中
for(int i = 0; i<listSize; i++)
{
// 测试
qDebug()<<"ReqHandler flushFriend " << friendList.at(i);
// 将每一个用户名都存储到 caMsg中
memcpy(resPdu->caMsg+i*32,friendList.at(i).toStdString().c_str(),32);
}
return resPdu;
}
2.4 客户端接收服务器响应
1、在好友类中,定义展示好友列表的函数,接收好友列表(QStringList类型)
friend.cpp showFriend(QStringList nameList)
将好友列表中的信息,添加到好友界面的列表框中
// 展示好友列表
void Friend::showFirend(QStringList nameList)
{
ui->friend_LW->clear();
ui->friend_LW->addItems(nameList);
}
2、在响应处理器中,添加刷新好友的函数
reshandler.cpp flushFrien()
在接收到刷新好友的响应时,调用该函数,取出响应pdu中的好友列表,将其存放在QStringList中,调用展示好友列表的函数,传入好友列表。
// 刷新好友响应
void ResHandler::flushFriend()
{
// 获取在线用户的个数
uint listSize = m_pdu->uiMsgLen/32;
qDebug()<<"listSize "<<listSize;
// 创建变量存储在线用户的用户名
char friendName[32];
QStringList friendList;
// 将caMsg中的用户名挨个取出,并放到nameList中
for(uint i = 0; i <listSize; i++)
{
// 挨个取出用户名
memcpy(friendName,m_pdu->caMsg+i*32,32);
// 测试
qDebug()<<"ResHandler flushFriend friendName"<<QString(friendName);
// 将取到的用户名存放到 nameList中
friendList.append(QString(friendName));
}
// 调用展示在线用户的函数
Index::getInstance().getFriend()->showFirend(friendList);
}
到这里,就完成了刷新好友的功能。
三、删除好友
删除好友功能展示
3.1 删除好友的流程
下面我们来分析一下,删除好友具体有那些流程?
- 客户端发送删除好友请求(发送要删除好友的用户名到服务器)
- 用户在好友界面选择要删除的好友,点击删除按钮
- 判断是否选择了好友
- 获取当前选中的好友的用户名
- 构建自定义消息结构体对象pdu,使用caData发送消息,因此在初始化PDU时,传入0
- 在PDU协议中的添加对应功能的请求和响应消息类型
- 在消息类型的枚举值中,添加删除好友的请求和响应
- 在pdu填入消息类型 (删除好友请求)和 要删除的用户名
- 发送 pdu到服务器
- 用户在好友界面选择要删除的好友,点击删除按钮
- 服务器接收客户端的删除好友请求,在好友关系表中删除数据
- 需要数据库操作,在数据库操作类中,增加一个处理删除好友的函数(在好友关系表中删除一条数据)
- 判断是否为好友,删除好友,返回是否删除成功
- 在服务器的请求处理器类中,增加删除好友的函数,处理消息类型为删除好友友请求的情况。
- 调用数据库操作类中处理删除好友的函数。并得到该函数的返回值(是否删除成功)
- 构建响应pdu,将返回值放入响应pdu,并发回客户端
- 需要数据库操作,在数据库操作类中,增加一个处理删除好友的函数(在好友关系表中删除一条数据)
- 客户端 接收删除好友响应,并对结果进行显示
- 在客户端,在响应处理器类中,定义删除好友响应的函数
- 根据返回值,进行不同的显示
- 删除成功后,刷新好友列表
- 在客户端,在响应处理器类中,定义删除好友响应的函数
3.2 客户端发送删除好友请求
在friend.ui中,选择删除好友的按钮,转到clicked() 槽函数
1、判断是否选择了好友,如果没选中,进行提示
2、如果选中了,获得该好友的用户名
3、再次提示用户,是否删除好友?
4、确认删除,发送删除好友请求到服务器(带着要删除好友关系的用户名)
friend.cpp 删除好友按钮的槽函数
// 删除好友的按钮
void Friend::on_delFriend_PB_clicked()
{
// 获取当前选中的值
QListWidgetItem* pItem = ui->friend_LW->currentItem();
if(!pItem)
{
// 当前没选中,进行提示
QMessageBox::information(this,"删除好友","请选择你要删除的好友");
return;
}
// 获得要删除好友的用户名
QString tarName = pItem->text();
// 再次询问是否要删除好友
int ret = QMessageBox::critical(this,"删除好友",QString("是否要删除 %1 ?").arg(tarName),"确认删除","取消");
if(ret == 0)
{
// 确认删除,发送pdu
PDU* pdu = initPDU(0);
memcpy(pdu->caData,tarName.toStdString().c_str(),32);
pdu->uiMsgType = ENUM_MSG_TYPE_DELETE_FRIEND_REQUEST;
// 发送消息
Client::getInstance().sendPDU(pdu);
}
else
{
qDebug()<<"取消";
}
}
3.3 服务器处理删除好友请求
3.3.1 在数据库操作类中,添加处理删除好友的函数
operatedb.cpp handleDeleteFriend(const QString &curName,const char *tarName)
该函数接收两个参数,当前用户名,目标用户名。目的是为了在用户信息表中,根据用户名,找到这两个用户的用户id。然后在好友关系表中,将这两位用户的好友关系删除。
1、判断两个用户是不是好友关系
想要删除好友关系,首先得确定是不是好友,都不是好友,还怎么删好友?
虽然在当前用户的好友列表中,要删除的好友是存在的。但存在一种情况,就是要删除的那个好友已经把你删除了(这种情况类似微信,虽然对面的人给你删了,但是只要你不发消息给他,他还是会在你的好友列表中)。所以要先判断双方是不是好友关系,才能去好友关系表中,删除这条数据
2、 如果当前两人是好友关系,则删除这条数据
注意:
(1)好友关系中,两个用户名无法确定谁在user_id字段、谁在friend_id字段,因此需要考虑这种情况。
(2)在删除一条数据时,一定要加where条件,不然会删除所有的数据!!!同时,在删除一条数据时,要先使用select语句,执行where条件,查看要删除的内容是否符合预期。
// 删除好友
int OperateDB::handleDeleteFriend(const QString &curName,const char *tarName)
{
// 返回值: 出错 0;不是好友 -1;删除成功 1;
if(curName==NULL||tarName == NULL)
{
return 0;
}
// 判断要删除的用户是否是好友
QString sql = QString(R"(
select * from friend
where
(
user_id =(select id from user_info where name='%1')
and
friend_id =(select id from user_info where name='%2')
)
or
(
friend_id =(select id from user_info where name='%1')
and
user_id =(select id from user_info where name='%2')
)
)").arg(curName).arg(tarName);
QSqlQuery q;
// 执行失败,返回-1
if(!q.exec(sql)) return 0;
// 如果执行的语句有内容,说明俩人是好友关系
// 如果没找到这条数据,说明俩人已经不是好友关系了
if(q.next())
{
sql = QString(R"(
delete from friend
where
(
user_id = (select id from user_info where name='%1')
and
friend_id = (select id from user_info where name='%2')
)
or
(
friend_id=(select id from user_info where name='%1')
and
user_id =(select id from user_info where name='%2')
))").arg(curName).arg(tarName);
return q.exec(sql);
}
else
{
// 已经不是好友了
return -1;
}
}
3.3.2 在请求处理器类中,添加删除好友的函数
reqhandler.cpp deleteFriend(QString& curName)
在请求处理器中,添加删除好友的函数,服务器在收到删除好友的请求时,调用该函数。
该函数接收一个参数(当前登录的用户名),目的让数据库知道要删除谁的数据
1、取出要删除的用户名,连同当前登录的用户名,调用数据库操作类中,处理删除好友的函数,将这两个用户名传入,得到返回值(是否删除成功)
2、构建响应pdu,将返回值放入pdu,发回客户端(消息类型为删除好友的响应)
// 删除好友
PDU *ReqHandler::deleteFriend(QString &curName)
{
// 取出要删除的用户名
char tarName[32] = {'\0'};
memcpy(tarName,m_pdu->caData,32);
// 将当前用户名和要删除好友的用户名传给数据库操作类中的函数
// 得到返回值
int ret = OperateDB::getInstance().handleDeleteFriend(curName,tarName);
// 构建响应PDU
PDU* resPdu = initPDU(0);
// 消息类型为删除好友的响应
resPdu->uiMsgType = ENUM_MSG_TYPE_DELETE_FRIEND_RESPOND;
memcpy(resPdu->caData,&ret,sizeof(int));
return resPdu;
}
3.4 客户端接收服务器响应
- 在客户端,在响应处理器类中,定义删除好友响应的函数
- 根据返回值,进行不同的显示
- 删除成功后,刷新好友列表
reshandler.cpp deleteFrien()
// 删除好友响应
void ResHandler::deleteFriend()
{
// 取出响应结果
int ret;
memcpy(&ret,m_pdu->caData,sizeof(int));
if(ret ==1)
{
QMessageBox::information(&Index::getInstance(),"删除好友","删除好友成功!");
// 删除好友成功后,刷新好友列表
Index::getInstance().getFriend()->flushFriendReq();
return;
}
else if(ret == -1)
{
QMessageBox::information(&Index::getInstance(),"删除好友","该好友已经不是你的好友了!");
// 删除好友成功后,刷新好友列表
Index::getInstance().getFriend()->flushFriendReq();
}
else
{
QMessageBox::information(&Index::getInstance(),"删除好友","删除好友出错!");
return;
}
}
总结
完成的内容
添加好友、刷新好友、删除好友
注意:
再次声明,本文未经授权禁止转载(转载的话可以私信联系我)
如果你发现了文章中的问题,勿喷!!请私信我,我会改正。(十分感谢)
1、对于添加好友,目前只实现了双方在线的添加过程,后续优化时,可以自行实现离线添加好友。(有时间优化的话,也会发出来的)
2、对于好友列表,可以在数据库获得好友列表时,同时获取好友的在线状态,并在好友列表中进行展示,用于显示当前好友是否在线
3、对于好友界面,只是简单测试功能是否完善,要做好看的UI比较繁琐(需要加头像ICon等),不做前端,想美化可以自行实现(实现完请把代码甩到我脸上 QAQ)
4、对于删除好友,在删除好友之后,目标客户端是不会自动更新好友列表的。可以给目标客户端也发一个响应(利用转发函数),目标客户端在收到删除好友的响应之后,也刷新一下好友列表即可。
完整代码
代码量增加,把所有代码复制粘贴到CSDN是不现实的,想要源码的话,就自己去历史提交里找吧。下面这几次提交。刷新好友、删除好友的也在