Chap19-NotificationsAndConfirm

Chap19-NotificationsAndConfirm

这一节要实现的比较多,包括前端的列表加载,确认,接受/拒绝。后端的加载,修改,查询,存储,发送等。

为了简化,这里关于后端要说的一句话就是,如果要发送信息的对象在Redis,也就是在线,那么直接查找uip所在服务器发送,否则就存入数据库。在这个用户登陆的时候,加载Notifications的列表。

数据库

由于为了修改添加了很多的操作,这里一一列出

// MysqlManager.h
/**
 * @brief 添加好友申请
 *
 * @param fromUid
 * @param toUid
 * @return true
 * @return false
 */
bool AddFriendApply(const std::string& fromUid, const std::string& toUid);
/**
 * @brief 获取用户信息,精确匹配
 *
 * @param uid
 * @return std::shared_ptr<UserInfo>
 */
std::shared_ptr<UserInfo> GetUser(int uid);
/**
 * @brief 获取用户信息,模糊查询
 *
 * @return std::vector<std::shared_ptr<UserInfo>>
 */
std::vector<std::shared_ptr<UserInfo>> GetUser(const std::string&);
/**
 * @brief 获取好友申请列表
 *
 * @param uid
 * @param applyList
 * @return true
 * @return false
 */
bool GetFriendApplyList(const std::string& uid, std::vector<std::shared_ptr<UserInfo>>& applyList);
/**
 * @brief 改变好友申请状态,1同意0拒绝
 *
 * @param fromUid
 * @param toUid
 * @param status
 * @return true
 * @return false
 */
bool ChangeApplyStatus(const std::string& fromUid, const std::string& toUid, int status);
/**
 * @brief 改变消息状态,1已读0未读
 *
 * @param fromUid
 * @param toUid
 * @param status
 * @return true
 * @return false
 */
bool ChangeMessageStatus(const std::string& uid, int status);
/**
 * @brief 建立好友关系
 *
 * @param fromUid
 * @param toUid
 * @return true
 * @return false
 */
bool MakeFriends(const std::string& fromUid, const std::string& toUid);
/**
 * @brief 检查是否是好友关系
 *
 * @param fromUid
 * @param toUid
 * @return true
 * @return false
 */
bool CheckIsFriend(const std::string& fromUid, const std::string& toUid);
/**
 * @brief 添加通知
 *
 * @param uid
 * @param type
 * @param message
 * @return true
 * @return false
 */
bool AddNotification(const std::string& uid, int type, const std::string& message);
/**
 * @brief 获取通知列表
 *
 * @param uid
 * @param notificationList
 * @return true
 * @return false
 */
bool GetNotificationList(const std::string& uid, std::vector<std::shared_ptr<UserInfo>>& notificationList);
// MysqlDao.cpp
bool MysqlDao::AddFriendApply(const std::string& fromUid, const std::string& toUid)
{
    if (fromUid == toUid) {
        return false; // 不允许自己添加自己
    }
    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 IGNORE INTO friend_apply (from_uid,to_uid) VALUES(%0,%1) ";
        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;
}

std::shared_ptr<UserInfo> MysqlDao::GetUser(int uid)
{
    auto conn = _pool->GetConnection();
    if (!conn) {
        SPDLOG_ERROR("Failed to get connection from pool");
        return nullptr;
    }

    try {
        mysqlpp::Query query = conn->query();
        query << "SELECT * FROM user WHERE uid = %0q";

        query.parse();
        mysqlpp::StoreQueryResult res = query.store(uid);

        /**
                            jj["uid"] = uid;
                            jj["name"] = user_info->name;
                            jj["email"] = user_info->email;
                            jj["nick"] = user_info->nick;
                            jj["sex"] = user_info->sex;
                            jj["desc"] = user_info->desc;
                            jj["icon"] = user_info->icon;
                            jj["token"] = token;
         *
         */

        if (res && res.num_rows() == 1) {
            auto user_info = std::make_shared<UserInfo>();
            user_info->uid = uid;
            user_info->sex = res[0]["sex"];
            user_info->status = res[0]["status"];
            user_info->name = std::string(res[0]["name"]);
            user_info->email = std::string(res[0]["email"]);
            user_info->icon = std::string(res[0]["icon"]);
            user_info->desc = std::string(res[0]["desc"]);
            // user_info->nick = std::string(res[0]["nick"]);

            _pool->ReturnConnection(std::move(conn));
            return user_info;
        } else {
            _pool->ReturnConnection(std::move(conn));
            SPDLOG_DEBUG("User not found with uid: {}", uid);
            return nullptr;
        }

    } catch (const mysqlpp::Exception& e) {
        SPDLOG_ERROR("MySQL++ exception: {}", e.what());
        if (conn)
            _pool->ReturnConnection(std::move(conn));
        return nullptr;
    }
}

std::vector<std::shared_ptr<UserInfo>> MysqlDao::GetUser(const std::string& name)
{
    auto conn = _pool->GetConnection();
    if (!conn) {
        SPDLOG_ERROR("Failed to get connection from pool");
        return {};
    }

    Defer defer([this, &conn]() {
        _pool->ReturnConnection(std::move(conn));
    });

    try {
        mysqlpp::Query query = conn->query();

        // 使用预处理语句进行模糊查询
        query << "SELECT * FROM user WHERE name LIKE " << mysqlpp::quote << ("%" + name + "%");

        mysqlpp::StoreQueryResult res = query.store();
        std::vector<std::shared_ptr<UserInfo>> users;

        if (res) {
            users.reserve(res.num_rows()); // 预分配内存
            for (size_t i = 0; i < res.num_rows(); ++i) {
                auto user_info = std::make_shared<UserInfo>();
                user_info->uid = res[i]["uid"];
                user_info->sex = res[i]["sex"];
                user_info->status = res[i]["status"];
                user_info->name = std::string(res[i]["name"]);
                user_info->email = std::string(res[i]["email"]);
                user_info->icon = std::string(res[0]["icon"]);
                user_info->desc = std::string(res[0]["desc"]);
                // user_info->nick = std::string(res[0]["nick"]);

                users.push_back(user_info);
            }
            SPDLOG_DEBUG("Found {} users matching pattern: '{}'", users.size(), search_pattern);
        }
        return users;
    } catch (const mysqlpp::Exception& e) {
        SPDLOG_ERROR("MySQL++ exception: {}", e.what());
        return {};
    }
}

