聊天项目(28) 分布式服务通知好友申请

简介

本文介绍如何实现用户查找和好友申请功能。查找和申请好友会涉及前后端通信和rpc服务间调用。所以目前先从客户端入手,搜索用户后发送查找好友申请请求给服务器,服务器收到后判断是否存在,如果不存在则显示未找到,如果存在则显示查找到的结果

点击查询

客户端点击搜索列表的添加好友item后,先弹出一个模态对话框,上面有loading动作表示加载,直到服务器返回结果

  1. void SearchList::slot_item_clicked(QListWidgetItem *item)
  2. {
  3. QWidget *widget = this->itemWidget(item); //获取自定义widget对象
  4. if(!widget){
  5. qDebug()<< "slot item clicked widget is nullptr";
  6. return;
  7. }
  8. // 对自定义widget进行操作, 将item 转化为基类ListItemBase
  9. ListItemBase *customItem = qobject_cast<ListItemBase*>(widget);
  10. if(!customItem){
  11. qDebug()<< "slot item clicked widget is nullptr";
  12. return;
  13. }
  14. auto itemType = customItem->GetItemType();
  15. if(itemType == ListItemType::INVALID_ITEM){
  16. qDebug()<< "slot invalid item clicked ";
  17. return;
  18. }
  19. if(itemType == ListItemType::ADD_USER_TIP_ITEM){
  20. if(_send_pending){
  21. return;
  22. }
  23. if (!_search_edit) {
  24. return;
  25. }
  26. waitPending(true);
  27. auto search_edit = dynamic_cast<CustomizeEdit*>(_search_edit);
  28. auto uid_str = search_edit->text();
  29. QJsonObject jsonObj;
  30. jsonObj["uid"] = uid_str;
  31. QJsonDocument doc(jsonObj);
  32. QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
  33. emit TcpMgr::GetInstance()->sig_send_data(ReqId::ID_SEARCH_USER_REQ,
  34. jsonData);
  35. return;
  36. }
  37. //清楚弹出框
  38. CloseFindDlg();
  39. }

_send_pending为新增的成员变量,如果为true则表示发送阻塞.构造函数中将其设置为false。

waitPending函数为根据pending状态展示加载框

  1. void SearchList::waitPending(bool pending)
  2. {
  3. if(pending){
  4. _loadingDialog = new LoadingDlg(this);
  5. _loadingDialog->setModal(true);
  6. _loadingDialog->show();
  7. _send_pending = pending;
  8. }else{
  9. _loadingDialog->hide();
  10. _loadingDialog->deleteLater();
  11. _send_pending = pending;
  12. }
  13. }

当我们发送数据后服务器会处理,返回ID_SEARCH_USER_RSP包,所以客户端要实现对ID_SEARCH_USER_RSP包的处理

  1. _handlers.insert(ID_SEARCH_USER_RSP, [this](ReqId id, int len, QByteArray data){
  2. Q_UNUSED(len);
  3. qDebug()<< "handle id is "<< id << " data is " << data;
  4. // 将QByteArray转换为QJsonDocument
  5. QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
  6. // 检查转换是否成功
  7. if(jsonDoc.isNull()){
  8. qDebug() << "Failed to create QJsonDocument.";
  9. return;
  10. }
  11. QJsonObject jsonObj = jsonDoc.object();
  12. if(!jsonObj.contains("error")){
  13. int err = ErrorCodes::ERR_JSON;
  14. qDebug() << "Login Failed, err is Json Parse Err" << err ;
  15. emit sig_login_failed(err);
  16. return;
  17. }
  18. int err = jsonObj["error"].toInt();
  19. if(err != ErrorCodes::SUCCESS){
  20. qDebug() << "Login Failed, err is " << err ;
  21. emit sig_login_failed(err);
  22. return;
  23. }
  24. auto search_info = std::make_shared<SearchInfo>(jsonObj["uid"].toInt(),
  25. jsonObj["name"].toString(), jsonObj["nick"].toString(),
  26. jsonObj["desc"].toString(), jsonObj["sex"].toInt(), jsonObj["icon"].toString());
  27. emit sig_user_search(search_info);
  28. });

将搜索到的结果封装为search_info发送给SearchList类做展示, search_list中连接信号和槽

  1. //连接搜索条目
  2. connect(TcpMgr::GetInstance().get(), &TcpMgr::sig_user_search, this, &SearchList::slot_user_search);

slot_user_search槽函数弹出搜索结果

  1. void SearchList::slot_user_search(std::shared_ptr<SearchInfo> si)
  2. {
  3. waitPending(false);
  4. if(si == nullptr){
  5. _find_dlg = std::make_shared<FindFailDlg>(this);
  6. }else{
  7. //此处分两种情况,一种是搜多到已经是自己的朋友了,一种是未添加好友
  8. //查找是否已经是好友 todo...
  9. _find_dlg = std::make_shared<FindSuccessDlg>(this);
  10. std::dynamic_pointer_cast<FindSuccessDlg>(_find_dlg)->SetSearchInfo(si);
  11. }
  12. _find_dlg->show();
  13. }

FindSuccessDlg是找到的结果展示,FindFailDlg是未找到结果展示。以下为FindSuccessDlg的ui布局

https://cdn.llfc.club/1722655438089.jpg

具体声明如下

  1. class FindSuccessDlg : public QDialog
  2. {
  3. Q_OBJECT
  4. public:
  5. explicit FindSuccessDlg(QWidget *parent = nullptr);
  6. ~FindSuccessDlg();
  7. void SetSearchInfo(std::shared_ptr<SearchInfo> si);
  8. private:
  9. Ui::FindSuccessDlg *ui;
  10. std::shared_ptr<SearchInfo> _si;
  11. QWidget * _parent;
  12. private slots:
  13. void on_add_friend_btn_clicked();
  14. };

