聊天项目(29) 好友认证和聊天通信

好友认证

服务器响应

服务器接受客户端发送过来的好友认证请求

  1. void LogicSystem::AuthFriendApply(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["fromuid"].asInt();
  6. auto touid = root["touid"].asInt();
  7. auto back_name = root["back"].asString();
  8. std::cout << "from " << uid << " auth friend to " << touid << std::endl;
  9. Json::Value rtvalue;
  10. rtvalue["error"] = ErrorCodes::Success;
  11. auto user_info = std::make_shared<UserInfo>();
  12. std::string base_key = USER_BASE_INFO + std::to_string(touid);
  13. bool b_info = GetBaseInfo(base_key, touid, user_info);
  14. if (b_info) {
  15. rtvalue["name"] = user_info->name;
  16. rtvalue["nick"] = user_info->nick;
  17. rtvalue["icon"] = user_info->icon;
  18. rtvalue["sex"] = user_info->sex;
  19. rtvalue["uid"] = touid;
  20. }
  21. else {
  22. rtvalue["error"] = ErrorCodes::UidInvalid;
  23. }
  24. Defer defer([this, &rtvalue, session]() {
  25. std::string return_str = rtvalue.toStyledString();
  26. session->Send(return_str, ID_AUTH_FRIEND_RSP);
  27. });
  28. //先更新数据库
  29. MysqlMgr::GetInstance()->AuthFriendApply(uid, touid);
  30. //更新数据库添加好友
  31. MysqlMgr::GetInstance()->AddFriend(uid, touid,back_name);
  32. //查询redis 查找touid对应的server ip
  33. auto to_str = std::to_string(touid);
  34. auto to_ip_key = USERIPPREFIX + to_str;
  35. std::string to_ip_value = "";
  36. bool b_ip = RedisMgr::GetInstance()->Get(to_ip_key, to_ip_value);
  37. if (!b_ip) {
  38. return;
  39. }
  40. auto& cfg = ConfigMgr::Inst();
  41. auto self_name = cfg["SelfServer"]["Name"];
  42. //直接通知对方有认证通过消息
  43. if (to_ip_value == self_name) {
  44. auto session = UserMgr::GetInstance()->GetSession(touid);
  45. if (session) {
  46. //在内存中则直接发送通知对方
  47. Json::Value notify;
  48. notify["error"] = ErrorCodes::Success;
  49. notify["fromuid"] = uid;
  50. notify["touid"] = touid;
  51. std::string base_key = USER_BASE_INFO + std::to_string(uid);
  52. auto user_info = std::make_shared<UserInfo>();
  53. bool b_info = GetBaseInfo(base_key, uid, user_info);
  54. if (b_info) {
  55. notify["name"] = user_info->name;
  56. notify["nick"] = user_info->nick;
  57. notify["icon"] = user_info->icon;
  58. notify["sex"] = user_info->sex;
  59. }
  60. else {
  61. notify["error"] = ErrorCodes::UidInvalid;
  62. }
  63. std::string return_str = notify.toStyledString();
  64. session->Send(return_str, ID_NOTIFY_AUTH_FRIEND_REQ);
  65. }
  66. return ;
  67. }
  68. AuthFriendReq auth_req;
  69. auth_req.set_fromuid(uid);
  70. auth_req.set_touid(touid);
  71. //发送通知
  72. ChatGrpcClient::GetInstance()->NotifyAuthFriend(to_ip_value, auth_req);
  73. }

将请求注册到map里,在LogicSystem::RegisterCallBacks中添加

  1. _fun_callbacks[ID_AUTH_FRIEND_REQ] = std::bind(&LogicSystem::AuthFriendApply, this,
  2. placeholders::_1, placeholders::_2, placeholders::_3);

