用户登录加载聊天资源

前情回顾

前面我们实现了聊天资源的下载和上传,以及异步通知断点下载等功能。

今天主要实现登录后聊天信息的加载,之前把文本信息的加载实现了,现在需要实现用户登录后图片信息的加载。

这个功能做完,聊天的基本功能就都收尾了,有意思的是好像一个圆圈,我们又回到了最初的原点,从登录逻辑出发,补充聊天资源加载

登录流程回顾

image-20260209150133552

点击登录按钮后,通过HttpMgr发送登录请求

  1. void LoginDialog::on_login_btn_clicked()
  2. {
  3. qDebug()<<"login btn clicked";
  4. if(checkUserValid() == false){
  5. return;
  6. }
  7. if(checkPwdValid() == false){
  8. return ;
  9. }
  10. enableBtn(false);
  11. auto email = ui->email_edit->text();
  12. auto pwd = ui->pass_edit->text();
  13. //发送http请求登录
  14. QJsonObject json_obj;
  15. json_obj["email"] = email;
  16. json_obj["passwd"] = xorString(pwd);
  17. HttpMgr::GetInstance()->PostHttpReq(QUrl(gate_url_prefix+"/user_login"),
  18. json_obj, ReqId::ID_LOGIN_USER,Modules::LOGINMOD);
  19. }

HttpMgr内部封装了PostHttpReq接口,这是异步http请求,会提前构造好一个reply,以及注册一个回调函数,将来GateServer将登录信息返回后会出发这个回调函数

  1. void HttpMgr::PostHttpReq(QUrl url, QJsonObject json, ReqId req_id, Modules mod)
  2. {
  3. //创建一个HTTP POST请求,并设置请求头和请求体
  4. QByteArray data = QJsonDocument(json).toJson();
  5. //通过url构造请求
  6. QNetworkRequest request(url);
  7. request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
  8. request.setHeader(QNetworkRequest::ContentLengthHeader, QByteArray::number(data.length()));
  9. //发送请求,并处理响应, 获取自己的智能指针,构造伪闭包并增加智能指针引用计数
  10. auto self = shared_from_this();
  11. QNetworkReply * reply = _manager.post(request, data);
  12. //设置信号和槽等待发送完成
  13. QObject::connect(reply, &QNetworkReply::finished, [reply, self, req_id, mod](){
  14. //处理错误的情况
  15. if(reply->error() != QNetworkReply::NoError){
  16. qDebug() << reply->errorString();
  17. //发送信号通知完成
  18. emit self->sig_http_finish(req_id, "", ErrorCodes::ERR_NETWORK, mod);
  19. reply->deleteLater();
  20. return;
  21. }
  22. //无错误则读回请求
  23. QString res = reply->readAll();
  24. //发送信号通知完成
  25. emit self->sig_http_finish(req_id, res, ErrorCodes::SUCCESS,mod);
  26. reply->deleteLater();
  27. return;
  28. });
  29. }

我们查看下GateServer的处理流程

  1. bool LogicSystem::HandlePost(std::string path, std::shared_ptr<HttpConnection> con) {
  2. if (_post_handlers.find(path) == _post_handlers.end()) {
  3. return false;
  4. }
  5. _post_handlers[path](con);
  6. return true;
  7. }

服务器会根据客户端传递的url进行分析,然后去_post_handlers中根据path查找并调用回调函数

回调函数在LogicSystem中提前注册到_post_handlers中

  1. void LogicSystem::RegPost(std::string url, HttpHandler handler) {
  2. _post_handlers.insert(make_pair(url, handler));
  3. }

在构造函数中调用RegPost注册消息

  1. //用户登录逻辑
  2. RegPost("/user_login", [](std::shared_ptr<HttpConnection> connection) {
  3. auto body_str = boost::beast::buffers_to_string(connection->_request.body().data());
  4. std::cout << "receive body is " << body_str << std::endl;
  5. connection->_response.set(http::field::content_type, "text/json");
  6. Json::Value root;
  7. Json::Reader reader;
  8. Json::Value src_root;
  9. bool parse_success = reader.parse(body_str, src_root);
  10. if (!parse_success) {
  11. std::cout << "Failed to parse JSON data!" << std::endl;
  12. root["error"] = ErrorCodes::Error_Json;
  13. std::string jsonstr = root.toStyledString();
  14. beast::ostream(connection->_response.body()) << jsonstr;
  15. return true;
  16. }
  17. auto email = src_root["email"].asString();
  18. auto pwd = src_root["passwd"].asString();
  19. UserInfo userInfo;
  20. //查询数据库判断用户名和密码是否匹配
  21. bool pwd_valid = MysqlMgr::GetInstance()->CheckPwd(email, pwd, userInfo);
  22. if (!pwd_valid) {
  23. std::cout << " user pwd not match" << std::endl;
  24. root["error"] = ErrorCodes::PasswdInvalid;
  25. std::string jsonstr = root.toStyledString();
  26. beast::ostream(connection->_response.body()) << jsonstr;
  27. return true;
  28. }
  29. //查询StatusServer找到合适的连接
  30. auto reply = StatusGrpcClient::GetInstance()->GetChatServer(userInfo.uid);
  31. if (reply.error()) {
  32. std::cout << " grpc get chat server failed, error is " << reply.error()<< std::endl;
  33. root["error"] = ErrorCodes::RPCFailed;
  34. std::string jsonstr = root.toStyledString();
  35. beast::ostream(connection->_response.body()) << jsonstr;
  36. return true;
  37. }
  38. std::cout << "succeed to load userinfo uid is " << userInfo.uid << std::endl;
  39. root["error"] = 0;
  40. root["email"] = email;
  41. root["uid"] = userInfo.uid;
  42. root["token"] = reply.token();
  43. root["chathost"] = reply.host();
  44. root["chatport"] = reply.port();
  45. auto& gCfgMgr = ConfigMgr::Inst();
  46. std::string res_port = gCfgMgr["ResServer"]["Port"];
  47. std::string res_host = gCfgMgr["ResServer"]["Host"];
  48. root["reshost"] = res_host;
  49. root["resport"] = res_port;
  50. std::string jsonstr = root.toStyledString();
  51. beast::ostream(connection->_response.body()) << jsonstr;
  52. return true;
  53. });