具体实现如下

  1. FindSuccessDlg::FindSuccessDlg(QWidget *parent) :
  2. QDialog(parent), _parent(parent),
  3. ui(new Ui::FindSuccessDlg)
  4. {
  5. ui->setupUi(this);
  6. // 设置对话框标题
  7. setWindowTitle("添加");
  8. // 隐藏对话框标题栏
  9. setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
  10. // 获取当前应用程序的路径
  11. QString app_path = QCoreApplication::applicationDirPath();
  12. QString pix_path = QDir::toNativeSeparators(app_path +
  13. QDir::separator() + "static"+QDir::separator()+"head_1.jpg");
  14. QPixmap head_pix(pix_path);
  15. head_pix = head_pix.scaled(ui->head_lb->size(),
  16. Qt::KeepAspectRatio, Qt::SmoothTransformation);
  17. ui->head_lb->setPixmap(head_pix);
  18. ui->add_friend_btn->SetState("normal","hover","press");
  19. this->setModal(true);
  20. }
  21. FindSuccessDlg::~FindSuccessDlg()
  22. {
  23. qDebug()<<"FindSuccessDlg destruct";
  24. delete ui;
  25. }
  26. void FindSuccessDlg::SetSearchInfo(std::shared_ptr<SearchInfo> si)
  27. {
  28. ui->name_lb->setText(si->_name);
  29. _si = si;
  30. }
  31. void FindSuccessDlg::on_add_friend_btn_clicked()
  32. {
  33. //todo... 添加好友界面弹出
  34. this->hide();
  35. //弹出加好友界面
  36. auto applyFriend = new ApplyFriend(_parent);
  37. applyFriend->SetSearchInfo(_si);
  38. applyFriend->setModal(true);
  39. applyFriend->show();
  40. }

类似的FindFailDlg也是这种思路,大家自己实现即可。

服务器查询逻辑

chatserver服务器要根据客户端发送过来的用户id进行查找,chatserver服务器需先注册ID_SEARCH_USER_REQ和回调函数

  1. void LogicSystem::RegisterCallBacks() {
  2. _fun_callbacks[MSG_CHAT_LOGIN] = std::bind(&LogicSystem::LoginHandler, this,
  3. placeholders::_1, placeholders::_2, placeholders::_3);
  4. _fun_callbacks[ID_SEARCH_USER_REQ] = std::bind(&LogicSystem::SearchInfo, this,
  5. placeholders::_1, placeholders::_2, placeholders::_3);
  6. }

SearchInfo根据用户uid查询具体信息

  1. void LogicSystem::SearchInfo(std::shared_ptr<CSession> session, const short& msg_id, const string& msg_data) {
  2. Json::Reader reader;
  3. Json::Value root;
  4. reader.parse(msg_data, root);
  5. auto uid_str = root["uid"].asString();
  6. std::cout << "user SearchInfo uid is " << uid_str << endl;
  7. Json::Value rtvalue;
  8. Defer deder([this, &rtvalue, session]() {
  9. std::string return_str = rtvalue.toStyledString();
  10. session->Send(return_str, ID_SEARCH_USER_RSP);
  11. });
  12. bool b_digit = isPureDigit(uid_str);
  13. if (b_digit) {
  14. GetUserByUid(uid_str, rtvalue);
  15. }
  16. else {
  17. GetUserByName(uid_str, rtvalue);
  18. }
  19. }

到此客户端和服务器搜索查询的联调功能已经解决了。

客户端添加好友

当Client1搜索到好友后,点击添加弹出信息界面,然后点击确定即可向对方Client2申请添加好友,这个请求要先发送到Client1所在的服务器Server1,服务器收到后判断Client2所在服务器,如果Client2在Server1则直接在Server1中查找Client2的连接信息,没找到说明Client2未在内存中,找到了则通过Session发送tcp给对方。如果Client2不在Server1而在Server2上,则需要让Server1通过grpc接口通知Server2,Server2收到后继续判断Client2是否在线,如果在线则通知。

如下图,Client1想和Client2以及Client3分别通信,需要先将请求发给Client1所在的Server1,再考虑是否rpc调用。

https://cdn.llfc.club/1722844689701.jpg

客户端在ApplySure槽函数中添加好友请求

  1. void ApplyFriend::SlotApplySure()
  2. {
  3. qDebug() << "Slot Apply Sure called" ;
  4. QJsonObject jsonObj;
  5. auto uid = UserMgr::GetInstance()->GetUid();
  6. jsonObj["uid"] = uid;
  7. auto name = ui->name_ed->text();
  8. if(name.isEmpty()){
  9. name = ui->name_ed->placeholderText();
  10. }
  11. jsonObj["applyname"] = name;
  12. auto bakname = ui->back_ed->text();
  13. if(bakname.isEmpty()){
  14. bakname = ui->back_ed->placeholderText();
  15. }
  16. jsonObj["bakname"] = bakname;
  17. jsonObj["touid"] = _si->_uid;
  18. QJsonDocument doc(jsonObj);
  19. QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
  20. //发送tcp请求给chat server
  21. emit TcpMgr::GetInstance()->sig_send_data(ReqId::ID_ADD_FRIEND_REQ, jsonData);
  22. this->hide();
  23. deleteLater();
  24. }

