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

浙公网安备 33010602011771号