所以GateServer会触发上面的lambda表达式处理

在lambda表达式中调用GRPC连接池,向StatusServer发送请求,获取可用的聊天服务器地址,将聊天服务器地址返回给GateServer,GateServer再将地址返回给客户端, rpc封装

  1. GetChatServerRsp StatusGrpcClient::GetChatServer(int uid)
  2. {
  3. ClientContext context;
  4. GetChatServerRsp reply;
  5. GetChatServerReq request;
  6. request.set_uid(uid);
  7. auto stub = pool_->getConnection();
  8. Status status = stub->GetChatServer(&context, request, &reply);
  9. Defer defer([&stub, this]() {
  10. pool_->returnConnection(std::move(stub));
  11. });
  12. if (status.ok()) {
  13. return reply;
  14. }
  15. else {
  16. reply.set_error(ErrorCodes::RPCFailed);
  17. return reply;
  18. }
  19. }

客户端收到GateServer回复后,客户端会根据消息ID为ReqId::ID_LOGIN_USER发送sig_http_finish信号通知

该信号连接了

  1. HttpMgr::HttpMgr()
  2. {
  3. //连接http请求和完成信号,信号槽机制保证队列消费
  4. connect(this, &HttpMgr::sig_http_finish, this, &HttpMgr::slot_http_finish);
  5. }

分别对应三个请求的处理

  1. void HttpMgr::slot_http_finish(ReqId id, QString res, ErrorCodes err, Modules mod)
  2. {
  3. if(mod == Modules::REGISTERMOD){
  4. //发送信号通知指定模块http响应结束
  5. emit sig_reg_mod_finish(id, res, err);
  6. }
  7. if(mod == Modules::RESETMOD){
  8. //发送信号通知指定模块http响应结束
  9. emit sig_reset_mod_finish(id, res, err);
  10. }
  11. if(mod == Modules::LOGINMOD){
  12. emit sig_login_mod_finish(id, res, err);
  13. }
  14. }

对于登录请求,主要逻辑在sig_login_mod_finish信号发出看,该信号在LoginDialog中连接

  1. //连接登录回包信号
  2. connect(HttpMgr::GetInstance().get(), &HttpMgr::sig_login_mod_finish, this,
  3. &LoginDialog::slot_login_mod_finish);

进而出发槽函数

  1. void LoginDialog::slot_login_mod_finish(ReqId id, QString res, ErrorCodes err)
  2. {
  3. if(err != ErrorCodes::SUCCESS){
  4. showTip(tr("网络请求错误"),false);
  5. return;
  6. }
  7. // 解析 JSON 字符串,res需转化为QByteArray
  8. QJsonDocument jsonDoc = QJsonDocument::fromJson(res.toUtf8());
  9. //json解析错误
  10. if(jsonDoc.isNull()){
  11. showTip(tr("json解析错误"),false);
  12. return;
  13. }
  14. //json解析错误
  15. if(!jsonDoc.isObject()){
  16. showTip(tr("json解析错误"),false);
  17. return;
  18. }
  19. //调用对应的逻辑,根据id回调。
  20. _handlers[id](jsonDoc.object());
  21. return;
  22. }

槽函数中根据id获取提前注册好的回调函数

  1. _handlers[id](jsonDoc.object());

之前的注册逻辑

  1. void LoginDialog::initHttpHandlers()
  2. {
  3. //注册获取登录回包逻辑
  4. _handlers.insert(ReqId::ID_LOGIN_USER, [this](QJsonObject jsonObj){
  5. int error = jsonObj["error"].toInt();
  6. if(error != ErrorCodes::SUCCESS){
  7. showTip(tr("参数错误"),false);
  8. enableBtn(true);
  9. return;
  10. }
  11. auto email = jsonObj["email"].toString();
  12. //发送信号通知tcpMgr发送长链接
  13. _si = std::make_shared<ServerInfo>();
  14. _si->_uid = jsonObj["uid"].toInt();
  15. _si->_chat_host = jsonObj["chathost"].toString();
  16. _si->_chat_port = jsonObj["chatport"].toString();
  17. _si->_token = jsonObj["token"].toString();
  18. _si->_res_host = jsonObj["reshost"].toString();
  19. _si->_res_port = jsonObj["resport"].toString();
  20. qDebug()<< "email is " << email << " uid is " << _si->_uid <<" chat host is "
  21. << _si->_chat_host << " chat port is "
  22. << _si->_chat_port << " token is " << _si->_token
  23. << " res host is " << _si->_res_host
  24. << " res port is " << _si->_res_port;
  25. emit sig_connect_tcp(_si);
  26. // qDebug() << "send thread is " << QThread::currentThread();
  27. // emit sig_test();
  28. });
  29. }

