Qt 实现软件自动更新

也可以参照本博主另外一篇Qt 实现软件自动更新

一、自动更新程序流程

  1. 加载本地配置文件获取获取保存的版本号
  2. 每次启动程序,首先从服务器请求最新的版本信息文件(包括版本号,压缩包下载地址,更新时间,更新说明)
  3. 解压下载的文件获取服务器版本号,与本地版本号进行比较,如果高于本地,就下载压缩文件
  4. 解压缩程序压缩包
  5. 更新本地版本号,启动压缩包程序,退出当前自动更新程序

二、代码实现

1. 加载配置文件(采用TinyXml解析xml文件)

TinyXml详细用法可参考:C++库(TinyXml)的安装和使用
XMl 文件内容

<?xml version="1.0" encoding="utf-8" ?>
<AutoUpdate>
    <Version>V4.0.0.2</Version>
    <Url>https://eval.zhihuiyunji.com/riskclient_update/</Url>
    <WindowIcon>:/AutoUpdate/Resources/RiskControlClient_24.png</WindowIcon>
    <RunExe>\RiskClient\RiskClient.exe</RunExe>
</AutoUpdate>

Configer 头文件

#pragma once
#include "tinyxml/tinyxml.h"
#include <iostream>
#include <string>
#include <memory>
#include <vector>

using namespace std;

class StaticClass
{
private:
	StaticClass();
	StaticClass(const StaticClass&);
	StaticClass& operator = (const StaticClass&);
};

class Configer : StaticClass
{
public:
	~Configer() {};
	static bool Load();
	static string FilePath();
	static string FilePath_Cfg();

	static bool LoadConfigFile();
	static bool SaveVersion(const char *version);
	static const string &GetVersion();

	static const string &GetWindowIcon();
	static const string &GetQurl();
	static const string &GetRunExe();

private:
	static string m_strVersion;
	static string m_strIcon;
	static string m_ptrQurl;
	static string m_ptrRunExe;
};

cpp 文件

#include "configer.h"
#include "windows.h"

string Configer::m_strVersion;
string Configer::m_strIcon;
string Configer::m_ptrQurl;
string Configer::m_ptrRunExe;

string Configer::FilePath()
{
	string strReturn;
	HMODULE hModule = GetModuleHandle(NULL);
	char pFileName[MAX_PATH] = { 0 };
	::GetModuleFileNameA(hModule, pFileName, MAX_PATH);

	string strModuleFullPath = pFileName;
	size_t npos = strModuleFullPath.rfind("\\", strModuleFullPath.length());
	if (npos != string::npos)
	{
		strReturn = strModuleFullPath.substr(0, npos);
	}
	return strReturn;
}
string Configer::FilePath_Cfg()
{
	return FilePath() + "\\cfg.xml";
}

bool Configer::Load()
{
	return LoadConfigFile();
}
bool Configer::LoadConfigFile()
{
	std::shared_ptr<TiXmlDocument> doc_(new TiXmlDocument);
	if (!doc_->LoadFile(FilePath_Cfg().c_str()))
	{
		std::cout << doc_->ErrorDesc() << std::endl;
		return false;
	}
	TiXmlElement *root = doc_->FirstChildElement();	//获取根节点元素

	if (root == NULL)
	{
		std::cerr << "Failed to load file:No curversion element." << std::endl;
		doc_->Clear();
		return false;
	}
	TiXmlElement *ptrVersion = root->FirstChildElement("Version");
	if (ptrVersion != nullptr)
	{
		m_strVersion = ptrVersion->GetText();
	}
	else
	{
		std::cerr << "Load version error" << endl;
		return false;
	}

	TiXmlElement *ptrUrl = root->FirstChildElement("Url");
	if (ptrUrl != nullptr)
	{
		m_ptrQurl = ptrUrl->GetText();
	}
	else
	{
		std::cerr << "Load Qurl error" << endl;
		return false;
	}
	
	TiXmlElement *ptrIcon = root->FirstChildElement("WindowIcon");
	if (ptrIcon != nullptr)
	{
		m_strIcon = ptrIcon->GetText();
	}
	else
	{
		std::cerr << "Load WindowIcon error" << endl;
		return false;
	}

	TiXmlElement *ptrRunExe = root->FirstChildElement("RunExe");
	if (ptrRunExe != nullptr)
	{
		m_ptrRunExe = ptrRunExe->GetText();
	}
	else
	{
		std::cerr << "Load RunExe error" << endl;
		return false;
	}
	
	return true;
}
bool Configer::SaveVersion(const char *version)
{
	std::shared_ptr<TiXmlDocument> doc_(new TiXmlDocument);
	if (!doc_->LoadFile(FilePath_Cfg().c_str()))
	{
		std::cout << doc_->ErrorDesc() << std::endl;
		return false;
	}
	TiXmlElement *root = doc_->FirstChildElement();	//获取根节点元素

	if (root == NULL)
	{
		std::cerr << "Failed to load file:No curversion element." << std::endl;
		doc_->Clear();
		return false;
	}
	TiXmlElement *ptrVersion = root->FirstChildElement("Version");
	if (ptrVersion != nullptr)
	{
		TiXmlNode *ptrText = ptrVersion->FirstChild();
		if (ptrText != nullptr)
		{
			ptrText->SetValue(version);		//设置节点文本值
		}
	}
	else
	{
		std::cerr << "Set Value false" << endl;
		return false;
	}

	doc_->SaveFile(FilePath_Cfg().c_str());

	return true;
}
const string &Configer::GetVersion()
{
	return m_strVersion;
}