因为上面的逻辑调用了grpc发送通知,所以实现grpc发送认证通知的逻辑

  1. AuthFriendRsp ChatGrpcClient::NotifyAuthFriend(std::string server_ip, const AuthFriendReq& req) {
  2. AuthFriendRsp rsp;
  3. rsp.set_error(ErrorCodes::Success);
  4. Defer defer([&rsp, &req]() {
  5. rsp.set_fromuid(req.fromuid());
  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->NotifyAuthFriend(&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. }

这里注意,stub之所以能发送通知,是因为proto里定义了认证通知等服务,大家记得更新proto和我的一样,这事完整的proto

  1. syntax = "proto3";
  2. package message;
  3. service VarifyService {
  4. rpc GetVarifyCode (GetVarifyReq) returns (GetVarifyRsp) {}
  5. }
  6. message GetVarifyReq {
  7. string email = 1;
  8. }
  9. message GetVarifyRsp {
  10. int32 error = 1;
  11. string email = 2;
  12. string code = 3;
  13. }
  14. message GetChatServerReq {
  15. int32 uid = 1;
  16. }
  17. message GetChatServerRsp {
  18. int32 error = 1;
  19. string host = 2;
  20. string port = 3;
  21. string token = 4;
  22. }
  23. message LoginReq{
  24. int32 uid = 1;
  25. string token= 2;
  26. }
  27. message LoginRsp {
  28. int32 error = 1;
  29. int32 uid = 2;
  30. string token = 3;
  31. }
  32. service StatusService {
  33. rpc GetChatServer (GetChatServerReq) returns (GetChatServerRsp) {}
  34. rpc Login(LoginReq) returns(LoginRsp);
  35. }
  36. message AddFriendReq {
  37. int32 applyuid = 1;
  38. string name = 2;
  39. string desc = 3;
  40. string icon = 4;
  41. string nick = 5;
  42. int32 sex = 6;
  43. int32 touid = 7;
  44. }
  45. message AddFriendRsp {
  46. int32 error = 1;
  47. int32 applyuid = 2;
  48. int32 touid = 3;
  49. }
  50. message RplyFriendReq {
  51. int32 rplyuid = 1;
  52. bool agree = 2;
  53. int32 touid = 3;
  54. }
  55. message RplyFriendRsp {
  56. int32 error = 1;
  57. int32 rplyuid = 2;
  58. int32 touid = 3;
  59. }
  60. message SendChatMsgReq{
  61. int32 fromuid = 1;
  62. int32 touid = 2;
  63. string message = 3;
  64. }
  65. message SendChatMsgRsp{
  66. int32 error = 1;
  67. int32 fromuid = 2;
  68. int32 touid = 3;
  69. }
  70. message AuthFriendReq{
  71. int32 fromuid = 1;
  72. int32 touid = 2;
  73. }
  74. message AuthFriendRsp{
  75. int32 error = 1;
  76. int32 fromuid = 2;
  77. int32 touid = 3;
  78. }
  79. message TextChatMsgReq {
  80. int32 fromuid = 1;
  81. int32 touid = 2;
  82. repeated TextChatData textmsgs = 3;
  83. }
  84. message TextChatData{
  85. string msgid = 1;
  86. string msgcontent = 2;
  87. }
  88. message TextChatMsgRsp {
  89. int32 error = 1;
  90. int32 fromuid = 2;
  91. int32 touid = 3;
  92. repeated TextChatData textmsgs = 4;
  93. }
  94. service ChatService {
  95. rpc NotifyAddFriend(AddFriendReq) returns (AddFriendRsp) {}
  96. rpc RplyAddFriend(RplyFriendReq) returns (RplyFriendRsp) {}
  97. rpc SendChatMsg(SendChatMsgReq) returns (SendChatMsgRsp) {}
  98. rpc NotifyAuthFriend(AuthFriendReq) returns (AuthFriendRsp) {}
  99. rpc NotifyTextChatMsg(TextChatMsgReq) returns (TextChatMsgRsp){}
  100. }

为了方便生成grpcpb文件,我写了一个start.bat批处理文件

  1. @echo off
  2. set PROTOC_PATH=D:\cppsoft\grpc\visualpro\third_party\protobuf\Debug\protoc.exe
  3. set GRPC_PLUGIN_PATH=D:\cppsoft\grpc\visualpro\Debug\grpc_cpp_plugin.exe
  4. set PROTO_FILE=message.proto
  5. echo Generating gRPC code...
  6. %PROTOC_PATH% -I="." --grpc_out="." --plugin=protoc-gen-grpc="%GRPC_PLUGIN_PATH%" "%PROTO_FILE%"
  7. echo Generating C++ code...
  8. %PROTOC_PATH% --cpp_out=. "%PROTO_FILE%"
  9. echo Done.

执行这个批处理文件就能生成最新的pb文件了。

接下来实现grpc服务对认证的处理

  1. Status ChatServiceImpl::NotifyAuthFriend(ServerContext* context, const AuthFriendReq* request,
  2. AuthFriendRsp* reply) {
  3. //查找用户是否在本服务器
  4. auto touid = request->touid();
  5. auto fromuid = request->fromuid();
  6. auto session = UserMgr::GetInstance()->GetSession(touid);
  7. Defer defer([request, reply]() {
  8. reply->set_error(ErrorCodes::Success);
  9. reply->set_fromuid(request->fromuid());
  10. reply->set_touid(request->touid());
  11. });
  12. //用户不在内存中则直接返回
  13. if (session == nullptr) {
  14. return Status::OK;
  15. }
  16. //在内存中则直接发送通知对方
  17. Json::Value rtvalue;
  18. rtvalue["error"] = ErrorCodes::Success;
  19. rtvalue["fromuid"] = request->fromuid();
  20. rtvalue["touid"] = request->touid();
  21. std::string base_key = USER_BASE_INFO + std::to_string(fromuid);
  22. auto user_info = std::make_shared<UserInfo>();
  23. bool b_info = GetBaseInfo(base_key, fromuid, user_info);
  24. if (b_info) {
  25. rtvalue["name"] = user_info->name;
  26. rtvalue["nick"] = user_info->nick;
  27. rtvalue["icon"] = user_info->icon;
  28. rtvalue["sex"] = user_info->sex;
  29. }
  30. else {
  31. rtvalue["error"] = ErrorCodes::UidInvalid;
  32. }
  33. std::string return_str = rtvalue.toStyledString();
  34. session->Send(return_str, ID_NOTIFY_AUTH_FRIEND_REQ);
  35. return Status::OK;
  36. }

所以A认证B为好友,A所在的服务器会给A回复一个ID_AUTH_FRIEND_RSP的消息,B所在的服务器会给B回复一个ID_NOTIFY_AUTH_FRIEND_REQ消息。

客户端响应

客户端需要响应服务器发过来的ID_AUTH_FRIEND_RSP和ID_NOTIFY_AUTH_FRIEND_REQ消息

客户端响应ID_AUTH_FRIEND_RSP,在initHandlers中添加

  1. _handlers.insert(ID_AUTH_FRIEND_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() << "Auth Friend Failed, err is Json Parse Err" << err;
  15. return;
  16. }
  17. int err = jsonObj["error"].toInt();
  18. if (err != ErrorCodes::SUCCESS) {
  19. qDebug() << "Auth Friend Failed, err is " << err;
  20. return;
  21. }
  22. auto name = jsonObj["name"].toString();
  23. auto nick = jsonObj["nick"].toString();
  24. auto icon = jsonObj["icon"].toString();
  25. auto sex = jsonObj["sex"].toInt();
  26. auto uid = jsonObj["uid"].toInt();
  27. auto rsp = std::make_shared<AuthRsp>(uid, name, nick, icon, sex);
  28. emit sig_auth_rsp(rsp);
  29. qDebug() << "Auth Friend Success " ;
  30. });

在initHandlers中添加ID_NOTIFY_AUTH_FRIEND_REQ

  1. _handlers.insert(ID_NOTIFY_AUTH_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() << "Auth Friend Failed, err is " << err;
  15. return;
  16. }
  17. int err = jsonObj["error"].toInt();
  18. if (err != ErrorCodes::SUCCESS) {
  19. qDebug() << "Auth Friend Failed, err is " << err;
  20. return;
  21. }
  22. int from_uid = jsonObj["fromuid"].toInt();
  23. QString name = jsonObj["name"].toString();
  24. QString nick = jsonObj["nick"].toString();
  25. QString icon = jsonObj["icon"].toString();
  26. int sex = jsonObj["sex"].toInt();
  27. auto auth_info = std::make_shared<AuthInfo>(from_uid,name,
  28. nick, icon, sex);
  29. emit sig_add_auth_friend(auth_info);
  30. });