bool MysqlDao::GetFriendApplyList(const std::string& uid, std::vector<std::shared_ptr<UserInfo>>& applyList)
{
    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 << "SELECT u.uid, u.name, u.icon, u.desc, u.sex, fa.time "
              << "FROM user u, friend_apply fa "
              << "WHERE u.uid = fa.from_uid " // 获取申请发送者的信息
              << "AND fa.to_uid = %0q " // 当前用户是接收方
              << "AND fa.status = 0 " // status = 0 表示等待处理
              << "ORDER BY fa.time desc";
        query.parse();
        mysqlpp::StoreQueryResult res = query.store(uid);

        if (res && res.num_rows() > 0) {
            applyList.reserve(res.num_rows()); // 预分配内存
            for (size_t i = 0; i < res.num_rows(); ++i) {
                auto user_info = std::make_shared<UserInfo>();
                user_info->uid = res[i]["uid"];
                user_info->sex = res[i]["sex"];
                user_info->name = std::string(res[i]["name"]);
                user_info->icon = std::string(res[i]["icon"]);
                user_info->desc = std::string(res[i]["desc"]);
                user_info->back = std::string(res[i]["time"]);
                applyList.push_back(user_info);
            }
            return true;
        }
        return false;
    } catch (const mysqlpp::Exception& e) {
        SPDLOG_ERROR("MySQL++ exception: {}", e.what());
        return false;
    } catch (const std::exception& e) {
        SPDLOG_ERROR("Exception: {}", e.what());
        return false;
    }
}

bool MysqlDao::CheckApplied(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 << "SELECT COUNT(*) as cnt from friend_apply where from_uid = %0q and to_uid = %1q";
        query.parse();
        mysqlpp::StoreQueryResult res = query.store(std::stoi(fromUid), std::stoi(toUid));
        if (res && res.num_rows() == 1) {
            int count = res[0]["cnt"];
            return count > 0;
        }
        return false;
    } catch (const mysqlpp::Exception& e) {
        SPDLOG_ERROR("MySQL++ exception: {}", e.what());
        return false;
    } catch (const std::exception& e) {
        SPDLOG_ERROR("Exception: {}", e.what());
        return false;
    }
}

bool MysqlDao::ChangeMessageStatus(const std::string& uid, int status)
{
    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 << "UPDATE notifications SET status = %0q WHERE uid = %1q";
        query.parse();
        mysqlpp::SimpleResult res = query.execute(status, std::stoi(uid));
        if (res) {
            int affected_rows = res.rows();
            if (affected_rows > 0) {
                SPDLOG_INFO("Message status changed successfully for uid: {}, status: {}", uid, status);
                return true;
            } else {
                SPDLOG_WARN("No message found with uid: {}", uid);
                return false;
            }
        } else {
            SPDLOG_ERROR("Failed to change message status: {}", query.error());
            return false;
        }
    } catch (const mysqlpp::Exception& e) {
        SPDLOG_ERROR("MySQL++ exception: {}", e.what());
        return false;
    } catch (const std::exception& e) {
        SPDLOG_ERROR("Exception: {}", e.what());
        return false;
    }
}

bool MysqlDao::ChangeApplyStatus(const std::string& fromUid, const std::string& toUid, int status)
{
    if (!status || fromUid == toUid) {
        return false;
    }
    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 << "UPDATE friend_apply SET status = %0q WHERE from_uid = %1q AND to_uid = %2q";
        query.parse();
        mysqlpp::SimpleResult res = query.execute(status, std::stoi(fromUid), std::stoi(toUid));
        if (res) {
            int affected_rows = res.rows();
            if (affected_rows > 0) {
                SPDLOG_INFO("Apply status changed successfully for from_uid: {}, to_uid: {}, status: {}", fromUid, toUid, status);
                return true;
            } else {
                SPDLOG_WARN("No apply found with from_uid: {}, to_uid: {}", fromUid, toUid);
                return false;
            }
        } else {
            SPDLOG_ERROR("Failed to change apply status: {}", query.error());
            return false;
        }

    } catch (const mysqlpp::Exception& e) {
        SPDLOG_ERROR("MySQL++ exception: {}", e.what());
        return false;
    } catch (const std::exception& e) {
        SPDLOG_ERROR("Exception: {}", e.what());
        return false;
    }
}

bool MysqlDao::CheckIsFriend(const std::string& fromUid, const std::string& toUid)
{
    if (fromUid == toUid) {
        return true;
    }
    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 << "SELECT COUNT(*) as cnt from friends where (self_id = %0q and friend_id = %1q)"
              << "OR (self_id = %1q and friend_id = %0q)";
        query.parse();
        mysqlpp::StoreQueryResult res = query.store(std::stoi(fromUid), std::stoi(toUid));
        if (res && !res.empty()) {
            int count = res[0]["cnt"];
            return count == 2;
        }
        return false;
    } catch (const mysqlpp::Exception& e) {
        SPDLOG_ERROR("MySQL++ exception: {}", e.what());
        return false;
    } catch (const std::exception& e) {
        SPDLOG_ERROR("Exception: {}", e.what());
        return false;
    }
}

bool MysqlDao::AddNotification(const std::string& uid, int type, const std::string& message)
{
    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 notifications (uid,type,message) VALUES(%0q,%1q,%2q)";
        query.parse();
        mysqlpp::SimpleResult res = query.execute(std::stoi(uid), type, message);
        if (res) {
            int affected_rows = res.rows();
            if (affected_rows > 0) {
                SPDLOG_INFO("Notification added successfully for uid: {}, type: {}, message: {}", uid, type, message);
                return true;
            } else {
                SPDLOG_WARN("Failed to add notification for uid: {}, type: {}, message: {}", uid, type, message);
                return false;
            }
        } else {
            SPDLOG_ERROR("Failed to add notification: {}", query.error());

            return false;
        }
    } catch (const mysqlpp::Exception& e) {
        SPDLOG_ERROR("MySQL++ exception: {}", e.what());
        return false;
    } catch (const std::exception& e) {
        SPDLOG_ERROR("Exception: {}", e.what());
        return false;
    }
}

bool MysqlDao::GetNotificationList(const std::string& uid, std::vector<std::shared_ptr<UserInfo>>& notificationList)
{
    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 << "SELECT u.uid,u.name,u.icon,u.sex,n.type,n.message,n.time from"
              << " user u, notifications n"
              << " WHERE u.uid = n.uid AND u.uid = %0q"
              << " AND n.status = 0"
              << " ORDER BY n.time DESC";
        query.parse();
        mysqlpp::StoreQueryResult res = query.store(std::stoi(uid));
        int count = res.num_rows();
        if (res && res.num_rows() > 0) {
            notificationList.reserve(res.num_rows()); // 预分配内存
            for (size_t i = 0; i < res.num_rows(); ++i) {
                auto user_info = std::make_shared<UserInfo>();
                user_info->uid = res[i]["uid"];
                user_info->status = res[i]["type"];
                user_info->desc = std::string(res[i]["message"]);
                user_info->back = std::string(res[i]["time"]);
                user_info->sex = res[i]["sex"];
                user_info->name = std::string(res[i]["name"]);
                user_info->icon = std::string(res[i]["icon"]);
                notificationList.push_back(user_info);
            }
            return true;
        }
        return false;
    } catch (const mysqlpp::Exception& e) {
        SPDLOG_ERROR("MySQL++ exception: {}", e.what());
        return false;
    } catch (const std::exception& e) {
        SPDLOG_ERROR("Exception: {}", e.what());
        return false;
    }
}

