先看效果图:
日常办公使用的桌面IM是基于Electron进行开发的,在日常使用中存在卡顿,进程暂用多以及弱网情况下加载缓慢等问题。因此萌生了能不能自己从零使用Qt重新构建跨平台的桌面客户端替代日常的使用呢?
1. 可行性
毕竟是自己个人业余项目,首先考察肯定是可行性,前面提到的要改造的客户端是基于Electron自然有网页版本,利用网页版可以拿到https请求的request以及response。而https的验证是通过浏览器中的cookie等信息。还有一个IM软件都有的长链接,使用Qt可以直接使用内置的websocket即可,官方也有相关的参考demo。这样接收消息推送的长链接有了,同时也可以通过https请求会话等信息,因此是可行的。
2. 技术选型
由于需要支持跨平台的原因,而原有客户端基于Electron虽然可以跨平台但是性能以及后续优化较难进行。因此选用了Qt进行开发,Qt5.15.2已经默认支持了cmake,所以也选用cmake进行构建。
3. 长连接
使用Qt的QWebSocket即可,在完成客户端登录后直接去open即可,具体的逻辑如下,
QNetworkRequest request(m_url);
request.setRawHeader("Pragma", "no-cache");
request.setRawHeader("Accept-Language", "zh,zh-CN;q=0.9,en;q=0.8,hu;q=0.7");
request.setRawHeader("Sec-WebSocket-Key", "t8ZjtY/6hV1FBto6iailxQ==");
request.setRawHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,"
"like Gecko) Chrome/89.0.4389.128 Safari/537.36");
request.setRawHeader("Upgrade", "websocket");
request.setRawHeader("Sec-WebSocket-Extensions", "permessage-deflate; client_max_window_bits");
request.setRawHeader("Cache-Control", "no-cache");
request.setRawHeader("Connection", "Upgrade");
request.setRawHeader("Sec-WebSocket-Version", "13");
m_webSocket.open(request);
具体的请求头部可以参考websocket协议的文章。
连接上websocket之后还需要发送心跳包,以维持当前客户端的登录状态。如果断开的连接需要重新连接
connect(&m_webSocket, &QWebSocket::disconnected, this, [&]() {
qDebug() << "disconnect";
qDebug() << "begin to reconnect";
QTimer::singleShot(ConstSpace::WebSocketsReconnectInterval, [&]() {
App->getSessService()->checkLoginSync(App->m_loginBaseInfo);
reconnect();
});
});
之后就是处理接收到不同的websocket包进行相应业务处理。当然其中还有一些websocket的握手认证则根据相应的接口实现来进行。
4. 最近会话列表的显示
会话列表的显示较为简单,使用QListView即可,当然也可以使用QListWidget如果数据量不多的话。由于获取最近会话接口是分页的,因此需要考虑滚动加载,而QListView是可以支持的,只需要实现下列逻辑即可
bool RecentListModel::canFetchMore(const QModelIndex& parent) const {
// 200条最近会话
if (m_recentSessInfoList.size() == 0)
return false;
return rowCount() < ConstSpace::kRecentSessListMaxVal;
}
void RecentListModel::fetchMore(const QModelIndex& parent) {
int remainder = ConstSpace::kRecentSessListMaxVal - rowCount();
int itemsToFetch = qMin(10, remainder);
if (itemsToFetch <= 0)
return;
emit onUiFetchMoreRecentSessList(m_recentSessInfoList.nextSeq, m_recentSessInfoList.nextHasStickied);
}
主要就是告诉listview在滚动在最底部的时候时候可以获取更多,以及在fetchMore获取相应的数据参入model即可。剩余的逻辑就是在会话更新是对列表进行重新排序,刷新,删除等操作。主要思路就是改变model中数据,并同时到listview即可。
5. 数据库存储
有了最近会话信息则需要处理最近会话信息的存储到数据库中,这里主要用到了sqlite。在程序启动时建立相应的表结构,在运行过程中对数据进行入口操作,在需要相应会话信息的时候从数据库中获取。访问数据库注意不能在多线程中访问,需要在单独的数据库线程进行访问。存储批量数据记得开启数据库事务。
6. 会话列表显示
采用自定义listwidget,中间每个消息是自定义的item。富文本的显示可以使用QTextEdit可以支持插入图片文字,html甚至markdown。还可以自定义textobject进行插入。功能还是很强大的。具体这里的逻辑较为复杂,也是一步一步完成,当前阶段我也是实现了纯文本以及图片的显示。还有较多类型的消息需要支持,如图文卡片消息,文件,语音,视频等。
7. 控制开发进度
虽然是自己一个人开发,但是开发流程上还是要规范化。git使用的是gitee的私有仓库,而项目管理自己使用的是jira进行项目管理,jira对于10人以下的团队是免费申请的。
以2周作为一个迭代,可以更好规划开发的进度以及优先级。
总结
兴趣是最好的驱动力。