客户端ChatDialog中添加对sig_add_auth_friend响应,实现添加好友到聊天列表中

  1. void ChatDialog::slot_add_auth_friend(std::shared_ptr<AuthInfo> auth_info) {
  2. qDebug() << "receive slot_add_auth__friend uid is " << auth_info->_uid
  3. << " name is " << auth_info->_name << " nick is " << auth_info->_nick;
  4. //判断如果已经是好友则跳过
  5. auto bfriend = UserMgr::GetInstance()->CheckFriendById(auth_info->_uid);
  6. if(bfriend){
  7. return;
  8. }
  9. UserMgr::GetInstance()->AddFriend(auth_info);
  10. int randomValue = QRandomGenerator::global()->bounded(100); // 生成0到99之间的随机整数
  11. int str_i = randomValue % strs.size();
  12. int head_i = randomValue % heads.size();
  13. int name_i = randomValue % names.size();
  14. auto* chat_user_wid = new ChatUserWid();
  15. auto user_info = std::make_shared<UserInfo>(auth_info);
  16. chat_user_wid->SetInfo(user_info);
  17. QListWidgetItem* item = new QListWidgetItem;
  18. //qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();
  19. item->setSizeHint(chat_user_wid->sizeHint());
  20. ui->chat_user_list->insertItem(0, item);
  21. ui->chat_user_list->setItemWidget(item, chat_user_wid);
  22. _chat_items_added.insert(auth_info->_uid, item);
  23. }