bool MysqlDao::MakeFriends(const std::string& fromUid, const std::string& toUid)
{
    if (fromUid == toUid) {
        return false;
    }

    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::Transaction trans(*conn);
        // 添加好友应该是双向的,所以需要插入两条记录
        mysqlpp::Query query1 = conn->query();
        query1 << "INSERT INTO friends (self_id,friend_id) VALUES(%0q,%1q),"
               << "(%1q,%0q)";
        query1.parse();
        mysqlpp::SimpleResult res1 = query1.execute(std::stoi(fromUid), std::stoi(toUid));
        if (res1) {
            int affected_rows1 = res1.rows();
            if (affected_rows1 > 0) {
                trans.commit();
                SPDLOG_INFO("Friends added successfully for from_uid: {}, to_uid: {}", fromUid, toUid);
                return true;
            } else {
                trans.rollback();
                SPDLOG_WARN("Failed to add friends for from_uid: {}, to_uid: {}", fromUid, toUid);
                return false;
            }
        } else {
            trans.rollback();
            SPDLOG_ERROR("Failed to add friends for from_uid: {}, to_uid: {}", fromUid, toUid);
            return false;
        }
    } catch (const mysqlpp::Exception& e) {
        SPDLOG_ERROR("MySQL++ exception: {}", e.what());
        return false;
    } catch (const std::exception& e) {
        SPDLOG_ERROR("Exception: {}", e.what());
        return false;
    }
}

前端

我们对于系统和好友消息的展示主要是通过一个“滑动的列表实现的”。

就是以程序的主窗口(根窗口)为父亲,创建一个高度略小于主窗口高度的QWidget,当点击消息按钮的时候,这个组件从右侧滑出,点击其他的位置,组件fade out.

这个通知栏分为三部分:

  • 最上侧的标题
  • 中间的好友信息列表
  • 下侧的系统通知列表

每部分列表都是一个QListWidget,但是我们自定义了一个QWidget,插入的时候指定我们创建QWidget,自定义显示。

目前我们主要完成好友信息列表的前后端通信。

下面是通知栏的代码:

// h
#ifndef NOTIFICATIONPANEL_H
#define NOTIFICATIONPANEL_H

#include <QWidget>
#include <QObject>
#include <QGraphicsOpacityEffect>

class QListWidgetItem;
class QPropertyAnimation;
class QListWidget;
class FriendsNewsItem;
class SystemNewsItem;
struct UserInfo;
class NotificationPanel:public QWidget
{
    Q_OBJECT
public:
    explicit NotificationPanel(QWidget*parent = nullptr);
    ~NotificationPanel() = default;
    void addFriendNews(bool isReply,int uid,int sex,const QString &iconPath, const QString &name, const QString &content);
    void addSystemNews(bool isReply,int uid,const QString &iconPath, const QString &name, const QString &content);
    void showPanel();
    void hidePanel();
    void setupUI();
    void setupConnections();
    void checkIsEmpty();
signals:
    void on_unshow_red_dot();  // to TopChatArea::do_unshow_red_news();
    void on_show_red_dot(); // to TopChatArea::do_show_red_news();
public slots:
    void do_friend_accept(QListWidgetItem*item);
    void do_friend_reject(QListWidgetItem*item);
    void do_friend_confirm_clicked(QListWidgetItem*item);

    void do_system_accept(QListWidgetItem*item);
    void do_system_reject(QListWidgetItem*item);
    void do_system_confirm_clicked(QListWidgetItem*item);

    void do_get_apply_list(const std::vector<std::shared_ptr<UserInfo>>&list); // from TcpManager::on_get_apply_list
    void do_add_friend(const UserInfo&info); // from TcpManager::on_add_friend();
    void do_auth_friend(std::shared_ptr<UserInfo>info); // from TcpManager::on_auth_friend
    void do_message_to_list(const std::vector<std::shared_ptr<UserInfo>>&list);
    void do_notify_friend(std::shared_ptr<UserInfo>info,bool accept);   // from TcpManager::on_notify_friend
    void do_notify_friend2(std::shared_ptr<UserInfo>info,bool accept);   // from TcpManager::on_notify_friend2
private:
    QListWidget *friendsNews;
    QListWidget *systemNews;
    QPropertyAnimation *slideAnimation;
    QPropertyAnimation *fadeAnimation;
    QGraphicsOpacityEffect *opacityEffect;
    // QWidget interface
protected:
    void closeEvent(QCloseEvent *event);

    // QWidget interface
protected:
    void focusOutEvent(QFocusEvent *event);
};

#endif // NOTIFICATIONPANEL_H

// cpp
#include "notificationpanel.h"
#include "systemnewsitem.h"
#include "friendsnewsitem.h"
#include "../../../../Properties/global.h"
#include "../../../../tcpmanager.h"

#include <QLabel>
#include <QVBoxLayout>
#include <QPropertyAnimation>
#include <QListWidget>
#include <QCloseEvent>
#include <QApplication>


NotificationPanel::NotificationPanel(QWidget *parent)
    : QWidget(parent)
{
    setParent(window());
    setWindowFlags(Qt::FramelessWindowHint | Qt::Popup);
    setAttribute(Qt::WA_StyledBackground, true);

    if (parent) {
        setAttribute(Qt::WA_ShowWithoutActivating);
    }

    setupUI();
    setupConnections();
}

void NotificationPanel::addFriendNews(bool isReply, int uid, int sex,const QString &iconPath, const QString &name, const QString &content)
{
    emit on_show_red_dot();
    qDebug() << "iconPath" << iconPath;
    QString icon = (iconPath.isNull() || iconPath.isEmpty())?  ":/Resources/main/header-default.png":iconPath;
    FriendsNewsItem *itemWidget = new FriendsNewsItem(isReply,uid,sex,icon, name, content);
    QListWidgetItem*item = new QListWidgetItem;
    item->setSizeHint(itemWidget->sizeHint());

    friendsNews->addItem(item);
    friendsNews->setItemWidget(item,itemWidget);

    friendsNews->update();

    connect(itemWidget, &FriendsNewsItem::on_accepted_clicked, [this, item]() {
        do_friend_accept(item);
    });
    connect(itemWidget, &FriendsNewsItem::on_rejected_clicked, [this, item]() {
        do_friend_reject(item);
    });
    connect(itemWidget, &FriendsNewsItem::on_confirm_clicked, [this, item]() {
        do_friend_confirm_clicked(item);
    });

}

