Qt 软件更新插件
在平时的发布版中,我们不能确定我们的软件始终都是一个版本的时候,我们为了更好,更合理的偷懒,我们就需要为我们的软件加上在线更新模块的功能。以便于我们的软件出了bug时不用慌乱,直接恢复上一个版本将损失降到最低,然后再分析、排查错误源并修复。
文章目录:
0x01功能概述
我们将发布版本发布到公司服务器上,然后使用我们App的客户便可以不重新安装软件包来达到使用最新的软件了。直接通过内部更新模块功能一键傻瓜式操作即可。
0x02实现的基本思路
/** 实现的基本思路介绍(通过伪代码的方式介绍): */
/** 启动界面 */
App->start();
/** 更新服务器最新的json文件 */
updateObj.requestStartUpdate(http://xxx/update.json);
/** 实体软件更新添加代码 */
if(当前版本号 != 服务器->最新版本号){
/** 以异步的方式启动更新软件 */
system("start ./update.exe");
/**
这里我们必须要关闭当前运行的软件,因为我们更新的 *.dll,*.exe,*.configue 文件需要替换或删除操作。
当文件的读写互斥时,我们的更新功能可能因为这个原因失败。
*/
App->close();
}
return App->exec();
/** 更新软件的实现伪代码: */
if(!uobj.解析json文件){
emit updateError("配置文件缺损");
return;
}
/**
开始下载服务器上的最新的资源。
为了本地执行环境的安全,将从服务器下载下来的资源文件集存放再temp文件夹下,
避免中途某一个文件下载错误而造成本地运行环境的污染。
在所有的资源从服务器下载完成后一次性替换掉本地的运行环境。
*/
uobj.requestStartUpdate(uobj.最后一个更新包数据);
0x03准备环境
-
0x031Json配置文件
{
"version":[
{
"num":"0.0.0.1",
"utime":"2020/07/16 16:28",
"uinfo":[
{"uinfoText":"更新xxx功能"},
{"uinfoText":"修复xxx功能Bug"}
],
"attribute":[
{
"url":"http://39.98.182.126/oms_pack_128/OMS.exe",
"target":"./OMS.exe"
},
{
"url":"http://39.98.182.126/oms_pack_128/OMSExcelEngine.dll",
"target":"./OMSExcelEngine.dll"
},
{
"url":"http://39.98.182.126/oms_pack_128/OMSWordEngine.dll",
"target":"./OMSWordEngine.dll"
},
{
"url":"http://39.98.182.126/oms_pack_128/qtBackstageOperationalManagementSystem.dll",
"target":"./qtBackstageOperationalManagementSystem.dll"
}
]
},
{
"num":"0.0.0.2",
"utime":"2020/07/18 10:28",
"uinfo":[
{"uinfoText":"更新xxx功能"},
{"uinfoText":"修复xxx功能Bug"}
],
"attribute":[
{
"url":"http://39.98.182.126/oms_pack_128/OMS.exe",
"target":"D:/OMS.exe"
},
{
"url":"http://39.98.182.126/oms_pack_128/OMSExcelEngine.dll",
"target":"D:/OMSExcelEngine.dll"
},
{
"url":"http://39.98.182.126/oms_pack_128/OMSWordEngine.dll",
"target":"D:/OMSWordEngine.dll"
},
{
"url":"http://39.98.182.126/oms_pack_128/qtBackstageOperationalManagementSystem.dll",
"target":"E:/qtBackstageOperationalManagementSystem.dll"
}
]
}
]
}
0x04实战代码
/** dll更新库代码 */
/**
\brief The header file precompiles the macro to prevent repeated inclusion.
Refer to document name for specific definition form.
\eg _<ProjectName>_<ModuleNmae>_<FileName>_H_.
*/
#ifndef _SOFTWAREUPDATEMODULE_SOFTWAREUPDATEMODULE_H_
#define _SOFTWAREUPDATEMODULE_SOFTWAREUPDATEMODULE_H_
#include "softwareupdatemodule_global.h"
/** include Qt header files. */
#include <QFile>
#include <QList>
#include <QVector>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
/**
\brief This is an update item data, which is composed of the URL address of the
server and the destination (local) storage address.
\author Mr.sun
*/
struct updateAttribute
{
/** This is the address on the server. */
QString m_url;
/** This is the address to replace or save to the local resource. */
QString m_target;
};
/**
\brief This is an updated version of the package. It may have the following styles:
Current version: xxx.xxx.xxx .xxx
Update time: XXX/XX/XX XX:XX
Update information:
1. Online XXX function.
2. Fix xxxbug.
\author Mr.sun
\see struct updateAttribute
*/
struct updateVersionPack
{
/** Version number. */
QString m_versionNum;
/** Update time. */
QString m_updateTime;
/** Update description. */
QVector<QString> m_updateInfoArray;
/** Update item data array. */
QList<updateAttribute> m_updateItemList;
};
/**
\brief This is a software update class. It only provides software update related
operations, but does not include data parsing or data encapsulation.
\author Mr.sun
\sa class QNetworkAccessManager,calss QNetworkReply,calss QFile
*/
class SOFTWAREUPDATEMODULE_EXPORT softwareUpdateModule :
public QObject
{
Q_OBJECT
public:
softwareUpdateModule();
signals:
/**
\brief Update progress signal.
\param Current update subscript.
\param Maximum number of updates.
\param Current Downloads.
\param Current maximum Downloads.
\author Mr.sun
*/
void updateProgress(qint64, qint64, qint64, qint64);
/**
\brief When an error is encountered during the update, this signal is sent
with the error message.
\param Description of the error message.
\author Mr.sun
*/
void updateError(QString);
/**
\brief This signal is triggered when the update process is all successful.
\author Mr.sun
*/
void updateSucceed();
public:
/**
\brief The request update module will download and replace the input version packet data.
\param Version package.
\author Mr.sun
\sa updateProgress(qint64, qint64, qint64, qint64),\
updateError(QString),updateSucceed()
*/
void requestStartUpdate(const updateVersionPack&);
void requestStartUpdate(const updateAttribute&);
protected:
/**
\brief If the file is newly added, it will be copied. Otherwise, when the file
exists, the original file will be deleted and the copy operation will
be carried out.
\return Return non-0 value exception.
\author Mr.sun
*/
int replaceTargetResource();
/**
\brief Request to update the next update item, automatically ended.
\author Mr.sun
*/
void requestNextUpdate();
protected slots:
void doReadyRead();
void doFinished();
void doDownloadProgress(qint64, qint64);
void doDownError(QNetworkReply::NetworkError);
protected:
/** The data that needs to be operated is passed in from outside. */
updateVersionPack m_pack;
QNetworkAccessManager m_netAccessManager;
QNetworkReply* m_reply;
QFile m_file;
QString m_tempDir;
int m_updateIndex;
};
#endif // !_SOFTWAREUPDATEMODULE_SOFTWAREUPDATEMODULE_H_
/** dll更新库cpp */
#include "softwareUpdateModule.h"
#include <QDir>
#include <QFileInfo>
softwareUpdateModule::softwareUpdateModule()
{
m_pack = {};
m_reply = nullptr;
m_file.setFileName("");
m_updateIndex = 0;
}
void softwareUpdateModule::requestStartUpdate(const updateVersionPack& pack)
{
m_updateIndex = 0;
m_pack = pack;
requestNextUpdate();
}
void softwareUpdateModule::requestStartUpdate(const updateAttribute& item)
{
updateVersionPack titem;
titem.m_updateItemList.push_back(item);
requestStartUpdate(titem);
}
int softwareUpdateModule::replaceTargetResource()
{
QString del_file = QString(m_tempDir);
QDir dir;
dir.setPath(del_file);
for (auto& it : m_pack.m_updateItemList) {
QFileInfo tfile = QFileInfo(it.m_target);
QString sourceDir = QString("%1/%2.%3")
.arg(m_tempDir).arg(tfile.fileName()).arg(tfile.suffix());
sourceDir.replace("\\", "/");
it.m_target.replace("\\", "/");
if (!tfile.dir().exists()) {
tfile.dir().mkpath(tfile.dir().path());
}
if (tfile.exists()) {
if (!tfile.dir().remove(tfile.fileName())) {
goto RM_TEMP_RESOUREC;
}
}
if (!QFile::copy(sourceDir, it.m_target)) {
goto RM_TEMP_RESOUREC;
}
}
dir.removeRecursively();
return 0;
RM_TEMP_RESOUREC:
dir.removeRecursively();
return 1;
}
void softwareUpdateModule::requestNextUpdate()
{
if (m_updateIndex + 1 <= m_pack.m_updateItemList.size()) {
QNetworkRequest request;
request.setUrl(QUrl(m_pack.m_updateItemList.at(m_updateIndex).m_url));
m_reply = m_netAccessManager.get(request);
connect(m_reply, SIGNAL(readyRead()), this, SLOT(doReadyRead()));
connect(m_reply, SIGNAL(finished()), this, SLOT(doFinished()));
connect(m_reply, SIGNAL(downloadProgress(qint64,qint64)),
this, SLOT(doDownloadProgress(qint64,qint64)));
connect(m_reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error),
this, &softwareUpdateModule::doDownError);
QFileInfo fileInfo = QFileInfo(m_pack.m_updateItemList[m_updateIndex].m_target);
if (!QDir().exists(m_tempDir)) {
QDir().mkpath(m_tempDir);
}
m_file.setFileName(QString("%1/%2.%3").
arg(m_tempDir). // temp dir
arg(fileInfo.fileName()). // file name
arg(fileInfo.suffix())); // file suffix
if (!m_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
if (!m_file.open(QIODevice::WriteOnly)) {
// send update not
emit updateError(QString::fromLocal8Bit("没有文件系统操作权限"));
return;
}
}
}
else if (replaceTargetResource() == 0) {
// send update ok
emit updateSucceed();
}
else {
// send update error
emit updateError(QString::fromLocal8Bit("本次更新失败,可能是没有文件的操作权限!"));
}
}
void softwareUpdateModule::doFinished()
{
m_file.close();
++m_updateIndex;
requestNextUpdate();
}
void softwareUpdateModule::doDownloadProgress(qint64 recv_total, qint64 all_total)
{
emit updateProgress(m_updateIndex,
m_pack.m_updateItemList.size(), recv_total, all_total);
}
void softwareUpdateModule::doDownError(QNetworkReply::NetworkError code)
{
emit updateError(QString::number(code));
}
void softwareUpdateModule::doReadyRead()
{
if (m_reply) {
while (!m_reply->atEnd())
{
QByteArray ba = m_reply->readAll();
m_file.write(ba);
}
}
}
/** 测试代码ui */
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>testSoftwareUpdateModuleClass</class>
<widget class="QMainWindow" name="testSoftwareUpdateModuleClass">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>400</height>
</rect>
</property>
<property name="windowTitle">
<string>testSoftwareUpdateModule</string>
</property>
<widget class="QWidget" name="centralWidget">
<widget class="QPushButton" name="pushButton">
<property name="geometry">
<rect>
<x>430</x>
<y>100</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>更 新</string>
</property>
</widget>
<widget class="QComboBox" name="comboBox">
<property name="geometry">
<rect>
<x>100</x>
<y>100</y>
<width>311</width>
<height>21</height>
</rect>
</property>
<item>
<property name="text">
<string>json</string>
</property>
</item>
<item>
<property name="text">
<string>version_pack</string>
</property>
</item>
</widget>
<widget class="QTextBrowser" name="textBrowser">
<property name="geometry">
<rect>
<x>100</x>
<y>140</y>
<width>401</width>
<height>171</height>
</rect>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>23</height>
</rect>
</property>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>
<include location="testSoftwareUpdateModule.qrc"/>
</resources>
<connections/>
</ui>
/** ui_testSoftwareUpdateModule.h */
/********************************************************************************
** Form generated from reading UI file 'testSoftwareUpdateModule.ui'
**
** Created by: Qt User Interface Compiler version 5.12.8
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_TESTSOFTWAREUPDATEMODULE_H
#define UI_TESTSOFTWAREUPDATEMODULE_H
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QTextBrowser>
#include <QtWidgets/QToolBar>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class Ui_testSoftwareUpdateModuleClass
{
public:
QWidget *centralWidget;
QPushButton *pushButton;
QComboBox *comboBox;
QTextBrowser *textBrowser;
QMenuBar *menuBar;
QToolBar *mainToolBar;
QStatusBar *statusBar;
void setupUi(QMainWindow *testSoftwareUpdateModuleClass)
{
if (testSoftwareUpdateModuleClass->objectName().isEmpty())
testSoftwareUpdateModuleClass->setObjectName(QString::fromUtf8("testSoftwareUpdateModuleClass"));
testSoftwareUpdateModuleClass->resize(600, 400);
centralWidget = new QWidget(testSoftwareUpdateModuleClass);
centralWidget->setObjectName(QString::fromUtf8("centralWidget"));
pushButton = new QPushButton(centralWidget);
pushButton->setObjectName(QString::fromUtf8("pushButton"));
pushButton->setGeometry(QRect(430, 100, 75, 23));
comboBox = new QComboBox(centralWidget);
comboBox->addItem(QString());
comboBox->addItem(QString());
comboBox->setObjectName(QString::fromUtf8("comboBox"));
comboBox->setGeometry(QRect(100, 100, 311, 21));
textBrowser = new QTextBrowser(centralWidget);
textBrowser->setObjectName(QString::fromUtf8("textBrowser"));
textBrowser->setGeometry(QRect(100, 140, 401, 171));
testSoftwareUpdateModuleClass->setCentralWidget(centralWidget);
menuBar = new QMenuBar(testSoftwareUpdateModuleClass);
menuBar->setObjectName(QString::fromUtf8("menuBar"));
menuBar->setGeometry(QRect(0, 0, 600, 23));
testSoftwareUpdateModuleClass->setMenuBar(menuBar);
mainToolBar = new QToolBar(testSoftwareUpdateModuleClass);
mainToolBar->setObjectName(QString::fromUtf8("mainToolBar"));
testSoftwareUpdateModuleClass->addToolBar(Qt::TopToolBarArea, mainToolBar);
statusBar = new QStatusBar(testSoftwareUpdateModuleClass);
statusBar->setObjectName(QString::fromUtf8("statusBar"));
testSoftwareUpdateModuleClass->setStatusBar(statusBar);
retranslateUi(testSoftwareUpdateModuleClass);
QMetaObject::connectSlotsByName(testSoftwareUpdateModuleClass);
} // setupUi
void retranslateUi(QMainWindow *testSoftwareUpdateModuleClass)
{
testSoftwareUpdateModuleClass->setWindowTitle(QApplication::translate("testSoftwareUpdateModuleClass", "testSoftwareUpdateModule", nullptr));
pushButton->setText(QApplication::translate("testSoftwareUpdateModuleClass", "\346\233\264 \346\226\260", nullptr));
comboBox->setItemText(0, QApplication::translate("testSoftwareUpdateModuleClass", "json", nullptr));
comboBox->setItemText(1, QApplication::translate("testSoftwareUpdateModuleClass", "version_pack", nullptr));
} // retranslateUi
};
namespace Ui {
class testSoftwareUpdateModuleClass: public Ui_testSoftwareUpdateModuleClass {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_TESTSOFTWAREUPDATEMODULE_H
/** 测试代码:testSoftwareUpdateModule.h */
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_testSoftwareUpdateModule.h"
#include <softwareUpdateModule.h>
class testSoftwareUpdateModule : public QMainWindow
{
Q_OBJECT
public:
testSoftwareUpdateModule(QWidget *parent = Q_NULLPTR);
protected slots:
void doReadyRead();
void doUpdateError(QString);
void doUpdateProgress(qint64, qint64, qint64, qint64);
void on_pushButton_clicked();
private:
softwareUpdateModule m_updateObj;
Ui::testSoftwareUpdateModuleClass ui;
};
/** 测试代码:testSoftwareUpdateModule.cpp */
#include "testSoftwareUpdateModule.h"
testSoftwareUpdateModule::testSoftwareUpdateModule(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
connect(&m_updateObj, SIGNAL(updateProgress(qint64, qint64, qint64, qint64)),
this, SLOT(doUpdateProgress(qint64, qint64, qint64, qint64)));
connect(&m_updateObj, SIGNAL(updateSucceed()), this, SLOT(doReadyRead()));
connect(&m_updateObj, SIGNAL(updateError(QString)), this, SLOT(doUpdateError(QString)));
}
void testSoftwareUpdateModule::doReadyRead()
{
ui.textBrowser->append(QString::fromLocal8Bit("更新成功"));
}
void testSoftwareUpdateModule::doUpdateError(QString errorString)
{
ui.textBrowser->append(QString("eroor %1").arg(errorString));
}
void testSoftwareUpdateModule::doUpdateProgress(qint64 qcur,
qint64 qmax,
qint64 recv_total,
qint64 all_total)
{
QString info = QString::fromLocal8Bit("正在下载 %1/%2 下载量: %3/%4")
.arg(QString::number(qcur)).arg(QString::number(qmax))
.arg(QString::number(recv_total)).arg(QString::number(all_total));
ui.textBrowser->append(info);
}
void testSoftwareUpdateModule::on_pushButton_clicked()
{
if (ui.comboBox->currentText() == "json") {
updateAttribute item{"http://39.98.182.126/update.json","./update.json"};
m_updateObj.requestStartUpdate(item);
return;
}
else {
updateVersionPack pack;
pack.m_updateItemList.push_back(
{ "http://39.98.182.126/oms_pack_128/OMS.exe","./OMS.exe" });
pack.m_updateItemList.push_back(
{ "http://39.98.182.126/oms_pack_128/OMSExcelEngine.dll","./OMSExcelEngine.dll" });
m_updateObj.requestStartUpdate(pack);
}
}