客户端ChatDialog中添加对sig_auth_rsp响应, 实现添加好友到聊天列表中

  1. void ChatDialog::slot_auth_rsp(std::shared_ptr<AuthRsp> auth_rsp)
  2. {
  3. qDebug() << "receive slot_auth_rsp uid is " << auth_rsp->_uid
  4. << " name is " << auth_rsp->_name << " nick is " << auth_rsp->_nick;
  5. //判断如果已经是好友则跳过
  6. auto bfriend = UserMgr::GetInstance()->CheckFriendById(auth_rsp->_uid);
  7. if(bfriend){
  8. return;
  9. }
  10. UserMgr::GetInstance()->AddFriend(auth_rsp);
  11. int randomValue = QRandomGenerator::global()->bounded(100); // 生成0到99之间的随机整数
  12. int str_i = randomValue % strs.size();
  13. int head_i = randomValue % heads.size();
  14. int name_i = randomValue % names.size();
  15. auto* chat_user_wid = new ChatUserWid();
  16. auto user_info = std::make_shared<UserInfo>(auth_rsp);
  17. chat_user_wid->SetInfo(user_info);
  18. QListWidgetItem* item = new QListWidgetItem;
  19. //qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();
  20. item->setSizeHint(chat_user_wid->sizeHint());
  21. ui->chat_user_list->insertItem(0, item);
  22. ui->chat_user_list->setItemWidget(item, chat_user_wid);
  23. _chat_items_added.insert(auth_rsp->_uid, item);
  24. }

因为认证对方为好友后,需要将申请页面的添加按钮变成已添加,所以ApplyFriendPage响应sig_auth_rsp信号

  1. void ApplyFriendPage::slot_auth_rsp(std::shared_ptr<AuthRsp> auth_rsp) {
  2. auto uid = auth_rsp->_uid;
  3. auto find_iter = _unauth_items.find(uid);
  4. if (find_iter == _unauth_items.end()) {
  5. return;
  6. }
  7. find_iter->second->ShowAddBtn(false);
  8. }

同意并认证对方为好友后,也需要将对方添加到联系人列表,ContactUserList响应sig_auth_rsp信号

  1. void ContactUserList::slot_auth_rsp(std::shared_ptr<AuthRsp> auth_rsp)
  2. {
  3. qDebug() << "slot auth rsp called";
  4. bool isFriend = UserMgr::GetInstance()->CheckFriendById(auth_rsp->_uid);
  5. if(isFriend){
  6. return;
  7. }
  8. // 在 groupitem 之后插入新项
  9. int randomValue = QRandomGenerator::global()->bounded(100); // 生成0到99之间的随机整数
  10. int str_i = randomValue%strs.size();
  11. int head_i = randomValue%heads.size();
  12. auto *con_user_wid = new ConUserItem();
  13. con_user_wid->SetInfo(auth_rsp->_uid ,auth_rsp->_name, heads[head_i]);
  14. QListWidgetItem *item = new QListWidgetItem;
  15. //qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();
  16. item->setSizeHint(con_user_wid->sizeHint());
  17. // 获取 groupitem 的索引
  18. int index = this->row(_groupitem);
  19. // 在 groupitem 之后插入新项
  20. this->insertItem(index + 1, item);
  21. this->setItemWidget(item, con_user_wid);
  22. }

登录加载好友