void NotificationPanel::addSystemNews(bool isReply, int uid, const QString &iconPath, const QString &name, const QString &content)
{
    emit on_show_red_dot();
    SystemNewsItem *itemWidget = new SystemNewsItem(isReply,uid,iconPath, name, content);
    QListWidgetItem*item = new QListWidgetItem;
    item->setSizeHint(itemWidget->sizeHint());

    systemNews->addItem(item);
    systemNews->setItemWidget(item,itemWidget);

    systemNews->update();

    connect(itemWidget, &SystemNewsItem::on_accepted_clicked, [this, item]() {
        do_system_accept(item);
    });
    connect(itemWidget, &SystemNewsItem::on_rejected_clicked, [this, item]() {
        do_system_reject(item);
    });
    connect(itemWidget, &SystemNewsItem::on_confirm_clicked, [this, item]() {
        do_system_confirm_clicked(item);
    });
}


void NotificationPanel::showPanel()
{
    if (parentWidget() != nullptr){
        QWidget*mainWindow = parentWidget();
        QRect parentR = mainWindow->geometry();

        QRect startRect = QRect(parentR.width(),10,width(),mainWindow->height() - 20);
        qDebug() << startRect;
        QRect endRect(parentR.width() - width() - 10,10,width(),mainWindow->height() - 20);
        setGeometry(startRect);

        friendsNews->setFixedHeight(height()/2-80);

        show();
        raise();
        slideAnimation->setStartValue(startRect);
        slideAnimation->setEndValue(endRect);
        slideAnimation->start();
    }
}

void NotificationPanel::hidePanel()
{
    fadeAnimation->setStartValue(1.0);
    fadeAnimation->setEndValue(0.0);
    fadeAnimation->start();
    connect(fadeAnimation, &QPropertyAnimation::finished, this, [this](){
        hide();
    });
}

void NotificationPanel::setupUI()
{
    QVBoxLayout *main_vlay = new QVBoxLayout(this);
    main_vlay->setContentsMargins(0,0,0,0);
    main_vlay->setSpacing(10);

    QLabel *title = new QLabel;
    title->setText("通知");
    QPalette palette = title->palette();
    palette.setColor(QPalette::WindowText,Qt::black);
    title->setPalette(palette);
    QFont font = title->font();
    font.setPointSize(16);
    font.setBold(true);
    title->setFont(font);
    title->setFixedHeight(40);
    title->setAlignment(Qt::AlignCenter);

    // 好友通知标签
    QLabel *friendLabel = new QLabel("好友请求");
    friendLabel->setFixedHeight(30);

    // 好友通知列表
    friendsNews = new QListWidget;
    friendsNews->setSpacing(1);

    // 系统通知标签
    QLabel *systemLabel = new QLabel("系统通知");
    systemLabel->setFixedHeight(30);

    // 系统通知列表
    systemNews = new QListWidget;
    systemNews->setSpacing(1);

    main_vlay->addWidget(title);
    main_vlay->addWidget(friendLabel);
    main_vlay->addWidget(friendsNews);
    main_vlay->addWidget(systemLabel);
    main_vlay->addWidget(systemNews);

    // 创建动画
    slideAnimation = new QPropertyAnimation(this,"geometry",this);
    slideAnimation->setEasingCurve(QEasingCurve::InOutCubic);
    slideAnimation->setDuration(100);

    opacityEffect = new QGraphicsOpacityEffect(this);
    opacityEffect->setOpacity(1.0);
    setGraphicsEffect(opacityEffect);   // 接管绘制

    fadeAnimation = new QPropertyAnimation(this);
    fadeAnimation->setEasingCurve(QEasingCurve::Linear);
    fadeAnimation->setTargetObject(opacityEffect);
    fadeAnimation->setDuration(100);

}

void NotificationPanel::setupConnections()
{
    connect(TcpManager::GetInstance().get(),&TcpManager::on_get_apply_list,this,&NotificationPanel::do_get_apply_list);
    connect(TcpManager::GetInstance().get(),&TcpManager::on_auth_friend,this,&NotificationPanel::do_auth_friend);
    connect(TcpManager::GetInstance().get(),&TcpManager::on_add_friend,this,&NotificationPanel::do_add_friend);
    connect(TcpManager::GetInstance().get(),&TcpManager::on_notify_friend,this,&NotificationPanel::do_notify_friend);
    connect(TcpManager::GetInstance().get(),&TcpManager::on_notify_friend2,this,&NotificationPanel::do_notify_friend2);
    connect(TcpManager::GetInstance().get(),&TcpManager::on_message_to_list,this,&NotificationPanel::do_message_to_list);
}

void NotificationPanel::checkIsEmpty()
{
    if (!friendsNews->count()&&!systemNews->count()){
        emit on_unshow_red_dot();
    }
}

void NotificationPanel::do_friend_accept(QListWidgetItem *item)
{
    int row = friendsNews->row(item);
    friendsNews->takeItem(row);
    delete item;
    checkIsEmpty();
}

void NotificationPanel::do_friend_reject(QListWidgetItem *item)
{
    int row = friendsNews->row(item);
    friendsNews->takeItem(row);
    delete item;
    checkIsEmpty();
}

void NotificationPanel::do_friend_confirm_clicked(QListWidgetItem *item)
{
    int row = friendsNews->row(item);
    friendsNews->takeItem(row);
    delete item;
    checkIsEmpty();
}

void NotificationPanel::do_system_accept(QListWidgetItem *item)
{
    int row = systemNews->row(item);
    systemNews->takeItem(row);
    delete item;
    checkIsEmpty();
}

void NotificationPanel::do_system_reject(QListWidgetItem *item)
{
    int row = systemNews->row(item);
    systemNews->takeItem(row);
    delete item;
    checkIsEmpty();
}

void NotificationPanel::do_system_confirm_clicked(QListWidgetItem *item)
{
    int row = systemNews->row(item);
    systemNews->takeItem(row);
    delete item;
    checkIsEmpty();
}

void NotificationPanel::do_auth_friend(std::shared_ptr<UserInfo> info)
{
    addFriendNews(false,info->id,info->sex,info->avatar,info->name,"😄向您发来好友申请😄");
}

void NotificationPanel::do_message_to_list(const std::vector<std::shared_ptr<UserInfo> > &list)
{
    for(auto&item:list){
        addFriendNews(true,item->id,-1,item->avatar,"时间"+item->back,item->desc);
    }
}

void NotificationPanel::do_notify_friend(std::shared_ptr<UserInfo> info, bool accept)
{
    if (accept)
        addFriendNews(true,info->id,info->sex,info->avatar,info->name,"😄双方已建立好友关系!😄");
}

void NotificationPanel::do_notify_friend2(std::shared_ptr<UserInfo> info, bool accept)
{
    if (accept)
        addFriendNews(true,info->id,info->sex,info->avatar,info->name,"😄对方同意了您的好友申请😄");
    else
        addFriendNews(true,info->id,info->sex,info->avatar,info->name,"😢对方拒绝了您的好友请求😢");
}

void NotificationPanel::do_get_apply_list(const std::vector<std::shared_ptr<UserInfo>>&list)
{

    for(const auto&apply:list){
        addFriendNews(false,apply->id,apply->sex,apply->avatar,apply->name,"😄向您发来好友申请😄");
    }
}