另一个客户端会收到服务器通知添加好友的请求,所以在TcpMgr里监听这个请求

  1. _handlers.insert(ID_NOTIFY_ADD_FRIEND_REQ, [this](ReqId id, int len, QByteArray data) {
  2. Q_UNUSED(len);
  3. qDebug() << "handle id is " << id << " data is " << data;
  4. // 将QByteArray转换为QJsonDocument
  5. QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
  6. // 检查转换是否成功
  7. if (jsonDoc.isNull()) {
  8. qDebug() << "Failed to create QJsonDocument.";
  9. return;
  10. }
  11. QJsonObject jsonObj = jsonDoc.object();
  12. if (!jsonObj.contains("error")) {
  13. int err = ErrorCodes::ERR_JSON;
  14. qDebug() << "Login Failed, err is Json Parse Err" << err;
  15. emit sig_user_search(nullptr);
  16. return;
  17. }
  18. int err = jsonObj["error"].toInt();
  19. if (err != ErrorCodes::SUCCESS) {
  20. qDebug() << "Login Failed, err is " << err;
  21. emit sig_user_search(nullptr);
  22. return;
  23. }
  24. int from_uid = jsonObj["applyuid"].toInt();
  25. QString name = jsonObj["name"].toString();
  26. QString desc = jsonObj["desc"].toString();
  27. QString icon = jsonObj["icon"].toString();
  28. QString nick = jsonObj["nick"].toString();
  29. int sex = jsonObj["sex"].toInt();
  30. auto apply_info = std::make_shared<AddFriendApply>(
  31. from_uid, name, desc,
  32. icon, nick, sex);
  33. emit sig_friend_apply(apply_info);
  34. });

服务调用

服务器要处理客户端发过来的添加好友的请求,并决定是否调用rpc通知其他服务。

先将AddFriendApply函数注册到回调map里

  1. void LogicSystem::RegisterCallBacks() {
  2. _fun_callbacks[MSG_CHAT_LOGIN] = std::bind(&LogicSystem::LoginHandler, this,
  3. placeholders::_1, placeholders::_2, placeholders::_3);
  4. _fun_callbacks[ID_SEARCH_USER_REQ] = std::bind(&LogicSystem::SearchInfo, this,
  5. placeholders::_1, placeholders::_2, placeholders::_3);
  6. _fun_callbacks[ID_ADD_FRIEND_REQ] = std::bind(&LogicSystem::AddFriendApply, this,
  7. placeholders::_1, placeholders::_2, placeholders::_3);
  8. }

接下来实现AddFriendApply

  1. void LogicSystem::AddFriendApply(std::shared_ptr<CSession> session, const short& msg_id, const string& msg_data) {
  2. Json::Reader reader;
  3. Json::Value root;
  4. reader.parse(msg_data, root);
  5. auto uid = root["uid"].asInt();
  6. auto applyname = root["applyname"].asString();
  7. auto bakname = root["bakname"].asString();
  8. auto touid = root["touid"].asInt();
  9. std::cout << "user login uid is " << uid << " applyname is "
  10. << applyname << " bakname is " << bakname << " touid is " << touid << endl;
  11. Json::Value rtvalue;
  12. rtvalue["error"] = ErrorCodes::Success;
  13. Defer defer([this, &rtvalue, session]() {
  14. std::string return_str = rtvalue.toStyledString();
  15. session->Send(return_str, ID_ADD_FRIEND_RSP);
  16. });
  17. //先更新数据库
  18. MysqlMgr::GetInstance()->AddFriendApply(uid, touid);
  19. //查询redis 查找touid对应的server ip
  20. auto to_str = std::to_string(touid);
  21. auto to_ip_key = USERIPPREFIX + to_str;
  22. std::string to_ip_value = "";
  23. bool b_ip = RedisMgr::GetInstance()->Get(to_ip_key, to_ip_value);
  24. if (!b_ip) {
  25. return;
  26. }
  27. auto& cfg = ConfigMgr::Inst();
  28. auto self_name = cfg["SelfServer"]["Name"];
  29. //直接通知对方有申请消息
  30. if (to_ip_value == self_name) {
  31. auto session = UserMgr::GetInstance()->GetSession(touid);
  32. if (session) {
  33. //在内存中则直接发送通知对方
  34. Json::Value notify;
  35. notify["error"] = ErrorCodes::Success;
  36. notify["applyuid"] = uid;
  37. notify["name"] = applyname;
  38. notify["desc"] = "";
  39. std::string return_str = notify.toStyledString();
  40. session->Send(return_str, ID_NOTIFY_ADD_FRIEND_REQ);
  41. }
  42. return;
  43. }
  44. std::string base_key = USER_BASE_INFO + std::to_string(uid);
  45. auto apply_info = std::make_shared<UserInfo>();
  46. bool b_info = GetBaseInfo(base_key, uid, apply_info);
  47. AddFriendReq add_req;
  48. add_req.set_applyuid(uid);
  49. add_req.set_touid(touid);
  50. add_req.set_name(applyname);
  51. add_req.set_desc("");
  52. if (b_info) {
  53. add_req.set_icon(apply_info->icon);
  54. add_req.set_sex(apply_info->sex);
  55. add_req.set_nick(apply_info->nick);
  56. }
  57. //发送通知
  58. ChatGrpcClient::GetInstance()->NotifyAddFriend(to_ip_value, add_req);
  59. }

上面的函数中先更新数据库将申请写入数据库中

  1. bool MysqlMgr::AddFriendApply(const int& from, const int& to) {
  2. return _dao.AddFriendApply(from, to);
  3. }