因为添加好友后,如果客户端重新登录,服务器LoginHandler需要加载好友列表,所以服务器要返回好友列表

  1. void LogicSystem::LoginHandler(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 token = root["token"].asString();
  7. std::cout << "user login uid is " << uid << " user token is "
  8. << token << endl;
  9. Json::Value rtvalue;
  10. Defer defer([this, &rtvalue, session]() {
  11. std::string return_str = rtvalue.toStyledString();
  12. session->Send(return_str, MSG_CHAT_LOGIN_RSP);
  13. });
  14. //从redis获取用户token是否正确
  15. std::string uid_str = std::to_string(uid);
  16. std::string token_key = USERTOKENPREFIX + uid_str;
  17. std::string token_value = "";
  18. bool success = RedisMgr::GetInstance()->Get(token_key, token_value);
  19. if (!success) {
  20. rtvalue["error"] = ErrorCodes::UidInvalid;
  21. return ;
  22. }
  23. if (token_value != token) {
  24. rtvalue["error"] = ErrorCodes::TokenInvalid;
  25. return ;
  26. }
  27. rtvalue["error"] = ErrorCodes::Success;
  28. std::string base_key = USER_BASE_INFO + uid_str;
  29. auto user_info = std::make_shared<UserInfo>();
  30. bool b_base = GetBaseInfo(base_key, uid, user_info);
  31. if (!b_base) {
  32. rtvalue["error"] = ErrorCodes::UidInvalid;
  33. return;
  34. }
  35. rtvalue["uid"] = uid;
  36. rtvalue["pwd"] = user_info->pwd;
  37. rtvalue["name"] = user_info->name;
  38. rtvalue["email"] = user_info->email;
  39. rtvalue["nick"] = user_info->nick;
  40. rtvalue["desc"] = user_info->desc;
  41. rtvalue["sex"] = user_info->sex;
  42. rtvalue["icon"] = user_info->icon;
  43. //从数据库获取申请列表
  44. std::vector<std::shared_ptr<ApplyInfo>> apply_list;
  45. auto b_apply = GetFriendApplyInfo(uid,apply_list);
  46. if (b_apply) {
  47. for (auto & apply : apply_list) {
  48. Json::Value obj;
  49. obj["name"] = apply->_name;
  50. obj["uid"] = apply->_uid;
  51. obj["icon"] = apply->_icon;
  52. obj["nick"] = apply->_nick;
  53. obj["sex"] = apply->_sex;
  54. obj["desc"] = apply->_desc;
  55. obj["status"] = apply->_status;
  56. rtvalue["apply_list"].append(obj);
  57. }
  58. }
  59. //获取好友列表
  60. std::vector<std::shared_ptr<UserInfo>> friend_list;
  61. bool b_friend_list = GetFriendList(uid, friend_list);
  62. for (auto& friend_ele : friend_list) {
  63. Json::Value obj;
  64. obj["name"] = friend_ele->name;
  65. obj["uid"] = friend_ele->uid;
  66. obj["icon"] = friend_ele->icon;
  67. obj["nick"] = friend_ele->nick;
  68. obj["sex"] = friend_ele->sex;
  69. obj["desc"] = friend_ele->desc;
  70. obj["back"] = friend_ele->back;
  71. rtvalue["friend_list"].append(obj);
  72. }
  73. auto server_name = ConfigMgr::Inst().GetValue("SelfServer", "Name");
  74. //将登录数量增加
  75. auto rd_res = RedisMgr::GetInstance()->HGet(LOGIN_COUNT, server_name);
  76. int count = 0;
  77. if (!rd_res.empty()) {
  78. count = std::stoi(rd_res);
  79. }
  80. count++;
  81. auto count_str = std::to_string(count);
  82. RedisMgr::GetInstance()->HSet(LOGIN_COUNT, server_name, count_str);
  83. //session绑定用户uid
  84. session->SetUserId(uid);
  85. //为用户设置登录ip server的名字
  86. std::string ipkey = USERIPPREFIX + uid_str;
  87. RedisMgr::GetInstance()->Set(ipkey, server_name);
  88. //uid和session绑定管理,方便以后踢人操作
  89. UserMgr::GetInstance()->SetUserSession(uid, session);
  90. return;
  91. }

客户端在initHandlers中加载聊天列表

  1. _handlers.insert(ID_CHAT_LOGIN_RSP, [this](ReqId id, int len, QByteArray data){
  2. Q_UNUSED(len);
  3. qDebug()<< "handle id is "<< id ;
  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. qDebug()<< "data jsonobj is " << jsonObj ;
  13. if(!jsonObj.contains("error")){
  14. int err = ErrorCodes::ERR_JSON;
  15. qDebug() << "Login Failed, err is Json Parse Err" << err ;
  16. emit sig_login_failed(err);
  17. return;
  18. }
  19. int err = jsonObj["error"].toInt();
  20. if(err != ErrorCodes::SUCCESS){
  21. qDebug() << "Login Failed, err is " << err ;
  22. emit sig_login_failed(err);
  23. return;
  24. }
  25. auto uid = jsonObj["uid"].toInt();
  26. auto name = jsonObj["name"].toString();
  27. auto nick = jsonObj["nick"].toString();
  28. auto icon = jsonObj["icon"].toString();
  29. auto sex = jsonObj["sex"].toInt();
  30. auto user_info = std::make_shared<UserInfo>(uid, name, nick, icon, sex);
  31. UserMgr::GetInstance()->SetUserInfo(user_info);
  32. UserMgr::GetInstance()->SetToken(jsonObj["token"].toString());
  33. if(jsonObj.contains("apply_list")){
  34. UserMgr::GetInstance()->AppendApplyList(jsonObj["apply_list"].toArray());
  35. }
  36. //添加好友列表
  37. if (jsonObj.contains("friend_list")) {
  38. UserMgr::GetInstance()->AppendFriendList(jsonObj["friend_list"].toArray());
  39. }
  40. emit sig_swich_chatdlg();
  41. });

好友聊天

客户端发送聊天消息

