设计目的
前后端交互系统中,创建并使用数据核心类的目的就是让该类作为客户端的数据中心,也就是说其负责管理客户端的所有数据与服务器的网络通信。
数据持久化
初始化数据文件
该函数设计的目的就是用于检查所需要的文件和目录是否存在,如果不存在创建,确保客户端和服务端的数据都是存储在同一位置上的。
cpp
void DataCenter::initDataFile()
{
QString basePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QString filePath = basePath + "/ChatClient.json";
QDir dir;
if (!dir.exists(basePath)) {
dir.mkpath(basePath);
}
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
LOG() << "打开文件失败!" << file.errorString();
return;
}
QString data = "{\n\n}";
file.write(data.toUtf8());
file.close();
}
加载数据文件
也就是从本地JSON文件中读取数据,将其解析为可用的对象,然后填充到内存中的数据结构中。
cpp
void DataCenter::loadDataFile()
{
QString filePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/ChatClient.json";
QFileInfo fileInfo(filePath);
if (!fileInfo.exists()) {
initDataFile();
}
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
LOG() << "打开文件失败!" << file.errorString();
return;
}
QJsonDocument jsonDoc = QJsonDocument::fromJson(file.readAll());
if (jsonDoc.isNull()) {
LOG() << "解析JSON文件失败! JSON文件格式错误";
file.close();
return;
}
QJsonObject jsonObj = jsonDoc.object();
this->loginSessionId = jsonObj["loginSessionId"].toString();
this->unreadMessageCount->clear();
QJsonObject jsonUnread = jsonObj["unread"].toObject();
for (auto it = jsonUnread.begin(); it != jsonUnread.end(); ++it) {
this->unreadMessageCount->insert(it.key(), it.value().toInt());
}
file.close();
}
保存数据文件
设计该方法的目的就是将当前的数据状态写回到JSON文件,从而确保数据在应用程序重启后依然可以使用。
cpp
void DataCenter::saveDataFile()
{
QString filePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/ChatClient.json";
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
LOG() << "文件打开失败" << file.errorString();
return;
}
QJsonObject jsonObj;
jsonObj["loginSessionId"] = loginSessionId;
QJsonObject jsonUnread;
for (auto it = unreadMessageCount->begin(); it != unreadMessageCount->end(); ++it) {
jsonUnread[it.key()] = it.value();
}
jsonObj["unread"] = jsonUnread;
QJsonDocument jsonDoc(jsonObj);
QString s = jsonDoc.toJson();
file.write(s.toUtf8());
file.close();
}
内存数据管理
用户信息管理
维护当前登录用户的信息,也就是用来保存当前登录用户的信息、好友列表等相关信息
cpp
UserInfo *DataCenter::getMyself()
{
return myself;
}
void DataCenter::resetMyself(std::shared_ptr<bite_im::GetUserInfoRsp> resp)
{
if (myself == nullptr) {
myself = new UserInfo();
}
const bite_im::UserInfo &userInfo = resp->userInfo();
myself->load(userInfo);
}
消息管理
负责管理最近的消息,聊天会话记录、未读信息等相关消息
cpp
// 在构造函数中初始化未读消息计数哈希表
DataCenter::DataCenter() : netClient(this)
{
unreadMessageCount = new QHash<QString, int>();
// 其他初始化操作
}
前后端通信
异步数据获取
通过netClient对象与服务器通信,使用异步方法来获取数据,避免阻塞线程,从而确保UI响应
cpp
void DataCenter::getMyselfAsync()
{
netClient.getMyself(loginSessionId);
}
数据同步
服务器接收到数据后,需要更新本地的数据
cpp
void DataCenter::resetMyself(std::shared_ptr<bite_im::GetUserInfoRsp> resp)
{
if (myself == nullptr) {
myself = new UserInfo();
}
const bite_im::UserInfo &userInfo = resp->userInfo();
myself->load(userInfo);
}
数据核心类与其他类的联系
前后端交互数据存储在DataCenter
数据核心类的作用就是在客户端应用程序 中集中管理和存储与前后端交互的相关数据。其是作为数据核心类存在的。
- 统一存储应用中所需要的数据,例如用户信息、好友列表、消息记录等,都集中存储在一个地方,方便访问和管理
- 通过集中管理保证数据一致性,避免不同模块之间的数据不一致的情况
- 数据共享,通过将需要使用的数据都存放在一个类中,从而供其他的对象对其数据进行调用
DataCenter和MainWindow类的关系
首先MainWindow就是应用程序的主界面,DataCenter是数据管理类,专门负责处理数据的存取与服务器通信。所以两者之间主要存在两种关系。
- **MainWindow是依赖于DataCenter。**因为主窗口需要从数据核心类中获取数据显示,就必须显示个人信息,就需要通过数据核心类来获取
- **DataCenter独立于MainWindow。**数据核心类是不与界面直接进行交互的,而是专门负责数据处理和存储。
其次从实例对应关系上分析,也就是从数据核心类实例后对象与其他部分的关系分析
- **DataCenter类设计成单例模式,**保证整个应用程序中只有一个DataCenter实例,这也是为了确保数据的一致性和共享性
- 从用户级别考虑,因为一个数据核心类中肯定是只可以存储一个用户的数据和信息,也就是说每个用户都会有自己的数据核心类和主窗口实例
- 多用户切换情况下,如果支持多用户的客户端应用程序,那么在多用户切换的情况下,需要清空核心数据类中存储的信息,然后重新加载新的数据
前后端交互过程中Qt应用层和原理层
应用层面
- **异步通信机制:**客户端发送请求后到服务器后,通常是不会阻塞等待服务器响应的,而是通过异步机制等待结果,所以应用层使用的是异步机制
- **信号与槽机制:**当服务器处理完成请求后并返回结果后,客户端的网络模块会发送一个信号,通知应用程序已经准备好
- **处理响应:**应用程序中预先连接了对应的槽函数,也就是在信号发出后,槽函数就会被调用,进行数据处理和更新页面
原理层面
- **事件驱动:**服务器的运行逻辑是依靠事件驱动模型实现的,也就是当有事件的时候,事件循环会触发相应的处理函数
- **异步I/O:**Qt的网络模块内部使用的是异步IO,当数据到达后,会自动触发信号,但是不会造成线程阻塞
- **线程安全:**信号和槽是可以跨线程工作的,确保不同线程下传递数据的安全性