内部调用dao层面的添加好友请求

  1. bool MysqlDao::AddFriendApply(const int& from, const int& to) {
  2. auto con = pool_->getConnection();
  3. if (con == nullptr) {
  4. return false;
  5. }
  6. Defer defer([this, &con]() {
  7. pool_->returnConnection(std::move(con));
  8. });
  9. try {
  10. std::unique_ptr<sql::PreparedStatement> pstmt(con->_con->prepareStatement("INSERT INTO friend_apply (from_uid, to_uid) values (?,?) "
  11. "ON DUPLICATE KEY UPDATE from_uid = from_uid, to_uid = to_uid "));
  12. pstmt->setInt(1, from);
  13. pstmt->setInt(2, to);
  14. //执行更新
  15. int rowAffected = pstmt->executeUpdate();
  16. if (rowAffected < 0) {
  17. return false;
  18. }
  19. return true;
  20. }
  21. catch (sql::SQLException& e) {
  22. std::cerr << "SQLException: " << e.what();
  23. std::cerr << " (MySQL error code: " << e.getErrorCode();
  24. std::cerr << ", SQLState: " << e.getSQLState() << " )" << std::endl;
  25. return false;
  26. }
  27. return true;
  28. }

添加完成后判断要通知的对端是否在本服务器,如果在本服务器则直接通过uid查找session,判断用户是否在线,如果在线则直接通知对端。

如果不在本服务器,则需要通过rpc通知对端服务器。rpc的客户端这么写即可。

  1. AddFriendRsp ChatGrpcClient::NotifyAddFriend(std::string server_ip, const AddFriendReq& req) {
  2. AddFriendRsp rsp;
  3. Defer defer([&rsp, &req]() {
  4. rsp.set_error(ErrorCodes::Success);
  5. rsp.set_applyuid(req.applyuid());
  6. rsp.set_touid(req.touid());
  7. });
  8. auto find_iter = _pools.find(server_ip);
  9. if (find_iter == _pools.end()) {
  10. return rsp;
  11. }
  12. auto& pool = find_iter->second;
  13. ClientContext context;
  14. auto stub = pool->getConnection();
  15. Status status = stub->NotifyAddFriend(&context, req, &rsp);
  16. Defer defercon([&stub, this, &pool]() {
  17. pool->returnConnection(std::move(stub));
  18. });
  19. if (!status.ok()) {
  20. rsp.set_error(ErrorCodes::RPCFailed);
  21. return rsp;
  22. }
  23. return rsp;
  24. }

同样rpc的服务端也要实现,我们先将rpc客户端和服务端的逻辑都在ChatServer1写好,然后复制给ChatServer2即可。 rpc的服务实现如下

  1. Status ChatServiceImpl::NotifyAddFriend(ServerContext* context, const AddFriendReq* request,
  2. AddFriendRsp* reply) {
  3. //查找用户是否在本服务器
  4. auto touid = request->touid();
  5. auto session = UserMgr::GetInstance()->GetSession(touid);
  6. Defer defer([request, reply]() {
  7. reply->set_error(ErrorCodes::Success);
  8. reply->set_applyuid(request->applyuid());
  9. reply->set_touid(request->touid());
  10. });
  11. //用户不在内存中则直接返回
  12. if (session == nullptr) {
  13. return Status::OK;
  14. }
  15. //在内存中则直接发送通知对方
  16. Json::Value rtvalue;
  17. rtvalue["error"] = ErrorCodes::Success;
  18. rtvalue["applyuid"] = request->applyuid();
  19. rtvalue["name"] = request->name();
  20. rtvalue["desc"] = request->desc();
  21. rtvalue["icon"] = request->icon();
  22. rtvalue["sex"] = request->sex();
  23. rtvalue["nick"] = request->nick();
  24. std::string return_str = rtvalue.toStyledString();
  25. session->Send(return_str, ID_NOTIFY_ADD_FRIEND_REQ);
  26. return Status::OK;
  27. }

上面的代码也是判断要通知的客户端是否在内存中,如果在就通过session发送tcp请求。

将ChatServer1的代码拷贝给ChatServer2,重启两个服务,再启动两个客户端,一个客户端申请另一个客户端,通过查看客户端日志是能看到申请信息的。

申请显示

接下来被通知申请的客户端要做界面显示,我们实现被通知的客户端收到sig_friend_apply信号的处理逻辑。在ChatDialog的构造函数中连接信号和槽

  1. //连接申请添加好友信号
  2. connect(TcpMgr::GetInstance().get(), &TcpMgr::sig_friend_apply, this, &ChatDialog::slot_apply_friend);

实现申请好友的槽函数

  1. void ChatDialog::slot_apply_friend(std::shared_ptr<AddFriendApply> apply)
  2. {
  3. qDebug() << "receive apply friend slot, applyuid is " << apply->_from_uid << " name is "
  4. << apply->_name << " desc is " << apply->_desc;
  5. bool b_already = UserMgr::GetInstance()->AlreadyApply(apply->_from_uid);
  6. if(b_already){
  7. return;
  8. }
  9. UserMgr::GetInstance()->AddApplyList(std::make_shared<ApplyInfo>(apply));
  10. ui->side_contact_lb->ShowRedPoint(true);
  11. ui->con_user_list->ShowRedPoint(true);
  12. ui->friend_apply_page->AddNewApply(apply);
  13. }

这样就能显示新的申请消息和红点了。具体添加一个新的申请条目到申请好友页面的逻辑如下:

  1. void ApplyFriendPage::AddNewApply(std::shared_ptr<AddFriendApply> apply)
  2. {
  3. //先模拟头像随机,以后头像资源增加资源服务器后再显示
  4. int randomValue = QRandomGenerator::global()->bounded(100); // 生成0到99之间的随机整数
  5. int head_i = randomValue % heads.size();
  6. auto* apply_item = new ApplyFriendItem();
  7. auto apply_info = std::make_shared<ApplyInfo>(apply->_from_uid,
  8. apply->_name, apply->_desc,heads[head_i], apply->_name, 0, 0);
  9. apply_item->SetInfo( apply_info);
  10. QListWidgetItem* item = new QListWidgetItem;
  11. //qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();
  12. item->setSizeHint(apply_item->sizeHint());
  13. item->setFlags(item->flags() & ~Qt::ItemIsEnabled & ~Qt::ItemIsSelectable);
  14. ui->apply_friend_list->insertItem(0,item);
  15. ui->apply_friend_list->setItemWidget(item, apply_item);
  16. apply_item->ShowAddBtn(true);
  17. //收到审核好友信号
  18. connect(apply_item, &ApplyFriendItem::sig_auth_friend, [this](std::shared_ptr<ApplyInfo> apply_info) {
  19. auto* authFriend = new AuthenFriend(this);
  20. authFriend->setModal(true);
  21. authFriend->SetApplyInfo(apply_info);
  22. authFriend->show();
  23. });
  24. }