客户端发送聊天消息,在输入框输入消息后,点击发送回执行下面的槽函数

  1. void ChatPage::on_send_btn_clicked()
  2. {
  3. if (_user_info == nullptr) {
  4. qDebug() << "friend_info is empty";
  5. return;
  6. }
  7. auto user_info = UserMgr::GetInstance()->GetUserInfo();
  8. auto pTextEdit = ui->chatEdit;
  9. ChatRole role = ChatRole::Self;
  10. QString userName = user_info->_name;
  11. QString userIcon = user_info->_icon;
  12. const QVector<MsgInfo>& msgList = pTextEdit->getMsgList();
  13. QJsonObject textObj;
  14. QJsonArray textArray;
  15. int txt_size = 0;
  16. for(int i=0; i<msgList.size(); ++i)
  17. {
  18. //消息内容长度不合规就跳过
  19. if(msgList[i].content.length() > 1024){
  20. continue;
  21. }
  22. QString type = msgList[i].msgFlag;
  23. ChatItemBase *pChatItem = new ChatItemBase(role);
  24. pChatItem->setUserName(userName);
  25. pChatItem->setUserIcon(QPixmap(userIcon));
  26. QWidget *pBubble = nullptr;
  27. if(type == "text")
  28. {
  29. //生成唯一id
  30. QUuid uuid = QUuid::createUuid();
  31. //转为字符串
  32. QString uuidString = uuid.toString();
  33. pBubble = new TextBubble(role, msgList[i].content);
  34. if(txt_size + msgList[i].content.length()> 1024){
  35. textObj["fromuid"] = user_info->_uid;
  36. textObj["touid"] = _user_info->_uid;
  37. textObj["text_array"] = textArray;
  38. QJsonDocument doc(textObj);
  39. QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
  40. //发送并清空之前累计的文本列表
  41. txt_size = 0;
  42. textArray = QJsonArray();
  43. textObj = QJsonObject();
  44. //发送tcp请求给chat server
  45. emit TcpMgr::GetInstance()->sig_send_data(ReqId::ID_TEXT_CHAT_MSG_REQ, jsonData);
  46. }
  47. //将bubble和uid绑定,以后可以等网络返回消息后设置是否送达
  48. //_bubble_map[uuidString] = pBubble;
  49. txt_size += msgList[i].content.length();
  50. QJsonObject obj;
  51. QByteArray utf8Message = msgList[i].content.toUtf8();
  52. obj["content"] = QString::fromUtf8(utf8Message);
  53. obj["msgid"] = uuidString;
  54. textArray.append(obj);
  55. auto txt_msg = std::make_shared<TextChatData>(uuidString, obj["content"].toString(),
  56. user_info->_uid, _user_info->_uid);
  57. emit sig_append_send_chat_msg(txt_msg);
  58. }
  59. else if(type == "image")
  60. {
  61. pBubble = new PictureBubble(QPixmap(msgList[i].content) , role);
  62. }
  63. else if(type == "file")
  64. {
  65. }
  66. //发送消息
  67. if(pBubble != nullptr)
  68. {
  69. pChatItem->setWidget(pBubble);
  70. ui->chat_data_list->appendChatItem(pChatItem);
  71. }
  72. }
  73. qDebug() << "textArray is " << textArray ;
  74. //发送给服务器
  75. textObj["text_array"] = textArray;
  76. textObj["fromuid"] = user_info->_uid;
  77. textObj["touid"] = _user_info->_uid;
  78. QJsonDocument doc(textObj);
  79. QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
  80. //发送并清空之前累计的文本列表
  81. txt_size = 0;
  82. textArray = QJsonArray();
  83. textObj = QJsonObject();
  84. //发送tcp请求给chat server
  85. emit TcpMgr::GetInstance()->sig_send_data(ReqId::ID_TEXT_CHAT_MSG_REQ, jsonData);
  86. }

TcpMgr响应发送信号

  1. void TcpMgr::slot_send_data(ReqId reqId, QByteArray dataBytes)
  2. {
  3. uint16_t id = reqId;
  4. // 计算长度(使用网络字节序转换)
  5. quint16 len = static_cast<quint16>(dataBytes.length());
  6. // 创建一个QByteArray用于存储要发送的所有数据
  7. QByteArray block;
  8. QDataStream out(&block, QIODevice::WriteOnly);
  9. // 设置数据流使用网络字节序
  10. out.setByteOrder(QDataStream::BigEndian);
  11. // 写入ID和长度
  12. out << id << len;
  13. // 添加字符串数据
  14. block.append(dataBytes);
  15. // 发送数据
  16. _socket.write(block);
  17. qDebug() << "tcp mgr send byte data is " << block ;
  18. }

服务器响应

