refactor: 大规模调整项目目录结构,将ChatClient和ChatServer整合为Monorepo结构,并分为两个独立文件夹:chatclient/ 和 chatserver/。更新了ChatClient的CMakeLists.txt配置以适配新结构。

This commit is contained in:
xyz
2025-09-16 19:47:22 +08:00
parent 89ff4fbac0
commit e7af9ad1d7
77 changed files with 146 additions and 168 deletions

View File

@ -0,0 +1,66 @@
#pragma once
#include <QDialog>
#include <QWidget>
#include <QGridLayout>
#include <QPushButton>
#include <QLineEdit>
#include <QLabel>
#include <QScrollArea>
#include <QScrollBar>
#include "debug.h"
#include "model/data.h"
using model::UserInfo;
/////////////////////////////////////////
//表示一个好友搜索的结果
/////////////////////////////////////////
class FriendResultItem : public QWidget {
Q_OBJECT
public:
FriendResultItem(const UserInfo& userInfo);
void clickAddBtn();
private:
const UserInfo& userInfo;
QPushButton* addBtn;
};
/////////////////////////////////////////
//整个搜索好友的窗口
/////////////////////////////////////////
class AddFriendDialog : public QDialog
{
Q_OBJECT
public:
AddFriendDialog(QWidget *parent);
//初始化结果区域
void initResultArea();
//往窗口中新增一个好友搜索的结果
void addResult(const UserInfo& userInfo);
//清空界面上所有的好友搜索结果
void clear();
//
void setSearchKey(const QString& searcheKey);
void clickSearchBtn();
void clickSearchDone();
private:
//整个窗口的网格布局
QGridLayout* layout;
//
QLineEdit* searchEdit;
//保存搜索好友的结果
QWidget* resultContainer;
};

View File

@ -0,0 +1,72 @@
#pragma once
#include <QDialog>
#include <QWidget>
#include <QHBoxLayout>
#include <QScrollArea>
#include <QScrollBar>
#include <QPushButton>
#include <QPainter>
#include <QCheckBox>
#include <QLabel>
#include "debug.h"
class ChooseFriendDialog;
////////////////////////////////////////////////
/// 选择好友窗口中的一个 元素/好友项
////////////////////////////////////////////////
class ChooseFriendItem : public QWidget {
Q_OBJECT
public:
ChooseFriendItem(ChooseFriendDialog* owner, const QString& userId, const QIcon& avatar, const QString& name, bool checked);
void paintEvent(QPaintEvent* event) override;
void enterEvent(QEnterEvent* event) override;
void leaveEvent(QEvent* event) override;
QString& getUserId() {
return userId;
}
QCheckBox* getCheckBox() {
return checkBox;
}
private:
bool isHover = false;
QCheckBox* checkBox;
QPushButton* avatarBtn;
QLabel* nameLabel;
ChooseFriendDialog* owner; //记录了哪个QWidget持有这个Item此处应该是ChooseFriendDialog
QString userId; //记录成员id用于删除唯一指定的成员Item
};
class ChooseFriendDialog : public QDialog
{
Q_OBJECT
public:
ChooseFriendDialog(QWidget *parent, const QString& userId);
void initLeft(QHBoxLayout* layout);
void initRight(QHBoxLayout* layout);
void initData();
void addFriend(const QString& userId, const QIcon& avatar, const QString& name, bool checked);
void addSelectedFriend(const QString& userId, const QIcon& avatar, const QString& name);
void deleteSelectedFriend(const QString& userId);
void clickOkBtn();
QList<QString> generateMemberList();
private:
QWidget* totalContainer;
QWidget* selectedContainer;
//当前选择窗口是点击哪个用户弹出的
QString userId;
};

View File

@ -0,0 +1,14 @@
#ifndef DEBUG_H
#define DEBUG_H
#define TEST_UI 0
#define TEST_GROUP_SESSION_DETAIL 0
#define TEST_SKIP_LOGIN 0
#define TEST_NETWORK 0
#define DEPOLY 1
#endif // DEBUG_H

View File

@ -0,0 +1,37 @@
#pragma once
#include <QDialog>
#include <QWidget>
#include <QLabel>
#include <QVBoxLayout>
#include <qscrollarea.h>
#include <qscrollbar.h>
#include <qgridlayout.h>
#include <qpushbutton.h>
#include <qgridlayout.h>
#include "sessiondetailwidget.h"
#include "debug.h"
class AvatarItem;
class GroupSessionDetailWidget : public QDialog
{
Q_OBJECT
public:
GroupSessionDetailWidget(QWidget* parent);
void initData();
void initMembers(const QString& chatSessionId);
void addMember(AvatarItem* avatarItem);
private:
QGridLayout* glayout;
QLabel* groupNameLabel;
//表示当前所要添加的AvatarItem处在的行和列
int curRow = 0;
int curCol = 1;
};

View File

@ -0,0 +1,108 @@
#pragma once
#include <QDialog>
#include <QGridLayout>
#include <QRadioButton>
#include <QLineEdit>
#include <QPushButton>
#include <QLabel>
#include <QDateTimeEdit>
#include <QScrollArea>
#include <QScrollBar>
#include <QFileDialog>
#include "debug.h"
#include "model/data.h"
using model::Message;
////////////////////////////////////////////////////////////////////
/// 表示一个历史消息元素
////////////////////////////////////////////////////////////////////
class HistoryItem : public QWidget {
Q_OBJECT
public:
HistoryItem() {}
static HistoryItem* makeHistoryItem(const Message& message);
};
////////////////////////////////////////////////////////////////////
/// 展示历史消息窗口
////////////////////////////////////////////////////////////////////
class HistoryMessageWidget : public QDialog
{
Q_OBJECT
public:
HistoryMessageWidget(QWidget *parent);
//在窗口中添加一个历史消息
void addHistoryMessage(const Message& message);
//清空窗口中所有的历史消息
void clear();
void clickSearchBtn();
void clickSearchBtnDone();
private:
void initScrollArea(QGridLayout* layout);
QRadioButton* keyRadioBtn;
QRadioButton* timeRadioBtn;
QLineEdit* searchEdit;
QDateTimeEdit* begTimeEdit;
QDateTimeEdit* endTimeEdit;
//持有所有的历史消息结果的容器对象
QWidget* container;
};
////////////////////////////////////////////////////////////////////
/// 展示图片历史消息
////////////////////////////////////////////////////////////////////
class ImageButton : public QPushButton
{
public:
ImageButton(const QString& fileId, const QByteArray& content);
void updateUI(const QString& fileId, const QByteArray& content);
private:
QString fileId;
};
////////////////////////////////////////////////////////////////////
/// 展示文件历史消息
////////////////////////////////////////////////////////////////////
class FileLabel : public QLabel {
public:
FileLabel(const QString& fileId, const QString& filename);
void getContentDone(const QString& fileId, const QByteArray& fileContent);
void mousePressEvent(QMouseEvent* event) override;
private:
QString fileId;
QByteArray content;
QString filename;
bool loadDone = false;
};
////////////////////////////////////////////////////////////////////
/// 展示语音历史消息
////////////////////////////////////////////////////////////////////
class SpeechLabel : public QLabel {
public:
SpeechLabel(const QString& fileId);
void getContentDone(const QString& fileId, const QByteArray& content);
void mousePressEvent(QMouseEvent* event);
private:
QString fileId;
QByteArray content;
bool loadDone = false;
};

