QT实战项目-DAY6-好友功能

本文未经授权,禁止转载

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响应进行处理,转发响应给Cur客户端
    • 需要数据库操作,在数据库操作类中,增加一个处理添加好友响应的函数(在数据库的好友表中,插入一条数据
      • 在数据库用户信息表(user_info)表中,根据Cur客户端与Tar客户端的用户名,得到用户id
      • 根据两个用户id,在friend表中,插入一条新的数据
      • 返回是否插入成功
    • 在服务器的请求处理器类中,增加添加好友响应的函数(判断Tar用户是否同意添加好友),处理消息类型为添加好友响应的情况。
      • 根据Tar客户都的返回值(是否同意添加好友)来决定如何操作
        • 同意添加好友:调用数据库操作类中,调用处理同意添加好友的函数,得到返回值(是否成功插入数据)
        • 不同意添加好友:直接将Tar客户端的响应消息通过转发函数转发给Cur客户端,消息类型为添加好友的响应。
    • 同意添加好友的情况
      • 调用数据库操作类中,处理同意添加好友的函数,得到是否插入成功的返回值
      • 将返回值分别转发给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客户端的选择,向服务器返回不同的内容。(同意不同意

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客户端,消息类型为添加好友的响应。
    • 同意添加好友的情况
      • 调用数据库操作类中,处理同意添加好友的函数,得到是否插入成功的返回值
      • 将返回值分别转发给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是不现实的,想要源码的话,就自己去历史提交里找吧。下面这几次提交。刷新好友、删除好友的也在

        

 郭拾叁/NetdiskProject - 码云 - 开源中国 (gitee.com)

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值