测试效果, 收到对方请求后如下图

https://cdn.llfc.club/1722851642815.jpg

登录加载申请

当用户登录后,服务器需要将申请列表同步给客户端, 写在登录逻辑里。

  1. //从数据库获取申请列表
  2. std::vector<std::shared_ptr<ApplyInfo>> apply_list;
  3. auto b_apply = GetFriendApplyInfo(uid,apply_list);
  4. if (b_apply) {
  5. for (auto & apply : apply_list) {
  6. Json::Value obj;
  7. obj["name"] = apply->_name;
  8. obj["uid"] = apply->_uid;
  9. obj["icon"] = apply->_icon;
  10. obj["nick"] = apply->_nick;
  11. obj["sex"] = apply->_sex;
  12. obj["desc"] = apply->_desc;
  13. obj["status"] = apply->_status;
  14. rtvalue["apply_list"].append(obj);
  15. }
  16. }

获取好友申请信息函数

  1. bool LogicSystem::GetFriendApplyInfo(int to_uid, std::vector<std::shared_ptr<ApplyInfo>> &list) {
  2. //从mysql获取好友申请列表
  3. return MysqlMgr::GetInstance()->GetApplyList(to_uid, list, 0, 10);
  4. }

dao层面实现获取申请列表

  1. bool MysqlMgr::GetApplyList(int touid,
  2. std::vector<std::shared_ptr<ApplyInfo>>& applyList, int begin, int limit) {
  3. return _dao.GetApplyList(touid, applyList, begin, limit);
  4. }
  5. bool MysqlDao::GetApplyList(int touid, std::vector<std::shared_ptr<ApplyInfo>>& applyList, int begin, int limit) {
  6. auto con = pool_->getConnection();
  7. if (con == nullptr) {
  8. return false;
  9. }
  10. Defer defer([this, &con]() {
  11. pool_->returnConnection(std::move(con));
  12. });
  13. try {
  14. // 准备SQL语句, 根据起始id和限制条数返回列表
  15. std::unique_ptr<sql::PreparedStatement> pstmt(con->_con->prepareStatement("select apply.from_uid, apply.status, user.name, "
  16. "user.nick, user.sex from friend_apply as apply join user on apply.from_uid = user.uid where apply.to_uid = ? "
  17. "and apply.id > ? order by apply.id ASC LIMIT ? "));
  18. pstmt->setInt(1, touid); // 将uid替换为你要查询的uid
  19. pstmt->setInt(2, begin); // 起始id
  20. pstmt->setInt(3, limit); //偏移量
  21. // 执行查询
  22. std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());
  23. // 遍历结果集
  24. while (res->next()) {
  25. auto name = res->getString("name");
  26. auto uid = res->getInt("from_uid");
  27. auto status = res->getInt("status");
  28. auto nick = res->getString("nick");
  29. auto sex = res->getInt("sex");
  30. auto apply_ptr = std::make_shared<ApplyInfo>(uid, name, "", "", nick, sex, status);
  31. applyList.push_back(apply_ptr);
  32. }
  33. return true;
  34. }
  35. catch (sql::SQLException& e) {
  36. std::cerr << "SQLException: " << e.what();
  37. std::cerr << " (MySQL error code: " << e.getErrorCode();
  38. std::cerr << ", SQLState: " << e.getSQLState() << " )" << std::endl;
  39. return false;
  40. }
  41. }

好友认证界面

客户端需要实现好友认证界面,当点击同意对方好友申请后,弹出认证信息,点击确定后将认证同意的请求发给服务器,服务器再通知申请方,告知对方被申请人已经同意加好友了。认证界面和申请界面类似, 这个大家自己实现即可。

https://cdn.llfc.club/1722854446243.jpg

认证界面的函数和逻辑可以照抄申请好友的逻辑。

  1. AuthenFriend::AuthenFriend(QWidget *parent) :
  2. QDialog(parent),
  3. ui(new Ui::AuthenFriend),_label_point(2,6)
  4. {
  5. ui->setupUi(this);
  6. // 隐藏对话框标题栏
  7. setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
  8. this->setObjectName("AuthenFriend");
  9. this->setModal(true);
  10. ui->lb_ed->setPlaceholderText("搜索、添加标签");
  11. ui->back_ed->setPlaceholderText("燃烧的胸毛");
  12. ui->lb_ed->SetMaxLength(21);
  13. ui->lb_ed->move(2, 2);
  14. ui->lb_ed->setFixedHeight(20);
  15. ui->lb_ed->setMaxLength(10);
  16. ui->input_tip_wid->hide();
  17. _tip_cur_point = QPoint(5, 5);
  18. _tip_data = { "同学","家人","菜鸟教程","C++ Primer","Rust 程序设计",
  19. "父与子学Python","nodejs开发指南","go 语言开发指南",
  20. "游戏伙伴","金融投资","微信读书","拼多多拼友" };
  21. connect(ui->more_lb, &ClickedOnceLabel::clicked, this, &AuthenFriend::ShowMoreLabel);
  22. InitTipLbs();
  23. //链接输入标签回车事件
  24. connect(ui->lb_ed, &CustomizeEdit::returnPressed, this, &AuthenFriend::SlotLabelEnter);
  25. connect(ui->lb_ed, &CustomizeEdit::textChanged, this, &AuthenFriend::SlotLabelTextChange);
  26. connect(ui->lb_ed, &CustomizeEdit::editingFinished, this, &AuthenFriend::SlotLabelEditFinished);
  27. connect(ui->tip_lb, &ClickedOnceLabel::clicked, this, &AuthenFriend::SlotAddFirendLabelByClickTip);
  28. ui->scrollArea->horizontalScrollBar()->setHidden(true);
  29. ui->scrollArea->verticalScrollBar()->setHidden(true);
  30. ui->scrollArea->installEventFilter(this);
  31. ui->sure_btn->SetState("normal","hover","press");
  32. ui->cancel_btn->SetState("normal","hover","press");
  33. //连接确认和取消按钮的槽函数
  34. connect(ui->cancel_btn, &QPushButton::clicked, this, &AuthenFriend::SlotApplyCancel);
  35. connect(ui->sure_btn, &QPushButton::clicked, this, &AuthenFriend::SlotApplySure);
  36. }
  37. AuthenFriend::~AuthenFriend()
  38. {
  39. qDebug()<< "AuthenFriend destruct";
  40. delete ui;
  41. }
  42. void AuthenFriend::InitTipLbs()
  43. {
  44. int lines = 1;
  45. for(int i = 0; i < _tip_data.size(); i++){
  46. auto* lb = new ClickedLabel(ui->lb_list);
  47. lb->SetState("normal", "hover", "pressed", "selected_normal",
  48. "selected_hover", "selected_pressed");
  49. lb->setObjectName("tipslb");
  50. lb->setText(_tip_data[i]);
  51. connect(lb, &ClickedLabel::clicked, this, &AuthenFriend::SlotChangeFriendLabelByTip);
  52. QFontMetrics fontMetrics(lb->font()); // 获取QLabel控件的字体信息
  53. int textWidth = fontMetrics.width(lb->text()); // 获取文本的宽度
  54. int textHeight = fontMetrics.height(); // 获取文本的高度
  55. if (_tip_cur_point.x() + textWidth + tip_offset > ui->lb_list->width()) {
  56. lines++;
  57. if (lines > 2) {
  58. delete lb;
  59. return;
  60. }
  61. _tip_cur_point.setX(tip_offset);
  62. _tip_cur_point.setY(_tip_cur_point.y() + textHeight + 15);
  63. }
  64. auto next_point = _tip_cur_point;
  65. AddTipLbs(lb, _tip_cur_point,next_point, textWidth, textHeight);
  66. _tip_cur_point = next_point;
  67. }
  68. }
  69. void AuthenFriend::AddTipLbs(ClickedLabel* lb, QPoint cur_point, QPoint& next_point, int text_width, int text_height)
  70. {
  71. lb->move(cur_point);
  72. lb->show();
  73. _add_labels.insert(lb->text(), lb);
  74. _add_label_keys.push_back(lb->text());
  75. next_point.setX(lb->pos().x() + text_width + 15);
  76. next_point.setY(lb->pos().y());
  77. }
  78. bool AuthenFriend::eventFilter(QObject *obj, QEvent *event)
  79. {
  80. if (obj == ui->scrollArea && event->type() == QEvent::Enter)
  81. {
  82. ui->scrollArea->verticalScrollBar()->setHidden(false);
  83. }
  84. else if (obj == ui->scrollArea && event->type() == QEvent::Leave)
  85. {
  86. ui->scrollArea->verticalScrollBar()->setHidden(true);
  87. }
  88. return QObject::eventFilter(obj, event);
  89. }
  90. void AuthenFriend::SetApplyInfo(std::shared_ptr<ApplyInfo> apply_info)
  91. {
  92. _apply_info = apply_info;
  93. ui->back_ed->setPlaceholderText(apply_info->_name);
  94. }
  95. void AuthenFriend::ShowMoreLabel()
  96. {
  97. qDebug()<< "receive more label clicked";
  98. ui->more_lb_wid->hide();
  99. ui->lb_list->setFixedWidth(325);
  100. _tip_cur_point = QPoint(5, 5);
  101. auto next_point = _tip_cur_point;
  102. int textWidth;
  103. int textHeight;
  104. //重拍现有的label
  105. for(auto & added_key : _add_label_keys){
  106. auto added_lb = _add_labels[added_key];
  107. QFontMetrics fontMetrics(added_lb->font()); // 获取QLabel控件的字体信息
  108. textWidth = fontMetrics.width(added_lb->text()); // 获取文本的宽度
  109. textHeight = fontMetrics.height(); // 获取文本的高度
  110. if(_tip_cur_point.x() +textWidth + tip_offset > ui->lb_list->width()){
  111. _tip_cur_point.setX(tip_offset);
  112. _tip_cur_point.setY(_tip_cur_point.y()+textHeight+15);
  113. }
  114. added_lb->move(_tip_cur_point);
  115. next_point.setX(added_lb->pos().x() + textWidth + 15);
  116. next_point.setY(_tip_cur_point.y());
  117. _tip_cur_point = next_point;
  118. }
  119. //添加未添加的
  120. for(int i = 0; i < _tip_data.size(); i++){
  121. auto iter = _add_labels.find(_tip_data[i]);
  122. if(iter != _add_labels.end()){
  123. continue;
  124. }
  125. auto* lb = new ClickedLabel(ui->lb_list);
  126. lb->SetState("normal", "hover", "pressed", "selected_normal",
  127. "selected_hover", "selected_pressed");
  128. lb->setObjectName("tipslb");
  129. lb->setText(_tip_data[i]);
  130. connect(lb, &ClickedLabel::clicked, this, &AuthenFriend::SlotChangeFriendLabelByTip);
  131. QFontMetrics fontMetrics(lb->font()); // 获取QLabel控件的字体信息
  132. int textWidth = fontMetrics.width(lb->text()); // 获取文本的宽度
  133. int textHeight = fontMetrics.height(); // 获取文本的高度
  134. if (_tip_cur_point.x() + textWidth + tip_offset > ui->lb_list->width()) {
  135. _tip_cur_point.setX(tip_offset);
  136. _tip_cur_point.setY(_tip_cur_point.y() + textHeight + 15);
  137. }
  138. next_point = _tip_cur_point;
  139. AddTipLbs(lb, _tip_cur_point, next_point, textWidth, textHeight);
  140. _tip_cur_point = next_point;
  141. }
  142. int diff_height = next_point.y() + textHeight + tip_offset - ui->lb_list->height();
  143. ui->lb_list->setFixedHeight(next_point.y() + textHeight + tip_offset);
  144. //qDebug()<<"after resize ui->lb_list size is " << ui->lb_list->size();
  145. ui->scrollcontent->setFixedHeight(ui->scrollcontent->height()+diff_height);
  146. }
  147. void AuthenFriend::resetLabels()
  148. {
  149. auto max_width = ui->gridWidget->width();
  150. auto label_height = 0;
  151. for(auto iter = _friend_labels.begin(); iter != _friend_labels.end(); iter++){
  152. //todo... 添加宽度统计
  153. if( _label_point.x() + iter.value()->width() > max_width) {
  154. _label_point.setY(_label_point.y()+iter.value()->height()+6);
  155. _label_point.setX(2);
  156. }
  157. iter.value()->move(_label_point);
  158. iter.value()->show();
  159. _label_point.setX(_label_point.x()+iter.value()->width()+2);
  160. _label_point.setY(_label_point.y());
  161. label_height = iter.value()->height();
  162. }
  163. if(_friend_labels.isEmpty()){
  164. ui->lb_ed->move(_label_point);
  165. return;
  166. }
  167. if(_label_point.x() + MIN_APPLY_LABEL_ED_LEN > ui->gridWidget->width()){
  168. ui->lb_ed->move(2,_label_point.y()+label_height+6);
  169. }else{
  170. ui->lb_ed->move(_label_point);
  171. }
  172. }
  173. void AuthenFriend::addLabel(QString name)
  174. {
  175. if (_friend_labels.find(name) != _friend_labels.end()) {
  176. return;
  177. }
  178. auto tmplabel = new FriendLabel(ui->gridWidget);
  179. tmplabel->SetText(name);
  180. tmplabel->setObjectName("FriendLabel");
  181. auto max_width = ui->gridWidget->width();
  182. //todo... 添加宽度统计
  183. if (_label_point.x() + tmplabel->width() > max_width) {
  184. _label_point.setY(_label_point.y() + tmplabel->height() + 6);
  185. _label_point.setX(2);
  186. }
  187. else {
  188. }
  189. tmplabel->move(_label_point);
  190. tmplabel->show();
  191. _friend_labels[tmplabel->Text()] = tmplabel;
  192. _friend_label_keys.push_back(tmplabel->Text());
  193. connect(tmplabel, &FriendLabel::sig_close, this, &AuthenFriend::SlotRemoveFriendLabel);
  194. _label_point.setX(_label_point.x() + tmplabel->width() + 2);
  195. if (_label_point.x() + MIN_APPLY_LABEL_ED_LEN > ui->gridWidget->width()) {
  196. ui->lb_ed->move(2, _label_point.y() + tmplabel->height() + 2);
  197. }
  198. else {
  199. ui->lb_ed->move(_label_point);
  200. }
  201. ui->lb_ed->clear();
  202. if (ui->gridWidget->height() < _label_point.y() + tmplabel->height() + 2) {
  203. ui->gridWidget->setFixedHeight(_label_point.y() + tmplabel->height() * 2 + 2);
  204. }
  205. }
  206. void AuthenFriend::SlotLabelEnter()
  207. {
  208. if(ui->lb_ed->text().isEmpty()){
  209. return;
  210. }
  211. addLabel(ui->lb_ed->text());
  212. ui->input_tip_wid->hide();
  213. }
  214. void AuthenFriend::SlotRemoveFriendLabel(QString name)
  215. {
  216. qDebug() << "receive close signal";
  217. _label_point.setX(2);
  218. _label_point.setY(6);
  219. auto find_iter = _friend_labels.find(name);
  220. if(find_iter == _friend_labels.end()){
  221. return;
  222. }
  223. auto find_key = _friend_label_keys.end();
  224. for(auto iter = _friend_label_keys.begin(); iter != _friend_label_keys.end();
  225. iter++){
  226. if(*iter == name){
  227. find_key = iter;
  228. break;
  229. }
  230. }
  231. if(find_key != _friend_label_keys.end()){
  232. _friend_label_keys.erase(find_key);
  233. }
  234. delete find_iter.value();
  235. _friend_labels.erase(find_iter);
  236. resetLabels();
  237. auto find_add = _add_labels.find(name);
  238. if(find_add == _add_labels.end()){
  239. return;
  240. }
  241. find_add.value()->ResetNormalState();
  242. }
  243. //点击标已有签添加或删除新联系人的标签
  244. void AuthenFriend::SlotChangeFriendLabelByTip(QString lbtext, ClickLbState state)
  245. {
  246. auto find_iter = _add_labels.find(lbtext);
  247. if(find_iter == _add_labels.end()){
  248. return;
  249. }
  250. if(state == ClickLbState::Selected){
  251. //编写添加逻辑
  252. addLabel(lbtext);
  253. return;
  254. }
  255. if(state == ClickLbState::Normal){
  256. //编写删除逻辑
  257. SlotRemoveFriendLabel(lbtext);
  258. return;
  259. }
  260. }
  261. void AuthenFriend::SlotLabelTextChange(const QString& text)
  262. {
  263. if (text.isEmpty()) {
  264. ui->tip_lb->setText("");
  265. ui->input_tip_wid->hide();
  266. return;
  267. }
  268. auto iter = std::find(_tip_data.begin(), _tip_data.end(), text);
  269. if (iter == _tip_data.end()) {
  270. auto new_text = add_prefix + text;
  271. ui->tip_lb->setText(new_text);
  272. ui->input_tip_wid->show();
  273. return;
  274. }
  275. ui->tip_lb->setText(text);
  276. ui->input_tip_wid->show();
  277. }
  278. void AuthenFriend::SlotLabelEditFinished()
  279. {
  280. ui->input_tip_wid->hide();
  281. }
  282. void AuthenFriend::SlotAddFirendLabelByClickTip(QString text)
  283. {
  284. int index = text.indexOf(add_prefix);
  285. if (index != -1) {
  286. text = text.mid(index + add_prefix.length());
  287. }
  288. addLabel(text);
  289. //标签展示栏也增加一个标签, 并设置绿色选中
  290. if (index != -1) {
  291. _tip_data.push_back(text);
  292. }
  293. auto* lb = new ClickedLabel(ui->lb_list);
  294. lb->SetState("normal", "hover", "pressed", "selected_normal",
  295. "selected_hover", "selected_pressed");
  296. lb->setObjectName("tipslb");
  297. lb->setText(text);
  298. connect(lb, &ClickedLabel::clicked, this, &AuthenFriend::SlotChangeFriendLabelByTip);
  299. qDebug() << "ui->lb_list->width() is " << ui->lb_list->width();
  300. qDebug() << "_tip_cur_point.x() is " << _tip_cur_point.x();
  301. QFontMetrics fontMetrics(lb->font()); // 获取QLabel控件的字体信息
  302. int textWidth = fontMetrics.width(lb->text()); // 获取文本的宽度
  303. int textHeight = fontMetrics.height(); // 获取文本的高度
  304. qDebug() << "textWidth is " << textWidth;
  305. if (_tip_cur_point.x() + textWidth+ tip_offset+3 > ui->lb_list->width()) {
  306. _tip_cur_point.setX(5);
  307. _tip_cur_point.setY(_tip_cur_point.y() + textHeight + 15);
  308. }
  309. auto next_point = _tip_cur_point;
  310. AddTipLbs(lb, _tip_cur_point, next_point, textWidth,textHeight);
  311. _tip_cur_point = next_point;
  312. int diff_height = next_point.y() + textHeight + tip_offset - ui->lb_list->height();
  313. ui->lb_list->setFixedHeight(next_point.y() + textHeight + tip_offset);
  314. lb->SetCurState(ClickLbState::Selected);
  315. ui->scrollcontent->setFixedHeight(ui->scrollcontent->height()+ diff_height );
  316. }
  317. void AuthenFriend::SlotApplySure()
  318. {
  319. qDebug() << "Slot Apply Sure ";
  320. //添加发送逻辑
  321. QJsonObject jsonObj;
  322. auto uid = UserMgr::GetInstance()->GetUid();
  323. jsonObj["fromuid"] = uid;
  324. jsonObj["touid"] = _apply_info->_uid;
  325. QString back_name = "";
  326. if(ui->back_ed->text().isEmpty()){
  327. back_name = ui->back_ed->placeholderText();
  328. }else{
  329. back_name = ui->back_ed->text();
  330. }
  331. jsonObj["back"] = back_name;
  332. QJsonDocument doc(jsonObj);
  333. QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
  334. //发送tcp请求给chat server
  335. emit TcpMgr::GetInstance()->sig_send_data(ReqId::ID_AUTH_FRIEND_REQ, jsonData);
  336. this->hide();
  337. deleteLater();
  338. }
  339. void AuthenFriend::SlotApplyCancel()
  340. {
  341. this->hide();
  342. deleteLater();
  343. }

