add sendMessage function

This commit is contained in:
xyz
2025-06-18 18:21:23 +08:00
parent c50f574eed
commit 971ab64769
11 changed files with 319 additions and 9 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}

View File

@ -12,6 +12,9 @@
//#include <QEvent>
#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;

View File

@ -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) {

View File

@ -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<Message>& messageList = (*recentMessages)[message.chatSessionId];
messageList.push_back(message);
}
} //end namespace model

View File

@ -122,12 +122,26 @@ namespace model
QList<Message>* getRecentMessageList(const QString& chatSessionId);
void resetRecentMessageList(const QString& chatSessionId, std::shared_ptr<bite_im::GetRecentMsgRsp> 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

View File

@ -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<bite_im::NewMessageRsp>(resp, &ok, &reason);
//判定响应是否正确
if (!ok) {
LOG() << "发送消息->处理出错 reason= " << reason;
return;
}
//此处只是记录成功和失败不需要把内写入到DataCenter中
//通知调用者,响应处理完毕
emit dataCenter->sendMessageDone(messageType, content, extraInfo);
//打印日志
LOG() << "发送消息->响应处理完毕 requestId=" << pbResp->requestId();
});
}
} //end namespace network

View File

@ -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;

View File

@ -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<Message>* 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);
}
////////////////////////////////////////

View File

@ -98,6 +98,8 @@ public:
const QString& name, const QString& lastmessage);
void active() override;
void updateLastMessage(const QString& chatSessionId);
private:
//当前会话Id
QString chatSessionId;