View File

@ -0,0 +1,38 @@
#pragma once
#include <QWidget>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include "phoneloginwidget.h"
#include "verifycodewidget.h"
#include "toast.h"
class LoginWidget : public QWidget
{
Q_OBJECT
public:
explicit LoginWidget(QWidget *parent);
void switchMode();
void clickSubmitBtn();
void userLoginDone(bool ok, const QString& reason);
void userRegisterDone(bool ok, const QString& reason);
private:
bool isLoginMode = true;
QLabel* titleLabel;
QLineEdit* usernameEdit;
QLineEdit* passwordEdit;
QLineEdit* verifyCodeEdit;
VerifyCodeWidget* verifyCodeWidget;
QPushButton* submitBtn;
QPushButton* phoneModeBtn;
QPushButton* switchModeBtn;
};

View File

@ -0,0 +1,114 @@
#ifndef MAINWIDGET_H
#define MAINWIDGET_H
#include <QWidget>
#include <QPushButton>
#include <QLineEdit>
#include <QMainWindow>
#include "debug.h"
#include "messageeditarea.h"
#include "messageshowarea.h"
#include "selfinfowidget.h"
#include "sessionfriendarea.h"
#include "groupsessiondetailwidget.h"
#include "addfrienddialog.h"
#include "model/datacenter.h"
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWidget;
}
QT_END_NAMESPACE
class MainWidget : public QWidget
{
Q_OBJECT
private:
//对于单例模式最关键的部分是限制别人创建实例
static MainWidget* instance;
public:
static MainWidget* getInstance();
public:
MainWidget(QWidget *parent = nullptr);
~MainWidget();
private:
Ui::MainWidget *ui;
//窗口左侧部分
QWidget* windowLeft;
//窗口中间部分
QWidget* windowMid;
//窗口右侧部分
QWidget* windowRight;
//用户头像
QPushButton* userAvatar;
//会话标签页按钮
QPushButton* sessionTabBtn;
//好友标签页按钮
QPushButton* friendTabBtn;
//好友申请标签页按钮
QPushButton* applyTabBtn;
//用户搜索框
QLineEdit* searchEdit;
//添加好友按钮
QPushButton* addFriendBtn;
//
SessionFriendArea* sessionFriendArea;
//显示会话标题
QLabel* sessionTitleLabel;
//显示会话详情按钮
QPushButton* extraBtn;
//消息展示区
MessageShowArea* messageShowArea;
//消息编辑区
MessageEditArea* messageEditArea;
enum ActiveTab {
SESSION_LIST,
FRIEND_LIST,
APPLY_LIST
};
ActiveTab activeTab = SESSION_LIST;
public:
void initMainWindow();
void initLeftWindow();
void initMidWindow();
void initRightWindow();
void initSignalSlot();
void initWebSocket();
void switchTabToSession();
void switchTabToFriend();
void switchTabToApply();
void loadSessionList();
void loadFriendList();
void loadApplyList();
void updateFriendList();
void updateChatSessionList();
void updateApplyList();
void loadRecentMessage(const QString& chatSessionId);
void updateRecentMessage(const QString& chatSessionId);
//点击好友项后切换到会话列表的总函数上方的switchTabToSession只是其中的一个环节
void switchSession(const QString& userId);
MessageShowArea* getMessageShowArea();
};
#endif // MAINWIDGET_H

View File

@ -0,0 +1,56 @@
#ifndef MESSAGEEDITAREA_H
#define MESSAGEEDITAREA_H
#include <QWidget>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QScrollBar>
//#include <QGraphicsDropShadowEffect>
//#include <qpropertyanimation.h>
//#include <QEvent>
#include "historymessagewidget.h"
#include "model/data.h"
using namespace model;
//编辑消息的区域
class MessageEditArea : public QWidget
{
Q_OBJECT
public:
explicit MessageEditArea(QWidget *parent = nullptr);
void initSignalSlot();
void sendTextMessage();
void addSelfMessage(MessageType messageType, const QByteArray& content, const QString& extraInfo);
void addOtherMessage(const model::Message& message);
//花式按钮事件
//bool eventFilter(QObject* obj, QEvent* event) override;
void clickSendImageBtn();
void clickSendFileBtn();
void soundRecordPressed();
void soundRecordReleased();
void soundSpeech(const QString& path);
private:
QPushButton* sendImageBtn;
QPushButton* sendFileBtn;
QPushButton* sendSpeechBtn;
QPushButton* showHistoryBtn;
QPlainTextEdit* textEdit;
QPushButton* sendTextButton;
QLabel* tipLabel;
//花式按钮
//QGraphicsDropShadowEffect* shadowEffect;
signals:
};
#endif // MESSAGEEDITAREA_H

View File

@ -0,0 +1,120 @@
#ifndef MESSAGESHOWAREA_H
#define MESSAGESHOWAREA_H
#include <QScrollArea>
#include <QWidget>
#include <QVBoxLayout>
#include <QScrollBar>
#include <QPushButton>
#include <QLabel>
#include <QFontMetrics>
#include <QPainter>
#include <QPainterPath>
#include "model/data.h"
#include "debug.h"
//.h文件中不宜使用namespace xxx
using model::Message;
////////////////////////////////////////////
/// 表示消息展示区
////////////////////////////////////////////
class MessageShowArea : public QScrollArea
{
Q_OBJECT
public:
MessageShowArea();
//头插
void addFrontMessage(bool isLeft, const Message& message);
//尾插
void addMessage(bool isLeft, const Message& message);
//清空
void clear();
//滚动到末尾
void scrollToEnd();
private:
QWidget* container;
};
////////////////////////////////////////////
/// 表示一个消息元素
/// 我们可能考虑要同时支持文本,文件,图片,视频,语音
////////////////////////////////////////////
class MessageItem : public QWidget {
public:
//isLeft表示当前的这个消息是否是左侧的消息
MessageItem(bool isLeft);
//通过工厂方法创建MessageItem实例
static MessageItem* makeMessageItem(bool isLeft, const Message& message);
//添加工厂函数
static QWidget* makeTextMessageItem(bool isLeft, const QString& message);
static QWidget* makeImageMessageItem(bool isLeft, const QString& fileId, const QByteArray& content);
static QWidget* makeFileMessageItem(bool isLeft, const Message& message);
static QWidget* makeSpeechMessageItem(bool isLeft, const Message& message);
private:
bool isLeft;
};
////////////////////////////////////////////
/// 创建类表示“文本消息”正文部分
//也让这个类表示文件消息
////////////////////////////////////////////
class MessageContentLabel : public QWidget {
public:
MessageContentLabel(const QString& text, bool isLeft, model::MessageType messageType, const QString& fileId,
const QByteArray& content);
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void updateUI(const QString& fileId, const QByteArray& fileContent);
void saveAsFile(const QByteArray& content);
void playDone();
void contextMenuEvent(QContextMenuEvent* event) override;
void speechConverTextDone(const QString& fileId, const QString& text);
private:
QLabel* label;
bool isLeft;
model::MessageType messageType;
QString fileId;
QByteArray content;
QString fileName;
bool loadContentDone = false;
};
////////////////////////////////////////////
/// 创建类表示“图片消息”部分
////////////////////////////////////////////
class MessageImageLabel : public QWidget {
Q_OBJECT
public:
MessageImageLabel(const QString& fileId, const QByteArray& content, bool isLeft);
void updateUI(const QString& fileId, const QByteArray& content);
void paintEvent(QPaintEvent* event) override;
private:
QPushButton* imageBtn;
QString fileId; //该图片在服务器对应的文件id
QByteArray content; //图片的二进制数据
bool isLeft;
};
#endif // MESSAGESHOWAREA_H

View File

@ -0,0 +1,318 @@
#pragma once
#include <QDateTime>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QIcon>
#include <QString>
#include <QUuid>
#include "base.qpb.h"
#include "gateway.qpb.h"
#include "user.qpb.h"
#include "friend.qpb.h"
#include "file.qpb.h"
#include "notify.qpb.h"
#include "speech_recognition.qpb.h"
#include "message_storage.qpb.h"
#include "message_transmit.qpb.h"
// 创建命名空间
namespace model {
///////////////////////////
//工具函数,后续很多模块可能会用到
///////////////////////////
//获取仅当前的源文件名
static inline QString getFileName(const QString& path) {
QFileInfo fileinfo(path);
return fileinfo.fileName();
}
//封装一个宏作为日志打印的方式
#define TAG QString("[%1:%2]").arg(model::getFileName(__FILE__), QString::number(__LINE__))
#define LOG() qDebug().noquote() << TAG
//避免链接阶段出现“函数重定义的问题”
static inline QString formatTime(int64_t timestamp) {
//先把时间戳转换为datetime对象
QDateTime datetime = QDateTime::fromSecsSinceEpoch(timestamp);
//把datetime对象转化为格式化的时间
return datetime.toString("MM-dd HH:mm:ss");
}
//通过这个函数得到秒级别的时间
static inline int64_t getTime() {
return QDateTime::currentSecsSinceEpoch();
}
//根据QByteArray转换为QIcon
static inline QIcon makeIcon(const QByteArray& byteArray) {
//存储和操作图像数据的类
QPixmap pixmap;
pixmap.loadFromData(byteArray);
QIcon icon(pixmap);
return icon;
}
// 读写文件操作
// 从指定的文件中读取所有的二进制内容得到一个QByteArray
static inline QByteArray loadFileToByteArray(const QString& path) {
QFile file(path);
bool ok = file.open(QFile::ReadOnly);
if(!ok) {
qDebug() << "文件打开失败!!!";
return QByteArray();
}
QByteArray content = file.readAll();
file.close();
return content;
}
//将QByteArray的内容写入到某个指定的文件里
static inline void writeByteArrayToFile(const QString& path, const QByteArray& content) {
QFile file(path);
bool ok = file.open(QFile::WriteOnly);
if(!ok) {
qDebug() << "文件打开失败!!!";
return;
}
file.write(content);
file.flush();
file.close();
}
///////////////////////////
// 用户信息
///////////////////////////
class UserInfo {
public:
//初始化,避免一些随机值可能的负面影响
QString userId = ""; //用户编号
QString nickname = ""; //用户昵称
QString description = ""; //用户签名
QString phone = ""; //手机号码
QIcon avatar; //用户头像
//从protobuffer的UserInfo对象转换为当前代码的UserInfo对象
void load(const bite_im::UserInfo& userInfo) {
this->userId = userInfo.userId();
this->nickname = userInfo.nickname();
this->phone = userInfo.phone();
this->description = userInfo.description();
if (userInfo.avatar().isEmpty()) {
//使用默认的头像即可
this->avatar = QIcon(":resource/image/defaultAvatar.png");
}
else {
this->avatar = makeIcon(userInfo.avatar());
}
}
};
///////////////////////////
//消息信息
///////////////////////////
enum MessageType {
TEXT_TYPE, //文本消息
IMAGE_TYPE, //图片消息
FILE_TYPE, //文件消息
SPEECH_TYPE //语音消息
};
class Message {
public:
QString messageId = ""; //消息的编号
QString chatSessionId = ""; //消息所属会话的编号
QString time = ""; //消息的时间,通过格式化的方式来表示
MessageType messageType = TEXT_TYPE; //消息类型
UserInfo sender; //发送者信息
QByteArray content; //消息的正文内容
QString fileId = ""; //文件的身份标识,当为文件,图片,语音,视频有效,当为文本则为""
QString fileName = ""; //文件名称
static Message makeMessage(MessageType messageType, const QString& chatSessionId, const UserInfo& sender,
const QByteArray& content, const QString& extraInfo) {
if(messageType == TEXT_TYPE) {
return makeTextMessage(chatSessionId, sender, content);
} else if(messageType == IMAGE_TYPE) {
return makeImageMessage(chatSessionId, sender, content);
} else if(messageType == FILE_TYPE) {
return makeFileMessage(chatSessionId, sender, content, extraInfo);
} else if(messageType == SPEECH_TYPE) {
return makeSpeechMessage(chatSessionId, sender, content);
} else {
//触发了未知消息类型
return Message();
}
}
void load(const bite_im::MessageInfo& messageInfo) {
this->messageId = messageInfo.messageId();
this->chatSessionId = messageInfo.chatSessionId();
this->time = formatTime(messageInfo.timestamp());
this->sender.load(messageInfo.sender());
//设置消息的类型
auto type = messageInfo.message().messageType();
if (type == bite_im::MessageTypeGadget::MessageType::STRING) {
this->messageType = TEXT_TYPE;
this->content = messageInfo.message().stringMessage().content().toUtf8();
}
else if (type == bite_im::MessageTypeGadget::MessageType::IMAGE) {
this->messageType = IMAGE_TYPE;
if (messageInfo.message().imageMessage().hasImageContent()) {
this->content = messageInfo.message().imageMessage().imageContent();
}
if (messageInfo.message().imageMessage().hasFileId()) {
this->fileId = messageInfo.message().imageMessage().fileId();
}
}
else if (type == bite_im::MessageTypeGadget::MessageType::FILE) {
this->messageType = FILE_TYPE;
if (messageInfo.message().fileMessage().hasFileContents()) {
this->content = messageInfo.message().fileMessage().fileContents();
}
if (messageInfo.message().fileMessage().hasFileId()) {
this->fileId = messageInfo.message().fileMessage().fileId();
}
this->fileName = messageInfo.message().fileMessage().fileName();
}
else if (type == bite_im::MessageTypeGadget::MessageType::SPEECH) {
this->messageType = SPEECH_TYPE;
if (messageInfo.message().speechMessage().hasFileContents()) {
this->content = messageInfo.message().speechMessage().fileContents();
}
if (messageInfo.message().speechMessage().hasFileId()) {
this->fileId = messageInfo.message().speechMessage().fileId();
}
}
else {
// 错误的类型, 啥都不做了, 只是打印一个日志
LOG() << "非法的消息类型! type=" << type;
}
}
private:
static QString makeId() {
//Uuid这东西背后也是一套算法能够生成“全球唯一的身份标识”
//Qt对UUID也有封装
//{75eb37e5-5af5-4bfb-8d0e-5261038a9107}(16进制的整数)
return "M" + QUuid::createUuid().toString().mid(25, 12);
}
static Message makeTextMessage(const QString& chatSessionId,const UserInfo& sender, const QByteArray& content) {
Message message;
//此处需要确保生成的messageId是唯一的
message.messageId = makeId();
message.chatSessionId = chatSessionId;
message.sender = sender;
message.time = formatTime(getTime());
message.content = content;
message.messageType = TEXT_TYPE;
//对于文本消息来说,以下属性并不使用,所以设置为""
message.fileId = "";
message.fileName = "";
return message;
}
static Message makeImageMessage(const QString& chatSessionId,const UserInfo& sender, const QByteArray& content) {
Message message;
message.messageId = makeId();
message.chatSessionId = chatSessionId;
message.sender = sender;
message.time = formatTime(getTime());
message.content = content;
message.messageType = IMAGE_TYPE;
//后续使用时再进一步进行设置
message.fileId = "";
//此处并不使用,设置为""
message.fileName = "";
return message;
}
static Message makeFileMessage(const QString& chatSessionId,const UserInfo& sender, const QByteArray& content, const QString& fileName) {
Message message;
message.messageId = makeId();
message.chatSessionId = chatSessionId;
message.sender = sender;
message.time = formatTime(getTime());
message.content = content;
message.messageType = FILE_TYPE;
//后续使用时再进一步进行设置
message.fileId = "";
//此处并不使用,设置为""
message.fileName = fileName;
return message;
}
static Message makeSpeechMessage(const QString& chatSessionId,const UserInfo& sender, const QByteArray& content) {
Message message;
message.messageId = makeId();
message.chatSessionId = chatSessionId;
message.sender = sender;
message.time = formatTime(getTime());
message.content = content;
message.messageType = SPEECH_TYPE;
//后续使用时再进一步进行设置
message.fileId = "";
//此处并不使用,设置为""
message.fileName = "";
return message;
}
};
///////////////////////////
//会话信息
///////////////////////////
class ChatSessionInfo {
public:
QString chatSessionId = ""; //会话编号
QString chatSessionName = ""; //会话名字(单聊或群聊)
Message lastMessage; //表示会话的最新消息
QIcon avatar; //会话的头像(单聊或群聊)
QString userId = ""; //(单聊为对方的id群聊为"")
void load(const bite_im::ChatSessionInfo& chatSessionInfo) {
this->chatSessionId = chatSessionInfo.chatSessionId();
this->chatSessionName = chatSessionInfo.chatSessionName();
if (chatSessionInfo.hasSingleChatFriendId()) {
this->userId = chatSessionInfo.singleChatFriendId();
}
if (chatSessionInfo.hasPrevMessage()) {
lastMessage.load(chatSessionInfo.prevMessage());
}
if (chatSessionInfo.hasAvatar() && !chatSessionInfo.avatar().isEmpty()) {
//如果有头像,则设置这个头像
this->avatar = makeIcon(chatSessionInfo.avatar());
}
else {
//如果没有,则会根据是单聊还是群聊,使用不同的默认头像
if (userId != "") {
//单聊
this->avatar = QIcon(":/resource/image/defaultAvatar.png");
}
else {
//群聊
this->avatar = QIcon(":/resource/image/groupAvatar.png");
}
}
}
};
} //end namespace model

View File

@ -0,0 +1,265 @@
#pragma once
#include <QWidget>
#include <qstandardpaths.h>
#include <QDir>
#include <QJsonObject>
//#include <QList>
#include "data.h"
#include "../network/netclient.h"
namespace model
{
class DataCenter : public QObject
{
Q_OBJECT
public:
static DataCenter* getInstance();
~DataCenter();
/// <summary>
/// 计算两个整数的和。
/// </summary>
/// <param name="a">第一个加数</param>
/// <param name="b">第二个加数</param>
/// <returns>返回 a + b 的结果</returns>
/*int add(int a, int b) {
return a + b;
}*/
private:
DataCenter();
static DataCenter* instance;
//列出DataCenter中要组织管理的所有数据
//当前客户端登录到服务器对应的登录会话Id
QString loginSessionId;
//当前的用户信息
UserInfo* myself = nullptr;
//好友列表
QList<UserInfo>* friendList = nullptr;
//会话列表
QList<ChatSessionInfo>* chatSessionList = nullptr;
//记录当前选中的会话是哪个
QString currentChatSessionId = "";
//记录每个会话中,都有哪些成员
QHash<QString, QList<UserInfo>>* memberList = nullptr;//unordered_map
//待处理的好友申请列表
QList<UserInfo>* applyList = nullptr;
//每个会话最近消息的列表,key为chatSessionIdvalue为消息列表
QHash<QString, QList<Message>>* recentMessages = nullptr;
//存储每个会话,表示未读消息的数量
QHash<QString, int>* unreadMessageCount = nullptr;
// 用户的好友搜索结果
QList<UserInfo>* searchUserResult = nullptr;
//保存一个历史消息搜索结果
QList<Message>* searchMessageResult = nullptr;
//短信邮箱验证码的验证Id
QString currentVerifyCodeId = "";
//让dataCenter持有Netclient实例
network::NetClient netClient;
public:
/// <summary>
/// 初始化数据文件
/// </summary>
void initDataFile();
//存储数据到文件中
void saveDataFile();
//从数据文件中加载数据到内存
void loadDataFile();
//清空未读消息数目
void clearUnread(const QString& chatSessionId);
//增加未读消息的数目
void addUnread(const QString& chatSessionId);
//获取未读消息的数目
int getUnread(const QString& chatSessionId);
//获取到当前的登录会话Id
const QString& getLoginSessionId() const{
return loginSessionId;
}
//验证网络的连通性
void ping() { netClient.ping(); }
//针对netclient中的websocket进行初始化
void initWebsocket();
//通过网络获取到用户的个人信息
void getMyselfAsync();
UserInfo* getMyselfsync();
//
void resetMyself(std::shared_ptr<bite_im::GetUserInfoRsp> resp);
//通过网络获取好友列表
QList<UserInfo>* getFriendList();
void getFriendListAsync();
void resetFriendList(std::shared_ptr<bite_im::GetFriendListRsp> resp);
//获取会话列表
QList<ChatSessionInfo>* getChatSessionList();
void getChatSessionListAsync();
void resetChatSessionList(std::shared_ptr<bite_im::GetChatSessionListRsp> resp);
//获取好友申请列表
QList<UserInfo>* getApplyList();
void getApplyListAsync();
void resetApplyList(std::shared_ptr<bite_im::GetPendingFriendEventListRsp> resp);
//获取最近的消息列表
void getRecnetMessageListAsync(const QString& chatSessionId, bool updateUI);
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);
void sendImageMessageAsync(const QString& chatSessionId, const QByteArray& content);
void sendFileMessageAsync(const QString& chatSessionId, const QString& fileName, const QByteArray& content);
void sendSpeechMessageAsync(const QString& chatSessionId, const QByteArray& content);
//修改用户昵称
void changeNicknameAsync(const QString& nickname);
void resetNickname(const QString& nickname);
//修改用户签名
void changeDescriptionAsync(const QString& desc);
void resetDescription(const QString& desc);
//获取邮箱验证码
void getVerifyCodeAsync(const QString& email);
void resetVerifyCodeId(const QString& verifyCodeId);
//获取verifyCodeId
const QString& getVerifyCodeId() const;
//修改邮箱号码
void changePhoneAsync(const QString& email, const QString& verifyCodeId, const QString& verifyCode);
void resetPhone(const QString& email);
//修改头像
void changeAvatarAsync(const QByteArray& imageBytes);
void resetAvatar(const QByteArray& avatar);
//删除好友
void deleteFriendAsync(const QString& userId);
void removeFriend(const QString& userId);
void addFriendApplyAsync(const QString& userId);
//发送同意好友申请操作
void acceptFriendApplyAsync(const QString& userId);
UserInfo removeFromApplyList(const QString& userId);
//拒绝好友申请操作
void rejectFriendApplyAsync(const QString& userId);
//创建群聊
void createGroupChatSessionAsync(const QList<QString>& userIdList);
//获取会话成员列表
void getMemberListAsync(const QString& chatSessionId);
QList<UserInfo>* getMemberList(const QString& chatSessionId);
void resetMemberList(const QString& chatSessionId, const QList<bite_im::UserInfo>& memberList);
//搜索用户
void searchUserAsync(const QString& searchKey);
QList<UserInfo>* getSearchUserResult();
void resetSearchUserResult(const QList<bite_im::UserInfo>& userList);
//搜索历史消息
void searchMessageAsync(const QString& searchKey);
void searchMessageByTimeAsync(const QDateTime& begTime, const QDateTime& endTime);
QList<Message>* getSearchMessageReuslt();
void resetSearchMessageResult(const QList<bite_im::MessageInfo>& msgList);
//登录注册
void userLoginAsync(const QString& username, const QString& password);
void resetLoginSessionId(const QString& loginSessionId);
void userRegisterAsync(const QString& username, const QString& password);
void phoneLoginAsync(const QString& phone, const QString& verifyCode);
void phoneRegisterAsync(const QString& phone, const QString& verifyCode);
//获取单个文件
void getSingleFileAsync(const QString& fileId);
//语音转文字
void speechConvertTextAsync(const QString& fileId, const QByteArray& content);
///////////////////////////////////////////////////////////////////////////////////
///辅助函数
///////////////////////////////////////////////////////////////////////////////
//根据会话id查询会话信息
ChatSessionInfo* findChatSessionById(const QString& chatSessionId);
//根据用户ID查询会话信息
ChatSessionInfo* findChatSessionByUserId(const QString& userId);
//把指定的会话信息,放到列表头部
void topCurrentChatSessionId(const ChatSessionInfo& chatSessionInfo);
//根据用户id查询好友信息
UserInfo* findFriendById(const QString& userId);
//设置/获取当前选中的会话
void setCurrentChatSessionId(const QString& chatSessionId);
const QString& getCurrentSessionId();
//添加消息到DataCenter中
void addMessage(const Message& message);
signals:
//自定义信号
void getMyselfDone();
void getFriendListDone();
void getChatSessionListDone();
void getApplyListDone();
void getRecentMessageListDone(const QString& chatSessionId);
void getRecentMessageListDoneNoUI(const QString& chatSessionId);
void sendMessageDone(MessageType messageType, const QByteArray& content, const QString& extraInfo);
void updateLastMessage(const QString& chatSessionId);
void receiveMessageDone(const Message& lastMessage);
void changeNicknameDone();
void changeDescriptionDone();
void getVerifyCodeDone(bool ok);
void changePhoneDone();
void changeAvatarDone();
void deleteFriendDone();
void clearCurrentSession();
void addFriendApplyDone();
void receiveFriendApplyDone();
void acceptFriendApplyDone();
void rejectFriendApplyDone();
void receiveFriendProcessDone(const QString& nickname, bool agree);
void createGroupChatSessionDone();
void receiveSessionCreateDone();
void getMemberListDone(const QString& chatSessionId);
void searchUserDone();
void searchMessageDone();
void userLoginDone(bool ok, const QString& reason);
void userRegisterDone(bool ok, const QString& reason);
void phoneLoginDone(bool ok, const QString& reason);
void phoneRegisterDone(bool ok, const QString& reason);
void getSingleFileDone(const QString& fileId, const QByteArray& fileContent);
void speechConvertTextDone(const QString& fileId, const QString& text);
};
} //end namespace model

View File

@ -0,0 +1,132 @@
#pragma once
#include <QObject>
#include <QNetworkAccessManager>
#include <QWebSocket>
#include <QProtobufSerializer>
#include <qnetworkreply.h>
#include <QUuid>
#include "../model/data.h"
//此处为了避免“循环包含”的问题,就需要使用前置声明的方法
// 代替包含头文件
namespace model {
class DataCenter;
} //end namespace model
class model::DataCenter;
namespace network {
class NetClient : public QObject
{
Q_OBJECT
private:
//定义重要的常量ip暂时使用本地的回环ip端口号暂定的8000和8001
const QString HTTP_URL = "http://127.0.0.1:8000";
const QString WEBSOCKET_URL = "ws://127.0.0.1:8001/ws";
public:
NetClient(model::DataCenter* dataCenter);
//验证网络的联通性
void ping();
//初始化websocket
void initWebSocket();
//针对websocket的处理
void handleWsResponse(const bite_im::NotifyMessage& notifyMessage);
void handleWsMessage(const model::Message& message);
void handleWsRemoveFriend(const QString& userId);
void handleWsAddfriendApply(const model::UserInfo& userInfo);
void handleWsAddFriendProcess(const model::UserInfo& userInfo, bool agree);
void handleWsSessionCreate(const model::ChatSessionInfo& chatSessionInfo);
//发送身份认证请求
void sendAuth();
//生成请求的Id
static QString makeRequestId();
//封装发送请求的逻辑
QNetworkReply* sendHttpRequest(const QString& apiPath, const QByteArray& body);
//封装处理响应的逻辑判定HTTP正确性反序列化判断业务的正确性
//由于不同的api返回的pb对象结构不同为了让一个函数能够处理多种不同的类型需要使用模板
//后面两个是输出型参数,用于表示这次的操作是成功还是失败
template <typename T>
std::shared_ptr<T> handleHttpResponse(QNetworkReply* httpResp, bool* ok, QString* reason) {
//判定HTTP层面上
if (httpResp->error() != QNetworkReply::NoError) {
*ok = false;
*reason = httpResp->errorString();
httpResp->deleteLater();
return std::shared_ptr<T>();
}
//说明并没有出错, 那就获取到响应的body
QByteArray respBody = httpResp->readAll();
//针对body反序列化
std::shared_ptr<T> respObj = std::make_shared<T>();
respObj->deserialize(&serializer, respBody);
//判定业务的结构是否正确
if (!respObj->success()) {
*ok = false;
*reason = respObj->errmsg();
httpResp->deleteLater();
return std::shared_ptr<T>();
}
//释放httpResp对象
httpResp->deleteLater();
*ok = true;
return respObj;
}
void getMyself(const QString& loginSessionId);
void getFriendList(const QString& loginSessionId);
void getChatSessionList(const QString& loginSessionId);
void getApplyList(const QString& loginSessionId);
void getRecentMessageList(const QString& loginSessionId, const QString& chatSessionId, bool updateUI);
void sendMessage(const QString& loginSessionId, const QString& chatSessionId, model::MessageType messageType,
const QByteArray& content, const QString& extraInfo);
void receiveMessage(const QString& chatSessionId);
void changeNickname(const QString& loginSessionId, const QString& nickname);
void changeDescription(const QString& loginSessionId, const QString& desc);
void getVerifyCode(const QString& email);
void changeEmail(const QString& loginSessionId, const QString& email, const QString& verifyCodeId, const QString& verifyCode);
void changeAvatar(const QString& loginSessionId, const QByteArray& avatar);
void deleteFriend(const QString& loginSessionId, const QString& userId);
void addFriendApply(const QString& loginSessionId, const QString& userId);
void acceptFriendApply(const QString& loginSessionId, const QString& userId);
void rejectFriendApply(const QString& loginSessionId, const QString& userId);
void createGroupChatSession(const QString& loginSessionId, const QList<QString>& userIdList);
void getMemberList(const QString& loginSessionId, const QString& chatSessionId);
void searchUser(const QString& loginSessionId, const QString& searchKey);
void searchMessage(const QString& loginSessionId, const QString& chatSessionId, const QString& searchKey);
void searchMessageByTime(const QString& loginSessionId, const QString& chatSessionId, const QDateTime& begTime, const QDateTime& endTime);
void userLogin(const QString& username, const QString& password);
void userRegister(const QString& username, const QString& password);
void phoneLogin(const QString& phone, const QString& verifyCodeId, const QString& verifyCode);
void phoneRegister(const QString& phone, const QString& verifyCodeId, const QString& verifyCode);
void getSingleFile(const QString& loginSessionId, const QString& fileId);
void speechConvertText(const QString& loginSessionId, const QString& fileId, const QByteArray& content);
private:
model::DataCenter* dataCenter;
//http客户端
QNetworkAccessManager httpClient;
//websocket客户端
QWebSocket websocketClient;
//序列化器
QProtobufSerializer serializer;
signals:
};
} //end namespace network

View File

@ -0,0 +1,40 @@
#pragma once
#include <QWidget>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include "loginwidget.h"
class PhoneLoginWidget : public QWidget
{
Q_OBJECT
public:
PhoneLoginWidget(QWidget *parent);
void sendVerifyCode();
void sendVerifyCodeDone(bool ok);
void clickSubmitBtn();
void phoneLoginDone(bool ok, const QString& reason);
void phoneRegisterDone(bool ok, const QString& reason);
void countDown();
void switchMode();
private:
QLineEdit* phoneEdit;
QPushButton* sendVerifyCodeBtn;
QLineEdit* verifyCodeEdit;
QLabel* titleLabel;
QPushButton* submitBtn;
QPushButton* switchModeBtn;
bool isLoginMode = true;
QString currentPhone = ""; // 记录是使用哪个手机号发送的验证码
QTimer* timer;
int leftTime = 30;
};

View File

@ -0,0 +1,68 @@
#pragma once
#include <QDialog>
#include <QWidget>
#include <QLabel>
#include <qlineedit.h>
#include <QPushButton>
#include <QCursor>
#include <QGridLayout>
#include <QTimer>
#include <QFileDialog>
#include "debug.h"
#include "toast.h"
class SelfInfoWidget : public QDialog
{
Q_OBJECT
public:
SelfInfoWidget(QWidget *parent);
//~SelfInfoWidget();
void initSingnalSlots();
void clickNameSubmitBtn();
void clickNameSubmitBtnDone();
void clickDescSubmitBtn();
void clickDescSubmitBtnDone();
void clickGetVerifyCodeBtn();
void clickPhoneSubmitBtn();
void clickPhoneSubmitBtnDone();
void clickAvatarBtn();
void clickAvatarBtnDone();
private:
QGridLayout* layout;
QPushButton* avatarBtn;
QLabel* idTag; //序号标签
QLabel* idLabel; //序号
QLabel* nameTag;//昵称标签
QLabel* nameLabel;//名字
QLineEdit* nameEdit;//编辑昵称
QPushButton* nameModifyBtn;//修改昵称
QPushButton* nameSubmitBtn;//提交修改
QLabel* descTag;//签名标签
QLabel* descLabel;//签名
QLineEdit* descEdit;//编辑签名
QPushButton* descModifyBtn;//修改签名
QPushButton* descSubmitBtn;//提交修改
QLabel* phoneTag;//电话标签
QLabel* phoneLabel;//电话号码
QLineEdit* phoneEdit;//编辑电话
QPushButton* phoneModifyBtn;//修改电话
QPushButton* phoneSubmitBtn;//提交修改
QLabel* verifyCodeTag;//显示验证码
QLineEdit* verifyCodeEdit;//输入验证码
QPushButton* getVerifyCodeBtn;//获取验证码按钮
//要修改新的手机号码
QString emailToChange;
int leftTime = 30;
};

View File

@ -0,0 +1,51 @@
#pragma once
#include <QDialog>
#include <QApplication>
#include <QMainWindow>
#include <QPushButton>
#include <QWidget>
#include <QLabel>
#include <QVBoxLayout>
#include <QFontMetrics>
#include <QMessageBox>
#include "debug.h"
#include "model/data.h"
#include "choosefrienddialog.h"
using namespace model;
///////////////////////////////////////
///表示 一个头像 + 一个名字的组合控件
///////////////////////////////////////
class AvatarItem : public QWidget {
public:
AvatarItem(const QIcon& avatar, const QString& name);
QPushButton* getAvatar() {
return avatarBtn;
}
private:
QPushButton* avatarBtn;
QLabel* nameLabel;
};
///////////////////////////////////////
///表示“单聊会话详情”窗口
///////////////////////////////////////
class SessionDetailWidget : public QDialog
{
Q_OBJECT
public:
SessionDetailWidget(QWidget *parent, const UserInfo& userInfo);
void clickDeleteFriendBtn();
private:
UserInfo userInfo;
QPushButton* deleteFriendBtn;
};

View File

@ -0,0 +1,141 @@
#ifndef SESSIONFRIENDAREA_H
#define SESSIONFRIENDAREA_H
#include <QWidget>
#include <QScrollArea>
#include <QScrollBar>
#include <QVBoxLayout>
#include <QPushButton>
#include <QGridLayout>
#include <QLabel>
#include <QIcon>
#include <QString>
// #include <iostream>
#include <QStyleOption>
#include <QPainter>
#include <QEnterEvent>
#include "model/data.h"
#include <QLabel>
////////////////////////////////////////
/// 滚动区域的Item的类型
////////////////////////////////////////
enum ItemType{
SessionItemType,
FriendItemType,
ApplyItemType
};
////////////////////////////////////////
/// 整个滚动区域的实现
////////////////////////////////////////
class SessionFriendArea : public QScrollArea
{
Q_OBJECT
public:
explicit SessionFriendArea(QWidget *parent = nullptr);
//清空区域内所有的Item
void clear();
//添加一个Item
void addItem(ItemType itemType, const QString& id, const QIcon& avatar, const QString& name, const QString& text);
//选中某一个指定的item通过index下标来访问item
void clickItem(int index);
private:
//后续向container中的layout添加元素,就会有QScrollArea的滚动
QWidget* container;
signals:
};
////////////////////////////////////////
/// 滚动区域的Item的实现
////////////////////////////////////////
class SessionFriendItem : public QWidget {
//可以使当前的类使用信号与槽相关的操作
Q_OBJECT
public:
SessionFriendItem(QWidget* owner, const QIcon& avatar, const QString& name, const QString& text);
//通过显式绘制控件的基础样式解决了自定义控件因未正确处理Qt样式表机制导致的QSS不生效问题
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void enterEvent(QEnterEvent* event) override;
void leaveEvent(QEvent* event) override;
void select();
//Active实现Item被点击后的业务逻辑
virtual void active();
private:
//owner 就是指向了 SessionFriendArea
QWidget* owner;
//表示当前的Item是否是选中的状态(选中时其背景色会有所不同)
bool selected = false;
protected:
QLabel* messageLabel;
};
////////////////////////////////////////
/// 会话Item的实现
////////////////////////////////////////
class SessionItem : public SessionFriendItem {
Q_OBJECT
public:
SessionItem(QWidget* owner, const QString& chatSessionId, const QIcon& avatar,
const QString& name, const QString& lastmessage);
void active() override;
void updateLastMessage(const QString& chatSessionId);
private:
//当前会话Id
QString chatSessionId;
//最后一条消息的文本预览
QString text;
};
////////////////////////////////////////
/// 好友Item的实现
////////////////////////////////////////
class FriendItem : public SessionFriendItem {
Q_OBJECT
public:
FriendItem(QWidget* owner, const QString& userId, const QIcon& avatar, const QString& name, const QString& description);
void active() override;
private:
// 好友的用户Id
QString userId;
};
////////////////////////////////////////
/// 好友申请Item的实现
////////////////////////////////////////
class ApplyItem : public SessionFriendItem {
Q_OBJECT
public:
ApplyItem(QWidget* owner, const QString& userId, const QIcon& avatar, const QString& name);
void acceptFriendApply();
void rejectFriendApply();
void active() override;
private:
//好友申请Id
QString userId;
};
#endif // SESSIONFRIENDAREA_H

View File

@ -0,0 +1,59 @@
#ifndef SOUNDRECORDER_H
#define SOUNDRECORDER_H
#include <QObject>
#include <QStandardPaths>
#include <QFile>
#include <QAudioSource>
#include <QAudioSink>
#include <QMediaDevices>
class SoundRecorder : public QObject
{
Q_OBJECT
public:
const QString RECORD_PATH = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/sound/tmpRecord.pcm";
const QString PLAY_PATH = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/sound/tmpPlay.pcm";
public:
static SoundRecorder* getInstance();
/////////////////////////////////////////////////
/// 录制语音语音
/////////////////////////////////////////////////
// 开始录制
void startRecord();
// 停止录制
void stopRecord();
private:
static SoundRecorder* instance;
explicit SoundRecorder(QObject *parent = nullptr);
QFile soundFile;
QAudioSource* audioSource;
/////////////////////////////////////////////////
/// 播放语音
/////////////////////////////////////////////////
public:
// 开始播放
void startPlay(const QByteArray& content);
// 停止播放
void stopPlay();
private:
QAudioSink *audioSink;
QMediaDevices *outputDevices;
QAudioDevice outputDevice;
QFile inputFile;
signals:
// 录制完毕后发送这个信号
void soundRecordDone(const QString& path);
// 播放完毕发送这个信号
void soundPlayDone();
};
#endif // SOUNDRECORDER_H

View File

@ -0,0 +1,22 @@
#pragma once
#include <QDialog>
#include <QWidget>
#include <QApplication>
#include <QScreen>
#include <QVBoxLayout>
#include <QLabel>
#include <QTimer>
class Toast : public QDialog
{
Q_OBJECT
public:
//此处并不需要指定父窗口,全局通知的父窗口就是桌面
Toast(const QString& text);
//并不需要手动的来new这个对象而是通过showMessage来弹出
static void showMessage(const QString& text);
};

View File

@ -0,0 +1,46 @@
#pragma once
#include <QDialog>
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QGridLayout>
#include <QMessageBox>
#include "mainwidget.h"
#include "model/data.h"
#include "model/datacenter.h"
//using model::Message;
using namespace model;
class UserInfoWidget : public QDialog
{
Q_OBJECT
public:
UserInfoWidget(const UserInfo& userInfo, QWidget *parent);
void initSingleSlot();
void clickDeleteFriendBtn();
void clickApplyBtn();
private:
////保存对应的Message对象暂时先放在这里
//Message message;
const UserInfo& userInfo;
QPushButton* avatarBtn;
QLabel* idTag;
QLabel* idLabel;
QLabel* nameTag;
QLabel* nameLabel;
QLabel* phoneTag;
QLabel* phoneLabel;
QPushButton* applyBtn;
QPushButton* sendMessageBtn;
QPushButton* deleteFriendBtn;
};

View File

@ -0,0 +1,32 @@
#pragma once
#include <QWidget>
#include <QRandomGenerator>
class VerifyCodeWidget : public QWidget
{
Q_OBJECT
public:
explicit VerifyCodeWidget(QWidget *parent = nullptr);
//通过这个函数生成随机的验证码字符串
QString generateVerifyCode();
//刷新并把当前验证码显示到界面上
void refreshVerifyCode();
//检验验证码是否匹配
bool checkVerifyCode(const QString& verifyCode);
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
private:
//随机数生成器
QRandomGenerator randomGenerator;
//保存验证码的值
QString verifyCode = "";
signals:
};