void NotificationPanel::do_add_friend(const UserInfo &info)
{
    /*addFriendNews(bool isReply, int uid, int sex,
     * const QString &iconPath, const QString &name, const QString &content) */
    addFriendNews(true,info.id,-1,":/Resources/main/header-default.png","好友申请通知",QString("😄向用户 %1 的请求已发出😄").arg(info.id));
}


void NotificationPanel::closeEvent(QCloseEvent *event)
{
    hidePanel();
    event->ignore();
}

void NotificationPanel::focusOutEvent(QFocusEvent *event)
{
    Q_UNUSED(event);
}

下面是FriendsNewsItem的实现,根据消息的类别,如果是纯通知类型的(isReply == true)就只有一个按钮(借用AcceptButton),如果是请求类型的(isReply == false),两个按钮,接受和拒绝。两种不同的情况,AcceptButton绑定不同的槽函数:

// h
#ifndef FRIENDSNEWSITEM_H
#define FRIENDSNEWSITEM_H

#include <QObject>
#include <QWidget>

class QLabel;
class QPushButton;

class FriendsNewsItem : public QWidget
{
    Q_OBJECT
public:
    explicit FriendsNewsItem(bool isReply,int uid,int sex,const QString&iconPath,const QString&name,const QString&content,QWidget *parent = nullptr);
private:
    void setupUI();
    void setConnections();
private:
    QLabel *iconLabel;
    QLabel *nameLabel;
    QLabel *contentLabel;
    QPushButton *acceptButton;
    QPushButton *rejectButton;

    int _uid;
    bool _isRely; // 判断是好友申请还是申请回复,申请需要两个按钮,回复只需要一个按钮:使用acceptButton代替确认
    int _sex;
    QString _icon;
private slots:
    void do_accept_clicked();
    void do_reject_clcked();

signals:
    void on_accepted_clicked();
    void on_rejected_clicked();
    void on_confirm_clicked();
};

#endif // FRIENDSNEWSITEM_H


// cpp
#include "friendsnewsitem.h"
#include <QJsonObject>
#include <QLabel>
#include <QPainter>
#include <QPainterPath>
#include <QPushButton>
#include <QVBoxLayout>
#include "../../../../usermanager.h"
#include "../../../../Properties/sourcemanager.h"
#include "../../../../tcpmanager.h"


FriendsNewsItem::FriendsNewsItem(bool isReply,int uid,int sex,const QString &iconPath, const QString &name, const QString &content, QWidget *parent)
    : QWidget(parent)
    , _uid(uid)
    , _isRely(isReply)
    , _sex(sex)
    , _icon(iconPath)

{
    setupUI();
    setConnections();

    nameLabel->setText(name);
    contentLabel->setText(content);



    QPixmap originalPixmap;
    // 创建带边框的圆形图片
    if (_icon.startsWith(":/")){
        originalPixmap = QPixmap(_icon);
    }else{
        QByteArray imageData = QByteArray::fromBase64(_icon.toUtf8());
        originalPixmap.loadFromData(imageData);
    }

    QPixmap finalPixmap(44, 44);
    finalPixmap.fill(Qt::transparent);

    QPainter painter(&finalPixmap);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setRenderHint(QPainter::SmoothPixmapTransform);

    // 1. 先绘制边框
    QColor borderColor = (_sex == 1) ? QColor("#00F5FF") : QColor("#FF69B4");
    painter.setBrush(borderColor);
    painter.setPen(Qt::NoPen);
    painter.drawEllipse(0, 0, 44, 44);

    // 2. 绘制背景
    painter.setBrush(QColor("#E3F2FD"));
    painter.drawEllipse(2, 2, 40, 40);  // 边框内部

    // 3. 裁剪并绘制头像
    QPainterPath clipPath;
    clipPath.addEllipse(2, 2, 40, 40);  // 头像区域
    painter.setClipPath(clipPath);
    painter.drawPixmap(2, 2, 40, 40, originalPixmap.scaled(40, 40, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation));

    iconLabel->setPixmap(finalPixmap);
}

void FriendsNewsItem::setupUI()
{
    setFixedSize({200,100});

    QVBoxLayout*main_vlay = new QVBoxLayout(this);
    main_vlay->setContentsMargins(0,10,0,10);
    main_vlay->setSpacing(0);

    iconLabel = new QLabel;
    iconLabel->setFixedSize(44,44);
    iconLabel->setAlignment(Qt::AlignCenter);

    // 名称+内容
    QVBoxLayout*text_vlay = new QVBoxLayout;
    text_vlay->setContentsMargins(0,0,0,0);

    nameLabel = new QLabel;
    nameLabel->setObjectName("FriendsNewItem_NameLabel");

    contentLabel = new QLabel;
    contentLabel->setObjectName("FriendsNewsItem_ContentLabel");
    text_vlay->addWidget(nameLabel);
    text_vlay->addWidget(contentLabel);

    QHBoxLayout*top_hlay = new QHBoxLayout;
    top_hlay->setContentsMargins(0,0,0,0);
    top_hlay->addWidget(iconLabel);
    top_hlay->addLayout(text_vlay);


    QHBoxLayout*button_hlay = new QHBoxLayout;
    button_hlay->setContentsMargins(0,0,0,0);
    button_hlay->setSpacing(10);
    if (_isRely){
        acceptButton = new QPushButton;
        acceptButton->setObjectName("FriendsNewsItem_AcceptButton");
        acceptButton->setFixedSize(90,30);
        acceptButton->setText("确认");
        button_hlay->addWidget(acceptButton,Qt::AlignCenter);
        button_hlay->addStretch();
    }else{
        acceptButton = new QPushButton;
        acceptButton->setObjectName("FriendsNewsItem_AcceptButton");
        acceptButton->setFixedSize(90,30);
        acceptButton->setText("接受");

        rejectButton = new QPushButton;
        rejectButton->setObjectName("FriendsNewsItem_RejectButton");
        rejectButton->setFixedSize(90,30);
        rejectButton->setText("拒绝");

        button_hlay->addWidget(acceptButton);
        button_hlay->addWidget(rejectButton);
    }


    main_vlay->addLayout(top_hlay);
    main_vlay->addLayout(button_hlay);
}

void FriendsNewsItem::setConnections()
{
    if (_isRely){
        connect(acceptButton,&QPushButton::clicked,this,&FriendsNewsItem::do_accept_clicked);
    }else{
        connect(acceptButton,&QPushButton::clicked,this,&FriendsNewsItem::do_accept_clicked);
        connect(rejectButton,&QPushButton::clicked,this,&FriendsNewsItem::do_reject_clcked);
    }
}