服务器响应客户端发送过来文本消息,在initHandlers中添加处理文本消息的逻辑

  1. void LogicSystem::DealChatTextMsg(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["fromuid"].asInt();
  6. auto touid = root["touid"].asInt();
  7. const Json::Value arrays = root["text_array"];
  8. Json::Value rtvalue;
  9. rtvalue["error"] = ErrorCodes::Success;
  10. rtvalue["text_array"] = arrays;
  11. rtvalue["fromuid"] = uid;
  12. rtvalue["touid"] = touid;
  13. Defer defer([this, &rtvalue, session]() {
  14. std::string return_str = rtvalue.toStyledString();
  15. session->Send(return_str, ID_TEXT_CHAT_MSG_RSP);
  16. });
  17. //查询redis 查找touid对应的server ip
  18. auto to_str = std::to_string(touid);
  19. auto to_ip_key = USERIPPREFIX + to_str;
  20. std::string to_ip_value = "";
  21. bool b_ip = RedisMgr::GetInstance()->Get(to_ip_key, to_ip_value);
  22. if (!b_ip) {
  23. return;
  24. }
  25. auto& cfg = ConfigMgr::Inst();
  26. auto self_name = cfg["SelfServer"]["Name"];
  27. //直接通知对方有认证通过消息
  28. if (to_ip_value == self_name) {
  29. auto session = UserMgr::GetInstance()->GetSession(touid);
  30. if (session) {
  31. //在内存中则直接发送通知对方
  32. std::string return_str = rtvalue.toStyledString();
  33. session->Send(return_str, ID_NOTIFY_TEXT_CHAT_MSG_REQ);
  34. }
  35. return ;
  36. }
  37. TextChatMsgReq text_msg_req;
  38. text_msg_req.set_fromuid(uid);
  39. text_msg_req.set_touid(touid);
  40. for (const auto& txt_obj : arrays) {
  41. auto content = txt_obj["content"].asString();
  42. auto msgid = txt_obj["msgid"].asString();
  43. std::cout << "content is " << content << std::endl;
  44. std::cout << "msgid is " << msgid << std::endl;
  45. auto *text_msg = text_msg_req.add_textmsgs();
  46. text_msg->set_msgid(msgid);
  47. text_msg->set_msgcontent(content);
  48. }
  49. //发送通知 todo...
  50. ChatGrpcClient::GetInstance()->NotifyTextChatMsg(to_ip_value, text_msg_req, rtvalue);
  51. }

服务器实现发送消息的rpc客户端

  1. TextChatMsgRsp ChatGrpcClient::NotifyTextChatMsg(std::string server_ip,
  2. const TextChatMsgReq& req, const Json::Value& rtvalue) {
  3. TextChatMsgRsp rsp;
  4. rsp.set_error(ErrorCodes::Success);
  5. Defer defer([&rsp, &req]() {
  6. rsp.set_fromuid(req.fromuid());
  7. rsp.set_touid(req.touid());
  8. for (const auto& text_data : req.textmsgs()) {
  9. TextChatData* new_msg = rsp.add_textmsgs();
  10. new_msg->set_msgid(text_data.msgid());
  11. new_msg->set_msgcontent(text_data.msgcontent());
  12. }
  13. });
  14. auto find_iter = _pools.find(server_ip);
  15. if (find_iter == _pools.end()) {
  16. return rsp;
  17. }
  18. auto& pool = find_iter->second;
  19. ClientContext context;
  20. auto stub = pool->getConnection();
  21. Status status = stub->NotifyTextChatMsg(&context, req, &rsp);
  22. Defer defercon([&stub, this, &pool]() {
  23. pool->returnConnection(std::move(stub));
  24. });
  25. if (!status.ok()) {
  26. rsp.set_error(ErrorCodes::RPCFailed);
  27. return rsp;
  28. }
  29. return rsp;
  30. }

服务器实现rpc服务端处理消息通知

  1. Status ChatServiceImpl::NotifyTextChatMsg(::grpc::ServerContext* context,
  2. const TextChatMsgReq* request, TextChatMsgRsp* reply) {
  3. //查找用户是否在本服务器
  4. auto touid = request->touid();
  5. auto session = UserMgr::GetInstance()->GetSession(touid);
  6. reply->set_error(ErrorCodes::Success);
  7. //用户不在内存中则直接返回
  8. if (session == nullptr) {
  9. return Status::OK;
  10. }
  11. //在内存中则直接发送通知对方
  12. Json::Value rtvalue;
  13. rtvalue["error"] = ErrorCodes::Success;
  14. rtvalue["fromuid"] = request->fromuid();
  15. rtvalue["touid"] = request->touid();
  16. //将聊天数据组织为数组
  17. Json::Value text_array;
  18. for (auto& msg : request->textmsgs()) {
  19. Json::Value element;
  20. element["content"] = msg.msgcontent();
  21. element["msgid"] = msg.msgid();
  22. text_array.append(element);
  23. }
  24. rtvalue["text_array"] = text_array;
  25. std::string return_str = rtvalue.toStyledString();
  26. session->Send(return_str, ID_NOTIFY_TEXT_CHAT_MSG_REQ);
  27. return Status::OK;
  28. }