源码连接

https://gitee.com/secondtonone1/llfcchat

视频连接

https://www.bilibili.com/video/BV1Ex4y1s7cq/

热门评论

热门文章

  1. C++ 类的继承封装和多态

    喜欢(588) 浏览(4233)
  2. slice介绍和使用

    喜欢(521) 浏览(2205)
  3. Linux环境搭建和编码

    喜欢(594) 浏览(9695)
  4. windows环境搭建和vscode配置

    喜欢(587) 浏览(2277)
  5. 解密定时器的实现细节

    喜欢(566) 浏览(2998)

最新评论

  1. 泛型算法的定制操作 secondtonone1:lambda和bind是C11新增的利器,善于利用这两个机制可以极大地提升编程安全性和效率。
  2. 类和对象 陈宇航:支持!!!!
  3. C++ 虚函数表原理和类成员内存分布 WangQi888888:class Test{ int m; int b; }中b成员是int,为什么在内存中只占了1个字节。不应该是4个字节吗?是不是int应该改为char。这样的话就会符合图上说明的情况
  4. 解决博客回复区被脚本注入的问题 secondtonone1:走到现在我忽然明白一个道理,无论工作也好生活也罢,最重要的是开心,即使一份安稳的工作不能给我带来事业上的积累也要合理的舍弃,所以我还是想去做喜欢的方向。
  5. asio多线程模型IOServicePool Lion:线程池一定要继承单例模式吗

个人公众号

个人微信