所以当消息到来时会出发上面的lambda表达式,进而发出sig_connect_tcp信号, 该信号链接槽函数slot_tcp_connect

  1. //连接tcp连接请求的信号和槽函数
  2. connect(this, &LoginDialog::sig_connect_tcp, TcpMgr::GetInstance().get(), &TcpMgr::slot_tcp_connect);

槽函数内根据host地址链接指定的tcpserver

  1. void TcpMgr::slot_tcp_connect(std::shared_ptr<ServerInfo> si)
  2. {
  3. qDebug()<< "receive tcp connect signal";
  4. // 尝试连接到服务器
  5. qDebug() << "Connecting to chat server...";
  6. _host = si->_chat_host;
  7. _port = static_cast<uint16_t>(si->_chat_port.toUInt());
  8. _socket.connectToHost(_host, _port);
  9. }

_socket 是QTcpSocket类型,发出连接请求后,如果和服务器建立好连接后,会触发链接成功的回调, 在TcpMgr的构造函数中提前注册了消息和回调函数

  1. TcpMgr::TcpMgr():_host(""),_port(0),_b_recv_pending(false),_message_id(0),_message_len(0),_bytes_sent(0),_pending(false)
  2. {
  3. registerMetaType();
  4. QObject::connect(&_socket, &QTcpSocket::connected, this, [&]() {
  5. qDebug() << "Connected to server!";
  6. // 连接建立后发送消息
  7. emit sig_con_success(true);
  8. });
  9. QObject::connect(&_socket, &QTcpSocket::readyRead, this, [&]() {
  10. // 当有数据可读时,读取所有数据
  11. // 读取所有数据并追加到缓冲区
  12. _buffer.append(_socket.readAll());
  13. forever {
  14. //先解析头部
  15. if(!_b_recv_pending){
  16. // 检查缓冲区中的数据是否足够解析出一个消息头(消息ID + 消息长度)
  17. if (_buffer.size() < static_cast<int>(sizeof(quint16) * 2)) {
  18. return; // 数据不够,等待更多数据
  19. }
  20. // ✅ 每次都重新创建stream
  21. QDataStream stream(_buffer);
  22. stream.setVersion(QDataStream::Qt_5_0);
  23. stream >> _message_id >> _message_len;
  24. _buffer.remove(0, sizeof(quint16) * 2); // 使用remove代替mid赋值
  25. qDebug() << "Message ID:" << _message_id << ", Length:" << _message_len;
  26. }
  27. //buffer剩余长读是否满足消息体长度,不满足则退出继续等待接受
  28. if(_buffer.size() < _message_len){
  29. _b_recv_pending = true;
  30. return;
  31. }
  32. _b_recv_pending = false;
  33. // 读取消息体
  34. QByteArray messageBody = _buffer.mid(0, _message_len);
  35. qDebug() << "receive body msg is " << messageBody ;
  36. _buffer = _buffer.mid(_message_len);
  37. handleMsg(ReqId(_message_id),_message_len, messageBody);
  38. }
  39. });
  40. //5.15 之后版本
  41. // QObject::connect(&_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::errorOccurred), [&](QAbstractSocket::SocketError socketError) {
  42. // Q_UNUSED(socketError)
  43. // qDebug() << "Error:" << _socket.errorString();
  44. // });
  45. // 处理错误(适用于Qt 5.15之前的版本)
  46. QObject::connect(&_socket, static_cast<void (QTcpSocket::*)(QTcpSocket::SocketError)>(&QTcpSocket::error),
  47. this,
  48. [&](QTcpSocket::SocketError socketError) {
  49. qDebug() << "Error:" << _socket.errorString() ;
  50. switch (socketError) {
  51. case QTcpSocket::ConnectionRefusedError:
  52. qDebug() << "Connection Refused!";
  53. emit sig_con_success(false);
  54. break;
  55. case QTcpSocket::RemoteHostClosedError:
  56. qDebug() << "Remote Host Closed Connection!";
  57. break;
  58. case QTcpSocket::HostNotFoundError:
  59. qDebug() << "Host Not Found!";
  60. emit sig_con_success(false);
  61. break;
  62. case QTcpSocket::SocketTimeoutError:
  63. qDebug() << "Connection Timeout!";
  64. emit sig_con_success(false);
  65. break;
  66. case QTcpSocket::NetworkError:
  67. qDebug() << "Network Error!";
  68. break;
  69. default:
  70. qDebug() << "Other Error!";
  71. break;
  72. }
  73. });
  74. // 处理连接断开
  75. QObject::connect(&_socket, &QTcpSocket::disconnected, this,[&]() {
  76. qDebug() << "Disconnected from server.";
  77. //并且发送通知到界面
  78. emit sig_connection_closed();
  79. });
  80. //连接发送信号用来发送数据
  81. QObject::connect(this, &TcpMgr::sig_send_data, this, &TcpMgr::slot_send_data);
  82. //连接发送信号
  83. QObject::connect(&_socket, &QTcpSocket::bytesWritten, this, [this](qint64 bytes) {
  84. //更新发送数据
  85. _bytes_sent += bytes;
  86. //未发送完整
  87. if (_bytes_sent < _current_block.size()) {
  88. //继续发送
  89. auto data_to_send = _current_block.mid(_bytes_sent);
  90. _socket.write(data_to_send);
  91. return;
  92. }
  93. //发送完全,则查看队列是否为空
  94. if (_send_queue.isEmpty()) {
  95. //队列为空,说明已经将所有数据发送完成,将pending设置为false,这样后续要发送数据时可以继续发送
  96. _current_block.clear();
  97. _pending = false;
  98. _bytes_sent = 0;
  99. return;
  100. }
  101. //队列不为空,则取出队首元素
  102. _current_block = _send_queue.dequeue();
  103. _bytes_sent = 0;
  104. _pending = true;
  105. qint64 w2 = _socket.write(_current_block);
  106. qDebug() << "[TcpMgr] Dequeued and write() returned" << w2;
  107. });
  108. //关闭socket
  109. connect(this, &TcpMgr::sig_close, this, &TcpMgr::slot_tcp_close);
  110. //注册消息
  111. initHandlers();
  112. }

当客户端和服务器建立连接后,会出发下面的lambda表达式

  1. QObject::connect(&_socket, &QTcpSocket::connected, this, [&]() {
  2. qDebug() << "Connected to server!";
  3. // 连接建立后发送消息
  4. emit sig_con_success(true);
  5. });

从而发出sig_con_success信号, 信号连接了槽函数slot_tcp_con_finish

  1. //连接tcp管理者发出的连接成功信号
  2. connect(TcpMgr::GetInstance().get(), &TcpMgr::sig_con_success, this, &LoginDialog::slot_tcp_con_finish);

槽函数里链接资源服务器

  1. void LoginDialog::slot_tcp_con_finish(bool bsuccess)
  2. {
  3. if(bsuccess){
  4. showTip(tr("聊天服务连接成功,正在连接资源服务器..."),true);
  5. emit sig_connect_res_server(_si);
  6. }else{
  7. showTip(tr("网络异常"), false);
  8. }
  9. }

该信号连接了槽函数

  1. //连接tcp连接资源服务器请求的信号和槽函数
  2. connect(this, &LoginDialog::sig_connect_res_server,
  3. FileTcpMgr::GetInstance().get(), &FileTcpMgr::slot_tcp_connect);

链接资源服务器的逻辑和之前链接ChatServer类似

  1. void FileTcpMgr::slot_tcp_connect(std::shared_ptr<ServerInfo> si)
  2. {
  3. qDebug() << "receive tcp connect signal";
  4. // 尝试连接到服务器
  5. qDebug() << "Connecting to server...";
  6. _host = si->_res_host;
  7. _port = static_cast<uint16_t>(si->_res_port.toUInt());
  8. _socket.connectToHost(_host, _port);
  9. }

当客户端链接资源服务器成功了,就会出发lambda表达式

  1. QObject::connect(&_socket, &QTcpSocket::connected, this, [&]() {
  2. qDebug() << "Connected to server!";
  3. emit sig_con_success(true);
  4. });

信号sig_con_success和槽函数链接

  1. connect(FileTcpMgr::GetInstance().get(), &FileTcpMgr::sig_con_success, this, &LoginDialog::slot_res_con_finish);

进而触发这个函数,内部发送登录请求给ChatServer

  1. void LoginDialog::slot_res_con_finish(bool bsuccess)
  2. {
  3. if(bsuccess){
  4. showTip(tr("聊天服务连接成功,正在登录..."),true);
  5. QJsonObject jsonObj;
  6. jsonObj["uid"] = _si->_uid;
  7. jsonObj["token"] = _si->_token;
  8. QJsonDocument doc(jsonObj);
  9. QByteArray jsonData = doc.toJson(QJsonDocument::Indented);
  10. //发送tcp请求给chat server
  11. emit TcpMgr::GetInstance()->sig_send_data(ReqId::ID_CHAT_LOGIN, jsonData);
  12. }else{
  13. showTip(tr("网络异常"),false);
  14. enableBtn(true);
  15. }
  16. }

这里略去ChatServer的处理,客户端收到ID_CHAT_LOGIN_RSP回复后,出发lambda表达式

  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 desc = jsonObj["desc"].toString();
  31. auto user_info = std::make_shared<UserInfo>(uid, name, nick, icon, sex,"",desc);
  32. UserMgr::GetInstance()->SetUserInfo(user_info);
  33. UserMgr::GetInstance()->SetToken(jsonObj["token"].toString());
  34. if(jsonObj.contains("apply_list")){
  35. UserMgr::GetInstance()->AppendApplyList(jsonObj["apply_list"].toArray());
  36. }
  37. //添加好友列表
  38. if (jsonObj.contains("friend_list")) {
  39. UserMgr::GetInstance()->AppendFriendList(jsonObj["friend_list"].toArray());
  40. }
  41. emit sig_swich_chatdlg();
  42. });

将用户信息,以及好友列表,申请列表等数据组织好后存储UserMgr中,然后发送信号sig_swich_chatdlg跳转到登录界面

链接信号

  1. //连接创建聊天界面信号
  2. connect(TcpMgr::GetInstance().get(),&TcpMgr::sig_swich_chatdlg, this, &MainWindow::SlotSwitchChat);

跳转逻辑

  1. void MainWindow::SlotSwitchChat()
  2. {
  3. _chat_dlg = new ChatDialog();
  4. _chat_dlg->setWindowFlags(Qt::CustomizeWindowHint|Qt::FramelessWindowHint);
  5. setCentralWidget(_chat_dlg);
  6. _chat_dlg->show();
  7. _login_dlg->hide();
  8. this->setMinimumSize(QSize(1050,900));
  9. this->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
  10. _ui_status = CHAT_UI;
  11. _chat_dlg->loadChatList();
  12. }

至此完成登录界面加载以及聊天记录加载,聊天记录加载具体流程看下面

加载聊天记录

其中加载聊天记录核心逻辑

  1. void ChatDialog::loadChatList()
  2. {
  3. showLoadingDlg(true);
  4. //发送请求逻辑
  5. QJsonObject jsonObj;
  6. auto uid = UserMgr::GetInstance()->GetUid();
  7. jsonObj["uid"] = uid;
  8. int last_chat_thread_id = UserMgr::GetInstance()->GetLastChatThreadId();
  9. jsonObj["thread_id"] = last_chat_thread_id;
  10. QJsonDocument doc(jsonObj);
  11. QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
  12. //发送tcp请求给chat server
  13. emit TcpMgr::GetInstance()->sig_send_data(ReqId::ID_LOAD_CHAT_THREAD_REQ, jsonData);
  14. }

发送ID_LOAD_CHAT_THREAD_REQ逻辑给ChatServer,略去服务器处理流程,客户端会受到会话列表的回复数据

  1. _handlers.insert(ID_LOAD_CHAT_THREAD_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 thread json parse failed " << err;
  15. return;
  16. }
  17. int err = jsonObj["error"].toInt();
  18. if (err != ErrorCodes::SUCCESS) {
  19. qDebug() << "get chat thread rsp failed, error is " << err;
  20. return;
  21. }
  22. qDebug() << "Receive chat thread rsp Success";
  23. auto thread_array = jsonObj["threads"].toArray();
  24. std::vector<std::shared_ptr<ChatThreadInfo>> chat_threads;
  25. for (const QJsonValue& value : thread_array) {
  26. auto cti = std::make_shared<ChatThreadInfo>();
  27. cti->_thread_id = value["thread_id"].toInt();
  28. cti->_type = value["type"].toString();
  29. cti->_user1_id = value["user1_id"].toInt();
  30. cti->_user2_id = value["user2_id"].toInt();
  31. chat_threads.push_back(cti);
  32. }
  33. bool load_more = jsonObj["load_more"].toBool();
  34. int next_last_id = jsonObj["next_last_id"].toInt();
  35. //发送信号通知界面
  36. emit sig_load_chat_thread(load_more, next_last_id, chat_threads);
  37. });

解析会话列表,按照会话id请求每个会话具体内容

  1. void ChatDialog::slot_load_chat_thread(bool load_more, int last_thread_id,
  2. std::vector<std::shared_ptr<ChatThreadInfo>> chat_threads)
  3. {
  4. for (auto& cti : chat_threads) {
  5. //先处理单聊,群聊跳过,以后添加
  6. if (cti->_type == "group") {
  7. continue;
  8. }
  9. auto uid = UserMgr::GetInstance()->GetUid();
  10. auto other_uid = 0;
  11. if (uid == cti->_user1_id) {
  12. other_uid = cti->_user2_id;
  13. }
  14. else {
  15. other_uid = cti->_user1_id;
  16. }
  17. auto chat_thread_data = std::make_shared<ChatThreadData>(other_uid, cti->_thread_id, 0);
  18. UserMgr::GetInstance()->AddChatThreadData(chat_thread_data, other_uid);
  19. auto* chat_user_wid = new ChatUserWid();
  20. chat_user_wid->SetChatData(chat_thread_data);
  21. QListWidgetItem* item = new QListWidgetItem;
  22. //qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();
  23. item->setSizeHint(chat_user_wid->sizeHint());
  24. ui->chat_user_list->addItem(item);
  25. ui->chat_user_list->setItemWidget(item, chat_user_wid);
  26. _chat_thread_items.insert(cti->_thread_id, item);
  27. }
  28. UserMgr::GetInstance()->SetLastChatThreadId(last_thread_id);
  29. if (load_more) {
  30. //发送请求逻辑
  31. QJsonObject jsonObj;
  32. auto uid = UserMgr::GetInstance()->GetUid();
  33. jsonObj["uid"] = uid;
  34. jsonObj["thread_id"] = last_thread_id;
  35. QJsonDocument doc(jsonObj);
  36. QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
  37. //发送tcp请求给chat server
  38. emit TcpMgr::GetInstance()->sig_send_data(ReqId::ID_LOAD_CHAT_THREAD_REQ, jsonData);
  39. return;
  40. }
  41. showLoadingDlg(false);
  42. //继续加载聊天数据
  43. loadChatMsg();
  44. }

如果load_more为true,说明有会话没加载完,需要继续加载,等到所有会话信息加载成功后,load_more为false,则将当前会话的消息列表添加到聊天界面。如果会话列表加载完成了,则继续加载会话内部的多个消息。

  1. void ChatDialog::loadChatMsg() {
  2. //发送聊天记录请求
  3. _cur_load_chat = UserMgr::GetInstance()->GetCurLoadData();
  4. if (_cur_load_chat == nullptr) {
  5. return;
  6. }
  7. showLoadingDlg(true);
  8. //发送请求给服务器
  9. //发送请求逻辑
  10. QJsonObject jsonObj;
  11. jsonObj["thread_id"] = _cur_load_chat->GetThreadId();
  12. jsonObj["message_id"] = _cur_load_chat->GetLastMsgId();
  13. QJsonDocument doc(jsonObj);
  14. QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
  15. //发送tcp请求给chat server
  16. emit TcpMgr::GetInstance()->sig_send_data(ReqId::ID_LOAD_CHAT_MSG_REQ, jsonData);
  17. }

回包消息处理

  1. _handlers.insert(ID_LOAD_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() << "parse create private chat json parse failed " << err;
  15. return;
  16. }
  17. int err = jsonObj["error"].toInt();
  18. if (err != ErrorCodes::SUCCESS) {
  19. qDebug() << "get create private chat failed, error is " << err;
  20. return;
  21. }
  22. qDebug() << "Receive create private chat rsp Success";
  23. int thread_id = jsonObj["thread_id"].toInt();
  24. int last_msg_id = jsonObj["last_message_id"].toInt();
  25. bool load_more = jsonObj["load_more"].toBool();
  26. std::vector<std::shared_ptr<ChatDataBase>> chat_datas;
  27. for (const QJsonValue& data : jsonObj["chat_datas"].toArray()) {
  28. auto send_uid = data["sender"].toInt();
  29. auto msg_id = data["msg_id"].toInt();
  30. auto thread_id = data["thread_id"].toInt();
  31. auto unique_id = data["unique_id"].toInt();
  32. auto msg_content = data["msg_content"].toString();
  33. QString chat_time = data["chat_time"].toString();
  34. int status = data["status"].toInt();
  35. int msg_type = data["msg_type"].toInt();
  36. int recv_id = data["receiver"].toInt();
  37. if (msg_type == int(ChatMsgType::TEXT)) {
  38. auto chat_data = std::make_shared<TextChatData>(msg_id, thread_id, ChatFormType::PRIVATE,
  39. ChatMsgType::TEXT, msg_content, send_uid, status, chat_time);
  40. chat_datas.push_back(chat_data);
  41. continue;
  42. }
  43. if (msg_type == int(ChatMsgType::PIC)) {
  44. auto uid = UserMgr::GetInstance()->GetUid();
  45. QString storageDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
  46. QString img_path_str = storageDir + "/user/" + QString::number(uid) + "/chatimg/" + QString::number(send_uid);
  47. QString img_path = img_path_str + "/" + msg_content;
  48. //文件不存在,则创建空白图片占位,同时组织数据准备发送
  49. if (QFile::exists(img_path) == false) {
  50. CreatePlaceholderImgMsgL(img_path_str, msg_content,
  51. msg_id, thread_id, send_uid, recv_id, status, chat_time,
  52. chat_datas);
  53. continue;
  54. }
  55. //如果文件存在
  56. //如果文件存在则直接构建MsgInfo
  57. // 获取文件大小
  58. QFileInfo fileInfo(img_path);
  59. qint64 file_size = fileInfo.size();
  60. //从文件路径加载QPixmap
  61. QPixmap pixmap(img_path);
  62. //如果图片加载失败,也是创建占位符,然后组织发送
  63. if (pixmap.isNull()) {
  64. CreatePlaceholderImgMsgL(img_path_str, msg_content,
  65. msg_id, thread_id, send_uid, recv_id, status, chat_time,
  66. chat_datas);
  67. continue;
  68. }
  69. //说明图片加载正确,构建真实图片
  70. auto file_info = std::make_shared<MsgInfo>(MsgType::IMG_MSG, img_path_str,
  71. pixmap, msg_content, file_size, "");
  72. file_info->_msg_id = msg_id;
  73. file_info->_sender = send_uid;
  74. file_info->_receiver = recv_id;
  75. file_info->_thread_id = thread_id;
  76. //设置文件传输的类型
  77. file_info->_transfer_type = TransferType::Download;
  78. //设置文件传输状态
  79. file_info->_transfer_state = TransferState::None;
  80. //放入chat_datas列表
  81. auto chat_data = std::make_shared<ImgChatData>(file_info,"", thread_id, ChatFormType::PRIVATE,
  82. ChatMsgType::PIC, send_uid, status, chat_time);
  83. chat_datas.push_back(chat_data);
  84. continue;
  85. }
  86. }
  87. //发送信号通知界面
  88. emit sig_load_chat_msg(thread_id, last_msg_id, load_more, chat_datas);
  89. });

这里加载了thread会话信息,以及每个会话的消息列表,如果消息列表没有加载完全,则继续发送信号sig_load_chat_msg信号继续加载消息。

最终在此处将所有会话的所有消息加载完成

  1. void ChatDialog::slot_load_chat_msg(int thread_id, int msg_id, bool load_more,
  2. std::vector<std::shared_ptr<ChatDataBase>> msglists)
  3. {
  4. _cur_load_chat->SetLastMsgId(msg_id);
  5. //加载聊天信息
  6. for (auto& chat_msg : msglists) {
  7. _cur_load_chat->AppendMsg(chat_msg->GetMsgId(), chat_msg);
  8. }
  9. //还有未加载完的消息,就继续加载
  10. if (load_more) {
  11. //发送请求给服务器
  12. //发送请求逻辑
  13. QJsonObject jsonObj;
  14. jsonObj["thread_id"] = _cur_load_chat->GetThreadId();
  15. jsonObj["message_id"] = _cur_load_chat->GetLastMsgId();
  16. QJsonDocument doc(jsonObj);
  17. QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
  18. //发送tcp请求给chat server
  19. emit TcpMgr::GetInstance()->sig_send_data(ReqId::ID_LOAD_CHAT_MSG_REQ, jsonData);
  20. return;
  21. }
  22. //获取下一个chat_thread
  23. _cur_load_chat = UserMgr::GetInstance()->GetNextLoadData();
  24. //都加载完了
  25. if(!_cur_load_chat){
  26. //更新聊天界面信息
  27. SetSelectChatItem();
  28. SetSelectChatPage();
  29. showLoadingDlg(false);
  30. return;
  31. }
  32. //继续加载下一个聊天
  33. //发送请求给服务器
  34. //发送请求逻辑
  35. QJsonObject jsonObj;
  36. jsonObj["thread_id"] = _cur_load_chat->GetThreadId();
  37. jsonObj["message_id"] = _cur_load_chat->GetLastMsgId();
  38. QJsonDocument doc(jsonObj);
  39. QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
  40. //发送tcp请求给chat server
  41. emit TcpMgr::GetInstance()->sig_send_data(ReqId::ID_LOAD_CHAT_MSG_REQ, jsonData);
  42. }

资源消息加载

核心的资源消息加载是在ID_LOAD_CHAT_MSG_RSP回报的逻辑里

  1. _handlers.insert(ID_LOAD_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() << "parse create private chat json parse failed " << err;
  15. return;
  16. }
  17. int err = jsonObj["error"].toInt();
  18. if (err != ErrorCodes::SUCCESS) {
  19. qDebug() << "get create private chat failed, error is " << err;
  20. return;
  21. }
  22. qDebug() << "Receive create private chat rsp Success";
  23. int thread_id = jsonObj["thread_id"].toInt();
  24. int last_msg_id = jsonObj["last_message_id"].toInt();
  25. bool load_more = jsonObj["load_more"].toBool();
  26. std::vector<std::shared_ptr<ChatDataBase>> chat_datas;
  27. for (const QJsonValue& data : jsonObj["chat_datas"].toArray()) {
  28. auto send_uid = data["sender"].toInt();
  29. auto msg_id = data["msg_id"].toInt();
  30. auto thread_id = data["thread_id"].toInt();
  31. auto unique_id = data["unique_id"].toInt();
  32. auto msg_content = data["msg_content"].toString();
  33. QString chat_time = data["chat_time"].toString();
  34. int status = data["status"].toInt();
  35. int msg_type = data["msg_type"].toInt();
  36. int recv_id = data["receiver"].toInt();
  37. if (msg_type == int(ChatMsgType::TEXT)) {
  38. auto chat_data = std::make_shared<TextChatData>(msg_id, thread_id, ChatFormType::PRIVATE,
  39. ChatMsgType::TEXT, msg_content, send_uid, status, chat_time);
  40. chat_datas.push_back(chat_data);
  41. continue;
  42. }
  43. if (msg_type == int(ChatMsgType::PIC)) {
  44. auto uid = UserMgr::GetInstance()->GetUid();
  45. QString storageDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
  46. QString img_path_str = storageDir + "/user/" + QString::number(uid) + "/chatimg/" + QString::number(send_uid);
  47. QString img_path = img_path_str + "/" + msg_content;
  48. //文件不存在,则创建空白图片占位,同时组织数据准备发送
  49. if (QFile::exists(img_path) == false) {
  50. CreatePlaceholderImgMsgL(img_path_str, msg_content,
  51. msg_id, thread_id, send_uid, recv_id, status, chat_time,
  52. chat_datas);
  53. continue;
  54. }
  55. //如果文件存在
  56. //如果文件存在则直接构建MsgInfo
  57. // 获取文件大小
  58. QFileInfo fileInfo(img_path);
  59. qint64 file_size = fileInfo.size();
  60. //从文件路径加载QPixmap
  61. QPixmap pixmap(img_path);
  62. //如果图片加载失败,也是创建占位符,然后组织发送
  63. if (pixmap.isNull()) {
  64. CreatePlaceholderImgMsgL(img_path_str, msg_content,
  65. msg_id, thread_id, send_uid, recv_id, status, chat_time,
  66. chat_datas);
  67. continue;
  68. }
  69. //说明图片加载正确,构建真实图片
  70. auto file_info = std::make_shared<MsgInfo>(MsgType::IMG_MSG, img_path_str,
  71. pixmap, msg_content, file_size, "");
  72. file_info->_msg_id = msg_id;
  73. file_info->_sender = send_uid;
  74. file_info->_receiver = recv_id;
  75. file_info->_thread_id = thread_id;
  76. //设置文件传输的类型
  77. file_info->_transfer_type = TransferType::Download;
  78. //设置文件传输状态
  79. file_info->_transfer_state = TransferState::None;
  80. //放入chat_datas列表
  81. auto chat_data = std::make_shared<ImgChatData>(file_info,"", thread_id, ChatFormType::PRIVATE,
  82. ChatMsgType::PIC, send_uid, status, chat_time);
  83. chat_datas.push_back(chat_data);
  84. continue;
  85. }
  86. }
  87. //发送信号通知界面
  88. emit sig_load_chat_msg(thread_id, last_msg_id, load_more, chat_datas);
  89. });

核心逻辑是这部分

  1. if (msg_type == int(ChatMsgType::TEXT)) {
  2. auto chat_data = std::make_shared<TextChatData>(msg_id, thread_id, ChatFormType::PRIVATE,
  3. ChatMsgType::TEXT, msg_content, send_uid, status, chat_time);
  4. chat_datas.push_back(chat_data);
  5. continue;
  6. }
  7. if (msg_type == int(ChatMsgType::PIC)) {
  8. auto uid = UserMgr::GetInstance()->GetUid();
  9. QString storageDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
  10. QString img_path_str = storageDir + "/user/" + QString::number(uid) + "/chatimg/" + QString::number(send_uid);
  11. QString img_path = img_path_str + "/" + msg_content;
  12. //文件不存在,则创建空白图片占位,同时组织数据准备发送
  13. if (QFile::exists(img_path) == false) {
  14. CreatePlaceholderImgMsgL(img_path_str, msg_content,
  15. msg_id, thread_id, send_uid, recv_id, status, chat_time,
  16. chat_datas);
  17. continue;
  18. }
  19. //如果文件存在
  20. //如果文件存在则直接构建MsgInfo
  21. // 获取文件大小
  22. QFileInfo fileInfo(img_path);
  23. qint64 file_size = fileInfo.size();
  24. //从文件路径加载QPixmap
  25. QPixmap pixmap(img_path);
  26. //如果图片加载失败,也是创建占位符,然后组织发送
  27. if (pixmap.isNull()) {
  28. CreatePlaceholderImgMsgL(img_path_str, msg_content,
  29. msg_id, thread_id, send_uid, recv_id, status, chat_time,
  30. chat_datas);
  31. continue;
  32. }
  33. //说明图片加载正确,构建真实图片
  34. auto file_info = std::make_shared<MsgInfo>(MsgType::IMG_MSG, img_path_str,
  35. pixmap, msg_content, file_size, "");
  36. file_info->_msg_id = msg_id;
  37. file_info->_sender = send_uid;
  38. file_info->_receiver = recv_id;
  39. file_info->_thread_id = thread_id;
  40. //设置文件传输的类型
  41. file_info->_transfer_type = TransferType::Download;
  42. //设置文件传输状态
  43. file_info->_transfer_state = TransferState::None;
  44. //放入chat_datas列表
  45. auto chat_data = std::make_shared<ImgChatData>(file_info,"", thread_id, ChatFormType::PRIVATE,
  46. ChatMsgType::PIC, send_uid, status, chat_time);
  47. chat_datas.push_back(chat_data);
  48. continue;
  49. }
热门评论

热门文章

  1. 使用hexo搭建个人博客

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

    喜欢(594) 浏览(16370)
  3. MarkDown在线编辑器

    喜欢(514) 浏览(16776)
  4. 聊天项目(28) 分布式服务通知好友申请

    喜欢(507) 浏览(7564)
  5. vscode搭建windows C++开发环境

    喜欢(596) 浏览(102638)

最新评论

  1. 解决博客回复区被脚本注入的问题 secondtonone1:走到现在我忽然明白一个道理,无论工作也好生活也罢,最重要的是开心,即使一份安稳的工作不能给我带来事业上的积累也要合理的舍弃,所以我还是想去做喜欢的方向。
  2. 处理网络粘包问题 zyouth: //消息的长度小于头部规定的长度,说明数据未收全,则先将部分消息放到接收节点里 if (bytes_transferred < data_len) { memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, bytes_transferred); _recv_msg_node->_cur_len += bytes_transferred; ::memset(_data, 0, MAX_LENGTH); _socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, shared_self)); //头部处理完成 _b_head_parse = true; return; } 把_b_head_parse = true;放在_socket.async_read_some前面是不是更好
  3. C++ 线程池原理和实现 mzx2023:两种方法解决,一种是改排序算法,就是当线程耗尽的时候,使用普通递归,另一种是当在线程池commit的时候,判断线程是否耗尽,耗尽的话就直接当前线程执行task
  4. 利用指针和容器实现文本查询 越今朝:应该添加一个过滤功能以解决部分单词无法被查询的问题: eg: "I am a teacher."中的teacher无法被查询,因为在示例代码中teacher.被解释为一个单词从而忽略了teacher本身。
  5. 无锁并发队列 TenThousandOne:_head  和 _tail  替换为原子变量。那里pop的逻辑,val = _data[h] 可以移到循环外面吗

个人公众号

个人微信