From 971ab647690117fbe00cc2629ad71e68e489a166 Mon Sep 17 00:00:00 2001 From: xyz <2050965275@qq.com> Date: Wed, 18 Jun 2025 18:21:23 +0800 Subject: [PATCH] add sendMessage function --- mainwidget.cpp | 32 +++++++++++++++-- mainwidget.h | 5 +++ messageeditarea.cpp | 68 ++++++++++++++++++++++++++++++++++- messageeditarea.h | 7 ++++ messageshowarea.cpp | 3 +- model/datacenter.cpp | 51 ++++++++++++++++++++++++++ model/datacenter.h | 16 +++++++++ network/netclient.cpp | 84 +++++++++++++++++++++++++++++++++++++++++++ network/netclient.h | 2 ++ sessionfriendarea.cpp | 58 +++++++++++++++++++++++++++--- sessionfriendarea.h | 2 ++ 11 files changed, 319 insertions(+), 9 deletions(-) diff --git a/mainwidget.cpp b/mainwidget.cpp index f795dd3..2776af6 100644 --- a/mainwidget.cpp +++ b/mainwidget.cpp @@ -97,7 +97,7 @@ void MainWidget::initLeftWindow() sessionTabBtn->setFixedSize(45, 45); sessionTabBtn->setIconSize(QSize(35, 35)); sessionTabBtn->setIcon(QIcon(":/resource/image/session_active.png")); - sessionTabBtn->setStyleSheet("QPushButton {background-color: transparent; }"); + sessionTabBtn->setStyleSheet("QPushButton {background-color: transparent; border: none; }"); layout->addWidget(sessionTabBtn, 1, Qt::AlignTop | Qt::AlignCenter); //添加好友标签页按钮 @@ -105,7 +105,7 @@ void MainWidget::initLeftWindow() friendTabBtn->setFixedSize(45, 45); friendTabBtn->setIconSize(QSize(35, 35)); friendTabBtn->setIcon(QIcon(":/resource/image/friend_inactive.png")); - friendTabBtn->setStyleSheet("QPushButton {background-color: transparent; }"); + friendTabBtn->setStyleSheet("QPushButton {background-color: transparent; border: none; }"); layout->addWidget(friendTabBtn, 1, Qt::AlignTop | Qt::AlignCenter); //添加好友申请标签页按钮 @@ -113,7 +113,7 @@ void MainWidget::initLeftWindow() applyTabBtn->setFixedSize(45, 45); applyTabBtn->setIconSize(QSize(35, 35)); applyTabBtn->setIcon(QIcon(":/resource/image/apply_inactive.png")); - applyTabBtn->setStyleSheet("QPushButton {background-color: transparent; }"); + applyTabBtn->setStyleSheet("QPushButton {background-color: transparent; border: none; }"); layout->addWidget(applyTabBtn, 1, Qt::AlignTop | Qt::AlignCenter); layout->addStretch(20); @@ -529,4 +529,30 @@ void MainWidget::updateRecentMessage(const QString& chatSessionId) messageShowArea->scrollToEnd(); } +void MainWidget::switchSession(const QString& userId) +{ + //找到对应的会话元素 + DataCenter* dataCenter = DataCenter::getInstance(); + ChatSessionInfo* chatSessionInfo = dataCenter->findChatSessionByUserId(userId); + if (chatSessionInfo == nullptr) { + //每个好友都会有一个对应的会话(即便没有说过话) + //添加好友的时候,就创建出来的会话 + LOG() << "严重错误->当前选中的好友对应的会话不存在"; + return; + } + //把选中的会话置顶 + dataCenter->topCurrentChatSessionId(*chatSessionInfo); + + //切换到会话列表标签页 + switchTabToSession(); + + // 加载这个会话对应的历史消息 + sessionFriendArea->clickItem(0); +} + +MessageShowArea* MainWidget::getMessageShowArea() +{ + return messageShowArea; +} + diff --git a/mainwidget.h b/mainwidget.h index 21e72ba..a288e56 100644 --- a/mainwidget.h +++ b/mainwidget.h @@ -104,5 +104,10 @@ public: void loadRecentMessage(const QString& chatSessionId); void updateRecentMessage(const QString& chatSessionId); + + //点击好友项后,切换到会话列表的总函数,上方的switchTabToSession只是其中的一个环节 + void switchSession(const QString& userId); + + MessageShowArea* getMessageShowArea(); }; #endif // MAINWIDGET_H diff --git a/messageeditarea.cpp b/messageeditarea.cpp index 15aebb6..392868c 100644 --- a/messageeditarea.cpp +++ b/messageeditarea.cpp @@ -1,5 +1,10 @@ #include "messageeditarea.h" +#include "mainwidget.h" + +#include "model/datacenter.h" +#include "toast.h" +using namespace model; MessageEditArea::MessageEditArea(QWidget *parent) : QWidget{parent} @@ -174,13 +179,74 @@ MessageEditArea::MessageEditArea(QWidget *parent) // sendTextButton->installEventFilter(this); - + vlayout->addWidget(sendTextButton, 0, Qt::AlignRight | Qt::AlignVCenter); + //统一初始化信号槽 + initSignalSlot(); +} + +void MessageEditArea::initSignalSlot() +{ + DataCenter* dataCenter = DataCenter::getInstance(); + + //关联“显示历史消息窗口”信号槽 connect(showHistoryBtn, &QPushButton::clicked, this, [=]() { HistoryMessageWidget* historyMessageWidget = new HistoryMessageWidget(this); historyMessageWidget->exec(); }); + + //关联“发送文本消息”信号槽 + connect(sendTextButton, &QPushButton::clicked, this, &MessageEditArea::sendTextMessage); + + connect(dataCenter, &DataCenter::sendMessageDone, this, &MessageEditArea::addSelfMessage); +} + +void MessageEditArea::sendTextMessage() +{ + //先确认当前是否有会话被选中 + DataCenter* dataCenter = DataCenter::getInstance(); + if (dataCenter->getCurrentSessionId().isEmpty()) { + LOG() << "当前未选中任何会话,不发送任何消息"; + //上述日志只是在开发阶段能看到,程序发布出去,就无法看到了 + //因此需要让普通用户也能看到提示 + Toast::showMessage("当前未选中会话,不发送任何消息"); + return; + } + + //获取到输入框的内容,没输入,则不做任何操作 + const QString& content = textEdit->toPlainText().trimmed(); + if (content.isEmpty()) { + LOG() << "输入框为空"; + return; + } + + //清空输入框已有的内容 + textEdit->setPlainText(""); + + //通过网络发送数据给服务器 + dataCenter->sendTextMessageAsync(dataCenter->getCurrentSessionId(), content); +} + +//针对自己发送消息的操作,做处理,把自己发送的消息显示到界面上 +void MessageEditArea::addSelfMessage(MessageType messageType, const QByteArray& content, const QString& extraInfo) +{ + DataCenter* dataCenter = DataCenter::getInstance(); + const QString& currentChatSessionId = dataCenter->getCurrentSessionId(); + //构造出一个消息对象 + Message message = Message::makeMessage(messageType, currentChatSessionId, *dataCenter->getMyselfsync(), content, extraInfo); + dataCenter->addMessage(message); + + //把这个新的消息,显示到消息展示区 + MainWidget* mainWidget = MainWidget::getInstance(); + MessageShowArea* messageShowArea = mainWidget->getMessageShowArea(); + messageShowArea->addMessage(false, message); + + //控制消息显示区,滚动条,滚动到末尾 + messageShowArea->scrollToEnd(); + + //发送信号,通知会话列表,更新最后一条消息 + emit dataCenter->updateLastMessage(currentChatSessionId); } diff --git a/messageeditarea.h b/messageeditarea.h index f51e16c..e3f9960 100644 --- a/messageeditarea.h +++ b/messageeditarea.h @@ -12,6 +12,9 @@ //#include #include "historymessagewidget.h" +#include "model/data.h" + +using namespace model; //编辑消息的区域 class MessageEditArea : public QWidget @@ -20,8 +23,12 @@ class MessageEditArea : public QWidget public: explicit MessageEditArea(QWidget *parent = nullptr); + void initSignalSlot(); + void sendTextMessage(); + void addSelfMessage(MessageType messageType, const QByteArray& content, const QString& extraInfo); //花式按钮事件 //bool eventFilter(QObject* obj, QEvent* event) override; + private: QPushButton* sendImageBtn; diff --git a/messageshowarea.cpp b/messageshowarea.cpp index fb792c9..da62bf6 100644 --- a/messageshowarea.cpp +++ b/messageshowarea.cpp @@ -125,6 +125,7 @@ MessageItem *MessageItem::makeMessageItem(bool isLeft, const Message &message) avatarBtn->setFixedSize(40, 40); avatarBtn->setIconSize(QSize(40, 40)); avatarBtn->setIcon(message.sender.avatar); + avatarBtn->setStyleSheet("QPushButton { border: none; }"); if(isLeft) { layout->addWidget(avatarBtn, 0, 0, 2, 1, Qt::AlignCenter | Qt::AlignLeft); } else { @@ -133,7 +134,7 @@ MessageItem *MessageItem::makeMessageItem(bool isLeft, const Message &message) //创建名字和时间 QLabel* nameLabel = new QLabel(); - nameLabel->setText(message.sender.nickname + " | " + message.time); + nameLabel->setText(message.sender.nickname + " " + message.time); nameLabel->setAlignment(Qt::AlignBottom); nameLabel->setStyleSheet("QLabel { font-size: 12px; color: rgb(178, 178, 178); }"); if(isLeft) { diff --git a/model/datacenter.cpp b/model/datacenter.cpp index a9c98c9..52814c3 100644 --- a/model/datacenter.cpp +++ b/model/datacenter.cpp @@ -268,6 +268,11 @@ namespace model } } + void DataCenter::sendTextMessageAsync(const QString& chatSessionId, const QString& content) + { + netClient.sendMessage(loginSessionId, chatSessionId, MessageType::TEXT_TYPE, content.toUtf8(), ""); + } + ChatSessionInfo* DataCenter::findChatSessionById(const QString& chatSessionId) { if (chatSessionList == nullptr) { @@ -281,6 +286,46 @@ namespace model return nullptr; } + ChatSessionInfo* DataCenter::findChatSessionByUserId(const QString& userId) + { + if (chatSessionList == nullptr) { + return nullptr; + } + for (auto& info : *chatSessionList) { + if (info.userId == userId) { + return &info; + } + } + return nullptr; + } + + void DataCenter::topCurrentChatSessionId(const ChatSessionInfo& chatSessionInfo) + { + if (chatSessionList == nullptr) { + return; + } + + //把这个元素从列表中找到 + auto it = chatSessionList->begin(); + for (; it != chatSessionList->end(); ++it) { + if (it->chatSessionId == chatSessionInfo.chatSessionId) { + break; + } + } + + if (it == chatSessionList->end()) { + //上面的循环没有找到匹配的元素,直接返回,正常来说,不会走这个逻辑 + return; + } + + //删除并重新插入头部 + ChatSessionInfo backup = chatSessionInfo; + chatSessionList->erase(it); + + //把备份的元素插入到头部 + chatSessionList->push_front(backup); + } + void DataCenter::setCurrentChatSessionId(const QString& chatSessionId) { this->currentChatSessionId = chatSessionId; @@ -291,4 +336,10 @@ namespace model return currentChatSessionId; } + void DataCenter::addMessage(const Message& message) + { + QList& messageList = (*recentMessages)[message.chatSessionId]; + messageList.push_back(message); + } + } //end namespace model diff --git a/model/datacenter.h b/model/datacenter.h index 6be2d20..e078649 100644 --- a/model/datacenter.h +++ b/model/datacenter.h @@ -122,12 +122,26 @@ namespace model QList* getRecentMessageList(const QString& chatSessionId); void resetRecentMessageList(const QString& chatSessionId, std::shared_ptr resp); + //发送消息给服务器 + void sendTextMessageAsync(const QString& chatSessionId, const QString& content); + + /////////////////////////////////////////////////////////////////////////////////// + ///辅助函数 + /////////////////////////////////////////////////////////////////////////////// + //根据会话id查询会话信息 ChatSessionInfo* findChatSessionById(const QString& chatSessionId); + //根据用户ID查询会话信息 + ChatSessionInfo* findChatSessionByUserId(const QString& userId); + //把指定的会话信息,放到列表头部 + void topCurrentChatSessionId(const ChatSessionInfo& chatSessionInfo); //设置/获取当前选中的会话 void setCurrentChatSessionId(const QString& chatSessionId); const QString& getCurrentSessionId(); + + //添加消息到DataCenter中 + void addMessage(const Message& message); signals: //自定义信号 void getMyselfDone(); @@ -135,5 +149,7 @@ namespace model void getChatSessionListDone(); void getApplyListDone(); void getRecentMessageListDone(const QString& chatSessionId); + void sendMessageDone(MessageType messageType, const QByteArray& content, const QString& extraInfo); + void updateLastMessage(const QString& chatSessionId); }; } //end namespace model diff --git a/network/netclient.cpp b/network/netclient.cpp index 3f8d5a4..bcdb2b3 100644 --- a/network/netclient.cpp +++ b/network/netclient.cpp @@ -30,6 +30,7 @@ namespace network { }); } + void NetClient::initWebSocket() { //准备好所需要的信号槽 @@ -283,4 +284,87 @@ namespace network { emit dataCenter->getRecentMessageListDone(chatSessionId); }); } + + //此处的extraInfo,可以用来传递扩展信息,尤其是对文件消息来说,通过这个字段表示“文件名” + //其他类型的消息暂时不涉及,就直接设置为空,如果后续有消息类型需要,都可以给这个参数,赋予一定的特殊意义 + void NetClient::sendMessage(const QString& loginSessionId, const QString& chatSessionId, + model::MessageType messageType, const QByteArray& content, const QString& extraInfo) + { + //通过protobuf 构造 body + bite_im::NewMessageReq req; + req.setRequestId(makeRequestId()); + req.setSessionId(loginSessionId); + req.setChatSessionId(chatSessionId); + + //构造MessageContent + bite_im::MessageContent messageContent; + if (messageType == MessageType::TEXT_TYPE) { + messageContent.setMessageType(bite_im::MessageTypeGadget::MessageType::STRING); + + bite_im::StringMessageInfo stringMessageInfo; + stringMessageInfo.setContent(content); + messageContent.setStringMessage(stringMessageInfo); + } + else if (messageType == MessageType::IMAGE_TYPE) { + messageContent.setMessageType(bite_im::MessageTypeGadget::MessageType::IMAGE); + + bite_im::ImageMessageInfo imageMessageInfo; + imageMessageInfo.setFileId(""); + imageMessageInfo.setImageContent(content); + messageContent.setImageMessage(imageMessageInfo); + } + else if (messageType == MessageType::FILE_TYPE) { + messageContent.setMessageType(bite_im::MessageTypeGadget::MessageType::FILE); + + bite_im::FileMessageInfo fileMessageInfo; + fileMessageInfo.setFileId(""); + fileMessageInfo.setFileSize(content.size()); + fileMessageInfo.setFileName(extraInfo); + fileMessageInfo.setFileContents(content); + messageContent.setFileMessage(fileMessageInfo); + } + else if (messageType == MessageType::SPEECH_TYPE) { + messageContent.setMessageType(bite_im::MessageTypeGadget::MessageType::SPEECH); + + bite_im::SpeechMessageInfo speechMessageInfo; + speechMessageInfo.setFileId(""); + speechMessageInfo.setFileContents(content); + messageContent.setSpeechMessage(speechMessageInfo); + } + else { + LOG() << "错误的消息类型 messageType= " << messageType; + } + req.setMessage(messageContent); + + //序列化操作 + QByteArray body = req.serialize(&serializer); + LOG() << "发送消息->发送请求 requestId= " << req.requestId() << ", loginSessionId=" << req.sessionId() << + ", chatSessionId= " << req.chatSessionId() << ", messageType=" << req.message().messageType(); + + //发送HTTP请求 + QNetworkReply* resp = this->sendHttpRequest("/service/message_transmit/new_message", body); + + //处理HTTP响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + //针对响应结果,进行解析 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + //判定响应是否正确 + if (!ok) { + LOG() << "发送消息->处理出错 reason= " << reason; + return; + } + + //此处只是记录成功和失败,不需要把内写入到DataCenter中 + + + //通知调用者,响应处理完毕 + emit dataCenter->sendMessageDone(messageType, content, extraInfo); + + //打印日志 + LOG() << "发送消息->响应处理完毕 requestId=" << pbResp->requestId(); + }); + } } //end namespace network \ No newline at end of file diff --git a/network/netclient.h b/network/netclient.h index f8656a5..d1a0ab8 100644 --- a/network/netclient.h +++ b/network/netclient.h @@ -84,6 +84,8 @@ namespace network { void getChatSessionList(const QString& loginSessionId); void getApplyList(const QString& loginSessionId); void getRecentMessageList(const QString& loginSessionId, const QString& chatSessionId); + void sendMessage(const QString& loginSessionId, const QString& chatSessionId, model::MessageType messageType, + const QByteArray& content, const QString& extraInfo); private: model::DataCenter* dataCenter; diff --git a/sessionfriendarea.cpp b/sessionfriendarea.cpp index 79524ed..a568e8e 100644 --- a/sessionfriendarea.cpp +++ b/sessionfriendarea.cpp @@ -3,12 +3,15 @@ #include "debug.h" #include "model/data.h" +#include "model/datacenter.h" #include "mainwidget.h" +using namespace model; + SessionFriendArea::SessionFriendArea(QWidget *parent) : QScrollArea {parent} { - //设置必要属性 + //设置必要属性 //设置这个才能有滚动效果 this->setWidgetResizable(true); //设置滚动条相关样式 @@ -114,9 +117,9 @@ SessionFriendItem::SessionFriendItem(QWidget* owner, const QIcon& avatar, const nameLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); //创建消息预览的label - QLabel* messageLabel = new QLabel(); + messageLabel = new QLabel(); messageLabel->setText(text); - messageLabel->setStyleSheet("QLabel { font-size: 18px; font-weight: 600; }"); + messageLabel->setStyleSheet("QLabel { font-size: 12px; font-weight: 600; color: rgb(153, 153, 153); }"); messageLabel->setFixedHeight(35); messageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); @@ -255,7 +258,6 @@ void SessionFriendArea::clickItem(int index) item->select(); } - //////////////////////////////////////// /// 会话Item的实现 //////////////////////////////////////// @@ -263,7 +265,52 @@ SessionItem::SessionItem(QWidget *owner, const QString &chatSessionId, const QIc const QString &name, const QString &lastmessage) :SessionFriendItem(owner, avatar, name, lastmessage), chatSessionId(chatSessionId) { + //处理更新最后一个消息的信号 + DataCenter* dataCenter = DataCenter::getInstance(); + connect(dataCenter, &DataCenter::updateLastMessage, this, &SessionItem::updateLastMessage); +} +void SessionItem::updateLastMessage(const QString& chatSessionId) +{ + DataCenter* dataCenter = DataCenter::getInstance(); + //先判定,信号中的会话id得和当前元素自身持有放到会话id一致,才真正处理 + if (this->chatSessionId != chatSessionId) { + //当前SessionItem不是正在发消息的SessionItem + return; + } + //chatSession匹配,真正更新最后一条消息 + + //把最后一条消息获取到 + QList* messageList = dataCenter->getRecentMessageList(chatSessionId); + if (messageList == nullptr || messageList->size() == 0) { + //当前会话没有任何消息,无需更新 + return; + } + const Message& lastMessage = messageList->back(); + + //明确显示的文本内容 + QString text; + if (lastMessage.messageType == TEXT_TYPE) { + text = lastMessage.content; + } + else if (lastMessage.messageType == IMAGE_TYPE) { + text = "[图片]"; + } + else if (lastMessage.messageType == FILE_TYPE) { + text = "[文件]"; + } + else if (lastMessage.messageType == SPEECH_TYPE) { + text = "[语音]"; + } + else { + LOG() << "错误的消息类型"; + return; + } + + //把这个内容显示到界面上 + //后续还要考虑到“未读消息”情况, + //TODO + this->messageLabel->setText(text); } void SessionItem::active() @@ -290,6 +337,9 @@ void FriendItem::active() { //点击之后,要激活对应的会话列元素 LOG() << "click FriendItem... userId: " << userId; + + MainWidget* mainWidget = MainWidget::getInstance(); + mainWidget->switchSession(userId); } //////////////////////////////////////// diff --git a/sessionfriendarea.h b/sessionfriendarea.h index 36710ff..ae01ccb 100644 --- a/sessionfriendarea.h +++ b/sessionfriendarea.h @@ -98,6 +98,8 @@ public: const QString& name, const QString& lastmessage); void active() override; + + void updateLastMessage(const QString& chatSessionId); private: //当前会话Id QString chatSessionId;