Qt之軟件更新


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM