异步回调实现续传持久化

续传信息持久化

增加redis接口

  1. bool RedisMgr::SetFileInfo(const std::string& md5, std::shared_ptr<FileInfo> file_info)
  2. {
  3. Json::Reader reader;
  4. Json::Value root;
  5. root["file_path_str"] = file_info->_file_path_str;
  6. root["name"] = file_info->_name;
  7. root["seq"] = file_info->_seq;
  8. root["total_size"] = file_info->_total_size;
  9. root["trans_size"] = file_info->_trans_size;
  10. auto file_info_str = root.toStyledString();
  11. auto redis_key = "file_upload_" + md5;
  12. bool success = SetExp(redis_key, file_info_str, 3600);
  13. return success;
  14. }

新增超时设置

  1. bool RedisMgr::SetExp(const std::string& key, const std::string& value, int expire_seconds) {
  2. //执行redis命令行
  3. auto connect = _con_pool->getConnection();
  4. if (connect == nullptr) {
  5. return false;
  6. }
  7. auto reply = (redisReply*)redisCommand(connect, "SETEX %s %d %s", key.c_str(),
  8. expire_seconds,
  9. value.c_str());
  10. if (NULL == reply) {
  11. std::cout << "Execute command [ SETEX " << key << " " << expire_seconds
  12. << " " << value << " ] failure ! " << std::endl;
  13. _con_pool->returnConnection(connect);
  14. return false;
  15. }
  16. if (!(reply->type == REDIS_REPLY_STATUS &&
  17. (strcmp(reply->str, "OK") == 0 || strcmp(reply->str, "ok") == 0))) {
  18. std::cout << "Execute command [ SETEX " << key << " " << expire_seconds
  19. << " " << value << " ] failure ! " << std::endl;
  20. freeReplyObject(reply);
  21. _con_pool->returnConnection(connect);
  22. return false;
  23. }
  24. freeReplyObject(reply);
  25. std::cout << "Execute command [ SETEX " << key << " " << expire_seconds
  26. << " " << value << " ] success ! " << std::endl;
  27. _con_pool->returnConnection(connect);
  28. return true;
  29. }

每次收到上传信息后,更新上传进度到redis中

  1. _fun_callbacks[ID_UPLOAD_HEAD_ICON_REQ] = [this](shared_ptr<CSession> session, const short& msg_id,
  2. const string& msg_data) {
  3. Json::Reader reader;
  4. Json::Value root;
  5. reader.parse(msg_data, root);
  6. auto md5 = root["md5"].asString();
  7. auto seq = root["seq"].asInt();
  8. auto name = root["name"].asString();
  9. auto total_size = root["total_size"].asInt();
  10. auto trans_size = root["trans_size"].asInt();
  11. auto last = root["last"].asInt();
  12. auto file_data = root["data"].asString();
  13. auto uid = root["uid"].asInt();
  14. auto token = root["token"].asString();
  15. auto last_seq = root["last_seq"].asInt();
  16. //转化为字符串
  17. auto uid_str = std::to_string(uid);
  18. auto file_path = ConfigMgr::Inst().GetFileOutPath();
  19. auto file_path_str = (file_path / uid_str / name).string();
  20. Json::Value rtvalue;
  21. auto callback = [=](const Json::Value& result) {
  22. // 在异步任务完成后调用
  23. Json::Value rtvalue = result;
  24. rtvalue["error"] = ErrorCodes::Success;
  25. rtvalue["total_size"] = total_size;
  26. rtvalue["seq"] = seq;
  27. rtvalue["name"] = name;
  28. rtvalue["trans_size"] = trans_size;
  29. rtvalue["last"] = last;
  30. rtvalue["md5"] = md5;
  31. rtvalue["uid"] = uid;
  32. rtvalue["last_seq"] = last_seq;
  33. std::string return_str = rtvalue.toStyledString();
  34. session->Send(return_str, ID_UPLOAD_HEAD_ICON_RSP);
  35. };
  36. //第一个包校验一下token是否合理
  37. if (seq == 1) {
  38. //从redis获取用户token是否正确
  39. std::string uid_str = std::to_string(uid);
  40. std::string token_key = USERTOKENPREFIX + uid_str;
  41. std::string token_value = "";
  42. bool success = RedisMgr::GetInstance()->Get(token_key, token_value);
  43. if (!success) {
  44. rtvalue["error"] = ErrorCodes::UidInvalid;
  45. std::string return_str = rtvalue.toStyledString();
  46. session->Send(return_str, ID_UPLOAD_HEAD_ICON_RSP);
  47. return;
  48. }
  49. if (token_value != token) {
  50. rtvalue["error"] = ErrorCodes::TokenInvalid;
  51. std::string return_str = rtvalue.toStyledString();
  52. session->Send(return_str, ID_UPLOAD_HEAD_ICON_RSP);
  53. return;
  54. }
  55. }
  56. // 使用 std::hash 对字符串进行哈希
  57. std::hash<std::string> hash_fn;
  58. size_t hash_value = hash_fn(name); // 生成哈希值
  59. int index = hash_value % FILE_WORKER_COUNT;
  60. std::cout << "Hash value: " << hash_value << std::endl;
  61. //第一个包
  62. if (seq == 1) {
  63. //构造数据存储
  64. auto file_info = std::make_shared<FileInfo>();
  65. file_info->_file_path_str = file_path_str;
  66. file_info->_name = name;
  67. file_info->_seq = seq;
  68. file_info->_total_size = total_size;
  69. file_info->_trans_size = trans_size;
  70. //LogicSystem::GetInstance()->AddMD5File(md5, file_info);
  71. //改为用redis存储
  72. bool success = RedisMgr::GetInstance()->SetFileInfo(name, file_info);
  73. if (!success) {
  74. rtvalue["error"] = ErrorCodes::FileSaveRedisFailed;
  75. std::string return_str = rtvalue.toStyledString();
  76. session->Send(return_str, ID_UPLOAD_HEAD_ICON_RSP);
  77. return;
  78. }
  79. }
  80. else {
  81. //auto file_info = LogicSystem::GetInstance()->GetFileInfo(md5);
  82. //改为从redis中加载
  83. auto file_info = RedisMgr::GetInstance()->GetFileInfo(name);
  84. if (file_info == nullptr) {
  85. rtvalue["error"] = ErrorCodes::FileNotExists;
  86. std::string return_str = rtvalue.toStyledString();
  87. session->Send(return_str, ID_UPLOAD_HEAD_ICON_RSP);
  88. return;
  89. }
  90. file_info->_seq = seq;
  91. file_info->_trans_size = trans_size;
  92. bool success = RedisMgr::GetInstance()->SetFileInfo(name, file_info);
  93. if (!success) {
  94. rtvalue["error"] = ErrorCodes::FileSaveRedisFailed;
  95. std::string return_str = rtvalue.toStyledString();
  96. session->Send(return_str, ID_UPLOAD_HEAD_ICON_RSP);
  97. return;
  98. }
  99. }
  100. FileSystem::GetInstance()->PostMsgToQue(
  101. std::make_shared<FileTask>(session, uid, file_path_str, name, seq, total_size,
  102. trans_size, last, file_data, callback),
  103. index
  104. );
  105. };

资源url更新

因为上头像传资源后,要将资源的路径存储到mysql数据库中,所以我们新增MysqlMgr,这个直接从ChatServer拷贝一份即可。

但是要注意,添加如下函数

  1. bool MysqlMgr::UpdateUserIcon(int uid, const std::string& icon) {
  2. return _dao.UpdateHeadInfo(uid, icon);
  3. }

Dao层面实现更新头像逻辑

  1. bool MysqlDao::UpdateHeadInfo(int uid, const std::string& icon)
  2. {
  3. auto con = pool_->getConnection();
  4. if (!con) {
  5. return false;
  6. }
  7. Defer defer([this, &con]() {
  8. pool_->returnConnection(std::move(con));
  9. });
  10. auto& conn = con->_con;
  11. try {
  12. std::string update_sql =
  13. "UPDATE user SET icon = ? WHERE uid = ?;";
  14. std::unique_ptr<sql::PreparedStatement> pstmt(conn->prepareStatement(update_sql));
  15. pstmt->setString(1, icon);
  16. pstmt->setInt64(2, uid);
  17. int affected_rows = pstmt->executeUpdate();
  18. // 检查是否有行被更新(可选)
  19. if (affected_rows == 0) {
  20. std::cerr << "No user found with uid: " << uid << std::endl;
  21. return false;
  22. }
  23. return true;
  24. }
  25. catch (sql::SQLException& e) {
  26. std::cerr << "SQLException in UpdateHeadInfo: " << e.what() << std::endl;
  27. return false;
  28. }
  29. return false;
  30. }

封装异步回调

之前我们处理文件上传是异步方式,将要保存的文件投递给消息队列,交给独立线程后台保存。我们没有等待处理完成就直接将消息回传给客户端,这么做不是很好,所以改为异步方式,简单的方式就是通过回调函数处理,或者包装一个future等待。这里考虑保留异步结构,所以还是用回调处理

  1. struct FileTask {
  2. FileTask(std::shared_ptr<CSession> session, int uid, std::string path, std::string name,
  3. int seq, int total_size, int trans_size, int last,
  4. std::string file_data,
  5. std::function<void(const Json::Value&)> callback) :_session(session), _uid(uid),
  6. _seq(seq), _path(path), _name(name), _total_size(total_size),
  7. _trans_size(trans_size), _last(last), _file_data(file_data), _callback(callback)
  8. {}
  9. ~FileTask(){}
  10. std::shared_ptr<CSession> _session;
  11. int _uid;
  12. int _seq ;
  13. std::string _path;
  14. std::string _name ;
  15. int _total_size ;
  16. int _trans_size ;
  17. int _last ;
  18. std::string _file_data;
  19. std::function<void(const Json::Value&)> _callback; //添加回调函数
  20. };

改进后的处理

  1. _fun_callbacks[ID_UPLOAD_HEAD_ICON_REQ] = [this](shared_ptr<CSession> session, const short& msg_id,
  2. const string& msg_data) {
  3. Json::Reader reader;
  4. Json::Value root;
  5. reader.parse(msg_data, root);
  6. auto md5 = root["md5"].asString();
  7. auto seq = root["seq"].asInt();
  8. auto name = root["name"].asString();
  9. auto total_size = root["total_size"].asInt();
  10. auto trans_size = root["trans_size"].asInt();
  11. auto last = root["last"].asInt();
  12. auto file_data = root["data"].asString();
  13. auto uid = root["uid"].asInt();
  14. auto token = root["token"].asString();
  15. auto last_seq = root["last_seq"].asInt();
  16. //转化为字符串
  17. auto uid_str = std::to_string(uid);
  18. auto file_path = ConfigMgr::Inst().GetFileOutPath();
  19. auto file_path_str = (file_path / uid_str / name).string();
  20. Json::Value rtvalue;
  21. auto callback = [=](const Json::Value& result) {
  22. // 在异步任务完成后调用
  23. Json::Value rtvalue = result;
  24. rtvalue["error"] = ErrorCodes::Success;
  25. rtvalue["total_size"] = total_size;
  26. rtvalue["seq"] = seq;
  27. rtvalue["name"] = name;
  28. rtvalue["trans_size"] = trans_size;
  29. rtvalue["last"] = last;
  30. rtvalue["md5"] = md5;
  31. rtvalue["uid"] = uid;
  32. rtvalue["last_seq"] = last_seq;
  33. std::string return_str = rtvalue.toStyledString();
  34. session->Send(return_str, ID_UPLOAD_HEAD_ICON_RSP);
  35. };
  36. //第一个包校验一下token是否合理
  37. if (seq == 1) {
  38. //从redis获取用户token是否正确
  39. std::string uid_str = std::to_string(uid);
  40. std::string token_key = USERTOKENPREFIX + uid_str;
  41. std::string token_value = "";
  42. bool success = RedisMgr::GetInstance()->Get(token_key, token_value);
  43. if (!success) {
  44. rtvalue["error"] = ErrorCodes::UidInvalid;
  45. std::string return_str = rtvalue.toStyledString();
  46. session->Send(return_str, ID_UPLOAD_HEAD_ICON_RSP);
  47. return;
  48. }
  49. if (token_value != token) {
  50. rtvalue["error"] = ErrorCodes::TokenInvalid;
  51. std::string return_str = rtvalue.toStyledString();
  52. session->Send(return_str, ID_UPLOAD_HEAD_ICON_RSP);
  53. return;
  54. }
  55. }
  56. // 使用 std::hash 对字符串进行哈希
  57. std::hash<std::string> hash_fn;
  58. size_t hash_value = hash_fn(name); // 生成哈希值
  59. int index = hash_value % FILE_WORKER_COUNT;
  60. std::cout << "Hash value: " << hash_value << std::endl;
  61. //第一个包
  62. if (seq == 1) {
  63. //构造数据存储
  64. auto file_info = std::make_shared<FileInfo>();
  65. file_info->_file_path_str = file_path_str;
  66. file_info->_name = name;
  67. file_info->_seq = seq;
  68. file_info->_total_size = total_size;
  69. file_info->_trans_size = trans_size;
  70. //LogicSystem::GetInstance()->AddMD5File(md5, file_info);
  71. //改为用redis存储
  72. bool success = RedisMgr::GetInstance()->SetFileInfo(name, file_info);
  73. if (!success) {
  74. rtvalue["error"] = ErrorCodes::FileSaveRedisFailed;
  75. std::string return_str = rtvalue.toStyledString();
  76. session->Send(return_str, ID_UPLOAD_HEAD_ICON_RSP);
  77. return;
  78. }
  79. }
  80. else {
  81. //auto file_info = LogicSystem::GetInstance()->GetFileInfo(md5);
  82. //改为从redis中加载
  83. auto file_info = RedisMgr::GetInstance()->GetFileInfo(name);
  84. if (file_info == nullptr) {
  85. rtvalue["error"] = ErrorCodes::FileNotExists;
  86. std::string return_str = rtvalue.toStyledString();
  87. session->Send(return_str, ID_UPLOAD_HEAD_ICON_RSP);
  88. return;
  89. }
  90. file_info->_seq = seq;
  91. file_info->_trans_size = trans_size;
  92. bool success = RedisMgr::GetInstance()->SetFileInfo(name, file_info);
  93. if (!success) {
  94. rtvalue["error"] = ErrorCodes::FileSaveRedisFailed;
  95. std::string return_str = rtvalue.toStyledString();
  96. session->Send(return_str, ID_UPLOAD_HEAD_ICON_RSP);
  97. return;
  98. }
  99. }
  100. FileSystem::GetInstance()->PostMsgToQue(
  101. std::make_shared<FileTask>(session, uid, file_path_str, name, seq, total_size,
  102. trans_size, last, file_data, callback),
  103. index
  104. );
  105. };

callback是我们封装的回调函数,投递给FileTask, 将来在后台线程处理FileTask时回调。

  1. void FileWorker::task_callback(std::shared_ptr<FileTask> task)
  2. {
  3. // 解码
  4. std::string decoded = base64_decode(task->_file_data);
  5. auto file_path_str = task->_path;
  6. auto last = task->_last;
  7. //std::cout << "file_path_str is " << file_path_str << std::endl;
  8. boost::filesystem::path file_path(file_path_str);
  9. boost::filesystem::path dir_path = file_path.parent_path();
  10. // 获取完整文件名(包含扩展名)
  11. std::string filename = file_path.filename().string();
  12. Json::Value result;
  13. // Check if directory exists, if not, create it
  14. if (!boost::filesystem::exists(dir_path)) {
  15. if (!boost::filesystem::create_directories(dir_path)) {
  16. std::cerr << "Failed to create directory: " << dir_path.string() << std::endl;
  17. result["error"] = ErrorCodes::FileNotExists;
  18. task->_callback(result);
  19. return;
  20. }
  21. }
  22. std::ofstream outfile;
  23. //第一个包
  24. if (task->_seq == 1) {
  25. // 打开文件,如果存在则清空,不存在则创建
  26. outfile.open(file_path_str, std::ios::binary | std::ios::trunc);
  27. }
  28. else {
  29. // 保存为文件
  30. outfile.open(file_path_str, std::ios::binary | std::ios::app);
  31. }
  32. if (!outfile) {
  33. std::cerr << "无法打开文件进行写入。" << std::endl;
  34. result["error"] = ErrorCodes::FileWritePermissionFailed;
  35. task->_callback(result);
  36. return ;
  37. }
  38. outfile.write(decoded.data(), decoded.size());
  39. if (!outfile) {
  40. std::cerr << "写入文件失败。" << std::endl;
  41. result["error"] = ErrorCodes::FileWritePermissionFailed;
  42. task->_callback(result);
  43. return ;
  44. }
  45. outfile.close();
  46. if (last) {
  47. std::cout << "文件已成功保存为: " << task->_name << std::endl;
  48. //更新头像
  49. MysqlMgr::GetInstance()->UpdateUserIcon(task->_uid, filename);
  50. //获取用户信息
  51. auto user_info = MysqlMgr::GetInstance()->GetUser(task->_uid);
  52. if (user_info == nullptr) {
  53. return ;
  54. }
  55. //将数据库内容写入redis缓存
  56. Json::Value redis_root;
  57. redis_root["uid"] = task->_uid;
  58. redis_root["pwd"] = user_info->pwd;
  59. redis_root["name"] = user_info->name;
  60. redis_root["email"] = user_info->email;
  61. redis_root["nick"] = user_info->nick;
  62. redis_root["desc"] = user_info->desc;
  63. redis_root["sex"] = user_info->sex;
  64. redis_root["icon"] = user_info->icon;
  65. std::string base_key = USER_BASE_INFO + std::to_string(task->_uid);
  66. RedisMgr::GetInstance()->Set(base_key, redis_root.toStyledString());
  67. }
  68. if (task->_callback) {
  69. task->_callback(result);
  70. }
  71. }

客户端上传逻辑修改

  1. //上传头像
  2. void UserInfoPage::slot_up_load()
  3. {
  4. // 1. 让对话框也能选 *.webp
  5. QString filename = QFileDialog::getOpenFileName(
  6. this,
  7. tr("选择图片"),
  8. QString(),
  9. tr("图片文件 (*.png *.jpg *.jpeg *.bmp *.webp)")
  10. );
  11. if (filename.isEmpty())
  12. return;
  13. // 2. 直接用 QPixmap::load() 加载,无需手动区分格式
  14. QPixmap inputImage;
  15. if (!inputImage.load(filename)) {
  16. QMessageBox::critical(
  17. this,
  18. tr("错误"),
  19. tr("加载图片失败!请确认已部署 WebP 插件。"),
  20. QMessageBox::Ok
  21. );
  22. return;
  23. }
  24. QPixmap image = ImageCropperDialog::getCroppedImage(filename, 600, 400, CropperShape::CIRCLE);
  25. if (image.isNull())
  26. return;
  27. QPixmap scaledPixmap = image.scaled( ui->head_lb->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); // 将图片缩放到label的大小
  28. ui->head_lb->setPixmap(scaledPixmap); // 将缩放后的图片设置到QLabel上
  29. ui->head_lb->setScaledContents(true); // 设置QLabel自动缩放图片内容以适应大小
  30. QString storageDir = QStandardPaths::writableLocation(
  31. QStandardPaths::AppDataLocation);
  32. // 2. 在其下再建一个 avatars 子目录
  33. QDir dir(storageDir);
  34. if (!dir.exists("avatars")) {
  35. if (!dir.mkpath("avatars")) {
  36. qWarning() << "无法创建 avatars 目录:" << dir.filePath("avatars");
  37. QMessageBox::warning(
  38. this,
  39. tr("错误"),
  40. tr("无法创建存储目录,请检查权限或磁盘空间。")
  41. );
  42. return;
  43. }
  44. }
  45. // 3. 拼接最终的文件名 head.png
  46. QString file_name = generateUniqueIconName();
  47. QString filePath = dir.filePath("avatars" +
  48. QString(QDir::separator()) + file_name);
  49. // 4. 保存 scaledPixmap 为 PNG(无损、最高质量)
  50. if (!scaledPixmap.save(filePath, "PNG")) {
  51. QMessageBox::warning(
  52. this,
  53. tr("保存失败"),
  54. tr("头像保存失败,请检查权限或磁盘空间。")
  55. );
  56. } else {
  57. qDebug() << "头像已保存到:" << filePath;
  58. // 以后读取直接用同一路径:storageDir/avatars/head.png
  59. }
  60. //实现头像上传
  61. QFile file(filePath);
  62. if(!file.open(QIODevice::ReadOnly)){
  63. qWarning() << "Could not open file:" << file.errorString();
  64. return;
  65. }
  66. //保存当前文件位置指针
  67. qint64 originalPos = file.pos();
  68. QCryptographicHash hash(QCryptographicHash::Md5);
  69. if (!hash.addData(&file)) {
  70. qWarning() << "Failed to read data from file:" << filePath;
  71. return ;
  72. }
  73. // 5. 转化为16进制字符串
  74. QString file_md5 = hash.result().toHex(); // 返回十六进制字符串
  75. //读取文件内容并发送
  76. QByteArray buffer;
  77. int seq = 0;
  78. //创建QFileInfo 对象
  79. auto fileInfo = std::make_shared<QFileInfo>(filePath);
  80. //获取文件名
  81. QString fileName = fileInfo->fileName();
  82. //文件名
  83. qDebug() << "文件名是: " << fileName;
  84. //获取文件大小
  85. int total_size = fileInfo->size();
  86. //最后一个发送序列
  87. int last_seq = 0;
  88. //获取最后一个发送序列
  89. if(total_size % MAX_FILE_LEN){
  90. last_seq = (total_size / MAX_FILE_LEN) +1;
  91. }else{
  92. last_seq = total_size / MAX_FILE_LEN;
  93. }
  94. // 恢复文件指针到原来的位置
  95. file.seek(originalPos);
  96. //每次读取MAX_FILE_LEN字节并发送
  97. buffer = file.read(MAX_FILE_LEN);
  98. QJsonObject jsonObj;
  99. //将文件内容转化为Base64 编码(可选)
  100. QString base64Data = buffer.toBase64();
  101. ++seq;
  102. jsonObj["md5"] = file_md5;
  103. jsonObj["name"] = file_name;
  104. jsonObj["seq"] = seq;
  105. jsonObj["trans_size"] = buffer.size() + (seq - 1) * MAX_FILE_LEN;
  106. jsonObj["total_size"] = total_size;
  107. jsonObj["token"] = UserMgr::GetInstance()->GetToken();
  108. jsonObj["uid"] = UserMgr::GetInstance()->GetUid();
  109. if (buffer.size() + (seq - 1) * MAX_FILE_LEN == total_size) {
  110. jsonObj["last"] = 1;
  111. } else {
  112. jsonObj["last"] = 0;
  113. }
  114. jsonObj["data"] = base64Data;
  115. jsonObj["last_seq"] = last_seq;
  116. QJsonDocument doc(jsonObj);
  117. auto send_data = doc.toJson();
  118. //将md5信息和文件信息关联存储
  119. UserMgr::GetInstance()->AddNameFile(file_name, fileInfo);
  120. //发送消息
  121. FileTcpMgr::GetInstance()->SendData(ID_UPLOAD_HEAD_ICON_REQ, send_data);
  122. file.close();
  123. }

客户端加载头像

服务器将上传的头像信息保存为url更新到mysql中,接下来客户端登录需要加载新的头像

ChatDialog的构造函数中将头像加载逻辑修改为

  1. //模拟加载自己头像
  2. QString head_icon = UserMgr::GetInstance()->GetIcon();
  3. //使用正则表达式检查是否使用默认头像
  4. QRegularExpression regex("^:/res/head_(\\d+)\\.jpg$");
  5. QRegularExpressionMatch match = regex.match(head_icon);
  6. if (match.hasMatch()) {
  7. // 如果是默认头像(:/res/head_X.jpg 格式)
  8. QPixmap pixmap(head_icon); // 加载默认头像图片
  9. QPixmap scaledPixmap = pixmap.scaled(ui->side_head_lb->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
  10. ui->side_head_lb->setPixmap(scaledPixmap); // 将缩放后的图片设置到QLabel上
  11. ui->side_head_lb->setScaledContents(true); // 设置QLabel自动缩放图片内容以适应大小
  12. }
  13. else {
  14. // 如果是用户上传的头像,获取存储目录
  15. QString storageDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
  16. QDir avatarsDir(storageDir + "/avatars");
  17. // 确保目录存在
  18. if (avatarsDir.exists()) {
  19. QString avatarPath = avatarsDir.filePath(QFileInfo(head_icon).fileName()); // 获取上传头像的完整路径
  20. QPixmap pixmap(avatarPath); // 加载上传的头像图片
  21. if (!pixmap.isNull()) {
  22. QPixmap scaledPixmap = pixmap.scaled(ui->side_head_lb->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
  23. ui->side_head_lb->setPixmap(scaledPixmap);
  24. ui->side_head_lb->setScaledContents(true);
  25. }
  26. else {
  27. qWarning() << "无法加载上传的头像:" << avatarPath;
  28. }
  29. }
  30. else {
  31. qWarning() << "头像存储目录不存在:" << avatarsDir.path();
  32. }
  33. }

聊天页面也需要修改头像加载逻辑

  1. void ChatPage::AppendChatMsg(std::shared_ptr<ChatDataBase> msg)
  2. {
  3. auto self_info = UserMgr::GetInstance()->GetUserInfo();
  4. ChatRole role;
  5. if (msg->GetSendUid() == self_info->_uid) {
  6. role = ChatRole::Self;
  7. ChatItemBase* pChatItem = new ChatItemBase(role);
  8. pChatItem->setUserName(self_info->_name);
  9. // 使用正则表达式检查是否是默认头像
  10. QRegularExpression regex("^:/res/head_(\\d+)\\.jpg$");
  11. QRegularExpressionMatch match = regex.match(self_info->_icon);
  12. if (match.hasMatch()) {
  13. pChatItem->setUserIcon(QPixmap(self_info->_icon));
  14. }
  15. else {
  16. // 如果是用户上传的头像,获取存储目录
  17. QString storageDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
  18. QDir avatarsDir(storageDir + "/avatars");
  19. // 确保目录存在
  20. if (avatarsDir.exists()) {
  21. QString avatarPath = avatarsDir.filePath(QFileInfo(self_info->_icon).fileName()); // 获取上传头像的完整路径
  22. QPixmap pixmap(avatarPath); // 加载上传的头像图片
  23. if (!pixmap.isNull()) {
  24. pChatItem->setUserIcon(pixmap);
  25. }
  26. else {
  27. qWarning() << "无法加载上传的头像:" << avatarPath;
  28. }
  29. }
  30. else {
  31. qWarning() << "头像存储目录不存在:" << avatarsDir.path();
  32. }
  33. }
  34. QWidget* pBubble = nullptr;
  35. if (msg->GetMsgType() == ChatMsgType::TEXT) {
  36. pBubble = new TextBubble(role, msg->GetMsgContent());
  37. }
  38. pChatItem->setWidget(pBubble);
  39. auto status = msg->GetStatus();
  40. pChatItem->setStatus(status);
  41. ui->chat_data_list->appendChatItem(pChatItem);
  42. if (status == 0) {
  43. _unrsp_item_map[msg->GetUniqueId()] = pChatItem;
  44. }
  45. }
  46. else {
  47. role = ChatRole::Other;
  48. ChatItemBase* pChatItem = new ChatItemBase(role);
  49. auto friend_info = UserMgr::GetInstance()->GetFriendById(msg->GetSendUid());
  50. if (friend_info == nullptr) {
  51. return;
  52. }
  53. pChatItem->setUserName(friend_info->_name);
  54. // 使用正则表达式检查是否是默认头像
  55. QRegularExpression regex("^:/res/head_(\\d+)\\.jpg$");
  56. QRegularExpressionMatch match = regex.match(friend_info->_icon);
  57. if (match.hasMatch()) {
  58. pChatItem->setUserIcon(QPixmap(friend_info->_icon));
  59. }
  60. else {
  61. // 如果是用户上传的头像,获取存储目录
  62. QString storageDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
  63. QDir avatarsDir(storageDir + "/avatars");
  64. // 确保目录存在
  65. if (avatarsDir.exists()) {
  66. QString avatarPath = avatarsDir.filePath(QFileInfo(friend_info->_icon).fileName()); // 获取上传头像的完整路径
  67. QPixmap pixmap(avatarPath); // 加载上传的头像图片
  68. if (!pixmap.isNull()) {
  69. pChatItem->setUserIcon(pixmap);
  70. }
  71. else {
  72. qWarning() << "无法加载上传的头像:" << avatarPath;
  73. }
  74. }
  75. else {
  76. qWarning() << "头像存储目录不存在:" << avatarsDir.path();
  77. }
  78. }
  79. QWidget* pBubble = nullptr;
  80. if (msg->GetMsgType() == ChatMsgType::TEXT) {
  81. pBubble = new TextBubble(role, msg->GetMsgContent());
  82. }
  83. pChatItem->setWidget(pBubble);
  84. auto status = msg->GetStatus();
  85. pChatItem->setStatus(status);
  86. ui->chat_data_list->appendChatItem(pChatItem);
  87. if (status == 0) {
  88. _unrsp_item_map[msg->GetUniqueId()] = pChatItem;
  89. }
  90. }
  91. }

聊天列表中加载头像逻辑修改一下

  1. void ChatUserWid::SetChatData(std::shared_ptr<ChatThreadData> chat_data) {
  2. _chat_data = chat_data;
  3. auto other_id = _chat_data->GetOtherId();
  4. auto other_info = UserMgr::GetInstance()->GetFriendById(other_id);
  5. // 加载图片
  6. QString head_icon = UserMgr::GetInstance()->GetIcon();
  7. // 使用正则表达式检查是否是默认头像
  8. QRegularExpression regex("^:/res/head_(\\d+)\\.jpg$");
  9. QRegularExpressionMatch match = regex.match(other_info->_icon);
  10. if (match.hasMatch()) {
  11. // 如果是默认头像(:/res/head_X.jpg 格式)
  12. QPixmap pixmap(other_info->_icon); // 加载默认头像图片
  13. QPixmap scaledPixmap = pixmap.scaled(ui->icon_lb->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
  14. ui->icon_lb->setPixmap(scaledPixmap); // 将缩放后的图片设置到QLabel上
  15. ui->icon_lb->setScaledContents(true); // 设置QLabel自动缩放图片内容以适应大小
  16. }
  17. else {
  18. // 如果是用户上传的头像,获取存储目录
  19. QString storageDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
  20. QDir avatarsDir(storageDir + "/avatars");
  21. // 确保目录存在
  22. if (avatarsDir.exists()) {
  23. QString avatarPath = avatarsDir.filePath(QFileInfo(other_info->_icon).fileName()); // 获取上传头像的完整路径
  24. QPixmap pixmap(avatarPath); // 加载上传的头像图片
  25. if (!pixmap.isNull()) {
  26. QPixmap scaledPixmap = pixmap.scaled(ui->icon_lb->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
  27. ui->icon_lb->setPixmap(scaledPixmap);
  28. ui->icon_lb->setScaledContents(true);
  29. }
  30. else {
  31. qWarning() << "无法加载上传的头像:" << avatarPath;
  32. }
  33. }
  34. else {
  35. qWarning() << "头像存储目录不存在:" << avatarsDir.path();
  36. }
  37. }
  38. ui->user_name_lb->setText(other_info->_name);
  39. ui->user_chat_lb->setText(chat_data->GetLastMsg());
  40. }

用户信息加载头像

  1. UserInfoPage::UserInfoPage(QWidget *parent) :
  2. QWidget(parent),
  3. ui(new Ui::UserInfoPage)
  4. {
  5. ui->setupUi(this);
  6. auto icon = UserMgr::GetInstance()->GetIcon();
  7. qDebug() << "icon is " << icon ;
  8. //使用正则表达式检查是否使用默认头像
  9. QRegularExpression regex("^:/res/head_(\\d+)\\.jpg$");
  10. QRegularExpressionMatch match = regex.match(icon);
  11. if (match.hasMatch()) {
  12. QPixmap pixmap(icon);
  13. QPixmap scaledPixmap = pixmap.scaled(ui->head_lb->size(),
  14. Qt::KeepAspectRatio, Qt::SmoothTransformation); // 将图片缩放到label的大小
  15. ui->head_lb->setPixmap(scaledPixmap); // 将缩放后的图片设置到QLabel上
  16. ui->head_lb->setScaledContents(true); // 设置QLabel自动缩放图片内容以适应大小
  17. }
  18. else {
  19. // 如果是用户上传的头像,获取存储目录
  20. QString storageDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
  21. QDir avatarsDir(storageDir + "/avatars");
  22. // 确保目录存在
  23. if (avatarsDir.exists()) {
  24. QString avatarPath = avatarsDir.filePath(QFileInfo(icon).fileName()); // 获取上传头像的完整路径
  25. QPixmap pixmap(avatarPath); // 加载上传的头像图片
  26. if (!pixmap.isNull()) {
  27. QPixmap scaledPixmap = pixmap.scaled(ui->head_lb->size(),
  28. Qt::KeepAspectRatio, Qt::SmoothTransformation); // 将图片缩放到label的大小
  29. ui->head_lb->setPixmap(scaledPixmap); // 将缩放后的图片设置到QLabel上
  30. ui->head_lb->setScaledContents(true); // 设置QLabel自动缩放图片内容以适应大小
  31. }
  32. else {
  33. qWarning() << "无法加载上传的头像:" << avatarPath;
  34. }
  35. }
  36. else {
  37. qWarning() << "头像存储目录不存在:" << avatarsDir.path();
  38. }
  39. }
  40. //获取nick
  41. auto nick = UserMgr::GetInstance()->GetNick();
  42. //获取name
  43. auto name = UserMgr::GetInstance()->GetName();
  44. //描述
  45. auto desc = UserMgr::GetInstance()->GetDesc();
  46. ui->nick_ed->setText(nick);
  47. ui->name_ed->setText(name);
  48. ui->desc_ed->setText(desc);
  49. //连接上
  50. connect(ui->up_btn, &QPushButton::clicked, this, &UserInfoPage::slot_up_load);
  51. }

测试效果

image-20250923094811744

热门评论

热门文章

  1. 使用hexo搭建个人博客

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

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

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

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

    喜欢(596) 浏览(96518)

最新评论

  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] 可以移到循环外面吗

个人公众号

个人微信