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