const string &Configer::GetWindowIcon()
{
	return m_strIcon;
}

const string &Configer::GetQurl()
{
	return m_ptrQurl;
}

const string &Configer::GetRunExe()
{
	return m_ptrRunExe;
}

2.计算下载文件速度,剩余时间相关

参考文件:Qt 之 HTTP 上传/下载

#include "Format.h"
#include <QString>

// 字节转KB、MB、GB
QString formatSize(qint64 bytes)
{
	QString strUnit;
	double dSize = bytes * 1.0;
	if (dSize <= 0)
	{
		dSize = 0.0;
	}
	else if (dSize < 1024)
	{
		strUnit = "Bytes";
	}
	else if (dSize < 1024 * 1024)
	{
		dSize /= 1024;
		strUnit = "KB";
	}
	else if (dSize < 1024 * 1024 * 1024)
	{
		dSize /= (1024 * 1024);
		strUnit = "MB";
	}
	else
	{
		dSize /= (1024 * 1024 * 1024);
		strUnit = "GB";
	}

	return QString("%1 %2").arg(QString::number(dSize, 'f', 2)).arg(strUnit);
}

// 速度转KB/S、MB/S、GB/S
QString speed(double speed)
{
	QString strUnit;
	if (speed <= 0)
	{
		speed = 0;
		strUnit = "Bytes/s";
	}
	else if (speed < 1024)
	{
		strUnit = "Bytes/s";
	}
	else if (speed < 1024 * 1024)
	{
		speed /= 1024;
		strUnit = "KB/s";
	}
	else if (speed < 1024 * 1024 * 1024)
	{
		speed /= (1024 * 1024);
		strUnit = "MB/s";
	}
	else
	{
		speed /= (1024 * 1024 * 1024);
		strUnit = "GB/s";
	}

	QString strSpeed = QString::number(speed, 'f', 2);
	return QString("%1 %2").arg(strSpeed).arg(strUnit);
}

// 秒转*d *h *m *s
QString timeFormat(int seconds)
{
	QString strValue;
	QString strSpacing(" ");
	if (seconds <= 0)
	{
		strValue = QString("%1s").arg(0);
	}
	else if (seconds < 60)
	{
		strValue = QString("%1s").arg(seconds);
	}
	else if (seconds < 60 * 60)
	{
		int nMinute = seconds / 60;
		int nSecond = seconds - nMinute * 60;

		strValue = QString("%1m").arg(nMinute);

		if (nSecond > 0)
			strValue += strSpacing + QString("%1s").arg(nSecond);
	}
	else if (seconds < 60 * 60 * 24)
	{
		int nHour = seconds / (60 * 60);
		int nMinute = (seconds - nHour * 60 * 60) / 60;
		int nSecond = seconds - nHour * 60 * 60 - nMinute * 60;

		strValue = QString("%1h").arg(nHour);

		if (nMinute > 0)
			strValue += strSpacing + QString("%1m").arg(nMinute);

		if (nSecond > 0)
			strValue += strSpacing + QString("%1s").arg(nSecond);
	}
	else
	{
		int nDay = seconds / (60 * 60 * 24);
		int nHour = (seconds - nDay * 60 * 60 * 24) / (60 * 60);
		int nMinute = (seconds - nDay * 60 * 60 * 24 - nHour * 60 * 60) / 60;
		int nSecond = seconds - nDay * 60 * 60 * 24 - nHour * 60 * 60 - nMinute * 60;

		strValue = QString("%1d").arg(nDay);

		if (nHour > 0)
			strValue += strSpacing + QString("%1h").arg(nHour);

		if (nMinute > 0)
			strValue += strSpacing + QString("%1m").arg(nMinute);

		if (nSecond > 0)
			strValue += strSpacing + QString("%1s").arg(nSecond);
	}

	return strValue;
}

3.网络下载解压相关

头文件

#pragma once

#include <QtWidgets/QDialog>
#include <QtWidgets/QMainWindow>
#include <QUrl>
#include <QDesktopServices>
//网络相关头文件
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
//JSON相关头文件
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QMessageBox>
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QTime>
#include <QProgressBar>
#include <QTextCodec>

#include <QProcess>
#include "zlib.h"
#include "zconf.h"
#include "unzip.h"
#include <direct.h>

#include "ui_AutoUpdate.h"

class AutoUpdate : public QDialog
{
	Q_OBJECT

public:
	AutoUpdate(QWidget *parent = Q_NULLPTR);

private:
	int ParseJson(const QString &str);      //解析数据函数的声明
	bool UnzipFile(const char *zipFileName, const char *goalPath);
	bool UnzipCurrentFile(unzFile &uf, const char *destFolder);
private:
	QNetworkAccessManager *	m_NetManager;     //定义网络请求对象
	QNetworkAccessManager *	m_NetManagerDown;     //定义网络请求对象
	QNetworkReply *			m_pReply;
	QString					m_CurVerison;    //定义当前软件的版本号
	QUrl					m_url;
	QString					m_strUrl;
	QString					m_strFileName;
	QTime					m_downloadTime;
	int						m_nTime;
	QString					m_lastVerison;	

	std::string				strFileName;

signals:
	void replyFinished_over(int);
private slots:
	void slotUpdateNow();
	void slotClose();
	void replyFinished(QNetworkReply *reply);   //json 文件下载结束
	void replyFinished_zip(QNetworkReply *reply);	//zip文件下载结束
	void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);	//下载进度
	void onReplyFinished(int statusCode);
	void readyRead();
private:
	Ui::AutoUpdate ui;
};

cpp 文件

# include <stdio.h>
# include <inttypes.h>
# include <Windows.h>
# include <WinInet.h>
#include "AutoUpdate.h"
#include "configer.h"
#include "Format.h"
//#include "zlib.h"
//#include "zconf.h"

# define DOWNLOAD_BUFFER_SIZE 512

AutoUpdate::AutoUpdate(QWidget *parent)
	: QDialog(parent),
	m_NetManager(NULL),
	m_NetManagerDown(NULL),
	m_nTime(0)
{
	ui.setupUi(this);
	m_strFileName = QString(QString::fromLocal8Bit(Configer::FilePath().c_str())) + "\\Test.zip";

	m_CurVerison = QString::fromStdString(Configer::GetVersion());
	setWindowIcon(QIcon(QString::fromStdString(Configer::GetWindowIcon())));
	setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowStaysOnTopHint);

	m_NetManager = new QNetworkAccessManager(this);          //新建QNetworkAccessManager对象
	connect(m_NetManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));//关联信号和槽

	QNetworkRequest quest;
	quest.setUrl(QUrl(Configer::GetQurl().c_str())); //包含最新版本软件的下载地址
	quest.setHeader(QNetworkRequest::UserAgentHeader, "RT-Thread ART");
	m_NetManager->get(quest);    //发送get网络请求
}
int AutoUpdate::ParseJson(const QString &str)
{
	QJsonParseError err_rpt;
	QJsonDocument  root_Doc = QJsonDocument::fromJson(str.toUtf8(), &err_rpt);//字符串格式化为JSON
	if (err_rpt.error != QJsonParseError::NoError)
	{
		QMessageBox::critical(this, QString::fromLocal8Bit("检查失败"), QString::fromLocal8Bit("服务器地址错误或JSON格式错误!"));
		return -1;
	}
	if (root_Doc.isObject())
	{
		QJsonObject  root_Obj = root_Doc.object();   //创建JSON对象,不是字符串

		m_lastVerison = root_Obj.value("LatestVerison").toString();  //V1.0
		m_strUrl = root_Obj.value("Url").toString();
		QString UpdateTime = root_Obj.value("UpdateTime").toString();
		QString ReleaseNote = root_Obj.value("ReleaseNote").toString();
		if (m_lastVerison > m_CurVerison)
		{
			this->show();
			QString warningStr = QString::fromLocal8Bit("检测到新版本!\n版本号:") + m_lastVerison + "\n" + QString::fromLocal8Bit("更新时间:") + UpdateTime + "\n" + QString::fromLocal8Bit("更新说明:") + ReleaseNote;
			ui.textBrowser->append(warningStr);
		}
		else
		{
			QProcess::startDetached(QString(QString::fromLocal8Bit(Configer::FilePath().c_str())) + Configer::GetRunExe().c_str(), QStringList());
			qApp->quit();
		}
	}
	return 0;
}
void AutoUpdate::replyFinished(QNetworkReply *reply)
{
	QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
	if (statusCode != 200)
	{
		return;
	}
	QString str = reply->readAll();//读取接收到的数据

	ParseJson(str);
	reply->deleteLater();
}
void AutoUpdate::replyFinished_zip(QNetworkReply *reply)
{
	// 获取响应的信息,状态码为200表示正常
	QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);

	// 无错误返回
	if (reply->error() == QNetworkReply::NoError)
	{
		// 重命名临时文件
		QFileInfo fileInfo(m_strFileName);
		QFileInfo newFileInfo = fileInfo.absolutePath() + "/"+ m_url.fileName();
		QDir dir;
		if (dir.exists(fileInfo.absolutePath()))
		{
			if (newFileInfo.exists())
				newFileInfo.dir().remove(newFileInfo.fileName());
			QFile::rename(m_strFileName, newFileInfo.absoluteFilePath());
		}
	}
	else
	{
		QString strError = reply->errorString();
		qDebug() << "Error:" << strError;
	}
	emit replyFinished_over(statusCode.toInt());
}
void AutoUpdate::slotUpdateNow()
{
	ui.stackedWidget->setCurrentWidget(ui.pageInfo);

	if(m_NetManagerDown == NULL)
	{
		m_NetManagerDown = new QNetworkAccessManager(this);
		connect(this, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onDownloadProgress(qint64, qint64)), Qt::QueuedConnection);
		connect(this, SIGNAL(replyFinished_over(int)), this, SLOT(onReplyFinished(int)), Qt::QueuedConnection);
	}
	m_url = QUrl(m_strUrl);		//包含最新版本软件的下载地址

	connect(m_NetManagerDown, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished_zip(QNetworkReply*)));//关联信号和槽

	QNetworkRequest quest;
	quest.setUrl(m_url); //包含最新版本软件的下载地址
	quest.setHeader(QNetworkRequest::ContentTypeHeader, "application/zip");
	m_pReply = m_NetManagerDown->get(quest);    //发送get网络请求

	connect(m_pReply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onDownloadProgress(qint64, qint64)), Qt::QueuedConnection);
	connect(m_pReply, SIGNAL(readyRead()), this, SLOT(readyRead()));
	
	m_downloadTime.start();
}
void AutoUpdate::slotClose()
{
	reject();
}
void AutoUpdate::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
	// 总时间
	int nTime = m_downloadTime.elapsed();

	// 本次下载所用时间
	nTime -= m_nTime;

	// 下载速度
	double dBytesSpeed = (bytesReceived * 1000.0) / nTime;
	double dSpeed = dBytesSpeed;

	//剩余时间
	qint64 leftBytes = (bytesTotal - bytesReceived);
	double dLeftTime = (leftBytes * 1.0) / dBytesSpeed;

	ui.lblSpeedInfo->setText(speed(dSpeed));
	ui.lblLeftTime->setText(timeFormat(dLeftTime));
	ui.lblFileInfoSize->setText(formatSize(bytesTotal));
	ui.lblDownLoadInfo->setText(formatSize(bytesReceived));
	ui.progressBar->setMaximum(bytesTotal);
	ui.progressBar->setValue(bytesReceived);

	// 获取上一次的时间
	m_nTime = nTime;
}
bool AutoUpdate::UnzipCurrentFile(unzFile &uf, const char *destFolder)
{
	char szFilePath[512] = {0};
	unz_file_info64 FileInfo;
	FILE *  fp4= NULL;   // 创建解压文件的文件指针 
	errno_t err = 0;

	if (unzGetCurrentFileInfo64(uf, &FileInfo, szFilePath, sizeof(szFilePath), NULL, 0, NULL, 0) != UNZ_OK)
		return false;
	
	std::string str(szFilePath);

	size_t len = strlen(str.c_str());
	if (len <= 0)
	{
		return false;
	}
	if (str == "RiskClient/cfg.xml")		//保证xml文件不更新
	{
		return true;
	}

	std::string fullFileName = destFolder;
	fullFileName = fullFileName+ "\\" +szFilePath;
	if (szFilePath[len - 1] == '\\' || szFilePath[len - 1] == '/' || str == "")
	{
		_mkdir(fullFileName.c_str());
		return true;
	}
	err = fopen_s(&fp4, fullFileName.c_str(), "wb");

	if (err)
	{
		return false;
	}

	const int BUFFER_SIZE = 4096;
	unsigned char byBuffer[BUFFER_SIZE];
	if (unzOpenCurrentFile(uf) != UNZ_OK)
	{
		fclose(fp4);
		return false;
	}

	while (true)
	{
		int nSize = unzReadCurrentFile(uf, byBuffer, BUFFER_SIZE);

		if (nSize < 0)
		{
			unzCloseCurrentFile(uf);
			fclose(fp4);
			return false;
		}
		else if (nSize == 0)
		{
			break;
		}
		else
		{
			size_t wSize = fwrite(byBuffer, 1, nSize, fp4);
			if (wSize != nSize)
			{
				unzCloseCurrentFile(uf);
				fclose(fp4);
				return false;
			}
		}
	}

	unzCloseCurrentFile(uf);
	fclose(fp4);
	return true;
}

bool AutoUpdate::UnzipFile(const char *zipFileName, const char *goalPath)
{
	unzFile uf = unzOpen64(zipFileName);
	if (uf == NULL)
		return false;

	unz_global_info64 gi;
	if (unzGetGlobalInfo64(uf, &gi) != UNZ_OK)
	{
		unzClose(uf);
		return false;
	}

	std::string path = zipFileName;
	auto pos = path.find_last_of("/\\");
	if (pos != std::string::npos)
		path.erase(path.begin() + pos, path.end());

	for (int i = 0; i < gi.number_entry; ++i)
	{
		if (!UnzipCurrentFile(uf, goalPath))
		{
			unzClose(uf);
			return false;
		}
		if (i < gi.number_entry - 1)
		{
			if (unzGoToNextFile(uf) != UNZ_OK)
			{
				unzClose(uf);
				return false;
			}
		}
	}
	unzClose(uf);
	return true;
}

void AutoUpdate::onReplyFinished(int statusCode)
{
	QString strStatus = (statusCode == 200) ? QStringLiteral("下载成功") : QStringLiteral("下载失败");
	ui.lblStatus->setText(strStatus);
	if (statusCode == 200)
	{
		bool flag = UnzipFile(strFileName.c_str(), Configer::FilePath().c_str());
		if (flag)
		{
			//更新配置文件版本号
			Configer::SaveVersion(m_lastVerison.toStdString().c_str());
			//启动解压好的可执行程序
			QProcess::startDetached(QString(QString::fromLocal8Bit(Configer::FilePath().c_str())) + Configer::GetRunExe().c_str(), QStringList());
			//退出当前进程
			qApp->quit();
		}
	}
}
void AutoUpdate::readyRead()
{
	QFileInfo fileInfo(m_strFileName);
	QString fileName = m_url.fileName();
	QFileInfo newFileInfo = fileInfo.absolutePath() + "/"+ m_url.fileName();
	
	QTextCodec *code = QTextCodec::codecForName("GB2312");//解决中文路径问题
	strFileName = code->fromUnicode(newFileInfo.absoluteFilePath()).data();

	ui.lblFileInfo->setText(fileName);

	// 写文件-形式为追加
	QFile file(m_strFileName);
	if (file.open(QIODevice::Append))
		file.write(m_pReply->readAll());
	file.close();
}

本文章没有实现文件比较的功能,是解压缩替换掉原来所有的文件

源码下载地址:Qt 实现软件升级

参考文章:Qt 实现自动更新的一种简单方法

  • 5
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值