void FriendsNewsItem::do_accept_clicked()
{
    if (_isRely){
        emit on_confirm_clicked();
        QJsonObject jsonObj;
        jsonObj["from_uid"] = UserManager::GetInstance()->GetUid();
        jsonObj["reply"] = true;
        QJsonDocument doc(jsonObj);
        TcpManager::GetInstance()->do_send_data(RequestType::ID_AUTH_FRIEND_REQ,doc.toJson(QJsonDocument::Compact));

    }else{
        emit on_accepted_clicked(); // 提示消除item
        // 下面回复请求为接受
        QJsonObject jsonObj;
        jsonObj["from_uid"] = UserManager::GetInstance()->GetUid();
        jsonObj["to_uid"] = _uid;
        jsonObj["from_name"] = UserManager::GetInstance()->GetName();
        jsonObj["from_sex"] = UserManager::GetInstance()->GetSex();
        jsonObj["from_icon"] =UserManager::GetInstance()->GetIcon();
        jsonObj["accept"] = true;
        QJsonDocument doc(jsonObj);
        TcpManager::GetInstance()->do_send_data(RequestType::ID_AUTH_FRIEND_REQ,doc.toJson(QJsonDocument::Compact));
    }
}

void FriendsNewsItem::do_reject_clcked()
{
    emit on_rejected_clicked();
    // 下面回复请求为拒绝
    QJsonObject jsonObj;
    jsonObj["from_uid"] = UserManager::GetInstance()->GetUid();
    jsonObj["to_uid"] = _uid;
    jsonObj["from_name"] = UserManager::GetInstance()->GetName();
    jsonObj["from_sex"] = UserManager::GetInstance()->GetSex();
    jsonObj["accept"] = false;
    QJsonDocument doc(jsonObj);
    TcpManager::GetInstance()->do_send_data(RequestType::ID_AUTH_FRIEND_REQ,doc.toJson(QJsonDocument::Compact));
}

SystemNewsItem同理:

// h
#ifndef SYSTEMNEWSITEM_H
#define SYSTEMNEWSITEM_H

#include <QObject>
#include <QWidget>

class QLabel;
class QPushButton;

class SystemNewsItem : public QWidget
{
    Q_OBJECT
public:
    explicit SystemNewsItem(bool isReply,int uid,const QString&iconPath,const QString&name,const QString&content,QWidget *parent = nullptr);
private:
    void setupUI();
    void setConnections();
private:
    QLabel *iconLabel;
    QLabel *nameLabel;
    QLabel *contentLabel;
    QPushButton *acceptButton;
    QPushButton *rejectButton;

    int _uid;
    bool _isRely;
private slots:
    void do_accept_clicked();
    void do_reject_clcked();

signals:
    void on_accepted_clicked();
    void on_rejected_clicked();
    void on_confirm_clicked();
};

#endif // SYSTEMNEWSITEM_H

// cpp
#include "systemnewsitem.h"
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include "../../../../Properties/sourcemanager.h"


SystemNewsItem::SystemNewsItem(bool isReply,int uid,const QString &iconPath, const QString &name, const QString &content, QWidget *parent)
    : QWidget(parent)
    , _uid(uid)
    , _isRely(isReply)

{
    setupUI();
    setConnections();

    nameLabel->setText(name);
    contentLabel->setText(content);
    iconLabel->setPixmap(SourceManager::GetInstance()->getPixmap(iconPath).scaled(40,40));
}

void SystemNewsItem::setupUI()
{
    setFixedSize({200,100});

    QVBoxLayout*main_vlay = new QVBoxLayout(this);
    main_vlay->setContentsMargins(0,10,0,10);
    main_vlay->setSpacing(0);

    iconLabel = new QLabel;
    iconLabel->setFixedSize(40,40);
    iconLabel->setObjectName("FriendsNewItem_IconLabel");

    iconLabel->setAlignment(Qt::AlignCenter);

    // 名称+内容
    QVBoxLayout*text_vlay = new QVBoxLayout;
    text_vlay->setContentsMargins(0,0,0,0);

    nameLabel = new QLabel;
    nameLabel->setObjectName("FriendsNewItem_NameLabel");

    contentLabel = new QLabel;
    contentLabel->setObjectName("FriendsNewsItem_ContentLabel");
    text_vlay->addWidget(nameLabel);
    text_vlay->addWidget(contentLabel);

    QHBoxLayout*top_hlay = new QHBoxLayout;
    top_hlay->setContentsMargins(0,0,0,0);
    top_hlay->addWidget(iconLabel);
    top_hlay->addLayout(text_vlay);


    QHBoxLayout*button_hlay = new QHBoxLayout;
    button_hlay->setContentsMargins(0,0,0,0);
    button_hlay->setSpacing(10);
    if (_isRely){
        acceptButton = new QPushButton;
        acceptButton->setObjectName("FriendsNewsItem_AcceptButton");
        acceptButton->setFixedSize(90,30);
        button_hlay->addWidget(acceptButton,Qt::AlignCenter);
        button_hlay->addStretch();
    }else{
        acceptButton = new QPushButton;
        acceptButton->setObjectName("FriendsNewsItem_AcceptButton");
        acceptButton->setFixedSize(90,30);

        rejectButton = new QPushButton;
        rejectButton->setObjectName("FriendsNewsItem_RejectButton");
        rejectButton->setFixedSize(90,30);

        button_hlay->addWidget(acceptButton);
        button_hlay->addWidget(rejectButton);
    }


    main_vlay->addLayout(top_hlay);
    main_vlay->addLayout(button_hlay);
}

void SystemNewsItem::setConnections()
{
    if (_isRely){
        connect(acceptButton,&QPushButton::clicked,this,&SystemNewsItem::do_accept_clicked);
    }else{
        connect(acceptButton,&QPushButton::clicked,this,&SystemNewsItem::do_accept_clicked);
        connect(rejectButton,&QPushButton::clicked,this,&SystemNewsItem::do_reject_clcked);
    }
}

void SystemNewsItem::do_accept_clicked()
{
    if (_isRely){
        emit on_confirm_clicked();
    }else{
        emit on_accepted_clicked();
        // tcp发送消息TODO:
    }
}

void SystemNewsItem::do_reject_clcked()
{
    emit on_rejected_clicked();
    // tcp发送消息TODO:
}

在其中有很多的信号,来回传送,比较繁多,不再一一列举吗可以借助TcpManager头文件的信号的注释进行分析:

signals:
    void on_connect_success(bool success); // to LoginScreen::do_connect_success
    void on_send_data(RequestType requestType,QByteArray data); // to TcpManager::do_send_data
    void on_switch_interface(); // to MainWindow::[](){}
    void on_login_failed(int);  // to LoginScreen::do_login_failed
    void on_users_searched(QList<std::shared_ptr<UserInfo>>list);   // to AnimatedSearchBox::do_users_searched
    void on_add_friend(const UserInfo&info); // to NotifycationPanel::do_add_friend
    void on_auth_friend(std::shared_ptr<UserInfo>info); // to NotificationPanel::do_auth_friend ; TopChatArea::do_show_red_dot
    void on_get_apply_list(const std::vector<std::shared_ptr<UserInfo>>&list); // to NotificationPanel::do_get_apply_list;
    void on_add_friend_to_list(std::shared_ptr<UserInfo>); // to Friend
    void on_message_to_list(const std::vector<std::shared_ptr<UserInfo>>&list);// to NotificationPanel::do_message_to_list
    void on_notify_friend(std::shared_ptr<UserInfo>info,bool accept);   // to NotificationPanel::do_notify_friend
    void on_notify_friend2(std::shared_ptr<UserInfo>info,bool accept);   // to_NotificationPanel::do_notify_friend2