客户端响应通知

客户端响应服务器返回的消息,包括两种:

  1. A给B发送文本消息,A所在的服务器会给A发送ID_TEXT_CHAT_MSG_RSP消息。
  2. B所在的服务器会通知B,告诉B有来自A的消息,通知消息为ID_NOTIFY_TEXT_CHAT_MSG_REQ

所以在tcpmgr的initHandlers中添加响应ID_TEXT_CHAT_MSG_RSP消息

  1. _handlers.insert(ID_TEXT_CHAT_MSG_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() << "Chat Msg Rsp Failed, err is Json Parse Err" << err;
  15. return;
  16. }
  17. int err = jsonObj["error"].toInt();
  18. if (err != ErrorCodes::SUCCESS) {
  19. qDebug() << "Chat Msg Rsp Failed, err is " << err;
  20. return;
  21. }
  22. qDebug() << "Receive Text Chat Rsp Success " ;
  23. //ui设置送达等标记 todo...
  24. });

在TcpMgr的initHandlers中添加ID_NOTIFY_TEXT_CHAT_MSG_REQ

  1. _handlers.insert(ID_NOTIFY_TEXT_CHAT_MSG_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() << "Notify Chat Msg Failed, err is Json Parse Err" << err;
  15. return;
  16. }
  17. int err = jsonObj["error"].toInt();
  18. if (err != ErrorCodes::SUCCESS) {
  19. qDebug() << "Notify Chat Msg Failed, err is " << err;
  20. return;
  21. }
  22. qDebug() << "Receive Text Chat Notify Success " ;
  23. auto msg_ptr = std::make_shared<TextChatMsg>(jsonObj["fromuid"].toInt(),
  24. jsonObj["touid"].toInt(),jsonObj["text_array"].toArray());
  25. emit sig_text_chat_msg(msg_ptr);
  26. });

客户端ChatDialog添加对sig_text_chat_msg的响应

  1. void ChatDialog::slot_text_chat_msg(std::shared_ptr<TextChatMsg> msg)
  2. {
  3. auto find_iter = _chat_items_added.find(msg->_from_uid);
  4. if(find_iter != _chat_items_added.end()){
  5. qDebug() << "set chat item msg, uid is " << msg->_from_uid;
  6. QWidget *widget = ui->chat_user_list->itemWidget(find_iter.value());
  7. auto chat_wid = qobject_cast<ChatUserWid*>(widget);
  8. if(!chat_wid){
  9. return;
  10. }
  11. chat_wid->updateLastMsg(msg->_chat_msgs);
  12. //更新当前聊天页面记录
  13. UpdateChatMsg(msg->_chat_msgs);
  14. UserMgr::GetInstance()->AppendFriendChatMsg(msg->_from_uid,msg->_chat_msgs);
  15. return;
  16. }
  17. //如果没找到,则创建新的插入listwidget
  18. auto* chat_user_wid = new ChatUserWid();
  19. //查询好友信息
  20. auto fi_ptr = UserMgr::GetInstance()->GetFriendById(msg->_from_uid);
  21. chat_user_wid->SetInfo(fi_ptr);
  22. QListWidgetItem* item = new QListWidgetItem;
  23. //qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();
  24. item->setSizeHint(chat_user_wid->sizeHint());
  25. chat_user_wid->updateLastMsg(msg->_chat_msgs);
  26. UserMgr::GetInstance()->AppendFriendChatMsg(msg->_from_uid,msg->_chat_msgs);
  27. ui->chat_user_list->insertItem(0, item);
  28. ui->chat_user_list->setItemWidget(item, chat_user_wid);
  29. _chat_items_added.insert(msg->_from_uid, item);
  30. }

效果展示

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

源码连接

https://gitee.com/secondtonone1/llfcchat

视频连接

https://www.bilibili.com/video/BV1ib421J745/?vd_source=8be9e83424c2ed2c9b2a3ed1d01385e9

热门评论

热门文章

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

    喜欢(588) 浏览(4233)
  2. Linux环境搭建和编码

    喜欢(594) 浏览(9695)
  3. slice介绍和使用

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

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

    喜欢(566) 浏览(2998)

最新评论

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

个人公众号

个人微信