下面是TcpManager的注册的回调函数,接受解析出一个信息或者一个列表,再将其通过信号传递给NotificationsPanel的槽函数进行展示:


    /**
     * @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();
      // TODO:
      qDebug() << "申请添加好友成功";
      emit on_add_friend(info);
  };

    /**
     * @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;

        emit on_auth_friend(user_info);
    };

    /**
     * @brief 用户请求添加好友通知回包
     */
    _handlers[RequestType::ID_AUTH_FRIEND_RSP] = [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;
        }
        // 接受信息如果成功,然后将对方信息加入好友列表
        if (jsonObj.contains("ok") && jsonObj["ok"].toBool()){
            auto info = std::make_shared<UserInfo>();
            info->id = jsonObj["to_uid"].toInt();
            info->status = jsonObj["to_status"].toInt();
            info->sex = jsonObj["to_sex"].toInt();
            info->name = jsonObj["to_name"].toString();
            info->avatar = jsonObj["to_icon"].toString();
            info->desc = jsonObj["to_desc"].toString();
            info->back = jsonObj["to_message"].toString();
            emit on_add_friend_to_list(info);
            emit on_notify_friend(info,jsonObj["accept"].toBool());
        }else{
            //TODO: 暂时忽略
        }
    };

    _handlers[RequestType::ID_NOTIFY_AUTH_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;
        }

        auto info = std::make_shared<UserInfo>();
        info->id = jsonObj["from_uid"].toInt();
        info->name = jsonObj["from_name"].toString();
        info->sex = jsonObj["from_sex"].toInt();
        info->avatar = jsonObj["from_icon"].toString();
        info->status = jsonObj["from_status"].toInt();
        info->desc = jsonObj["message"].toString();     // 临时存放消息
        if (jsonObj["type"].toInt() == static_cast<int>(NotificationCodes::ID_NOTIFY_MAKE_FRIENDS)){
            emit on_add_friend_to_list(info);
            emit on_notify_friend2(info,true);
        }else{
            emit on_notify_friend2(info,false);
        }

    };

后端

我们添加了新的回调ID_AUTH_FRIEND_REQ,以及修改和补充了之前的ID_ADD_FRIEND_REQ/ID_CHAT_LOGIN

修改

ID_CHAT_LOGIN

在用户登陆的时候,我们返回给用户一些必要的信息,比如持久化的通知,好友申请,更新状态等等:

// 将登陆人的状态信息改变为1
std::string key = USER_STATUS_PREFIX + uid_str;
RedisManager::GetInstance()->Set(key, "1");

// 登陆成功,通知所有在线好友
// TODO:

jj["uid"] = uid;
jj["name"] = user_info->name;
jj["email"] = user_info->email;
jj["nick"] = user_info->nick;
jj["sex"] = user_info->sex;
jj["desc"] = user_info->desc;
jj["icon"] = user_info->icon;
jj["token"] = token;

// 获取申请列表
std::vector<std::shared_ptr<UserInfo>> apply_list;
bool b_apply = MysqlManager::GetInstance()->GetFriendApplyList(uid_str, apply_list);
if (b_apply && apply_list.size() > 0) {
    // 我们这里规定哪怕数据库操作成功,但是没有数据也算失败,就直接跳过,避免多余判断。
    json apply_friends;
    for (auto& apply_user : apply_list) {
        json apply_friend;
        apply_friend["uid"] = apply_user->uid;
        apply_friend["name"] = apply_user->name;
        apply_friend["email"] = apply_user->email;
        apply_friend["icon"] = apply_user->icon;
        apply_friend["sex"] = apply_user->sex;
        apply_friend["desc"] = apply_user->desc;
        apply_friend["back"] = apply_user->back; // 时间
        apply_friends.push_back(apply_friend);
    }
    jj["apply_friends"] = apply_friends;
}
// 获取通知列表
std::vector<std::shared_ptr<UserInfo>> notification_list;
bool b_notify = MysqlManager::GetInstance()->GetNotificationList(uid_str, notification_list);
if (b_notify && notification_list.size() > 0) {
    json notifications;
    for (auto& notification : notification_list) {
        json item;
        item["uid"] = notification->uid;
        item["type"] = notification->status; // 用status代表type借用UserInfo的结构。
        item["message"] = notification->desc; // 用desc代表message借用UserInfo的结构。
        item["time"] = notification->back; // 备用字段表示时间。
        notifications.push_back(item);
    }
    jj["notifications"] = notifications;
}

// 获取消息列表

// 获取好友列表

// 更新登陆数量
auto server_name = ConfigManager::GetInstance()["SelfServer"]["name"];
auto count_str = RedisManager::GetInstance()->HGet(LOGIN_COUNT_PREFIX, server_name);
int count = 0;
if (!count_str.empty()) {
    count = std::stoi(count_str);
}
count++;
count_str = std::to_string(count);
RedisManager::GetInstance()->HSet(LOGIN_COUNT_PREFIX, server_name, count_str);

// session绑定uid
session->SetUid(uid);
// 绑定连接的服务器名称和用户uid
std::string ip_key = USERIP_PREFIX + std::to_string(uid);
RedisManager::GetInstance()->Set(ip_key, server_name);
// uid和session绑定管理,方便之后踢人
UserManager::GetInstance()->SetUserSession(uid, session);
// 设置用户状态在线
std::string status_key = USER_STATUS_PREFIX + uid_str;
RedisManager::GetInstance()->Set(status_key, "1");

ID_ADD_FRIEND_REQ

我们增加了如果双方互发还有申请,直接成为好友同时发送通知。其次是如果通知用户在线就直接发送,否则就数据库存储,下次发送

_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 apply_each = MysqlManager::GetInstance()->CheckApplied(std::to_string(toUid), std::to_string(fromUid));
        if (apply_each) {
            json jj;
            jj["error"] = ErrorCodes::SUCCESS;
            jj["from_uid"] = fromUid;
            jj["from_name"] = fromName;
            jj["from_sex"] = fromSex;
            jj["from_icon"] = fromIcon;
            std::string key;
            bool b_get = RedisManager::GetInstance()->Get(USER_STATUS_PREFIX + std::to_string(fromUid), key);
            if (b_get) {
                jj["from_status"] = std::stoi(key);
            } else {
                jj["from_status"] = 0;
            }
            jj["ok"] = true; // 标记成功
            MysqlManager::GetInstance()->AddNotification(uid_str, static_cast<int>(NotificationCodes::ID_NOTIFY_MAKE_FRIENDS), "成功和" + fromName + "成为好友");
            // 给对方发送请求信息
            auto& cfg = ConfigManager::GetInstance();
            auto self_name = cfg["SelfServer"]["name"];

            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) {
                if (to_ip_value == self_name) {
                    auto session2 = UserManager::GetInstance()->GetSession(toUid);
                    if (session2) {
                        SPDLOG_INFO("FROM UID:{},to:{}", fromUid, toUid);
                        session2->Send(jj.dump(), static_cast<int>(MsgId::ID_NOTIFY_AUTH_FRIEND_REQ));
                    }
                    return;
                } else {
                    NotifyMakeFriendsRequest req;
                    req.set_fromuid(fromUid);
                    req.set_touid(toUid);
                    req.set_fromname(fromName);
                    req.set_fromsex(fromSex);
                    req.set_fromicon(fromIcon);
                    req.set_type(static_cast<int>(NotificationCodes::ID_NOTIFY_MAKE_FRIENDS));
                    req.set_message("成功和" + fromName + "成为好友");
                    ChatGrpcClient::GetInstance()->NotifyMakeFriends(to_ip_value, req);
                }
            } else {
                // 这里没有查询到,不发送无妨。因为已经存入数据库,用户登录就可以直接获取。
                return;
            }
        }

        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;
        }
        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);
    };

添加

ID_AUTH_FRIEND_REQ

被申请人点击同意或者拒绝之后,首先将好友信息或者申请信息存储在数据库中(比如是否成为好友,申请请求处理状态等,一般是0未处理,1已处理),同时检测申请人目前是否在线,在线直接发送,否则将通知存储在数据库。最后还要将处理结果发送给被申请人,消息处理ok了,被申请人只要点击确认即可。同理申请人上线接收到或直接接收到通知后,点击确认表示受到信息,这时候将Notifications的信息状态置为1.

_function_callbacks[MsgId::ID_AUTH_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;
    j["ok"] = false; // 标记失败

    if (bool b = j.value("reply", false)) {
        if (b) {
            // 只是收到通知回复,我们把数据库状态更新一下
            // 如果失败说明当前双方都在线,消息就没有入库,所以这里不做处理。
            auto fromUid = j["from_uid"].get<int>();
            bool ok1 = MysqlManager::GetInstance()->ChangeMessageStatus(std::to_string(fromUid), 1);
            return;
        }
    }

    Defer defer([this, &j, session]() {
        // 这是给fromUid的回复信息
        // 目地是如果同意,那么就返回好友的信息
        session->Send(j.dump(), static_cast<int>(MsgId::ID_AUTH_FRIEND_RSP));
    });

    auto toUid = j["to_uid"].get<int>();
    auto fromUid = j["from_uid"].get<int>();
    auto fromName = j["from_name"].get<std::string>();
    auto fromSex = j["from_sex"].get<int>();
    auto fromIcon = j["from_icon"].get<std::string>();
    int fromStatus = 1;

    bool accept = j["accept"].get<bool>();
    // 不需要解析其他的信息,只需要按需发给对方即可
    // fromUid接受或者拒绝,服务器回复给toUid
    std::string base_key = USER_BASE_INFO_PREFIX + std::to_string(toUid);
    auto apply_info = std::make_shared<UserInfo>();
    bool b_info = GetBaseInfo(base_key, toUid, apply_info);
    if (!b_info) {
        j["ok"] = true;
        // 发送请求的用户不在线,所以数据库持久存储
        if (!accept) {
            MysqlManager::GetInstance()->AddNotification(std::to_string(toUid), static_cast<int>(NotificationCodes::ID_NOTIFY_NOT_FRIENDS), "😭" + fromName + "拒绝了您的好友申请😭");
        } else {
            MysqlManager::GetInstance()->AddNotification(std::to_string(toUid), static_cast<int>(NotificationCodes::ID_NOTIFY_MAKE_FRIENDS), "😄" + fromName + "同意了您的好友申请😄");
        }
        return;
    } else {
        j["to_uid"] = apply_info->uid;
        j["to_sex"] = apply_info->sex;
        j["to_status"] = apply_info->status;
        j["to_name"] = apply_info->name;
        j["to_email"] = apply_info->email;
        j["to_icon"] = apply_info->icon;
        j["to_desc"] = apply_info->desc;
        j["to_meseage"] = apply_info->back; // 备用字段,用来展示最近消息
        j["ok"] = true;
        if (!accept) {
            j["type"] = static_cast<int>(NotificationCodes::ID_NOTIFY_NOT_FRIENDS);
        } else {
            j["type"] = static_cast<int>(NotificationCodes::ID_NOTIFY_MAKE_FRIENDS);
        }
    }
    if (accept) {
        bool ok1 = MysqlManager::GetInstance()->ChangeApplyStatus(std::to_string(toUid), std::to_string(fromUid), 1);
        bool ok2 = MysqlManager::GetInstance()->MakeFriends(std::to_string(toUid), std::to_string(fromUid));
        // 接下来就是获取好友信息,发送给被申请人
    } else {
        MysqlManager::GetInstance()->ChangeApplyStatus(std::to_string(toUid), std::to_string(fromUid), -1);
    }

    // TODO:接下来就是发送给申请人,也就是将from_uid的信息发送给to_uid
    std::string to_key = USERIP_PREFIX + std::to_string(toUid);
    std::string to_ip_value;
    bool b_ip = RedisManager::GetInstance()->Get(to_key, to_ip_value);
    if (!b_ip) {
        // 不存在我们就需要加入mysqk持续等待下次用户登录处理
        bool ok = MysqlManager::GetInstance()->AddNotification(std::to_string(toUid), static_cast<int>(NotificationCodes::ID_NOTIFY_MAKE_FRIENDS), "😄" + fromName + "已经和您成为好友😄");
        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());
            session2->Send(j.dump(), static_cast<int>(MsgId::ID_NOTIFY_AUTH_FRIEND_REQ));
        }
    } else {
        NotifyMakeFriendsRequest req;
        req.set_fromuid(fromUid);
        req.set_touid(toUid);
        req.set_fromname(fromName);
        req.set_fromsex(fromSex);
        req.set_fromicon(fromIcon);
        req.set_fromstatus(fromStatus);
        if (!accept) {
            req.set_type(static_cast<int>(NotificationCodes::ID_NOTIFY_NOT_FRIENDS));
            req.set_message(fromName + "拒绝了你的好友申请");
        } else {
            req.set_type(static_cast<int>(NotificationCodes::ID_NOTIFY_MAKE_FRIENDS));
            req.set_message(fromName + "同意了您的好友申请");
        }
        ChatGrpcClient::GetInstance()->NotifyMakeFriends(to_ip_value, req);
    }
};
posted @ 2025-12-24 23:18  大胖熊哈  阅读(2)  评论(0)    收藏  举报